GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

prepareDisplayingExtendedSummary()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 0
cts 5
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * ## TbExtendedGridView class file
4
 *
5
 * @author Antonio Ramirez <[email protected]>
6
 * @copyright Copyright &copy; Clevertech 2012-
7
 * @license [New BSD License](http://www.opensource.org/licenses/bsd-license.php) 
8
 */
9
10
Yii::import('booster.widgets.TbGridView');
11
12
/**
13
 *## TbExtendedGridView is an extended version of TbGridView.
14
 *
15
 * Features are:
16
 *  - Display an extended summary of the records shown. The extended summary can be configured to any of the
17
 *  {@link TbOperation} type of widgets.
18
 *  - Automatic chart display (using TbHighCharts widget), where user can 'switch' between views.
19
 *  - Selectable cells
20
 *  - Sortable rows
21
 *
22
 * @property CActiveDataProvider $dataProvider the data provider for the view.
23
 * @property TbDataColumn[] $columns
24
 *
25
 * @package booster.widgets.grids
26
 */
27
class TbExtendedGridView extends TbGridView {
28
	
29
	/**
30
	 * @var bool $fixedHeader if set to true will keep the header fixed  position
31
	 */
32
	public $fixedHeader = false;
33
34
	/**
35
	 * @var integer $headerOffset, when $fixedHeader is set to true, headerOffset will position table header top position
36
	 * at $headerOffset. If you are using bootstrap and has navigation top fixed, its height is 40px, so it is recommended
37
	 * to use $headerOffset=40;
38
	 */
39
	public $headerOffset = 0;
40
41
	/**
42
	 * @var string the template to be used to control the layout of various sections in the view.
43
	 * These tokens are recognized: {extendedSummary}, {summary}, {items} and {pager}. They will be replaced with the
44
	 * extended summary, summary text, the items, and the pager.
45
	 */
46
	public $template = "{summary}\n{items}\n{pager}\n{extendedSummary}";
47
48
	/**
49
	 * @var array $extendedSummary displays an extended summary version.
50
	 * There are different types of summary types,
51
	 * please, see {@link TbSumOperation}, {@link TbSumOfTypeOperation},{@link TbPercentOfTypeGooglePieOperation}
52
	 * {@link TbPercentOfTypeOperation} and {@link TbPercentOfTypeEasyPieOperation}.
53
	 *
54
	 * The following is an example, please review the different types of TbOperation classes to find out more about
55
	 * its configuration parameters.
56
	 *
57
	 * <pre>
58
	 *  'extendedSummary' => array(
59
	 *      'title' => '',      // the extended summary title
60
	 *      'columns' => array( // the 'columns' that will be displayed at the extended summary
61
	 *          'id' => array(  // column name "id"
62
	 *              'class' => 'TbSumOperation', // what is the type of TbOperation we are going to display
63
	 *              'label' => 'Sum of Ids'     // label is name of label of the resulted value (ie Sum of Ids:)
64
	 *          ),
65
	 *          'results' => array(   // column name "results"
66
	 *              'class' => 'TbPercentOfTypeGooglePieOperation', // the type of TbOperation
67
	 *              'label' => 'How Many Of Each? ', // the label of the operation
68
	 *              'types' => array(               // TbPercentOfTypeGooglePieOperation "types" attributes
69
	 *                  '0' => array('label' => 'zeros'),   // a value of "0" will be labelled "zeros"
70
	 *                  '1' => array('label' => 'ones'),    // a value of "1" will be labelled "ones"
71
	 *                  '2' => array('label' => 'twos'))    // a value of "2" will be labelled "twos"
72
	 *          )
73
	 *      )
74
	 * ),
75
	 * </pre>
76
	 */
77
	public $extendedSummary = array();
78
79
	/**
80
	 * @var string $extendedSummaryCssClass is the class name of the layer containing the extended summary
81
	 */
82
	public $extendedSummaryCssClass = 'extended-summary';
83
84
	/**
85
	 * @var array $extendedSummaryOptions the HTML attributes of the layer containing the extended summary
86
	 */
87
	public $extendedSummaryOptions = array();
88
89
	/**
90
	 * @var array $componentsAfterAjaxUpdate has scripts that will be executed after components have updated.
91
	 * It is used internally to render scripts required for components to work correctly.  You may use it for your own
92
	 * scripts, just make sure it is of type array.
93
	 */
94
	public $componentsAfterAjaxUpdate = array();
95
96
	/**
97
	 * @var array $componentsReadyScripts hold scripts that will be executed on document ready.
98
	 * It is used internally to render scripts required for components to work correctly. You may use it for your own
99
	 * scripts, just make sure it is of type array.
100
	 */
101
	public $componentsReadyScripts = array();
102
103
	/**
104
	 * @var array $chartOptions if configured, the extended view will display a highcharts chart.
105
	 */
106
	public $chartOptions = array();
107
108
	/**
109
	 * @var bool $sortableRows. If true the rows at the table will be sortable.
110
	 */
111
	public $sortableRows = false;
112
113
	/**
114
	 * @var string Database field name for row sorting
115
	 */
116
	public $sortableAttribute = 'sort_order';
117
118
	/**
119
	 * @var boolean Save sort order by ajax defaults to false
120
	 * @see bootstrap.action.TbSortableAction for an easy way to use with your controller
121
	 */
122
	public $sortableAjaxSave = false;
123
124
	/**
125
	 * @var string Name of the action to call and sort values
126
	 * @see bootstrap.action.TbSortableAction for an easy way to use with your controller
127
	 *
128
	 * <pre>
129
	 *  'sortableAction' => 'module/controller/sortable' | 'controller/sortable'
130
	 * </pre>
131
	 *
132
	 * The widget will make use of the string to create the URL and then append $sortableAttribute
133
	 * @see $sortableAttribute
134
	 */
135
	public $sortableAction;
136
137
	/**
138
	 * @var string a javascript function that will be invoked after a successful sorting is done.
139
	 * The function signature is <code>function(id, position)</code> where 'id' refers to the ID of the model id key,
140
	 * 'position' the new position in the list.
141
	 */
142
	public $afterSortableUpdate;
143
144
	/**
145
	 * @var bool whether to allow selecting of cells
146
	 */
147
	public $selectableCells = false;
148
149
	/**
150
	 * @var string the filter to use to allow selection. For example, if you set the "htmlOptions" property of a column to have a
151
	 * "class" of "tobeselected", you could set this property as: "td.tobeselected" in order to allow  selection to
152
	 * those columns with that class only.
153
	 */
154
	public $selectableCellsFilter = 'td';
155
156
	/**
157
	 * @var string a javascript function that will be invoked after a selection is done.
158
	 * The function signature is <code>function(selected)</code> where 'selected' refers to the selected columns.
159
	 */
160
	public $afterSelectableCells;
161
	/**
162
	 * @var array the configuration options to display a TbBulkActions widget
163
	 * @see TbBulkActions widget for its configuration
164
	 */
165
	public $bulkActions = array();
166
167
	/**
168
	 * @var string the aligment of the bulk actions. It can be 'left' or 'right'.
169
	 */
170
	public $bulkActionAlign = 'right';
171
172
	/**
173
	 * @var TbBulkActions component that will display the bulk actions to the grid
174
	 */
175
	protected $bulk;
176
177
	/**
178
	 * @var boolean $displayExtendedSummary a helper property that is set to true if we have to render the
179
	 * extended summary
180
	 */
181
	protected $displayExtendedSummary;
182
	/**
183
	 * @var boolean $displayChart a helper property that is set to true if we have to render a chart.
184
	 */
185
	protected $displayChart;
186
187
	/**
188
	 * @var TbOperation[] $extendedSummaryTypes hold the current configured TbOperation that will process column values.
189
	 */
190
	protected $extendedSummaryTypes = array();
191
192
	/**
193
	 * @var array $extendedSummaryOperations hold the supported operation types
194
	 */
195
	protected $extendedSummaryOperations = array(
196
		'TbSumOperation',
197
		'TbCountOfTypeOperation',
198
		'TbPercentOfTypeOperation',
199
		'TbPercentOfTypeEasyPieOperation',
200
		'TbPercentOfTypeGooglePieOperation'
201
	);
202
203
	/**
204
	 *### .init()
205
	 *
206
	 * Widget initialization
207
	 */
208
	public function init(){
209
210
		if ($this->shouldEnableExtendedSummary())
211
			$this->prepareDisplayingExtendedSummary();
212
213
		if ($this->shouldEnableChart() && $this->hasData())
214
			$this->enableChart();
215
216
		if ($this->shouldEnableBulkActions())
217
			$this->enableBulkActions();
218
219
		$this->fillSelectionChangedProperty();
220
221
		parent::init();
222
	}
223
224
	/**
225
	 *### .renderContent()
226
	 *
227
	 * Renders grid content
228
	 */
229
	public function renderContent()
230
	{
231
		parent::renderContent();
232
		$this->registerCustomClientScript();
233
	}
234
235
	/**
236
	 *### .renderKeys()
237
	 *
238
	 * Renders the key values of the data in a hidden tag.
239
	 */
240
	public function renderKeys()
241
	{
242
		$data = $this->dataProvider->getData();
243
		
244
		if (!$this->sortableRows || (isset($data[0]) && !isset($data[0]->attributes[(string)$this->sortableAttribute]))) {
245
			parent::renderKeys();
246
		}
247
248
		echo CHtml::openTag(
249
			'div',
250
			array(
251
				'class' => 'keys',
252
				'style' => 'display:none',
253
				'title' => Yii::app()->getRequest()->getUrl(),
254
			)
255
		);
256
		foreach ($data as $d) {
257
			echo CHtml::tag(
258
				'span',
259
				array('data-order' => $this->getAttribute($d, $this->sortableAttribute)),
260
				CHtml::encode($this->getPrimaryKey($d))
261
			);
262
		}
263
		echo "</div>\n";
264
		return true;
265
	}
266
267
	/**
268
	 *### .getAttribute()
269
	 *
270
	 * Helper function to get an attribute from the data
271
	 *
272
	 * @param CActiveRecord $data
273
	 * @param string $attribute the attribute to get
274
	 *
275
	 * @return mixed the attribute value null if none found
276
	 */
277
	protected function getAttribute($data, $attribute)
278
	{
279
		if ($this->dataProvider instanceof CActiveDataProvider && $data->hasAttribute($attribute)) {
0 ignored issues
show
Bug introduced by
The class CActiveDataProvider does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
280
			return $data->{$attribute};
281
		}
282
283
		if ($this->dataProvider instanceof CArrayDataProvider || $this->dataProvider instanceof CSqlDataProvider) {
0 ignored issues
show
Bug introduced by
The class CArrayDataProvider does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
Bug introduced by
The class CSqlDataProvider does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
284
			if (is_object($data) && isset($data->{$attribute})) {
285
				return $data->{$attribute};
286
			}
287
			if (isset($data[$attribute])) {
288
				return $data[$attribute];
289
			}
290
		}
291
		return null;
292
	}
293
294
	/**
295
	 *### .getPrimaryKey()
296
	 *
297
	 * Helper function to return the primary key of the $data
298
	 * IMPORTANT: composite keys on CActiveDataProviders will return the keys joined by comma
299
	 *
300
	 * @param CActiveRecord $data
301
	 *
302
	 * @return null|string
303
	 */
304
	protected function getPrimaryKey($data)
305
	{
306
		if ($this->dataProvider instanceof CActiveDataProvider) {
0 ignored issues
show
Bug introduced by
The class CActiveDataProvider does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
307
			$key = $this->dataProvider->keyAttribute === null ? $data->getPrimaryKey() : $data->{$this->dataProvider->keyAttribute};
308
			return is_array($key) ? implode(',', $key) : $key;
309
		}
310
		if (($this->dataProvider instanceof CArrayDataProvider || $this->dataProvider instanceof CSqlDataProvider) && !empty($this->dataProvider->keyField)) {
0 ignored issues
show
Bug introduced by
The class CArrayDataProvider does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
Bug introduced by
The class CSqlDataProvider does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
311
			return is_object($data) ? $data->{$this->dataProvider->keyField}
312
				: $data[$this->dataProvider->keyField];
313
		}
314
315
		return null;
316
	}
317
318
	/**
319
	 *### .renderTableHeader()
320
	 *
321
	 * Renders grid header
322
	 */
323
	public function renderTableHeader() {
324
		
325
		$this->renderChart();
326
		parent::renderTableHeader();
327
	}
328
329
	/**
330
	 *### .renderTableFooter()
331
	 *
332
	 * Renders the table footer.
333
	 */
334
	public function renderTableFooter()
335
	{
336
		$hasFilter = $this->filter !== null && $this->filterPosition === self::FILTER_POS_FOOTER;
337
338
		$hasFooter = $this->getHasFooter();
339
		if ($this->bulk !== null || $hasFilter || $hasFooter) {
340
			echo "<tfoot>\n";
341
			if ($hasFooter) {
342
				echo "<tr>\n";
343
				/** @var $column CDataColumn */
344
				foreach ($this->columns as $column) {
345
					$column->renderFooterCell();
346
				}
347
				echo "</tr>\n";
348
			}
349
			if ($hasFilter) {
350
				$this->renderFilter();
351
			}
352
353
			if ($this->bulk !== null) {
354
				$this->renderBulkActions();
355
			}
356
			echo "</tfoot>\n";
357
		}
358
	}
359
360
	/**
361
	 *### .renderBulkActions()
362
	 */
363
	public function renderBulkActions() {
364
		
365
        Booster::getBooster()->registerAssetJs('jquery.saveselection.gridview.js');
366
        $this->componentsAfterAjaxUpdate[] = "$.fn.yiiGridView.afterUpdateGrid('".$this->id."');";
367
		echo '<tr><td colspan="' . count($this->columns) . '">';
368
		$this->bulk->renderButtons();
369
		echo '</td></tr>';
370
	}
371
372
373
	/**
374
	 *### .renderChart()
375
	 *
376
	 * Renders chart
377
	 * @throws CException
378
	 */
379
	public function renderChart() {
380
		
381
		if (!$this->displayChart || $this->dataProvider->getItemCount() <= 0) {
382
			return;
383
		}
384
385
		if (!isset($this->chartOptions['data']['series'])) {
386
			throw new CException(Yii::t(
387
				'zii',
388
				'You need to set the "series" attribute in order to render a chart'
389
			));
390
		}
391
392
		$configSeries = $this->chartOptions['data']['series'];
393
		if (!is_array($configSeries)) {
394
			throw new CException(Yii::t('zii', '"chartOptions.series" is expected to be an array.'));
395
		}
396
397
398
		if (!isset($this->chartOptions['config'])) {
399
			$this->chartOptions['config'] = array();
400
		}
401
402
		// ****************************************
403
		// render switch buttons
404
		$buttons = Yii::createComponent(
405
			array(
406
				'class' => 'booster.widgets.TbButtonGroup',
407
				'toggle' => 'radio',
408
				'buttons' => array(
409
					array(
410
						'label' => Yii::t('zii', 'Grid'),
411
						'url' => '#',
412
						'htmlOptions' => array('class' => 'active ' . $this->getId() . '-grid-control grid')
413
					),
414
					array(
415
						'label' => Yii::t('zii', 'Chart'),
416
						'url' => '#',
417
						'htmlOptions' => array('class' => $this->getId() . '-grid-control chart')
418
					),
419
				),
420
				'htmlOptions' => array('style' => 'margin-bottom:5px')
421
			)
422
		);
423
		echo '<div>';
424
		$buttons->init();
425
		$buttons->run();
426
		echo '</div>';
427
428
		$chartId = preg_replace('[-\\ ?]', '_', 'exgvwChart' . $this->getId()); // cleaning out most possible characters invalid as javascript variable identifiers.
429
430
		$this->componentsReadyScripts[] = '$(document).on("click",".' . $this->getId() . '-grid-control", function() {
431
			$(this).parent().find("input[type=\"radio\"]").parent().toggleClass("active");
432
			if ($(this).hasClass("grid") && $("#' . $this->getId() . ' #' . $chartId . '").is(":visible"))
433
			{
434
				$("#' . $this->getId() . ' #' . $chartId . '").hide();
435
				$("#' . $this->getId() . ' table.items").show();
436
			}
437
			if ($(this).hasClass("chart") && $("#' . $this->getId() . ' table.items").is(":visible"))
438
			{
439
				$("#' . $this->getId() . ' table.items").hide();
440
				$("#' . $this->getId() . ' #' . $chartId . '").show();
441
			}
442
			return false;
443
		});';
444
		
445
		$this->componentsAfterAjaxUpdate[] = '
446
			if($("label.grid.'.$this->getId().'-grid-control").hasClass("active")) {
447
				$("#' . $this->getId() . ' #' . $chartId . '").hide();
448
				$("#' . $this->getId() . ' table.items").show();
449
			} else {
450
				$("#' . $this->getId() . ' table.items").hide();
451
				$("#' . $this->getId() . ' #' . $chartId . '").show();
452
			}
453
		';
454
		// end switch buttons
455
		// ****************************************
456
457
		// render Chart
458
		// chart options
459
		$data = $this->dataProvider->getData();
460
		$count = count($data);
461
		$seriesData = array();
462
		$cnt = 0;
463
		foreach ($configSeries as $set) {
464
			$seriesData[$cnt] = array('name' => isset($set['name']) ? $set['name'] : null, 'data' => array());
465
466
			for ($row = 0; $row < $count; ++$row) {
467
				$column = $this->getColumnByName($set['attribute']);
468
				if (!is_null($column) && $column->value !== null) {
469
					$seriesData[$cnt]['data'][] = $this->evaluateExpression(
470
						$column->value,
471
						array('data' => $data[$row], 'row' => $row)
472
					);
473
				} else {
474
					$value = CHtml::value($data[$row], $set['attribute']);
475
					$seriesData[$cnt]['data'][] = is_numeric($value) ? (float)$value : $value;
476
				}
477
478
			}
479
			++$cnt;
480
		}
481
482
		$xAxisData = array();
483
		
484
		$xAxisData[] = array('categories'=>array());
485
		if(!empty($this->chartOptions['data']['xAxis'])){
486
			$xAxis = $this->chartOptions['data']['xAxis'];
487
			$categories = $xAxis['categories'];
488
			if(is_array($categories)) {
489
				$xAxisData['categories'] = $categories;
490
			} else { // field name
491
				for ($row = 0; $row < $count; ++$row) {
492
					$column = $this->getColumnByName($categories);
493
					if (!is_null($column) && $column->value !== null) {
494
						$xAxisData['categories'][] = $this->evaluateExpression(
495
								$column->value,
496
								array('data' => $data[$row], 'row' => $row)
497
						);
498
					} else {
499
						$value = CHtml::value($data[$row], $categories);
500
						$xAxisData['categories'][] = $value;
501
					}
502
				}
503
			}
504
		}
505
		
506
		// ****************************************
507
		// render chart
508
		$options = CMap::mergeArray(
509
			$this->chartOptions['config'],
510
			array('series' => $seriesData, 'xAxis' => $xAxisData)
511
		);
512
		$this->chartOptions['htmlOptions'] = isset($this->chartOptions['htmlOptions'])
513
			? $this->chartOptions['htmlOptions'] : array();
514
		
515
		// sorry but use a class to provide styles, we need this
516
		if(empty($this->chartOptions['htmlOptions']['style']))
517
			$this->chartOptions['htmlOptions']['style'] = 'width: 100%; height: 100%;';
518
		else
519
			$this->chartOptions['htmlOptions']['style'] = $this->chartOptions['htmlOptions']['style'].'; width: 100%; height: 100%;';
520
		
521
		// build unique ID
522
		// important!
523
		echo '<div>';
524
		if ($this->ajaxUpdate !== false) {
525
			if (isset($options['chart']) && is_array($options['chart'])) {
526
				$options['chart']['renderTo'] = $chartId;
527
			} else {
528
				$options['chart'] = array('renderTo' => $chartId);
529
			}
530
			$jsOptions = CJSON::encode($options);
531
532
			if (isset($this->chartOptions['htmlOptions']['data-config'])) {
533
				unset($this->chartOptions['htmlOptions']['data-config']);
534
			}
535
536
			echo "<div id='{$chartId}' " . CHtml::renderAttributes(
537
				$this->chartOptions['htmlOptions']
538
			) . " data-config='{$jsOptions}'></div>";
539
			
540
			/* fix for chart dimensions changing after ajax */
541
			$this->componentsAfterAjaxUpdate[] = "
542
				$('#".$chartId."').width($('#".$this->id." table').width());
543
				$('#".$chartId."').height($('#".$this->id." table').height() + 150);
544
				highchart{$chartId} = new Highcharts.Chart($('#{$chartId}').data('config'));
545
			";
546
		}
547
		$configChart = array(
548
			'class' => 'booster.widgets.TbHighCharts',
549
			'id' => $chartId,
550
			'options' => $options,
551
			'htmlOptions' => $this->chartOptions['htmlOptions']
552
		);
553
		$chart = Yii::createComponent($configChart);
554
		$chart->init();
555
		$chart->run();
556
		echo '</div>';
557
		// end chart display
558
		// ****************************************
559
		
560
		// check if the chart should appear by default
561
		if(isset($this->chartOptions['defaultView']) && $this->chartOptions['defaultView'] === true) {
562
			$this->componentsReadyScripts[] = '
563
				$(".' . $this->getId() . '-grid-control.grid").removeClass("active");
564
				$(".' . $this->getId() . '-grid-control.chart").addClass("active");
565
				$("#' . $this->getId() . ' table.items").hide();
566
				$("#' . $this->getId() . ' #' . $chartId . '").show();
567
			';
568
		} else {
569
			$this->componentsReadyScripts[] = '
570
				$(".' . $this->getId() . '-grid-control.grid").addClass("active");
571
				$(".' . $this->getId() . '-grid-control.chart").removeClass("active");
572
				$("#' . $this->getId() . ' table.items").show();
573
				$("#' . $this->getId() . ' #' . $chartId . '").hide();
574
			';
575
		}
576
	}
577
578
	/**
579
	 *### .renderTableRow()
580
	 *
581
	 * Renders a table body row.
582
	 *
583
	 * This method is a copy-paste from CGridView.renderTableRow(),
584
	 * because we cannot override *just* the `renderDataCell` routine (it's polymorphic)
585
	 * and Yii doesn't have a seam in place for us to do this.
586
	 * @see https://github.com/yiisoft/yii/pull/3571
587
	 *
588
	 * Only meaningful change in here is the replacement of the `$column->renderDataCell($row)`
589
	 * with our own method.
590
	 *
591
	 * @param integer $row the row number (zero-based).
592
	 *
593
	 * @deprecated This method will be removed after Yii 1.1.17 release.
594
	 */
595
	public function renderTableRow($row)
596
	{
597
		$htmlOptions = array();
598
		if ($this->rowHtmlOptionsExpression !== null) {
599
			$data = $this->dataProvider->data[$row];
600
			$options = $this->evaluateExpression(
601
				$this->rowHtmlOptionsExpression,
602
				array('row' => $row, 'data' => $data)
603
			);
604
			if (is_array($options)) {
605
				$htmlOptions = $options;
606
			}
607
		}
608
609
		if ($this->rowCssClassExpression !== null) {
610
			$data = $this->dataProvider->data[$row];
611
			$class = $this->evaluateExpression($this->rowCssClassExpression, array('row' => $row, 'data' => $data));
612
		} elseif (is_array($this->rowCssClass) && ($n = count($this->rowCssClass)) > 0) {
613
			$class = $this->rowCssClass[$row % $n];
614
		}
615
616
		if (!empty($class)) {
617
			if (isset($htmlOptions['class'])) {
618
				$htmlOptions['class'] .= ' ' . $class;
619
			} else {
620
				$htmlOptions['class'] = $class;
621
			}
622
		}
623
624
		echo CHtml::openTag('tr', $htmlOptions);
625
		foreach ($this->columns as $column)
626
			$this->renderDataCellProcessingSummariesIfNeeded($column, $row);
627
		echo CHtml::closeTag('tr');
628
	}
629
630
	/**
631
	 *### .renderExtendedSummary()
632
	 *
633
	 * Renders summary
634
	 */
635
	public function renderExtendedSummary()
636
	{
637
		if (!isset($this->extendedSummaryOptions['class'])) {
638
			$this->extendedSummaryOptions['class'] = $this->extendedSummaryCssClass;
639
		} else {
640
			$this->extendedSummaryOptions['class'] .= ' ' . $this->extendedSummaryCssClass;
641
		}
642
643
		echo '<div ' . CHtml::renderAttributes($this->extendedSummaryOptions) . '></div>';
644
	}
645
646
	/**
647
	 *### .renderExtendedSummaryContent()
648
	 *
649
	 * Renders summary content. Will be appended to
650
	 */
651
	public function renderExtendedSummaryContent()
652
	{
653
		if ($this->dataProvider->getItemCount() <= 0)
654
			return;
655
656
		if (empty($this->extendedSummaryTypes))
657
			return;
658
659
		echo '<div id="' . $this->id . '-extended-summary" style="display:none">';
660
		if (isset($this->extendedSummary['title'])) {
661
			echo '<h3>' . $this->extendedSummary['title'] . '</h3>';
662
		}
663
		foreach ($this->extendedSummaryTypes as $summaryType) {
664
			/** @var $summaryType TbOperation */
665
			$summaryType->run();
666
			echo '<br/>';
667
		}
668
		echo '</div>';
669
	}
670
671
	/**
672
	 *### .registerCustomClientScript()
673
	 *
674
	 * This script must be run at the end of content rendering not at the beginning as it is common with normal CGridViews
675
	 */
676
	public function registerCustomClientScript()
677
	{
678
		/** @var $cs CClientScript */
679
		$cs = Yii::app()->getClientScript();
680
681
		$fixedHeaderJs = '';
682
		if ($this->fixedHeader) {
683
            Booster::getBooster()->registerAssetJs('jquery.stickytableheaders' . (!YII_DEBUG ? '.min' : '') . '.js');
684
			$fixedHeaderJs = "$('#{$this->id} table.items').stickyTableHeaders({fixedOffset:{$this->headerOffset}});";
685
			$this->componentsAfterAjaxUpdate[] = $fixedHeaderJs;
686
		}
687
688
		if ($this->sortableRows) {
689
			$afterSortableUpdate = '';
690
			if ($this->afterSortableUpdate !== null) {
691
				if (!($this->afterSortableUpdate instanceof CJavaScriptExpression) && strpos(
0 ignored issues
show
Bug introduced by
The class CJavaScriptExpression does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
692
					$this->afterSortableUpdate,
693
					'js:'
694
				) !== 0
695
				) {
696
					$afterSortableUpdate = new CJavaScriptExpression($this->afterSortableUpdate);
697
				} else {
698
					$afterSortableUpdate = $this->afterSortableUpdate;
699
				}
700
			}
701
702
			$this->selectableRows = 1;
703
			$cs->registerCoreScript('jquery.ui');
704
            Booster::getBooster()->registerAssetJs('jquery.sortable.gridview.js');
705
706
			if ($this->sortableAjaxSave && $this->sortableAction !== null) {
707
				$sortableAction = Yii::app()->createUrl(
708
					$this->sortableAction,
709
					array('sortableAttribute' => $this->sortableAttribute)
710
				);
711
			} else {
712
				$sortableAction = '';
713
			}
714
715
			$afterSortableUpdate = CJavaScript::encode($afterSortableUpdate);
716
			if (Yii::app()->request->enableCsrfValidation)
717
			{
718
				$csrfTokenName = Yii::app()->request->csrfTokenName;
719
				$csrfToken = Yii::app()->request->csrfToken;
720
				$csrf = "{'$csrfTokenName':'$csrfToken' }";
721
			} else
722
				$csrf = '{}';
723
724
			$this->componentsReadyScripts[] = "$.fn.yiiGridView.sortable('{$this->id}', '{$sortableAction}', {$afterSortableUpdate}, $csrf);";
725
			$this->componentsAfterAjaxUpdate[] = "$.fn.yiiGridView.sortable('{$this->id}', '{$sortableAction}', {$afterSortableUpdate}, $csrf);";
726
		}
727
728
		if ($this->selectableCells) {
729
			$afterSelectableCells = '';
730
			if ($this->afterSelectableCells !== null) {
731
				if (!($this->afterSelectableCells instanceof CJavaScriptExpression) && strpos($this->afterSelectableCells,'js:') !== 0) {
0 ignored issues
show
Bug introduced by
The class CJavaScriptExpression does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
732
					$afterSelectableCells = new CJavaScriptExpression($this->afterSelectableCells);
733
				} else {
734
					$afterSelectableCells = $this->afterSelectableCells;
735
				}
736
			}
737
			$cs->registerCoreScript('jquery.ui');
738
            Booster::getBooster()->registerAssetJs('jquery.selectable.gridview.js');
739
			$afterSelectableCells = CJavaScript::encode($afterSelectableCells);
740
			$this->componentsReadyScripts[] = "$.fn.yiiGridView.selectable('{$this->id}','{$this->selectableCellsFilter}',{$afterSelectableCells});";
741
			$this->componentsAfterAjaxUpdate[] = "$.fn.yiiGridView.selectable('{$this->id}','{$this->selectableCellsFilter}', {$afterSelectableCells});";
742
		}
743
744
		$cs->registerScript(
745
			__CLASS__ . '#' . $this->id . 'Ex',
746
			'
747
			var $grid = $("#' . $this->id . '");
748
			' . $fixedHeaderJs . '
749
			if ($(".' . $this->extendedSummaryCssClass . '", $grid).length)
750
			{
751
				$(".' . $this->extendedSummaryCssClass . '", $grid).html($("#' . $this->id . '-extended-summary", $grid).html());
752
			}
753
			' . (count($this->componentsReadyScripts) ? implode(PHP_EOL, $this->componentsReadyScripts) : '') . '
754
			$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
755
				var qs = $.deparam.querystring(options.url);
756
				if (qs.hasOwnProperty("ajax") && qs.ajax == "' . $this->id . '")
757
				{
758
				    if (typeof (options.realsuccess) == "undefined" || options.realsuccess !== options.success)
759
				    {
760
                        options.realsuccess = options.success;
761
                        options.success = function(data)
762
                        {
763
                            if (options.realsuccess) {
764
                                options.realsuccess(data);
765
                                var $data = $("<div>" + data + "</div>");
766
                                // we need to get the grid again... as it has been updated
767
                                if ($(".' . $this->extendedSummaryCssClass . '", $("#' . $this->id . '")))
768
                                {
769
                                    $(".' . $this->extendedSummaryCssClass . '", $("#' . $this->id . '")).html($("#' . $this->id . '-extended-summary", $data).html());
770
                                }
771
                                ' . (count($this->componentsAfterAjaxUpdate) ? implode(
772
                    PHP_EOL,
773
                    $this->componentsAfterAjaxUpdate
774
                ) : '') . '
775
                            }
776
                        }
777
				    }
778
				}
779
			});'
780
		);
781
	}
782
783
	/**
784
	 *### .parseColumnValue()
785
	 *
786
	 * @param CGridColumn $column
787
	 * @param string $value Value of the $column rendered by $column->renderDataCell($row).
788
	 */
789
	protected function processColumnValue($column, $value)
790
	{
791
		if (!($column instanceof CDataColumn))
0 ignored issues
show
Bug introduced by
The class CDataColumn does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
792
			return;
793
794
		if (!array_key_exists($column->name, $this->extendedSummary['columns']))
795
			return;
796
797
		$config = $this->extendedSummary['columns'][$column->name];
798
		$config['column'] = $column;
799
800
		$this->getSummaryOperationInstance($column->name, $config)
801
			->processValue($value);
802
	}
803
804
	/**
805
	 *### .getSummaryOperationInstance()
806
	 *
807
	 * Each type of 'extended' summary
808
	 *
809
	 * @param string $name the name of the column
810
	 * @param array $config the configuration of the column at the extendedSummary
811
	 *
812
	 * @return mixed
813
	 * @throws CException
814
	 */
815
	protected function getSummaryOperationInstance($name, $config)
816
	{
817
		if (!isset($config['class'])) {
818
			throw new CException(Yii::t(
819
				'zii',
820
				'Column summary configuration must be an array containing a "type" element.'
821
			));
822
		}
823
824
		if (!in_array($config['class'], $this->extendedSummaryOperations)) {
825
			throw new CException(Yii::t(
826
				'zii',
827
				'"{operation}" is an unsupported class operation.',
828
				array('{operation}' => $config['class'])
829
			));
830
		}
831
832
		// name of the column should be unique
833
		if (!isset($this->extendedSummaryTypes[$name])) {
834
			$this->extendedSummaryTypes[$name] = Yii::createComponent($config);
835
			$this->extendedSummaryTypes[$name]->init();
836
		}
837
		return $this->extendedSummaryTypes[$name];
838
	}
839
840
	/**
841
	 *### .getColumnByName()
842
	 *
843
	 * Helper function to get a column by its name
844
	 *
845
	 * @param string $name
846
	 *
847
	 * @return TbDataColumn|null
848
	 */
849
	protected function getColumnByName($name)
850
	{
851
		foreach ($this->columns as $column) {
852
			if (strcmp($column->name, $name) === 0) {
853
				return $column;
854
			}
855
		}
856
		return null;
857
	}
858
859
	/**
860
	 * @return bool
861
	 */
862
	private function shouldEnableExtendedSummary()
863
	{
864
		return preg_match(
865
			'/extendedsummary/i',
866
			$this->template
867
		) && !empty($this->extendedSummary) && isset($this->extendedSummary['columns']);
868
	}
869
870
	private function prepareDisplayingExtendedSummary()
871
	{
872
		$this->template .= "\n{extendedSummaryContent}";
873
		$this->displayExtendedSummary = true;
874
	}
875
876
	private function shouldEnableChart()
877
	{
878
		return !empty($this->chartOptions) && (bool)@$this->chartOptions['data'];
879
	}
880
881
	/**
882
	 * @return int
883
	 */
884
	private function hasData()
885
	{
886
		return $this->dataProvider->getItemCount();
887
	}
888
889
	private function enableChart()
890
	{
891
		$this->displayChart = true;
892
	}
893
894
	/**
895
	 * @return bool
896
	 */
897
	private function shouldEnableBulkActions()
898
	{
899
		return $this->bulkActions !== array() && isset($this->bulkActions['actionButtons']);
900
	}
901
902
	private function enableBulkActions()
903
	{
904
		if (!isset($this->bulkActions['class'])) {
905
			$this->bulkActions['class'] = 'booster.widgets.TbBulkActions';
906
		}
907
908
		$this->bulk = Yii::createComponent($this->bulkActions, $this);
909
		$this->bulk->init();
910
	}
911
912
	private function fillSelectionChangedProperty()
913
	{
914
		$this->selectionChanged = $this->makeDefaultSelectionChangedJavascript();
915
	}
916
917
	/**
918
	 * @return string
919
	 */
920
	private function makeDefaultSelectionChangedJavascript()
921
	{
922
		return 'js:function(id) {
923
			$("#"+id+" input[type=checkbox]").change();
924
		}';
925
	}
926
927
	/**
928
	 * This method will become `renderDataCell` after Yii 1.1.17 will be released
929
	 * @see https://github.com/yiisoft/yii/pull/3571
930
	 *
931
	 * @param CGridColumn $column
932
	 * @param integer $row
933
	 */
934
	private function renderDataCellProcessingSummariesIfNeeded($column, $row)
935
	{
936
		$value = $this->getRenderedDataCellValue($column, $row);
937
938
		if ($this->isExtendedSummaryEnabled())
939
			$this->processColumnValue($column, $value);
940
941
		echo $value;
942
	}
943
944
	/**
945
	 * @param CGridColumn $column
946
	 * @param integer $row
947
	 *
948
	 * @return string
949
	 */
950
	private function getRenderedDataCellValue($column, $row)
951
	{
952
		ob_start();
953
		$column->renderDataCell($row);
954
		return ob_get_clean();
955
	}
956
957
	/**
958
	 * @return bool
959
	 */
960
	private function isExtendedSummaryEnabled()
961
	{
962
		return $this->displayExtendedSummary && !empty($this->extendedSummary['columns']);
963
	}
964
965
}
966
967
/**
968
 *## TbOperation class
969
 *
970
 * Abstract class where all types of operations extend from
971
 *
972
 * @package booster.widgets.grids.operations
973
 */
974
abstract class TbOperation extends CWidget
975
{
976
	/**
977
	 * @var string $template the template to display label and value of the operation at the summary
978
	 */
979
	public $template = '{label}: {value}';
980
981
	/**
982
	 * @var int $value the resulted value of operation
983
	 */
984
	public $value = 0;
985
986
	/**
987
	 * @var string $label the label of the calculated value
988
	 */
989
	public $label;
990
991
	/**
992
	 * @var TbDataColumn $column
993
	 */
994
	public $column;
995
996
	/**
997
	 * Widget initialization
998
	 * @throws CException
999
	 */
1000
	public function init()
1001
	{
1002
		if (null == $this->column) {
1003
			throw new CException(Yii::t(
1004
				'zii',
1005
				'"{attribute}" attribute must be defined',
1006
				array('{attribute}' => 'column')
1007
			));
1008
		}
1009
	}
1010
1011
	/**
1012
	 * Widget's run method
1013
	 */
1014
	public function run()
1015
	{
1016
		$this->displaySummary();
1017
	}
1018
1019
	/**
1020
	 * Process the row data value
1021
	 *
1022
	 * @param $value
1023
	 *
1024
	 * @return mixed
1025
	 */
1026
	abstract public function processValue($value);
1027
1028
	/**
1029
	 * Displays the resulting summary
1030
	 * @return mixed
1031
	 */
1032
	abstract public function displaySummary();
1033
1034
}
1035
1036
/**
1037
 * TbSumOperation class
1038
 *
1039
 * Displays a total of specified column name.
1040
 *
1041
 * @package booster.widgets.grids.operations
1042
 */
1043
class TbSumOperation extends TbOperation
1044
{
1045
	/**
1046
	 * @var float $total the total sum
1047
	 */
1048
	protected $total;
1049
1050
	/**
1051
	 * @var array $supportedTypes the supported type of values
1052
	 */
1053
	protected $supportedTypes = array('raw', 'text', 'ntext', 'number');
1054
1055
	/**
1056
	 * Widget's initialization method
1057
	 * @throws CException
1058
	 */
1059
	public function init()
1060
	{
1061
		parent::init();
1062
1063
		if (!in_array($this->column->type, $this->supportedTypes)) {
1064
			throw new CException(Yii::t(
1065
				'zii',
1066
				'Unsupported column type. Supported column types are: "{types}"',
1067
				array(
1068
					'{types}' => implode(', ', $this->supportedTypes)
1069
				)
1070
			));
1071
		}
1072
	}
1073
1074
	/**
1075
	 * Extracts the digital part of the calculated value.
1076
	 *
1077
	 * @param int $value
1078
	 *
1079
	 * @return bool
1080
	 */
1081
	protected function extractNumber($value)
1082
	{
1083
		preg_match_all('/([+-]?[0-9]+[,\.]?)+/', $value, $matches);
1084
		return !empty($matches[0]) && @$matches[0][0] ? $matches[0][0] : 0;
1085
	}
1086
1087
	/**
1088
	 * Process the value to calculate
1089
	 *
1090
	 * @param $value
1091
	 *
1092
	 * @return mixed|void
1093
	 */
1094
	public function processValue($value)
1095
	{
1096
		// remove html tags as we cannot access renderDataCellContent from the column
1097
		$clean = strip_tags($value);
1098
		$this->total += ((float)$this->extractNumber($clean));
1099
	}
1100
1101
	/**
1102
	 * Displays the summary
1103
	 * @return mixed|void
1104
	 */
1105
	public function displaySummary()
1106
	{
1107
		echo strtr(
1108
			$this->template,
1109
			array(
1110
				'{label}' => $this->label,
1111
				'{value}' => $this->total === null ? '' : Yii::app()->format->format($this->total, $this->column->type)
1112
			)
1113
		);
1114
	}
1115
}
1116
1117
/**
1118
 * TbCountOfTypeOperation class
1119
 *
1120
 * Renders a summary based on the count of specified types. For example, if a value has a type 'blue', this class will
1121
 * count the number of times the value 'blue' has on that column.
1122
 *
1123
 * @package booster.widgets.grids.operations
1124
 */
1125
class TbCountOfTypeOperation extends TbOperation
1126
{
1127
	/**
1128
	 * @var string $template
1129
	 * @see parent class
1130
	 */
1131
	public $template = '{label}: {types}';
1132
1133
	/**
1134
	 * @var string $typeTemplate holds the template of each calculated type
1135
	 */
1136
	public $typeTemplate = '{label}({value})';
1137
1138
	/**
1139
	 * @var array $types hold the configuration of types to calculate. The configuration is set by an array which keys
1140
	 * are the value types to count. You can set their 'label' independently.
1141
	 *
1142
	 * <pre>
1143
	 *  'types' => array(
1144
	 *      '0' => array('label' => 'zeros'),
1145
	 *      '1' => array('label' => 'ones'),
1146
	 *      '2' => array('label' => 'twos')
1147
	 * </pre>
1148
	 */
1149
	public $types = array();
1150
1151
	/**
1152
	 * Widget's initialization
1153
	 * @throws CException
1154
	 */
1155
	public function init()
1156
	{
1157
		if (empty($this->types)) {
1158
			throw new CException(Yii::t(
1159
				'zii',
1160
				'"{attribute}" attribute must be defined',
1161
				array('{attribute}' => 'types')
1162
			));
1163
		}
1164
		foreach ($this->types as $type) {
1165
			if (!isset($type['label'])) {
1166
				throw new CException(Yii::t('zii', 'The "label" of a type must be defined.'));
1167
			}
1168
		}
1169
		parent::init();
1170
	}
1171
1172
	/**
1173
	 * (no phpDoc)
1174
	 * @see TbOperation
1175
	 *
1176
	 * @param $value
1177
	 *
1178
	 * @return mixed|void
1179
	 */
1180
	public function processValue($value)
1181
	{
1182
		$clean = strip_tags($value);
1183
1184
		if (array_key_exists($clean, $this->types)) {
1185
			if (!isset($this->types[$clean]['value'])) {
1186
				$this->types[$clean]['value'] = 0;
1187
			}
1188
			$this->types[$clean]['value'] += 1;
1189
		}
1190
	}
1191
1192
	/**
1193
	 * (no phpDoc)
1194
	 * @see TbOperation
1195
	 * @return mixed|void
1196
	 */
1197
	public function displaySummary()
1198
	{
1199
		$typesResults = array();
1200
		foreach ($this->types as $type) {
1201
			if (!isset($type['value'])) {
1202
				$type['value'] = 0;
1203
			}
1204
1205
			$typesResults[] = strtr(
1206
				$this->typeTemplate,
1207
				array('{label}' => $type['label'], '{value}' => $type['value'])
1208
			);
1209
		}
1210
		echo strtr($this->template, array('{label}' => $this->label, '{types}' => implode(' ', $typesResults)));
1211
	}
1212
}
1213
1214
/**
1215
 *## TbPercentOfTypeOperation class
1216
 *
1217
 * Renders a summary based on the percent count of specified types. For example, if a value has a type 'blue', this class will
1218
 * count the percentage number of times the value 'blue' has on that column.
1219
 *
1220
 * @package booster.widgets.grids.operations
1221
 */
1222
class TbPercentOfTypeOperation extends TbCountOfTypeOperation
1223
{
1224
	/**
1225
	 * @var string $typeTemplate
1226
	 * @see TbCountOfTypeOperation
1227
	 */
1228
	public $typeTemplate = '{label}({value}%)';
1229
1230
	/**
1231
	 * @var integer $_total holds the total sum of the values. Required to get the percentage.
1232
	 */
1233
	protected $_total;
1234
1235
	/**
1236
	 * @return mixed|void
1237
	 * @see TbOperation
1238
	 */
1239
	public function displaySummary()
1240
	{
1241
		$typesResults = array();
1242
1243
		foreach ($this->types as $type) {
1244
			if (!isset($type['value'])) {
1245
				$type['value'] = 0;
1246
			}
1247
1248
			$type['value'] = $this->getTotal() ? number_format((float)($type['value'] / $this->getTotal()) * 100, 1)
1249
				: 0;
1250
			$typesResults[] = strtr(
1251
				$this->typeTemplate,
1252
				array('{label}' => $type['label'], '{value}' => $type['value'])
1253
			);
1254
		}
1255
1256
		echo strtr($this->template, array('{label}' => $this->label, '{types}' => implode(' ', $typesResults)));
1257
	}
1258
1259
	/**
1260
	 * Returns the total of types
1261
	 * @return int holds
1262
	 */
1263
	protected function getTotal()
1264
	{
1265
		if (null == $this->_total) {
1266
			$this->_total = 0;
1267
			foreach ($this->types as $type) {
1268
				if (isset($type['value'])) {
1269
					$this->_total += $type['value'];
1270
				}
1271
			}
1272
		}
1273
		return $this->_total;
1274
	}
1275
}
1276
1277
/**
1278
 *## TbPercentOfTypeGooglePieOperation class
1279
 *
1280
 * Displays a Google visualization  pie chart based on the percentage count of type.
1281
 *
1282
 * @package booster.widgets.grids.operations
1283
 */
1284
class TbPercentOfTypeGooglePieOperation extends TbPercentOfTypeOperation
1285
{
1286
	/**
1287
	 * @var string $chartCssClass the class name of the layer holding the chart
1288
	 */
1289
	public $chartCssClass = 'bootstrap-operation-google-pie-chart';
1290
1291
	/**
1292
	 * The options
1293
	 * @var array $chartOptions
1294
	 * @see https://google-developers.appspot.com/chart/interactive/docs/gallery/piechart
1295
	 */
1296
	public $chartOptions = array(
1297
		'title' => 'Google Pie Chart'
1298
	);
1299
1300
	/**
1301
	 * @var array $data the configuration data of the chart
1302
	 */
1303
	protected $data = array();
1304
1305
	/**
1306
	 * @see TbOperation
1307
	 * @return mixed|void
1308
	 */
1309
	public function displaySummary() {
1310
		
1311
		$this->data[] = array('Label', 'Percent');
1312
1313
		foreach ($this->types as $type) {
1314
			if (!isset($type['value'])) {
1315
				$type['value'] = 0;
1316
			}
1317
1318
			$this->data[] = $this->getTotal() ? array(
1319
				$type['label'],
1320
				(float)number_format(($type['value'] / $this->getTotal()) * 100, 1)
1321
			) : 0;
1322
		}
1323
		$data = CJavaScript::jsonEncode($this->data);
1324
		$options = CJavaScript::jsonEncode($this->chartOptions);
1325
		echo "<div id='{$this->id}' class='{$this->chartCssClass}' data-data='{$data}' data-options='{$options}'></div>";
1326
1327
		$this->registerClientScript();
1328
	}
1329
1330
	/**
1331
	 * Registers required scripts
1332
	 */
1333
	public function registerClientScript()
1334
	{
1335
		$chart = Yii::createComponent(
1336
			array(
1337
				'class' => 'booster.widgets.TbGoogleVisualizationChart',
1338
				'visualization' => 'PieChart',
1339
				'containerId' => $this->getId(),
1340
				'data' => $this->data,
1341
				'options' => $this->chartOptions
1342
			)
1343
		);
1344
		$chart->init();
1345
		$chart->run();
1346
1347
		/**
1348
		 * create custom chart update by using the global chart variable
1349
		 * @see TbGoogleVisualizationChart
1350
		 */
1351
		$this->column->grid->componentsAfterAjaxUpdate[__CLASS__] =
1352
			'var $el = $("#' . $this->getId() . '");var data = $el.data("data");var opts = $el.data("options");
1353
			data = google.visualization.arrayToDataTable(data);
1354
			' . $chart->getId() . '=new google.visualization.PieChart(document.getElementById("' . $this->getId() . '"));
1355
			' . $chart->getId() . '.draw(data,opts);';
1356
	}
1357
1358
}
1359
1360
/**
1361
 *## TbPercentOfTypeEasyPieOperation class
1362
 *
1363
 * Displays an chart based on jquery.easy.pie plugin
1364
 *
1365
 * @package booster.widgets.grids.operations
1366
 */
1367
class TbPercentOfTypeEasyPieOperation extends TbPercentOfTypeOperation
1368
{
1369
	/**
1370
	 * @var string $chartCssClass the class of the layer containing the class
1371
	 */
1372
	public $chartCssClass = 'bootstrap-operation-easy-pie-chart';
1373
1374
	/**
1375
	 * @var string $template
1376
	 * @see TbOperation
1377
	 */
1378
	public $template = '<div style="clear:both">{label}: </div>{types}';
1379
1380
	/**
1381
	 * @var string $typeTemplate
1382
	 * @see parent class
1383
	 */
1384
	public $typeTemplate = '<div style="float:left;text-align:center;margin:2px"><div class="{class}" data-percent="{value}">{value}%</div><div>{label}</div></div>';
1385
1386
	// easy-pie-chart plugin options
1387
	// @see https://github.com/rendro/easy-pie-chart#configuration-parameter
1388
	public $chartOptions = array(
1389
		'barColor' => '#ef1e25',
1390
		// The color of the curcular bar. You can pass either a css valid color string like rgb,
1391
		// rgba hex or string colors. But you can also pass a function that accepts the current
1392
		// percentage as a value to return a dynamically generated color.
1393
		'trackColor' => '#f2f2f2',
1394
		// The color of the track for the bar, false to disable rendering.
1395
		'scaleColor' => '#dfe0e0',
1396
		// The color of the scale lines, false to disable rendering.
1397
		'lineCap' => 'round',
1398
		// Defines how the ending of the bar line looks like. Possible values are: butt, round and square.
1399
		'lineWidth' => 5,
1400
		// Width of the bar line in px.
1401
		'size' => 80,
1402
		// Size of the pie chart in px. It will always be a square.
1403
		'animate' => false,
1404
		// Time in milliseconds for a eased animation of the bar growing, or false to deactivate.
1405
		'onStart' => 'js:$.noop',
1406
		// Callback function that is called at the start of any animation (only if animate is not false).
1407
		'onStop' => 'js:$.noop'
1408
		// Callback function that is called at the end of any animation (only if animate is not false).
1409
	);
1410
1411
	/**
1412
	 * @see TbOperation
1413
	 * @return mixed|void
1414
	 */
1415
	public function displaySummary()
1416
	{
1417
		$this->typeTemplate = strtr($this->typeTemplate, array('{class}' => $this->chartCssClass));
1418
1419
		parent::displaySummary();
1420
		$this->registerClientScripts();
1421
	}
1422
1423
	/**
1424
	 * Register required scripts
1425
	 */
1426
	protected function registerClientScripts()
1427
	{
1428
        $booster = Booster::getBooster();
1429
        $booster->registerAssetCss('easy-pie-chart.css');
1430
        $booster->registerAssetJs('jquery.easy.pie.chart.js');
1431
1432
		$options = CJavaScript::encode($this->chartOptions);
1433
		Yii::app()->getClientScript()->registerScript(
1434
			__CLASS__ . '#percent-of-type-operation-simple-pie',
1435
			'
1436
					   $("#' . $this->column->grid->id . ' .' . $this->column->grid->extendedSummaryCssClass . ' .' . $this->chartCssClass . '")
1437
				.easyPieChart(' . $options . ');
1438
		'
1439
		);
1440
		$this->column->grid->componentsReadyScripts[__CLASS__] =
1441
		$this->column->grid->componentsAfterAjaxUpdate[__CLASS__] =
1442
			'$("#' . $this->column->grid->id . ' .' . $this->column->grid->extendedSummaryCssClass . ' .' . $this->chartCssClass . '")
1443
				.easyPieChart(' . $options . ');';
1444
	}
1445
}
1446