HEX
Server: Apache
System: Linux WWW 6.1.0-40-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.153-1 (2025-09-20) x86_64
User: web11 (1011)
PHP: 8.2.29
Disabled: NONE
Upload Files
File: /var/www/apklausos/application/extensions/yiiwheels/widgets/grid/WhGridView.php
<?php
/**
 * WhGridView class
 *
 * This grid is an extended version of TbGridView.
 *
 * Features are:
 *  - Display an extended summary of the records shown. The extended summary can be configured to any of the
 *  WhOperation type of widgets.
 *  - Automatic chart display (using WhHighCharts widget), where user can 'switch' between views.
 *
 * @author Antonio Ramirez <amigo.cobos@gmail.com>
 * @copyright Copyright &copy; 2amigos.us 2013-
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
 * @package YiiWheels.widgets.grid
 * @uses Yiistrap.widgets.TbHtml
 * @uses Yiistrap.widgets.TbGridView
 */
Yii::import('yiistrap_fork.helpers.TbHtml');
Yii::import('yiistrap_fork.widgets.TbGridView');

class WhGridView extends TbGridView
{
	/**
	 * @var bool $fixedHeader if set to true will keep the header fixed  position
	 */
	public $fixedHeader = false;

	/**
	 * @var integer $headerOffset, when $fixedHeader is set to true, headerOffset will position table header top position
	 * at $headerOffset. If you are using bootstrap and has navigation top fixed, its height is 40px, so it is recommended
	 * to use $headerOffset=40;
	 */
	public $headerOffset = 0;

	/**
	 * @var string the template to be used to control the layout of various sections in the view.
	 * These tokens are recognized: {extendedSummary}, {summary}, {items} and {pager}. They will be replaced with the
	 * extended summary, summary text, the items, and the pager.
	 */
	public $template = "{summary}\n{items}\n{pager}\n{extendedSummary}";

	/**
	 * @var array $extendedSummary displays an extended summary version.
	 * There are different types of summary types,
	 * please, see {@link TbSumOperation}, {@link TbSumOfTypeOperation},{@link TbPercentOfTypeGooglePieOperation}
	 * {@link TbPercentOfTypeOperation} and {@link TbPercentOfTypeEasyPieOperation}.
	 *
	 * The following is an example, please review the different types of TbOperation classes to find out more about
	 * its configuration parameters.
	 *
	 * <pre>
	 *  'extendedSummary' => array(
	 *      'title' => '',      // the extended summary title
	 *      'columns' => array( // the 'columns' that will be displayed at the extended summary
	 *          'id' => array(  // column name "id"
	 *              'class' => 'TbSumOperation', // what is the type of TbOperation we are going to display
	 *              'label' => 'Sum of Ids'     // label is name of label of the resulted value (ie Sum of Ids:)
	 *          ),
	 *          'results' => array(   // column name "results"
	 *              'class' => 'TbPercentOfTypeGooglePieOperation', // the type of TbOperation
	 *              'label' => 'How Many Of Each? ', // the label of the operation
	 *              'types' => array(               // TbPercentOfTypeGooglePieOperation "types" attributes
	 *                  '0' => array('label' => 'zeros'),   // a value of "0" will be labelled "zeros"
	 *                  '1' => array('label' => 'ones'),    // a value of "1" will be labelled "ones"
	 *                  '2' => array('label' => 'twos'))    // a value of "2" will be labelled "twos"
	 *          )
	 *      )
	 * ),
	 * </pre>
	 */
	public $extendedSummary = array();

	/**
	 * @var string $extendedSummaryCssClass is the class name of the layer containing the extended summary
	 */
	public $extendedSummaryCssClass = 'extended-summary';

	/**
	 * @var array $extendedSummaryOptions the HTML attributes of the layer containing the extended summary
	 */
	public $extendedSummaryOptions = array();

	/**
	 * @var array $componentsAfterAjaxUpdate has scripts that will be executed after components have updated.
	 * It is used internally to render scripts required for components to work correctly.  You may use it for your own
	 * scripts, just make sure it is of type array.
	 */
	public $componentsAfterAjaxUpdate = array();

	/**
	 * @var array $componentsReadyScripts hold scripts that will be executed on document ready.
	 * It is used internally to render scripts required for components to work correctly. You may use it for your own
	 * scripts, just make sure it is of type array.
	 */
	public $componentsReadyScripts = array();

	/**
	 * @var array $chartOptions if configured, the extended view will display a highcharts chart.
	 */
	public $chartOptions = array();

	/**
	 * @var bool whether to make the grid responsive
	 */
	public $responsiveTable = false;

	/**
	 * @var boolean $displayExtendedSummary a helper property that is set to true if we have to render the
	 * extended summary
	 */
	protected $displayExtendedSummary;

	/**
	 * @var WhOperation[] $extendedSummaryTypes hold the current configured TbOperation that will process column values.
	 */
	protected $extendedSummaryTypes = array();

	/**
	 * @var array $extendedSummaryOperations hold the supported operation types
	 */
	protected $extendedSummaryOperations = array(
		'yiiwheels.widgets.grid.operations.WhSumOperation',
		'yiiwheels.widgets.grid.operations.WhCountOfTypeOperation',
		'yiiwheels.widgets.grid.operations.WhPercentOfTypeOperation',
		'yiiwheels.widgets.grid.operations.WhPercentOfTypeEasyPieOperation',
		'yiiwheels.widgets.grid.operations.WhPercentOfTypeGooglePieOperation'
	);

	/**
	 * Widget initialization
	 */
	public function init()
	{
		if (preg_match(
				'/extendedsummary/i',
				$this->template
			) && !empty($this->extendedSummary) && isset($this->extendedSummary['columns'])
		) {
			$this->template .= "\n{extendedSummaryContent}";
			$this->displayExtendedSummary = true;
		}

		$this->attachBehavior('ywplugin', array('class' => 'yiiwheels.behaviors.WhPlugin'));
		$this->attachBehavior('ywchart', array('class' => 'yiiwheels.widgets.grid.behaviors.WhChart'));

		parent::init();
	}

	/**
	 * Renders grid content
	 */
	public function renderContent()
	{
		parent::renderContent();
		$this->registerCustomClientScript();
	}

	/**
	 * Helper function to get an attribute from the data
	 * @param CActiveRecord $data
	 * @param string $attribute the attribute to get
	 * @return mixed the attribute value null if none found
	 */
	protected function getAttribute($data, $attribute)
	{
		if ($this->dataProvider instanceof CActiveDataProvider && $data->hasAttribute($attribute)) {
			return $data->{$attribute};
		}

		if ($this->dataProvider instanceof CArrayDataProvider || $this->dataProvider instanceof CSqlDataProvider) {
			if (is_object($data) && isset($data->{$attribute})) {
				return $data->{$attribute};
			}
			if (isset($data[$attribute])) {
				return $data[$attribute];
			}
		}
		return null;
	}

	/**
	 * Helper function to return the primary key of the $data
	 * IMPORTANT: composite keys on CActiveDataProviders will return the keys joined by comma
	 * @param CActiveRecord $data
	 * @return null|string
	 */
	protected function getPrimaryKey($data)
	{
		if ($this->dataProvider instanceof CActiveDataProvider) {
			$key = $this->dataProvider->keyAttribute === null ? $data->getPrimaryKey() : $data->{$this->dataProvider->keyAttribute};
			return is_array($key) ? implode(',', $key) : $key;
		}
		if ($this->dataProvider instanceof CArrayDataProvider || $this->dataProvider instanceof CSqlDataProvider) {
			return is_object($data) ? $data->{$this->dataProvider->keyField}
				: $data[$this->dataProvider->keyField];
		}

		return null;
	}

	/**
	 * Renders grid header
	 */
	public function renderTableHeader()
	{
		$this->renderChart();
		parent::renderTableHeader();
	}

	/**
	 * Renders the table footer.
	 */
	public function renderTableFooter()
	{
		$hasFilter = $this->filter !== null && $this->filterPosition === self::FILTER_POS_FOOTER;
		$hasFooter = $this->getHasFooter();
		if ($hasFilter || $hasFooter) {
			echo "<tfoot>\n";
			if ($hasFooter) {
				echo "<tr>\n";
				/** @var $column CDataColumn */
				foreach ($this->columns as $column) {
					$column->renderFooterCell();
				}
				echo "</tr>\n";
			}
			if ($hasFilter) {
				$this->renderFilter();
			}
			echo "</tfoot>\n";
		}
	}

	/**
	 * Renders a table body row.
	 * @param integer $row the row number (zero-based).
	 */
	public function renderTableRow($row)
	{
		$htmlOptions = array();
		if ($this->rowHtmlOptionsExpression !== null) {
			$data = $this->dataProvider->data[$row];
			$options = $this->evaluateExpression(
				$this->rowHtmlOptionsExpression,
				array('row' => $row, 'data' => $data)
			);
			if (is_array($options)) {
				$htmlOptions = $options;
			}
		}

		if ($this->rowCssClassExpression !== null) {
			$data = $this->dataProvider->data[$row];
			$class = $this->evaluateExpression($this->rowCssClassExpression, array('row' => $row, 'data' => $data));
		} elseif (is_array($this->rowCssClass) && ($n = count($this->rowCssClass)) > 0) {
			$class = $this->rowCssClass[$row % $n];
		}

		if (!empty($class)) {
			if (isset($htmlOptions['class'])) {
				$htmlOptions['class'] .= ' ' . $class;
			} else {
				$htmlOptions['class'] = $class;
			}
		}

		echo TbHtml::openTag('tr', $htmlOptions);
		foreach ($this->columns as $column) {
			echo $this->displayExtendedSummary && !empty($this->extendedSummary['columns']) ? $this->parseColumnValue(
				$column,
				$row
			) : $column->renderDataCell($row);
		}
		echo TbHtml::closeTag('tr');
	}

	/**
	 * Renders summary
	 */
	public function renderExtendedSummary()
	{
		if (!isset($this->extendedSummaryOptions['class'])) {
			$this->extendedSummaryOptions['class'] = $this->extendedSummaryCssClass;
		} else {
			$this->extendedSummaryOptions['class'] .= ' ' . $this->extendedSummaryCssClass;
		}
		echo '<div ' . TbHtml::renderAttributes($this->extendedSummaryOptions) . '></div>';
	}

	/**
	 * Renders summary content. Will be appended to
	 */
	public function renderExtendedSummaryContent()
	{
		if (($count = $this->dataProvider->getItemCount()) <= 0) {
			return;
		}

		if (!empty($this->extendedSummaryTypes)) {
			echo '<div id="' . $this->id . '-extended-summary" style="display:none">';
			if (isset($this->extendedSummary['title'])) {
				echo '<h3>' . $this->extendedSummary['title'] . '</h3>';
			}
			foreach ($this->extendedSummaryTypes as $summaryType) {
				/** @var $summaryType TbOperation */
				$summaryType->run();
				echo '<br/>';
			}
			echo '</div>';
		}
	}

	/**
	 * Registers required css, js and scripts
	 * Note: This script must be run at the end of content rendering not at the beginning as it is common with normal
	 * CGridViews
	 */
	public function registerCustomClientScript()
	{
		/* publish assets dir */
		$path = __DIR__ . DIRECTORY_SEPARATOR . 'assets';
		$assetsUrl = $this->getAssetsUrl($path);

		/** @var $cs CClientScript */
		$cs = Yii::app()->getClientScript();

		$fixedHeaderJs = '';
		if ($this->fixedHeader) {
			$cs->registerScriptFile(
				$assetsUrl . '/js/jquery.stickytableheaders' . (!YII_DEBUG ? '.min' : '') . '.js',
				CClientScript::POS_END);
			$fixedHeaderJs = "$('#{$this->id} table.items').stickyTableHeaders({fixedOffset:{$this->headerOffset}});";
			$this->componentsAfterAjaxUpdate[] = $fixedHeaderJs;
		}

		$cs->registerScript(__CLASS__ . '#Wh' . $this->id,
			'$grid = $("#' . $this->id . '");' .
			$fixedHeaderJs . '
			if ($(".' . $this->extendedSummaryCssClass . '", $grid).length)
			{
				$(".' . $this->extendedSummaryCssClass . '", $grid).html($("#' . $this->id . '-extended-summary", $grid).html());
			}
			' . (count($this->componentsReadyScripts) ? implode("\n", $this->componentsReadyScripts) : '') . '
			$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
				var qs = $.deparam.querystring(options.url);
				if (qs.hasOwnProperty("ajax") && qs.ajax == "' . $this->id . '")
				{
					options.realsuccess = options.success;
					options.success = function(data)
					{
						if (options.realsuccess) {
							options.realsuccess(data);
							var $data = $("<div>" + data + "</div>");
							// we need to get the grid again... as it has been updated
							if ($(".' . $this->extendedSummaryCssClass . '", $("#' . $this->id . '")))
							{
								$(".' .
			$this->extendedSummaryCssClass .
			'", $("#' . $this->id .
			'")).html($("#' . $this->id .
			'-extended-summary", $data).html());
		}
		' .
			(count($this->componentsAfterAjaxUpdate)
				? implode("\n", $this->componentsAfterAjaxUpdate)
				: '') .
			'
		}
	}
}
});'
		);
	}

	/**
	 * Helper function to get a column by its name
	 * @param string $name
	 * @return CDataColumn|null
	 */
	public function getColumnByName($name)
	{
		foreach ($this->columns as $column) {
			if (strcmp((string) $column->name, $name) === 0) {
				return $column;
			}
		}
		return null;
	}

	/**
	 * Creates column objects and initializes them.
	 */
	protected function initColumns()
	{
		parent::initColumns();
		if($this->responsiveTable) {
			$this->attachBehavior('ywresponsive', array('class' => 'yiiwheels.widgets.grid.behaviors.WhResponsive'));
			$this->writeResponsiveCss($this->columns, $this->id);
		}
	}

	/**
	 * Parses the value of a column by an operation
	 * @param CDataColumn $column
	 * @param integer $row the current row number
	 * @return string
	 */
	protected function parseColumnValue($column, $row)
	{
		ob_start();
		$column->renderDataCell($row);
		$value = ob_get_clean();

		if ($column instanceof CDataColumn && array_key_exists($column->name, $this->extendedSummary['columns'])) {
			// lets get the configuration
			$config = $this->extendedSummary['columns'][$column->name];
			// add the required column object in
			$config['column'] = $column;
			// build the summary operation object
			$op = $this->getSummaryOperationInstance($column->name, $config);
			// process the value
			$op->processValue($value);
		}
		return $value;
	}

	/**
	 * Each type of 'extended' summary
	 * @param string $name the name of the column
	 * @param array $config the configuration of the column at the extendedSummary
	 * @return mixed
	 * @throws CException
	 */
	protected function getSummaryOperationInstance($name, $config)
	{
		if (!isset($config['class'])) {
			throw new CException(Yii::t(
				'zii',
				'Column summary configuration must be an array containing a "type" element.'
			));
		}

		if (!in_array($config['class'], $this->extendedSummaryOperations)) {
			throw new CException(Yii::t(
				'zii',
				'"{operation}" is an unsupported class operation.',
				array('{operation}' => $config['class'])
			));
		}

		// name of the column should be unique
		if (!isset($this->extendedSummaryTypes[$name])) {
			$this->extendedSummaryTypes[$name] = Yii::createComponent($config);
			$this->extendedSummaryTypes[$name]->init();
		}
		return $this->extendedSummaryTypes[$name];
	}

}