1: <?php
2: /*****************************************************************************************
3: * X2Engine Open Source Edition is a customer relationship management program developed by
4: * X2Engine, Inc. Copyright (C) 2011-2016 X2Engine Inc.
5: *
6: * This program is free software; you can redistribute it and/or modify it under
7: * the terms of the GNU Affero General Public License version 3 as published by the
8: * Free Software Foundation with the addition of the following permission added
9: * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
10: * IN WHICH THE COPYRIGHT IS OWNED BY X2ENGINE, X2ENGINE DISCLAIMS THE WARRANTY
11: * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
12: *
13: * This program is distributed in the hope that it will be useful, but WITHOUT
14: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15: * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
16: * details.
17: *
18: * You should have received a copy of the GNU Affero General Public License along with
19: * this program; if not, see http://www.gnu.org/licenses or write to the Free
20: * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21: * 02110-1301 USA.
22: *
23: * You can contact X2Engine, Inc. P.O. Box 66752, Scotts Valley,
24: * California 95067, USA. or at email address contact@x2engine.com.
25: *
26: * The interactive user interfaces in modified source and object code versions
27: * of this program must display Appropriate Legal Notices, as required under
28: * Section 5 of the GNU Affero General Public License version 3.
29: *
30: * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
31: * these Appropriate Legal Notices must retain the display of the "Powered by
32: * X2Engine" logo. If the display of the logo is not reasonably feasible for
33: * technical reasons, the Appropriate Legal Notices must display the words
34: * "Powered by X2Engine".
35: *****************************************************************************************/
36:
37: /**
38: * CActiveDataProvider with persistent sort order and filters and optional id checksum calculation
39: *
40: * @package application.components
41: */
42: class SmartActiveDataProvider extends CActiveDataProvider {
43:
44: public $uid = null;
45: public $dbPersistentGridSettings = false;
46: public $disablePersistentGridSettings = false;
47:
48: /**
49: * @var bool $calculateChecksum If true, id checksum will be calculated when data is fetched.
50: * This also disables the ability to refresh the data provider
51: */
52: public $calculateChecksum = false;
53:
54: /**
55: * @var string $_idChecksum checksum of ids joined by commas
56: */
57: private $_idChecksum;
58:
59: /**
60: * @var array record ids used to calculate the checksum
61: */
62: private $_recordIds;
63:
64: private $_pagination;
65: private $_countCriteria;
66:
67: /**
68: * Overrides parent __construct ().
69: * @param string $uid (optional) If set, will be used to uniquely identify this data
70: * provider. This overrides the default behavior of using the model name as the uid.
71: *
72: * This method is Copyright (c) 2008-2014 by Yii Software LLC
73: * http://www.yiiframework.com/license/
74: */
75: public function __construct($modelClass,$config=array()) {
76: if (isset ($config['dbPersistentGridSettings'])) {
77: $this->dbPersistentGridSettings = $config['dbPersistentGridSettings'];
78: }
79: if (isset ($config['disablePersistentGridSettings'])) {
80: $this->disablePersistentGridSettings = $config['disablePersistentGridSettings'];
81: }
82: if (isset ($config['uid'])) {
83: $this->uid = $config['uid'];
84: }
85: if(is_string($modelClass))
86: {
87: $this->modelClass=$modelClass;
88: $this->model=$this->getModel($this->modelClass);
89: if (property_exists ($this->model, 'uid')) {
90: $this->model->uid = $this->uid;
91: }
92: }
93: elseif($modelClass instanceof CActiveRecord)
94: {
95: $this->modelClass=get_class($modelClass);
96: $this->model=$modelClass;
97: }
98: /* x2modstart */
99: $this->attachBehaviors (array (
100: 'SmartDataProviderBehavior' => array (
101: 'class' => 'SmartDataProviderBehavior',
102: 'settingsBehavior' => ($this->dbPersistentGridSettings ?
103: 'GridViewDbSettingsBehavior' : 'GridViewSessionSettingsBehavior'),
104: )
105: ));
106: /* x2modend */
107:
108:
109: /* x2modstart */
110: if ($this->uid !== null) {
111: $this->setId($this->uid);
112: } else {
113: /* x2modend */
114: $this->setId(CHtml::modelName($this->model));
115: /* x2modstart */
116: }
117: /* x2modend */
118:
119: foreach($config as $key=>$value)
120: $this->$key=$value;
121:
122: /* x2modstart */
123: $this->storeSettings ();
124: /* x2modend */
125: }
126:
127: /**
128: * Returns the pagination object.
129: * @return CPagination the pagination object. If this is false, it means the pagination is
130: * disabled.
131: */
132: public function getPagination() {
133: if($this->_pagination===null) {
134: $this->_pagination = $this->getSmartPagination ();
135: }
136: return $this->_pagination;
137: }
138:
139: /**
140: * Overrides parent fetchData ().
141: * Fetches the data from the persistent data storage.
142: *
143: * Modified to always sort by id DESC as well as the chosen sort
144: * @return array list of data items
145: * @throws CException if checksum couldn't be generated correctly
146: *
147: * This method is Copyright (c) 2008-2014 by Yii Software LLC
148: * http://www.yiiframework.com/license/
149: */
150: protected function fetchData() {
151: $criteria=clone $this->getCriteria();
152: /* x2modstart */
153: $criteria->with = array();
154: /* x2modend */
155:
156: if(($pagination=$this->getPagination())!==false) {
157: $pagination->setItemCount($this->getTotalItemCount());
158: $pagination->applyLimit($criteria);
159: }
160:
161: // prevent side effects to model's criteria by cloning criteria and then restoring
162: $baseCriteria=$this->model->getDbCriteria(false);
163:
164: if(($sort=$this->getSort())!==false) {
165: // set model criteria so that CSort can use its table alias setting
166: if($baseCriteria!==null) {
167: $c=clone $baseCriteria;
168: $c->mergeWith($criteria);
169: $this->model->setDbCriteria($c);
170: } else
171: $this->model->setDbCriteria($criteria);
172: $sort->applyOrder($criteria);
173: }
174:
175: /* x2modstart */
176: $orderBy = $criteria->order;
177: if(!preg_match('/\bid\b/',$orderBy)) {
178: if(!empty($orderBy))
179: $orderBy .= ',';
180: $orderBy .= 't.id DESC';
181: $criteria->order = $orderBy;
182: }
183: /* x2modend */
184:
185: $this->model->setDbCriteria($baseCriteria!==null ? clone $baseCriteria : null);
186: $data=$this->model->findAll($criteria);
187: $this->model->setDbCriteria($baseCriteria); // restore original criteria
188:
189: /* x2modstart */
190: // using the same criteria, calculate and save the checksum of the ids
191: $this->model->setDbCriteria($baseCriteria!==null ? clone $baseCriteria : null);
192: if ($this->calculateChecksum) {
193: ini_set ('memory_limit', -1);
194: $critClone = clone $criteria;
195: $critClone->limit = -1;
196: $critClone->offset = -1;
197: $critClone->select = array ('t.id');
198: $command = $this->model->findAll ($critClone, array (), true);
199: if (!$command) {
200: throw new CException ('could not generate checksum, invalid command');
201: }
202: $ids = $command->queryColumn ();
203:
204: // attempt to verify ids array
205: if (count ($ids) !== intval ($this->totalItemCount)) {
206: throw new CException ('could not generate checksum');
207: }
208: $this->_idChecksum = $this->calculateChecksumFromIds ($ids);
209: $this->_recordIds = $ids;
210: }
211: $this->model->setDbCriteria($baseCriteria); // restore original criteria
212: /* x2modend */
213:
214: return $data;
215: }
216:
217: /* x2modstart */
218:
219: /**
220: * @throws CException if fetchData has not yet been called
221: */
222: public function getIdChecksum () {
223: if (!$this->calculateChecksum) {
224: throw new CException ('calculcateChecksum is set to false');
225: }
226: if (!isset ($this->_idChecksum)) {
227: throw new CException ('fetchData must be called before getting the id checksum');
228: }
229: return $this->_idChecksum;
230: }
231:
232: /**
233: * @throws CException if fetchData has not yet been called
234: */
235: public function getRecordIds () {
236: if (!$this->calculateChecksum) {
237: throw new CException ('calculcateChecksum is set to false');
238: }
239: if (!isset ($this->_recordIds)) {
240: throw new CException ('fetchData must be called before getting the id checksum');
241: }
242: return $this->_recordIds;
243: }
244: /* x2modend */
245:
246: /**
247: * Returns the data items currently available.
248: * @param boolean $refresh whether the data should be re-fetched from persistent storage.
249: * @return array the list of data items currently available in this data provider.
250: */
251: public function getData($refresh=false)
252: {
253: /* x2modstart */
254: if ($this->calculateChecksum && $refresh) {
255: throw new CException ('refresh cannot be called if calculcateChecksum is set to true');
256: }
257: /* x2modend */
258: return parent::getData ($refresh);
259: }
260:
261: /**
262: * Returns the key values associated with the data items.
263: * @param boolean $refresh whether the keys should be re-calculated.
264: * @return array the list of key values corresponding to {@link data}. Each data item in
265: * {@link data}
266: * is uniquely identified by the corresponding key value in this array.
267: */
268: public function getKeys($refresh=false)
269: {
270: /* x2modstart */
271: if ($this->calculateChecksum && $refresh) {
272: throw new CException ('refresh cannot be called if calculcateChecksum is set to true');
273: }
274: /* x2modend */
275: return parent::getKeys ($refresh);
276: }
277:
278: /**
279: * Returns the total number of data items.
280: * When {@link pagination} is set false, this returns the same value as {@link itemCount}.
281: * @param boolean $refresh whether the total number of data items should be re-calculated.
282: * @return integer total number of possible data items.
283: */
284: public function getTotalItemCount($refresh=false)
285: {
286: /* x2modstart */
287: if ($this->calculateChecksum && $refresh) {
288: throw new CException ('refresh cannot be called if calculcateChecksum is set to true');
289: }
290: /* x2modend */
291: return parent::getTotalItemCount ($refresh);
292: }
293:
294: /**
295: * Generates the item count without eager loading, to improve performance.
296: * @return type
297: */
298: public function calculateTotalItemCount(){
299: if($this->model instanceof X2Model) {
300: if(!isset($this->_countCriteria)) {
301: $this->_countCriteria = clone $this->getCriteria();
302: $this->_countCriteria->with = array();
303: }
304: return X2Model::model($this->modelClass)->count($this->_countCriteria);
305: }else{
306: return parent::calculateTotalItemCount();
307: }
308: }
309:
310: /* x2modstart */
311: /**
312: * @param array ids array on integers
313: * @return string
314: */
315: public static function calculateChecksumFromIds (array $ids) {
316: return md5 (implode (',', $ids));
317: }
318: /* x2modend */
319:
320: /* x2modstart */
321: /**
322: * Applies default order to criteria
323: */
324: private function applyDefaultOrder ($criteria) {
325: $orderBy = $criteria->order;
326: if(!preg_match('/\bid\b/',$orderBy)) {
327: if(!empty($orderBy))
328: $orderBy .= ',';
329: $orderBy .= 't.id DESC';
330: $criteria->order = $orderBy;
331: }
332: }
333: /* x2modend */
334:
335: /**
336: * Moved out of fetchData so that it could be reused elsewhere
337: */
338: private function applySortOrder ($criteria) {
339: if(($sort=$this->getSort())!==false) {
340: /* x2modstart */
341: // prevent side effects to model's criteria by cloning & modifying it, then restoring
342: // to original.
343: $baseCriteria=$this->model->getDbCriteria(false);
344: /* x2modend */
345:
346: // set model criteria so that CSort can use its table alias setting
347: if($baseCriteria!==null) {
348: $c=clone $baseCriteria;
349: $c->mergeWith($criteria);
350: $this->model->setDbCriteria($c);
351: } else
352: $this->model->setDbCriteria($criteria);
353: $sort->applyOrder($criteria);
354:
355: /* x2modstart */
356: $this->model->setDbCriteria($baseCriteria);
357: /* x2modend */
358: }
359: }
360:
361: }
362: