Overview

Packages

  • application
    • commands
    • components
      • actions
      • filters
      • leftWidget
      • permissions
      • sortableWidget
      • util
      • webupdater
      • x2flow
        • actions
        • triggers
      • X2GridView
      • X2Settings
    • controllers
    • models
      • embedded
    • modules
      • accounts
        • controllers
        • models
      • actions
        • controllers
        • models
      • calendar
        • controllers
        • models
      • charts
        • models
      • contacts
        • controllers
        • models
      • docs
        • components
        • controllers
        • models
      • groups
        • controllers
        • models
      • marketing
        • components
        • controllers
        • models
      • media
        • controllers
        • models
      • mobile
        • components
      • opportunities
        • controllers
        • models
      • products
        • controllers
        • models
      • quotes
        • controllers
        • models
      • services
        • controllers
        • models
      • template
        • models
      • users
        • controllers
        • models
      • workflow
        • controllers
        • models
      • x2Leads
        • controllers
        • models
  • None
  • system
    • base
    • caching
    • console
    • db
      • ar
      • schema
    • validators
    • web
      • actions
      • auth
      • helpers
      • widgets
        • captcha
        • pagers
  • zii
    • widgets
      • grid

Classes

  • WorkflowController
  • Overview
  • Package
  • Class
  • Tree
  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:  * @package application.modules.workflow.controllers 
 39:  */
 40: class WorkflowController extends x2base {
 41:     public $modelClass="Workflow";
 42: 
 43:     /**
 44:      * Specifies the access control rules.
 45:      * This method is used by the 'accessControl' filter.
 46:      * @return array access control rules
 47:      */
 48:     public function accessRules() {
 49:         return array(
 50:             array('allow', // allow authenticated user to perform 'create' and 'update' actions
 51:                 'actions'=>array(
 52:                     'index','view','getWorkflow','getStageDetails','updateStageDetails',
 53:                     'startStage','completeStage','revertStage','getStageMembers','getStages',
 54:                     'changeUi', 'addADeal', 'getStageNameItems', 'getStageNames'),
 55:                 'users'=>array('@'),
 56:             ),
 57:             array('allow', // allow admin user to perform 'admin' and 'delete' actions
 58:                 'actions'=>array('admin','create','update','delete'),
 59:                 'users'=>array('admin'),
 60:             ),
 61:             array('deny',  // deny all users
 62:                 'users'=>array('*'),
 63:             ),
 64:         );
 65:     }
 66:     
 67:     // Lists all workflows
 68:     public function actionIndex() {
 69:         $dataProvider = new CActiveDataProvider('Workflow');
 70:         $this->render('index',array(
 71:             'dataProvider'=>$dataProvider,
 72:         ));
 73:     }
 74:     
 75:     public function actionAdmin(){
 76:         $this->redirect('index');
 77:     }
 78: 
 79:     /**
 80:      * Displays workflow table/funnel diagram or the pipeline
 81:      * @param int $id id of the workflow record
 82:      */
 83:     public function actionView($id) {
 84: 
 85:         // check for optional GET param, if it's not set, use the profile settings
 86:         if (!isset ($_GET['perStageWorkflowView'])) {
 87:             $perStageWorkflowView = 
 88:                 Yii::app()->params->profile->miscLayoutSettings['perStageWorkflowView'];
 89:         } else {
 90:             $perStageWorkflowView = $_GET['perStageWorkflowView']; 
 91:             if ($perStageWorkflowView !== 
 92:                 Yii::app()->params->profile->miscLayoutSettings['perStageWorkflowView']) {
 93: 
 94:                 $perStageWorkflowView = $perStageWorkflowView === 'true' ? true : false;
 95:                 Profile::setMiscLayoutSetting (
 96:                     'perStageWorkflowView', $perStageWorkflowView, true);
 97:             }
 98:         }
 99: 
100:         $users=isset($_GET['users'])?$_GET['users']:''; 
101: 
102:         if(isset($_GET['stage']) && is_numeric($_GET['stage']))
103:             $viewStage = $_GET['stage'];
104:         else
105:             $viewStage = null;
106: 
107:         // add workflow to user's recent item list
108:         User::addRecentItem('w', $id, Yii::app()->user->getId()); 
109: 
110:         $workflows = Workflow::getList(false);  // no "none" options
111: 
112:         $this->render('view',
113:             array_merge (
114:                 array (
115:                     'perStageWorkflowView' => $perStageWorkflowView,
116:                     'workflows' => $workflows,
117:                 ),
118:                 ($perStageWorkflowView ? 
119:                     $this->getPerStageViewParams ($id, $viewStage, $users) :
120:                     $this->getDragAndDropViewParams ($id, $users)
121:                 )
122:             )
123:         );
124:     }
125:     
126:     // Creates a new Workflow model
127:     // Creates 1 or more associated WorkflowStage models
128:     // If creation is successful, the browser will be redirected to the 'view' page.
129:     public function actionCreate() {
130:         $workflowModel = new Workflow;
131:         $workflowModel->lastUpdated = time();
132: 
133:         $stages = array();
134:         
135:         $workflowAttr = filter_input(INPUT_POST,'Workflow', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
136:         $workflowStages = filter_input(INPUT_POST,'WorkflowStages', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
137:         if(!empty($workflowAttr) && !empty($workflowStages)) {
138:         
139:         
140:             $workflowModel->attributes = $workflowAttr;
141:             $colors = filter_input(INPUT_POST, 'colors', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
142:             if (!empty($colors)) {
143:                 $colors =  array_filter ($colors, function ($a) { return $a !== ''; });
144:                 $workflowModel->colors = $colors;
145:             }
146: 
147:             if($workflowModel->save()) {
148:                 $validStages = true;
149:                 for($i=0; $i<count($workflowStages); $i++) {
150:                     
151:                     $stages[$i] = new WorkflowStage;
152:                     $stages[$i]->workflowId = $workflowModel->id;
153:                     $stages[$i]->attributes = $workflowStages[$i+1];
154:                     $stages[$i]->stageNumber = $i+1;
155:                     $stages[$i]->roles = $workflowStages[$i+1]['roles'];
156:                     if(empty($stages[$i]->roles) || in_array('',$stages[$i]->roles))
157:                         $stages[$i]->roles = array();
158: 
159:                     if(!$stages[$i]->validate())
160:                         $validStages = false;
161:                 }
162: 
163:                 if($validStages) {
164: 
165:                     foreach($stages as &$stage) {
166:                         $stage->save();
167:                         foreach($stage->roles as $roleId)
168:                             Yii::app()->db->createCommand()->insert('x2_role_to_workflow',array(
169:                                 'stageId'=>$stage->id,
170:                                 'roleId'=>$roleId,
171:                                 'workflowId'=>$workflowModel->id,
172:                             ));
173:                     }
174:                     if($workflowModel->save())
175:                         $this->redirect(array('view','id'=>$workflowModel->id));
176:                 }
177:             }
178:         }
179: 
180:         $this->render('create',array(
181:             'model'=>$workflowModel,
182:         ));
183:     }
184: 
185:     // Updates a particular model
186:     // Deletes and recreates all associated WorkflowStage models
187:     // If update is successful, the browser will be redirected to the 'view' page.
188:     public function actionUpdate($id) {
189:         $workflowModel = $this->loadModel($id);
190: 
191:         $workflowAttr = filter_input(INPUT_POST,'Workflow', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
192:         $workflowStages = filter_input(INPUT_POST,'WorkflowStages', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
193:         if(!empty($workflowAttr) && !empty($workflowStages)) {
194: 
195:             list($validStages, $newStages, $forDeletion) = $workflowModel->updateStages($workflowStages);
196: 
197:             $workflowModel->attributes = $workflowAttr;
198:             $workflowModel->lastUpdated = time();
199:             $colors = filter_input(INPUT_POST, 'colors', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
200:             if (!empty($colors)) {
201:                 $colors =  array_filter ($colors, function ($a) { return $a !== ''; });
202:                 $workflowModel->colors = $colors;
203:             }
204:             
205:             if($validStages && $workflowModel->validate()) {
206:             
207:                 WorkflowStage::model()->deleteByPk($forDeletion);
208:                 
209:                 // delete role stuff too
210:                 Yii::app()->db->createCommand()->delete('x2_role_to_workflow','workflowId='.$id);    
211:                 foreach($newStages as &$stage) {
212:                     $stage->save();
213:                     foreach($stage->roles as $roleId){
214:                         Yii::app()->db->createCommand()->insert('x2_role_to_workflow',array(
215:                             'stageId'=>$stage->id,
216:                             'roleId'=>$roleId,
217:                             'workflowId'=>$id,
218:                         ));
219:                     }
220:                 }
221:                 if ($workflowModel->save()){
222:                     $this->redirect(array('view','id'=>$workflowModel->id));
223:                 }
224:             }
225:         }
226:         $this->render('update',array(
227:             'model'=>$workflowModel,
228:         ));
229:     }
230: 
231:     // Deletes Workflow model
232:     // Associated WorkflowStage models will automatically be deleted by the DB
233:     public function actionDelete($id) {
234:         if(Yii::app()->request->isPostRequest) {
235:             // we only allow deletion via POST request
236:             $this->loadModel($id)->delete();
237: 
238:             /* if AJAX request (triggered by deletion via admin grid view), we should not 
239:                redirect the browser */
240:             if(!isset($_GET['ajax']))
241:                 $this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('index'));
242:         }
243:         else
244:             throw new CHttpException(
245:                 400,'Invalid request. Please do not repeat this request again.');
246:     }
247: 
248:     /**
249:      * Render funnel for inline workflow widget
250:      */
251:     public function actionGetWorkflow($workflowId,$modelId,$type) {
252:         assert (is_numeric($workflowId));
253:         assert (is_numeric($modelId));
254:     
255:         if(is_numeric($workflowId) && is_numeric($modelId)) {
256:             //$workflowStatus = Workflow::getWorkflowStatus($workflowId,$modelId,$type);
257:             // echo var_dump($workflowStatus);
258:             //echo Workflow::renderWorkflow($workflowStatus);
259: 
260:             $workflowStatus = Workflow::getWorkflowStatus($workflowId, $modelId, $type);
261:             if (sizeof ($workflowStatus['stages']) < 1) return;
262: 
263:             $model = $this->loadModel($workflowId);
264:             $colors = $model->getWorkflowStageColors (sizeof ($workflowStatus['stages']));
265: 
266:             $this->renderPartial ('_inlineFunnel', array (
267:                 'workflowStatus' => $workflowStatus,
268:                 'stageCount' => sizeof ($workflowStatus['stages']),
269:                 'colors' => $colors,
270:             ), false, true);
271:         }
272:     }
273: 
274:     /**
275:      * Render funnel for workflow view 
276:      */
277:     public function renderFunnelView (
278:         $workflowId, $dateRange, $users='', $modelId=0, 
279:         $modelType='') {
280: 
281:         $workflowStatus = Workflow::getWorkflowStatus($workflowId, $modelId, $modelType);
282:         $stageCounts = Workflow::getStageCounts (
283:             $workflowStatus, $dateRange, $users, $modelType);
284:         $stageValues = Workflow::getStageValues(
285:             $workflowStatus, $dateRange, $users, $modelType);
286: 
287:         $model = $this->loadModel($workflowId);
288:         $colors = $model->getWorkflowStageColors (sizeof ($stageCounts));
289: 
290:         $stageNameLinks = Workflow::getStageNameLinks (
291:             $workflowStatus, $dateRange, $users);
292: 
293:         $this->renderPartial ('_funnel', array (
294:             'workflowStatus' => $workflowStatus,
295:             'recordsPerStage' => $stageCounts,
296:             'stageCount' => sizeof ($stageCounts),
297:             'stageValues' => array_map (
298:                 function ($a) { return (is_null($a) ? '' : Formatter::formatCurrency ($a)); }, $stageValues),
299:             'totalValue' => Formatter::formatCurrency (array_sum ($stageValues)),
300:             'stageNameLinks' => $stageNameLinks,    
301:             'colors' => $colors,
302:         ));
303:     
304:     }
305: 
306:     public function renderInlineFunnelView ($workflowId, $modelId=0, $type='') {
307:         $workflowStatus = Workflow::getWorkflowStatus($workflowId,$modelId,$type);
308:     }
309:     
310:     
311:     public function actionGetStageDetails($workflowId,$stage,$modelId,$type) {
312:         if(is_numeric($workflowId) && is_numeric($stage) && is_numeric($modelId)) {
313: 
314:             $workflowStatus = Workflow::getWorkflowStatus($workflowId,$modelId,$type);
315:             $stageArr = $workflowStatus['stages'][$stage];
316:             if(isset($stageArr)) {
317:                 $model = X2Model::model('Actions')->findByAttributes(array(
318:                     'associationId'=>$modelId,
319:                     'associationType'=>$type,
320:                     'type'=>'workflow',
321:                     'workflowId'=>$workflowId,
322:                     'stageNumber'=>$stageArr['id']
323:                 ));
324:                 
325:                 if($model->complete != 'Yes')
326:                     $model->completedBy = Yii::app()->user->name;
327:                 
328:                 $editable = true;    // default is full permission for everybody
329:                 
330:                 // if roles are specified, check if user has any of them
331:                 if(!empty($stageArr['roles'])) {    
332:                     $editable = count(array_intersect(Yii::app()->params->roles, 
333:                         $stageArr['roles'])) > 0;
334:                 }
335: 
336:                 // if the workflow backdate window isn't unlimited, check if the window has passed
337:                 if(Yii::app()->settings->workflowBackdateWindow > 0 && 
338:                    (time() - $model->completeDate) > 
339:                         Yii::app()->settings->workflowBackdateWindow) {
340:                     $editable = false;
341:                 }
342:                     
343:                 if(Yii::app()->user->checkAccess('WorkflowAdmin') || Yii::app()->params->isAdmin)
344:                     $editable = true;
345:                     
346:                 $minDate = Yii::app()->settings->workflowBackdateRange;
347:                 if($minDate < 0)
348:                     $minDate = null;    // if workflowBackdateRange = -1, no limit on backdating
349:                 else
350:                     $minDate = '-'.$minDate;    // otherwise, we can only go back this far
351:                     
352:                 if(Yii::app()->user->checkAccess('WorkflowAdmin') || Yii::app()->params->isAdmin)
353:                     $minDate = null;
354: 
355:                 $this->renderPartialAjax('_workflowDetail',array(
356:                     'model'=>$model,
357:                     'editable'=>$editable,
358:                     'minDate'=>$minDate,
359:                     'allowReassignment'=>
360:                         Yii::app()->settings->workflowBackdateReassignment || 
361:                             Yii::app()->params->isAdmin,
362:                 ),false);
363:             }
364:         }
365:     }
366:     
367:     public function actionUpdateStageDetails($id) {
368: 
369:         $action = X2Model::model('Actions')->findByPk($id);
370:         $previouslyComplete = $action->complete === 'Yes';
371:         
372:         $model = X2Model::getModelOfTypeWithId(
373:             $action->associationType,$action->associationId, true);
374:         
375:         if(isset($model,$action,$_POST['Actions'])) {
376:             $action->setScenario('workflow');
377: 
378:             $action->createDate = Formatter::parseDate($_POST['Actions']['createDate']);
379:             $action->completeDate = Formatter::parseDate($_POST['Actions']['completeDate']);
380:             $action->actionDescription = $_POST['Actions']['actionDescription'];
381: 
382:             if(isset($_POST['Actions']['completedBy']) && 
383:                (Yii::app()->params->isAdmin || 
384:                 Yii::app()->settings->workflowBackdateReassignment)) {
385:                 $action->completedBy = $_POST['Actions']['completedBy'];
386:             }
387: 
388:             // don't save if createDate isn't valid
389:             if($action->createDate === false)
390:                 return;
391:             
392:             if($action->completeDate === false) {
393:                 $action->complete = 'No';
394:                 $action->completedBy = null;
395:                 
396:                 $model->updateLastActivity();
397:                 
398:                 if($previouslyComplete)    // we're uncompleting this thing
399:                     Workflow::updateWorkflowChangelog($action,'revert',$model);
400:                 
401:                 unset ($action->completeDate); // remove invalid value
402:             } else {
403:                 if($action->completeDate < $action->createDate) {
404:                     // we can't have the completeDate before the createDate now can we
405:                     $action->completeDate = $action->createDate;    
406:                 }
407:                 $action->complete = 'Yes';
408:                 
409:                 if(!$previouslyComplete)    // we're completing it
410:                     Workflow::updateWorkflowChangelog($action,'complete',$model);
411:             }
412:             if ($action->save()) {
413:                 if ($action->complete === 'Yes') {
414:                     echo 'complete';
415:                 } else {
416:                     echo 'success';
417:                 }
418:             }
419:         }
420: 
421:     }
422: 
423:     /**
424:      * Moves a record up or down through a workflow. Called via AJAX.
425:      */
426:     public function actionMoveFromStageAToStageB () {
427:         if(!(isset($_POST['workflowId']) && isset ($_POST['stageA']) && 
428:            isset ($_POST['stageB']) && isset ($_POST['modelId']) && isset ($_POST['type']) && 
429:            (!isset ($_POST['comments']) || is_array ($_POST['comments'])))) {
430: 
431:             throw new CHttpException(
432:                 400, 'Invalid request. Please do not repeat this request again.');
433:         }
434: 
435:         $workflowId = (int) $_POST['workflowId'];
436:         $stageA = (int) $_POST['stageA'];
437:         $stageB = (int) $_POST['stageB'];
438:         $modelId = (int) $_POST['modelId'];
439:         $comments = isset ($_POST['comments']) ? $_POST['comments'] : array ();
440:         $type = $_POST['type'];
441:         $model = X2Model::getModelOfTypeWithId($type,$modelId, true);
442: 
443:         if ($stageA === $stageB || $model === null) {
444:             echo 'failure';
445:             return;
446:         }
447: 
448:         $retVal = Workflow::moveFromStageAToStageB (
449:             $workflowId, $stageA, $stageB, $model, $comments);
450:         echo CJSON::encode (array (
451:             'workflowStatus' => Workflow::getWorkflowStatus ($workflowId, $modelId, $type),
452:             'flashes' => array ('error' => isset ($retVal[1]) ? array ($retVal[1]) : array ())
453:         ));
454:     }
455: 
456:     public function actionStartStage(
457:         $workflowId,$stageNumber,$modelId,$type) {
458: 
459:         $model = $this->validateParams ($workflowId, $stageNumber, $modelId, $type);
460: 
461:         $workflowStatus = Workflow::getWorkflowStatus($workflowId,$model->id,$type);
462:         $message = '';
463:         if (Workflow::validateAction (
464:             'start', $workflowStatus, $stageNumber, '', $message)) {
465: 
466:             list ($started, $workflowStatus) = 
467:                 Workflow::startStage (
468:                     $workflowId, $stageNumber, $model, $workflowStatus);
469:             assert ($started);
470:         }
471:         echo CJSON::encode (array (
472:             'workflowStatus' => $workflowStatus,
473:             'flashes' => array (
474:                 'error' => !empty ($message) ? array ($message) : array (),
475:                 'success' => empty ($message) ? 
476:                     array (Yii::t('workflow', 'Stage started')) : array (),
477:             )
478:         ));
479:     }
480: 
481:     /**
482:      * Helper method for action<workflow action> actions to validate get parameters and to 
483:      * retrieve the associated model.
484:      * @param int $workflowId the id of the workflow 
485:      * @param int $stageNumber the number of the stage
486:      * @param int $modelId the id of the associated model
487:      * @param string $type the association type of the associated model
488:      * @return object model with specified id and associationType
489:      */
490:     private function validateParams ($workflowId,$stageNumber,$modelId,$type,$recordName=null) {
491:         if(!is_numeric($workflowId) || !is_numeric($stageNumber) || 
492:            (!is_numeric($modelId) && $recordName === null)) {
493:             throw new CHttpException (400, 'Bad Request');
494:         }
495: 
496:         if (!is_numeric ($modelId)) {
497:             $model = X2Model::getModelOfTypeWithName($type,$recordName);
498:         } else {
499:             $model = X2Model::getModelOfTypeWithId($type,$modelId, true);
500:         }
501: 
502:         if ($model === null) throw new CHttpException (400, 'Bad Request');
503:         return $model;
504:     }
505: 
506:     /**
507:      * Called via ajax to add a deal to the pipeline
508:      * @param int $workflowId the id of the workflow 
509:      * @param int $stageNumber the number of the stage
510:      * @param int $modelId the id of the associated model
511:      * @param string $type the association type of the associated model
512:      */
513:     public function actionAjaxAddADeal (
514:         $workflowId,$stageNumber,$modelId=null,$type,$recordName=null) {
515: 
516:         $model = $this->validateParams ($workflowId, $stageNumber, $modelId, $type,$recordName);
517:         $workflowStatus = Workflow::getWorkflowStatus($workflowId,$model->id,$type);
518:         $message = '';
519:         if (Workflow::validateAction (
520:             'start', $workflowStatus, $stageNumber, '', $message)) {
521: 
522:             list ($started, $workflowStatus) = 
523:                 Workflow::startStage (
524:                     $workflowId, $stageNumber, $model, $workflowStatus);
525:             assert ($started);
526:         }
527:         echo CJSON::encode (array (
528:             'workflowStatus' => $workflowStatus,
529:             'flashes' => array (
530:                 'error' => !empty ($message) ? array ($message) : array (),
531:                 'success' => empty ($message) ? 
532:                     array (Yii::t('workflow', 'Stage started')) : array (),
533:             )
534:         ));
535:     }
536:     
537:     public function actionCompleteStage(
538:         $workflowId,$stageNumber,$modelId,$type,$comment='') {
539: 
540:         $model = $this->validateParams ($workflowId, $stageNumber, $modelId, $type);
541: 
542:         $workflowStatus = Workflow::getWorkflowStatus($workflowId,$modelId,$type);
543:         $message = '';
544:         if (Workflow::validateAction (
545:             'complete', $workflowStatus, $stageNumber, $comment, $message)) {
546: 
547:             list ($completed, $workflowStatus) = Workflow::completeStage (
548:                 $workflowId, $stageNumber, $model, $comment, true);
549:         }
550: 
551:         // $record=X2Model::model(ucfirst($type))->findByPk($modelId);
552:         // if($record->hasAttribute('lastActivity')){
553:             // $record->lastActivity=time();
554:             // $record->save();
555:         // }
556: 
557:         echo CJSON::encode (array (
558:             'workflowStatus' => $workflowStatus,
559:             'flashes' => array (
560:                 'error' => !empty ($message) ? array ($message) : array (),
561:                 'success' => empty ($message) ? 
562:                     array (Yii::t('workflow', 'Stage completed')) : array (),
563:             )
564:         ));
565:     }
566: 
567:     public function actionRevertStage($workflowId,$stageNumber,$modelId,$type) {
568:         $model = $this->validateParams ($workflowId, $stageNumber, $modelId, $type);
569: 
570:         if ($model === null) throw new CHttpException (400, 'Bad Request');
571:         $workflowStatus = Workflow::getWorkflowStatus($workflowId,$modelId,$type);
572:         $message = '';
573:         if (Workflow::validateAction (
574:             'revert', $workflowStatus, $stageNumber, '', $message)) {
575: 
576:             list ($completed, $workflowStatus) = Workflow::revertStage (
577:                 $workflowId, $stageNumber, $model);
578:         }
579: 
580:         echo CJSON::encode (array (
581:             'workflowStatus' => $workflowStatus,
582:             'flashes' => array (
583:                 'error' => !empty ($message) ? array ($message) : array (),
584:                 'success' => empty ($message) ? 
585:                     array (Yii::t('workflow', 'Stage reverted')) : array (),
586:             )
587:         ));
588:     }
589: 
590: 
591:     /**
592:      * Get a data provider for members of a given type in a given stage
593:      * @param string $type {'contacts', 'opportunities', 'accounts'}
594:      */
595:     public function getStageMemberDataProvider (
596:         $type, $workflowId, $dateRange, $stage, $user) {
597: 
598:         $modelName = X2Model::getModelName ($type);
599:         if(!$modelName){
600:             $type = 'contacts';
601:             $modelName = 'Contacts';
602:         }
603:         $model = X2Model::model ($modelName);
604:         $tableName = $model->tableName ();
605:         $stageModel = WorkflowStage::model()->findByAttributes(array(
606:             'workflowId' => $workflowId,
607:             'stageNumber' => $stage,
608:         ));
609:         $params = array (
610:             ':workflowId' => $workflowId,
611:             ':stage' => $stageModel->id,
612:             ':start' => $dateRange['start'],
613:             ':end' => $dateRange['end'],
614:             ':type' => $type,
615:         );
616: 
617:         if(!empty($user)){
618:             $userString=" AND x2_actions.assignedTo=:user ";
619:             $params[':user'] = $user;
620:         }else{
621:             $userString="";
622:         }
623: 
624:         list ($accessCondition, $accessConditionParams) = 
625:             $modelName::model ()->getAccessSQLCondition ($tableName);
626:         $params = array_merge ($params, $accessConditionParams);
627:         $stageMemberSql = Yii::app()->db->createCommand()
628:             ->select("$tableName.*, x2_actions.lastUpdated as actionLastUpdated")
629:             ->from($tableName)
630:             ->join('x2_actions',"$tableName.id = x2_actions.associationId")
631:             ->where("x2_actions.workflowId=:workflowId AND x2_actions.stageNumber=:stage AND
632:                 x2_actions.associationType=:type AND complete!='Yes' AND 
633:                 (completeDate IS NULL OR completeDate=0) AND 
634:                 x2_actions.createDate BETWEEN :start AND :end ".$userString." AND ".$accessCondition
635:                 )
636:             ->getText();
637: 
638:         $memberCount = Yii::app()->db->createCommand()
639:             ->select("COUNT(*)")
640:             ->from($tableName)
641:             ->join('x2_actions',"$tableName.id = x2_actions.associationId")
642:             ->where("x2_actions.workflowId=:workflowId AND x2_actions.stageNumber=:stage AND
643:                 x2_actions.associationType=:type AND complete!='Yes' AND 
644:                 (completeDate IS NULL OR completeDate=0) AND 
645:                 x2_actions.createDate BETWEEN :start AND :end ".$userString.' AND '.$accessCondition,
646:                 $params
647:                 )
648:             ->queryScalar();
649: 
650:         $membersDataProvider = new CSqlDataProvider($stageMemberSql,array(
651:             'totalItemCount'=>$memberCount,
652:             'sort'=>array(
653:                 'defaultOrder'=>'lastUpdated DESC',
654:             ),
655:             'pagination'=> array('pageSize'=>20),
656:             'params' => $params
657:         ));
658: 
659:         return $membersDataProvider;
660:     }
661: 
662:     public function actionGetStageMembers(
663:         $id,$stage,$start,$end,$range, $users,$modelType) {
664:         
665:         $this->getStageMembers (
666:             $id, $stage, $start, $end, $range, $users, $modelType);
667:     }
668: 
669:     public function getStageMembers($workflowId,$stage,$start,$end,$range,
670:         $users, $modelType='') {
671: 
672:         $params = array ();
673:         
674:         $dateRange=self::getDateRange();
675:         
676:         if(!is_numeric($workflowId) || !is_numeric($stage))
677:             return new CActiveDataProvider();
678: 
679:         $dataProvider = $this->getStageMemberDataProvider (
680:                 $modelType, $workflowId, $dateRange, $stage, $users);
681: 
682:         $gridConfig = array (
683:             'baseScriptUrl'=>Yii::app()->theme->getBaseUrl().'/css/gridview',
684:             'template'=> 
685:                 '<div class="page-title">{title}{buttons}'.
686:                 '</div>{items}{pager}',
687:             'fixedHeader' => false,
688:             'fullscreen' => false,
689:             'buttons'=>array('columnSelector','autoResize'),
690:         );
691:         $modelName = X2Model::getModelName($modelType);
692:         $this->renderPartial ('_ajaxRequestedStageMembers', 
693:             array ('gridConfig' => array_merge (
694:                 $gridConfig,
695:                 array (
696:                     'gvSettingsName' => $modelType.'-stageMembers',
697:                     'viewName' => $modelType.'-workflowView',
698:                     'defaultGvSettings'=>array(
699:                         'name' => 125,
700:                         'lastUpdated' => 80,
701:                         'assignedTo' => 80,
702:                     ),
703:                     'dataProvider' => $dataProvider,
704:                     'id'=>'workflow-stage-grid',
705:                     'ajaxUpdate'=>'workflow-stage-grid',
706:                     'modelName' => $modelName,
707:                     'title' => Modules::displayName(true, $modelType),
708:                     'specialColumns'=>array(
709:                         'name' => array(
710:                             'name'=>'name',
711:                             'header'=>Yii::t('app','Name'),
712:                             'value'=>'X2Model::getModelLinkMock (
713:                                 "'.$modelName.'",
714:                                 $data[\'nameId\'],
715:                                 array (
716:                                     \'data-qtip-title\' => $data[\'name\']
717:                                 )
718:                             )',
719:                             'type'=>'raw',
720:                             'htmlOptions'=>array('width'=>'30%')
721:                         ),
722:                         'lastUpdated' => array(
723:                             'name'=>'lastUpdated',
724:                             'header'=>X2Model::model($modelName)->getAttributeLabel('lastUpdated'),
725:                             'value'=>'Formatter::formatDate($data["lastUpdated"])',
726:                             'type'=>'raw',
727:                             'htmlOptions'=>array('width'=>'15%')
728:                         ),
729:                         'assignedTo' => array(
730:                             'header'=>X2Model::model($modelName)->getAttributeLabel('assignedTo'),
731:                             'name'=>'assignedTo',
732:                             'value'=>'empty($data["assignedTo"])?'.
733:                                 'Yii::t("app","Anyone"):User::getUserLinks($data["assignedTo"])',
734:                             'type'=>'raw',
735:                         ),
736:                     ),
737:                 )
738:             )), false, true);
739:         
740:     }
741: 
742:     public function actionGetStageValue($id,$stage,$users,$modelType,$start,$end){
743:         $dateRange = self::getDateRange ();
744:         $workflow = Workflow::model ()->findByPk ($id);
745:         if ($workflow !== null) {
746:             $workflowStatus = Workflow::getWorkflowStatus ($id);
747:             $counts = Workflow::getStageCounts($workflowStatus, $dateRange, $users, $modelType);
748:             $this->renderPartial ('_dataSummary', array (
749:                 'stageName' => $workflow->getStageName ($stage),
750:                 'count' => $counts[$stage - 1],
751:             ));
752:         }
753:     }
754: 
755:     
756:     public function actionGetStages() {
757:         if(isset($_GET['id'])) {
758:             $stages = Yii::app()->db->createCommand()
759:                 ->select('name')
760:                 ->from('x2_workflow_stages')
761:                 ->where('workflowId=:id',array(':id'=>$_GET['id']))
762:                 ->order('id ASC')
763:                 ->queryColumn();
764: 
765:             echo CJSON::encode($stages);
766:         }
767:     }
768:     
769:    /**
770:     * Called via AJAX. Returns partial view for specified view
771:     */
772:     public function actionChangeUI () {
773:         if (!isset ($_GET['perStageWorkflowView']) ||
774:             !isset ($_GET['id'])) {
775:             return;
776:         }
777: 
778:         $perStageWorkflowView = $_GET['perStageWorkflowView'] === 'true' ? true : false;
779:         $id = (int) $_GET['id'];
780:         $users = isset ($_GET['users']) ? $_GET['users'] : '';
781:         
782:         Profile::setMiscLayoutSetting ('perStageWorkflowView', $perStageWorkflowView, true);
783: 
784:         if ($perStageWorkflowView) {
785:             $this->renderPartial (
786:                 '_perStageView', $this->getPerStageViewParams ($id, null, $users), false, true);
787:         } else {
788:             $this->renderPartial (
789:                 '_dragAndDropView',$this->getDragAndDropViewParams ($id, $users), false, true);
790:         }
791:     }
792: 
793:     /**
794:      * Performs the AJAX validation.
795:      * @param CModel the model to be validated
796:      */
797:     protected function performAjaxValidation($model)
798:     {
799:         if(isset($_POST['ajax']) && $_POST['ajax']==='workflow-form')
800:         {
801:             echo CActiveForm::validate($model);
802:             Yii::app()->end();
803:         }
804:     }
805: 
806:     private function getDragAndDropViewParams ($id, $users='') {
807:         $model = $this->loadModel($id);
808:         if(isset($_GET['modelType'])){
809:             $modelType = $_GET['modelType'];
810:         }elseif(!empty($model->financialModel)){
811:             if(X2Model::getModelName($model->financialModel)){
812:                 $modelType = $model->financialModel;
813:             }else{
814:                 $modelType = 'contacts';
815:             }
816:         }else{
817:             $modelType = 'contacts';
818:         }
819:         $dateRange=self::getDateRange();
820: 
821:         $memberListContainerSelectors = array ();
822:         $stageCount = count ($model->stages);
823:         for ($i = 1; $i <= $stageCount; $i++) {
824:             $memberListContainerSelectors[] = '#workflow-stage-'.$i.' .items';
825:         }
826:         $workflowStatus = Workflow::getWorkflowStatus ($id);
827:         $stagePermissions = Workflow::getStagePermissions ($workflowStatus);
828:         $stagesWhichRequireComments = Workflow::getStageCommentRequirements ($workflowStatus);
829:         $stageNames = Workflow::getStageNames ($workflowStatus);
830:         $colors = $model->getWorkflowStageColors ($stageCount, true);
831:         $stageCounts = Workflow::getStageCounts (
832:             $workflowStatus, $dateRange, $users, $modelType);
833:         $stageValues = Workflow::getStageValues(
834:             $workflowStatus, $dateRange, $users, $modelType);
835:         return array (
836:             'model'=>$model,
837:             'modelType'=>$modelType,
838:             'dateRange'=>$dateRange,
839:             'users'=>$users,
840:             'colors'=>$colors,
841:             'listItemColors'=>Workflow::getPipelineListItemColors ($colors),
842:             'memberListContainerSelectors'=>$memberListContainerSelectors,
843:             'stagePermissions'=>$stagePermissions,
844:             'stagesWhichRequireComments'=>$stagesWhichRequireComments,
845:             'stageNames'=>$stageNames,
846:             'stageCounts' => $stageCounts,
847:             'stageValues' => $stageValues,
848:         );
849:     }
850: 
851:     private function getPerStageViewParams ($id, $viewStage=null, $users='') {
852:         $dateRange=self::getDateRange();
853:         $model = $this->loadModel($id);
854:         $modelType = isset ($_GET['modelType']) ? $_GET['modelType'] : (!empty($model->financialModel)?$model->financialModel:'contacts');
855:         $stageCount = count ($model->stages);
856:         return array (
857:             'model'=>$model,
858:             'modelType'=>$modelType,
859:             'viewStage'=>$viewStage,
860:             'colors'=>$model->getWorkflowStageColors ($stageCount, true),
861:             'dateRange'=>$dateRange,
862:             'users'=>$users,
863:         );
864:     }
865: 
866:     /**
867:      *  Used for auto-complete methods.
868:      */
869:     public function actionGetItems($term){
870:         X2LinkableBehavior::getItems ($term);
871:     }
872: 
873:     /**
874:      * Used to populate options of stage name dropdowns
875:      */
876:     public function actionGetStageNames($workflowId, $optional='true'){
877:         $stages = Workflow::getStagesByNumber($workflowId);
878:         if ($optional === 'true') 
879:             echo CJSON::encode (
880:                 AuxLib::dropdownForJson (array ('any' => Yii::t('app', 'Any')) + $stages));
881:         else
882:             echo CJSON::encode (AuxLib::dropdownForJson ($stages));
883:     }
884: 
885:     /**
886:      * Used for stage name autocomplete inputs 
887:      */
888:     public function actionGetStageNameItems($workflowId, $term){
889:         $stageNames = Yii::app()->db->createCommand()
890:             ->select('name')
891:             ->from('x2_workflow_stages')
892:             ->where(
893:                 'workflowId=:id and name like :qterm',
894:                 array(':id'=>$workflowId, ':qterm' => $term.'%'))
895:             ->order('stageNumber ASC')
896:             ->queryColumn();
897:         echo CJSON::encode ($stageNames);
898:     }
899:     
900:     public function actionGetFinancialFields($modelType){
901:         $ret = array('' => 'Select a field');
902:         $ret = array_merge($ret, Workflow::getCurrencyFields($modelType));
903:         echo CJSON::encode($ret);
904:     }
905:     
906:     /**
907:      * Calls getDateRange and sets to default range to 'all' if the date range is the 
908:      * expected close date date range
909:      */
910:     public static function getDateRange ( 
911:         $startKey='start',$endKey='end',$rangeKey='range') {
912:         return X2DateUtil::getDateRange ($startKey, $endKey, $rangeKey, 'custom');
913:     }
914: 
915:     /**
916:      * Create a menu for Process
917:      * @param array Menu options to remove
918:      * @param X2Model Model object passed to the view
919:      * @param array Additional menu parameters
920:      */
921:     public function insertMenu($selectOptions = array(), $model = null, $menuParams = null) {
922:         $Processes = Modules::displayName();
923:         $Process = Modules::displayName(false);
924:         $modelId = isset($model) ? $model->id : 0;
925: 
926:         /**
927:          * To show all options:
928:          * $menuOptions = array(
929:          *     'index', 'create', 'edit', 'view', 'funnel', 'pipeline', 'delete',
930:          * );
931:          */
932: 
933:         $menuItems = array(
934:             array(
935:                 'name'=>'index',
936:                 'label'=>Yii::t('workflow','All {processes}', array(
937:                     '{processes}' => $Processes,
938:                 )),
939:                 'url'=>array('index')
940:             ),
941:             array(
942:                 'name'=>'create',
943:                 'label'=>Yii::t('app','Create'),
944:                 'url'=>array('create'),
945:                 'visible'=>Yii::app()->params->isAdmin
946:             ),
947:             array(
948:                 'name'=>'edit',
949:                 'label'=>Yii::t('workflow','Edit {process}', array(
950:                     '{process}' => $Process,
951:                 )), 
952:                 'url'=>array('update', 'id'=>$modelId), 
953:                 'visible'=>Yii::app()->params->isAdmin
954:             ),
955:             array(
956:                 'name'=>'funnel',
957:                 'label'=>Yii::t('app','Funnel View'),
958:                 'url'=>array('view', 'id'=>$modelId, 'perStageWorkflowView'=>'true'),
959:                 'linkOptions' => array ('id' => 'funnel-view-menu-item'),
960:             ),
961:             array(
962:                 'name'=>'pipeline',
963:                 'label'=>Yii::t('app','Pipeline View'),
964:                 'url'=>array('view', 'id'=>$modelId, 'perStageWorkflowView'=>'false'),
965:                 'linkOptions' => array ('id' => 'pipeline-view-menu-item'),
966:             ),
967:             array(
968:                 'name'=>'delete',
969:                 'label'=>Yii::t('workflow','Delete {process}', array(
970:                     '{process}' => $Process,
971:                 )), 
972:                 'url'=>'#', 
973:                 'linkOptions'=>array('submit'=>array('delete','id'=>$modelId),
974:                 'confirm'=>Yii::t('app','Are you sure you want to delete this item?')), 
975:                 'visible'=>Yii::app()->params->isAdmin
976:             ),
977:         );
978: 
979:         $this->prepareMenu($menuItems, $selectOptions);
980:         $this->actionMenu = $this->formatMenu($menuItems, $menuParams);
981:     }
982: }
983: 
984: 
X2CRM Documentation API documentation generated by ApiGen 2.8.0