1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35:
36:
37: Yii::import('application.components.X2GridView.massActions.*');
38:
39: abstract class MassAction extends CComponent {
40:
41: const SESSION_KEY_PREFIX = 'superMassAction';
42: const SESSION_KEY_PREFIX_PASS_CONFIRM = 'superMassActionPassConfirm';
43: const BAD_CHECKSUM = 1;
44: const BAD_ITEM_COUNT = 2;
45: const BAD_COUNT_AND_CHECKSUM = 3;
46:
47: protected static $responseForm = '';
48:
49: 50: 51: 52:
53: public $hasButton = false;
54:
55: 56: 57:
58: public $allowMultiple = true;
59:
60: 61: 62:
63: public $owner = null;
64:
65: 66: 67:
68: protected $requiresPasswordConfirmation = false;
69:
70: protected $_label;
71:
72: private $_packages;
73:
74: 75: 76:
77: abstract public function getLabel ();
78:
79: 80: 81:
82: abstract public function execute (array $gvSelection);
83:
84: public function renderDialog ($gridId, $modelName) {}
85:
86: public function beforeExecute () {
87: if ($this->getFormModel () && !$this->getFormModel ()->validate ()) {
88: $that = $this;
89: self::$responseForm = X2Widget::ajaxRender (function () use ($that) {
90: $that->renderForm (false);
91: }, true);
92: return false;
93: }
94: return true;
95: }
96:
97: 98: 99: 100:
101: public static function getMassActionObjects (array $classNames, X2GridViewBase $owner) {
102: $objs = array ();
103: foreach ($classNames as $className) {
104: $obj = new $className;
105: $obj->owner = $owner;
106: $objs[] = $obj;
107: }
108: return $objs;
109: }
110:
111: private $_formModel;
112: public function getFormModel () {
113: $formModelName = get_called_class ().'FormModel';
114: if (!in_array ($formModelName, array (
115: 'MassAddRelationshipFormModel',
116: 'MassConvertRecordFormModel'
117: )) ||
118: !class_exists ($formModelName)) {
119:
120: return null;
121: }
122: if (!isset ($this->_formModel)) {
123: $this->_formModel = new $formModelName;
124: $this->_formModel->massAction = $this;
125: if (isset ($_POST[$formModelName])) {
126: $this->_formModel->setAttributes ($_POST[$formModelName]);
127: }
128: }
129: return $this->_formModel;
130: }
131:
132: public function getModelClass () {
133: return $this->owner ? $this->owner->modelName : Yii::app()->controller->modelClass;
134: }
135:
136: public function getModelDisplayName ($plural=true) {
137: $modelClass = $this->getModelClass ();
138: return $modelClass::model ()->getDisplayName ($plural);
139: }
140:
141: public function registerPackages () {
142: Yii::app()->clientScript->registerPackages ($this->getPackages (), true);
143: }
144:
145: public function getJSClassParams () {
146: return array (
147: 'massActionName' => get_class ($this),
148: 'allowMultiple' => $this->allowMultiple,
149: );
150: }
151:
152: public function getPackages () {
153: if (!isset ($this->_packages)) {
154: $this->_packages = array (
155: 'X2MassAction' => array(
156: 'baseUrl' => Yii::app()->request->baseUrl,
157: 'js' => array(
158: 'js/X2GridView/MassAction.js',
159: ),
160: 'depends' => array ('auxlib'),
161: ),
162: );
163: }
164: return $this->_packages;
165: }
166:
167: 168: 169:
170: public static function echoResponse () {
171: echo CJSON::encode (static::getResponse ());
172: }
173:
174:
175: protected static $successFlashes = array ();
176: protected static $noticeFlashes = array ();
177: protected static $errorFlashes = array ();
178:
179: protected static function getResponse () {
180:
181: foreach (array ('notice', 'success', 'error') as $flashType) {
182: $prop = $flashType.'Flashes';
183: foreach (self::$$prop as &$flash) {
184: if (is_array ($flash)) {
185: if (!$flash['encode']) {
186: $flash = $flash['message'];
187: } else {
188: $flash = CHtml::encode ($flash['message']);
189: }
190: } else {
191: $flash = CHtml::encode ($flash);
192: }
193: }
194: }
195: return array (
196: 'form' => self::$responseForm,
197: 'notice' => self::$noticeFlashes,
198: 'success' => self::$successFlashes,
199: 'error' => self::$errorFlashes
200: );
201: }
202:
203: 204: 205:
206: public function getDialogId ($gridId) {
207: return "$gridId-".get_class ($this)."-dialog'" ;
208: }
209:
210: 211: 212:
213: public function renderButton () {
214: if (!$this->hasButton) return;
215:
216: echo "
217: <a href='#' title='".CHtml::encode ($this->getLabel ())."'
218: data-mass-action='".get_class ($this)."'
219: data-allow-multiple='".($this->allowMultiple ? 'true' : 'false')."'
220: class='mass-action-button x2-button mass-action-button-".get_class ($this)."'>
221: <span></span>
222: </a>";
223: }
224:
225: 226: 227:
228: public function renderListItem () {
229: echo "
230: <li class='mass-action-button mass-action-".get_class ($this)."'
231: data-mass-action='".get_class ($this)."'
232: data-allow-multiple='".($this->allowMultiple ? 'true' : 'false')."'".
233: ($this->hasButton ? ' style="display: none;"' : '').">
234: ".CHtml::encode ($this->getLabel ())."
235: </li>";
236: }
237:
238: 239: 240: 241:
242: public static function superMassActionPasswordConfirmation () {
243: if (!isset ($_POST['password']))
244: throw new CHttpException (400, Yii::t('app', 'Bad Request'));
245: $loginForm = new LoginForm;
246: $loginForm->username = Yii::app()->params->profile->username;
247: $loginForm->password = $_POST['password'];
248: if ($loginForm->validate ()) {
249: do {
250: $uid = EncryptUtil::secureUniqueIdHash64 ();
251: } while (isset ($_SESSION[self::SESSION_KEY_PREFIX_PASS_CONFIRM.$uid]));
252: $_SESSION[self::SESSION_KEY_PREFIX_PASS_CONFIRM.$uid] = true;
253: echo CJSON::encode (array (true, $uid));
254: } else {
255: echo CJSON::encode (array (false, Yii::t('app', 'incorrect password')));
256: }
257: }
258:
259: protected function renderForm () {}
260:
261: 262: 263: 264:
265: protected function isValidAttribute ($className, $attr) {
266: $staticModel = X2Model::model ($className);
267: return
268: ($staticModel->hasAttribute ($attr) ||
269: $attr === 'tags' && $staticModel->asa ('TagBehavior'));
270: }
271:
272: 273: 274: 275: 276:
277: protected function getIdsFromSearchResults ($modelClass) {
278:
279:
280:
281: if (isset ($_POST[$modelClass])) {
282: $_GET[$modelClass] = $_POST[$modelClass];
283:
284:
285: foreach ($_GET[$modelClass] as $attr => $val) {
286:
287: if (!$this->isValidAttribute ($modelClass, $attr)) {
288: throw new CHttpException (400, Yii::t('app', 'Bad Request'));
289: }
290: }
291: }
292:
293: if (isset ($_POST[$modelClass.'_sort'])) {
294: $_GET[$modelClass.'_sort'] = $_POST[$modelClass.'_sort'];
295:
296:
297: $sortAttr = preg_replace ('/\.desc$/', '', $_GET[$modelClass.'_sort']);
298:
299: if (!$this->isValidAttribute ($modelClass, $sortAttr)) {
300: throw new CHttpException (400, Yii::t('app', 'Bad Request'));
301: }
302: }
303:
304:
305: $model = new $modelClass ('search', null, false, true);
306: $dataProvider = $model->search (0);
307: $dataProvider->calculateChecksum = true;
308: $dataProvider->getData ();
309: $ids = $dataProvider->getRecordIds ();
310:
311: $idChecksum = $dataProvider->getidChecksum ();
312:
313:
314: $ids = array_reverse ($ids);
315:
316: return array ($ids, $idChecksum);
317: }
318:
319: 320: 321: 322: 323: 324: 325:
326: public function superExecute ($uid, $totalItemCount, $expectedIdChecksum) {
327:
328:
329:
330: if (isset ($_POST['clearSavedIds']) && $_POST['clearSavedIds']) {
331: if (!empty ($uid)) {
332: unset ($_SESSION[self::SESSION_KEY_PREFIX.$uid]);
333: unset ($_SESSION[self::SESSION_KEY_PREFIX_PASS_CONFIRM.$uid]);
334: }
335: echo 'success';
336: return;
337: }
338:
339:
340: if ($this->requiresPasswordConfirmation && (empty ($uid) ||
341: !isset ($_SESSION[self::SESSION_KEY_PREFIX_PASS_CONFIRM.$uid]) ||
342: !$_SESSION[self::SESSION_KEY_PREFIX_PASS_CONFIRM.$uid])) {
343:
344: throw new CHttpException (
345: 401, Yii::t('app', 'You are not authorized to perform this action'));
346: }
347: if (!$this->requiresPasswordConfirmation && !empty ($uid) &&
348: !isset ($_SESSION[self::SESSION_KEY_PREFIX.$uid])) {
349:
350: AuxLib::debugLogR ('Error: $uid is not empty and SESSION key is not set');
351: throw new CHttpException (400, Yii::t('app', 'Bad Request'));
352: }
353:
354: $modelClass = Yii::app()->controller->modelClass;
355:
356:
357:
358:
359:
360: if (empty ($uid) ||
361: (!isset ($_SESSION[self::SESSION_KEY_PREFIX.$uid]) &&
362: $this->requiresPasswordConfirmation)) {
363:
364: if (!$this->requiresPasswordConfirmation) {
365:
366: do {
367: $uid = uniqid (false, true);
368: } while (isset ($_SESSION[self::SESSION_KEY_PREFIX.$uid]));
369: }
370: list ($ids, $idChecksum) = $this->getIdsFromSearchResults ($modelClass);
371:
372:
373:
374:
375: if (count ($ids) !== $totalItemCount || $idChecksum !== $expectedIdChecksum) {
376: if (count ($ids) !== $totalItemCount && $idChecksum !== $expectedIdChecksum) {
377: $errorCode = self::BAD_COUNT_AND_CHECKSUM;
378: } else if (count ($ids) !== $totalItemCount) {
379: $errorCode = self::BAD_ITEM_COUNT;
380: } else {
381: $errorCode = self::BAD_CHECKSUM;
382: }
383: echo CJSON::encode (array (
384: 'failure' => true,
385: 'errorMessage' => Yii::t('app',
386: 'The data being displayed in this grid view is out of date. Close '.
387: 'this dialog and allow the grid to refresh before attempting this '.
388: 'mass action again.'),
389: 'errorCode' => $errorCode,
390: ));
391: return;
392: }
393: $_SESSION[self::SESSION_KEY_PREFIX.$uid] = $ids;
394: }
395:
396:
397:
398:
399: $selectedRecords = $_SESSION[self::SESSION_KEY_PREFIX.$uid];
400: $selectedRecordsCount = count ($selectedRecords);
401: $batchSize = Yii::app()->settings->massActionsBatchSize;
402: $batchSize = $selectedRecordsCount < $batchSize ? $selectedRecordsCount : $batchSize;
403: $batch = array ();
404: for ($i = 0; $i < $batchSize; $i++) {
405:
406:
407: $batch[] = array_pop ($selectedRecords);
408: }
409: $_SESSION[self::SESSION_KEY_PREFIX.$uid] = $selectedRecords;
410:
411:
412: $successes = $this->execute ($batch);
413:
414:
415: if (count ($selectedRecords) === 0) {
416: unset ($_SESSION[self::SESSION_KEY_PREFIX.$uid]);
417: unset ($_SESSION[self::SESSION_KEY_PREFIX_PASS_CONFIRM.$uid]);
418: }
419:
420: $response = $this->generateSuperMassActionResponse ($successes, $selectedRecords, $uid);
421:
422:
423:
424: echo CJSON::encode ($response);
425: }
426:
427: 428: 429: 430: 431:
432: protected function generateSuperMassActionResponse ($successes, $selectedRecords, $uid) {
433: $flashes = self::getResponse ();
434: $response = $flashes;
435: $response['successes'] = $successes;
436: $response['uid'] = $uid;
437: if (count ($selectedRecords) === 0) {
438: $response['complete'] = true;
439: } else {
440: $response['batchComplete'] = true;
441: }
442: return $response;
443: }
444:
445: }
446:
447: abstract class MassActionFormModel extends CFormModel {
448: public $massAction = null;
449: }
450: