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

  • ActionToRecord
  • ContactsNameBehavior
  • ERememberFiltersBehavior
  • FileSystemObjectBehavior
  • MobileLayouts
  • RecordAliases
  • RelationshipsBehavior
  • TopicReplies
  • X2ActiveRecord
  • X2Flow
  • 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:  * This is the model class for table "x2_flows".
 39:  *
 40:  * The followings are the available columns in table 'x2_flows':
 41:  * @property integer $id
 42:  * @property integer $active
 43:  * @property string $name
 44:  * @property string $createDate
 45:  *
 46:  * The followings are the available model relations:
 47:  * @property FlowItems[] $flowItems
 48:  * @property FlowParams[] $flowParams
 49:  * @package application.models
 50:  */
 51: Yii::import('application.components.x2flow.X2FlowItem');
 52: Yii::import('application.components.x2flow.X2FlowFormatter');
 53: Yii::import('application.components.x2flow.actions.*');
 54: Yii::import('application.components.x2flow.triggers.*');
 55: Yii::import('application.models.ApiHook');
 56: 
 57: class X2Flow extends X2ActiveRecord {
 58: 
 59:     /**
 60:      * @const max number of nested calls to {@link X2Flow::trigger()}
 61:      */
 62:     const MAX_TRIGGER_DEPTH = 0;
 63: 
 64:     /**
 65:      * Returns the static model of the specified AR class.
 66:      * @param string $className active record class name.
 67:      * @return X2Flow the static model class
 68:      */
 69:     public static function model($className = __CLASS__){
 70:         return parent::model($className);
 71:     }
 72: 
 73:     /**
 74:      * @var the current depth of nested trigger calls
 75:      */
 76:     private static $_triggerDepth = 0;
 77: 
 78:     /**
 79:      * @return string the associated database table name
 80:      */
 81:     public function tableName(){
 82:         return 'x2_flows';
 83:     }
 84: 
 85:     /**
 86:      * @return array validation rules for model attributes.
 87:      */
 88:     public function rules(){
 89:         return array(
 90:             array('name, createDate, lastUpdated, triggerType, flow', 'required'),
 91:             array('createDate, lastUpdated', 'numerical', 'integerOnly' => true),
 92:             array('active', 'boolean'),
 93:             array('name', 'length', 'max' => 100),
 94:             array ('flow', 'validateFlow'),
 95:             array('triggerType, modelClass', 'length', 'max' => 40),
 96:             // The following rule is used by search().
 97:             // Please remove those attributes that should not be searched.
 98:             array('id, active, name, createDate, lastUpdated', 'safe', 'on' => 'search'),
 99:         );
100:     }
101: 
102:     private $_flow;
103:     public function getFlow ($refresh=false) {
104:         if (!isset ($this->_flow) || $refresh) {
105:             $this->_flow = CJSON::decode ($this->flow);
106:         }
107:         return $this->_flow;
108:     }
109: 
110:     public function setFlow (array $flow) {
111:         $this->_flow = $flow;
112:         $this->flow = CJSON::encode ($flow);
113:     }
114: 
115:     /**
116:      * Ensure validity of specified config options
117:      */
118:     public function validateFlow ($attribute) {
119:         $flow = CJSON::decode ($this->$attribute);
120: 
121:         if(isset($flow['trigger']) && isset ($flow['items'])) {
122:             if (!$this->validateFlowItem ($flow['trigger'])) {
123:                 return false;
124:             }
125:             if ($this->validateFlowPrime ($flow['items'])) {
126:                 return true;
127:             } 
128:         } else {
129:             $this->addError ($attribute, Yii::t('studio', 'Invalid flow'));
130:             return false;
131:         }
132:     }
133: 
134:     public function afterSave () {
135:         $flow = CJSON::decode ($this->flow);
136:         $triggerClass = $flow['trigger']['type'];
137:         if ($triggerClass === 'PeriodicTrigger') {
138:             $trigger = X2FlowTrigger::create ($flow['trigger']);
139:             $trigger->afterFlowSave ($this);
140:         }
141:         parent::afterSave ();
142:     }
143: 
144:     /**
145:      * Returns a list of behaviors that this model should behave as.
146:      * @return array the behavior configurations (behavior name=>behavior configuration)
147:      */
148:     public function behaviors(){
149:         return array(
150:             'X2TimestampBehavior' => array('class' => 'X2TimestampBehavior'),
151:         );
152:     }
153: 
154:     /**
155:      * @return array customized attribute labels (name=>label)
156:      */
157:     public function attributeLabels(){
158:         return array(
159:             'id' => Yii::t('admin', 'ID'),
160:             'active' => Yii::t('admin', 'Active'),
161:             'name' => Yii::t('admin', 'Name'),
162:             'triggerType' => Yii::t('admin', 'Trigger'),
163:             'modelClass' => Yii::t('admin', 'Type'),
164:             'name' => Yii::t('admin', 'Name'),
165:             'createDate' => Yii::t('admin', 'Create Date'),
166:             'lastUpdated' => Yii::t('admin', 'Last Updated'),
167:             'description' => Yii::t('admin', 'Description'),
168:         );
169:     }
170: 
171:     /**
172:      * Retrieves a list of models based on the current search/filter conditions.
173:      * @return CActiveDataProvider the data provider that can return the models based on the 
174:      *  search/filter conditions.
175:      */
176:     public function search(){
177:         $criteria = new CDbCriteria;
178: 
179:         $criteria->compare('id', $this->id);
180:         $criteria->compare('active', $this->active);
181:         $criteria->compare('name', $this->name, true);
182:         $criteria->compare('createDate', $this->createDate, true);
183:         $criteria->compare('lastUpdated', $this->lastUpdated, true);
184: 
185:         return new CActiveDataProvider($this, array(
186:             'criteria' => $criteria,
187:             'pagination' => array (
188:                 'pageSize' => Profile::getResultsPerPage(),
189:             ),
190:         ));
191:     }
192: 
193:     /**
194:      * Validates the JSON data in $flow.
195:      * Sets createDate and lastUpdated.
196:      * @return boolean whether or not to proceed to validation
197:      */
198:     public function beforeValidate(){
199:         $flowData = CJSON::decode($this->flow);
200: 
201:         if($flowData === false){
202:             $this->addError('flow', Yii::t('studio', 'Flow configuration data appears to be '.
203:                 'corrupt.'));
204:             return false;
205:         }
206:         if(isset($flowData['trigger']['type'])){
207:             $this->triggerType = $flowData['trigger']['type'];
208:             if(isset($flowData['trigger']['modelClass']))
209:                 $this->modelClass = $flowData['trigger']['modelClass'];
210:         } else{
211:             // $this->addError('flow',Yii::t('studio','You must configure a trigger event.'));
212:         }
213:         if(!isset($flowData['items']) || empty($flowData['items'])){
214:             $this->addError('flow', Yii::t('studio', 'There must be at least one action in the '.
215:                 'flow.'));
216:         }
217: 
218:         $this->lastUpdated = time();
219:         if($this->isNewRecord)
220:             $this->createDate = $this->lastUpdated;
221:         return parent::beforeValidate();
222:     }
223: 
224:     /**
225:      * Returns the current trigger stack depth {@link X2Flow::$_triggerDepth}
226:      * @return int the stack depth
227:      */
228:     public static function triggerDepth(){
229:         return self::$_triggerDepth;
230:     }
231: 
232:     /**
233:      * Looks up and runs automation actions that match the provided trigger name and parameters.
234:      *
235:      * @param string $trigger the name of the trigger to fire
236:      * @param array $params an associative array of params, usually including 'model'=>$model,
237:      * the primary X2Model to which this trigger applies.
238:      * @staticvar int $triggerDepth the current depth of the call stack
239:      * @return mixed Null or return value of extractRetValFromTrace
240:      */
241:     public static function trigger($triggerName, $params = array()){
242:         if(self::$_triggerDepth > self::MAX_TRIGGER_DEPTH) // ...have we delved too deep?
243:             return;
244: 
245:         $triggeredAt = time ();
246: 
247:         if(isset($params['model']) &&
248:            (!is_object($params['model']) || !($params['model'] instanceof X2Model))) {
249:             // Invalid model provided
250:             return false;
251:         }
252:         
253:         // Communicate the event to third-party systems, if any
254:         ApiHook::runAll($triggerName,$params);
255: 
256:         // increment stack depth before doing anything that might call X2Flow::trigger()
257:         self::$_triggerDepth++;
258: 
259:         $flowAttributes = array('triggerType' => $triggerName, 'active' => 1);
260: 
261:         if(isset($params['model'])){
262:             $flowAttributes['modelClass'] = get_class($params['model']);
263:             $params['modelClass'] = get_class($params['model']);
264:         }
265: 
266:         // if flow id is specified, only execute flow with specified id
267:         if (isset ($params['flowId'])) $flowAttributes['id'] = $params['flowId'];
268: 
269:         $flows = CActiveRecord::model('X2Flow')->findAllByAttributes($flowAttributes);
270: 
271:         // collect information about trigger for the trigger log.
272:         $triggerInfo = array (
273:             'triggerName' => Yii::t('studio', X2FlowItem::getTitle ($triggerName))
274:         );
275:         if (isset ($params['model']) && is_subclass_of($params['model'],'X2Model') &&
276:             $params['model']->asa ('X2LinkableBehavior')) {
277:             $triggerInfo['modelLink'] =
278:                 Yii::t('studio', 'View record: ').$params['model']->getLink ();
279:         }
280: 
281: 
282:         // find all flows matching this trigger and modelClass
283:         $triggerLog;
284:         $flowTrace;
285:         $flowRetVal = null;
286:         foreach($flows as &$flow) {
287:             $triggerLog = new TriggerLog;
288:             $triggerLog->triggeredAt = $triggeredAt;
289:             $triggerLog->flowId = $flow->id;
290:             $triggerLog->save ();
291: 
292:             $flowRetArr = self::_executeFlow($flow, $params, null, $triggerLog->id);
293:             $flowTrace = $flowRetArr['trace'];
294:             $flowRetVal = (isset ($flowRetArr['retVal'])) ? $flowRetArr['retVal'] : null;
295:             $flowRetVal = self::extractRetValFromTrace ($flowTrace);
296: 
297:             // save log for triggered flow
298:             $triggerLog->triggerLog =
299:                 CJSON::encode (array_merge (array ($triggerInfo), array ($flowTrace)));
300:             $triggerLog->save ();
301:         }
302: 
303:         self::$_triggerDepth--;  // this trigger call is done; decrement the stack depth
304:         return $flowRetVal;
305:     }
306: 
307:     /**
308:      * Traverses the trace tree and checks if the last action has a return value. If so, that 
309:      * value is returned. Otherwise, null is returned.
310:      * 
311:      * @param array the trace returned by executeFlow
312:      * @return mixed null if there's no return value, mixed otherwise
313:      */
314:     public static function extractRetValFromTrace ($flowTrace) {
315:         // trigger itself has return val
316: 
317:         if (sizeof ($flowTrace) === 3 && $flowTrace[0] && !is_array ($flowTrace[1])) {
318:             return $flowTrace[2];
319:         }
320: 
321:         // ensure that initial branch executed without errors
322:         if (sizeof ($flowTrace) < 2 || !$flowTrace[0] || !is_array ($flowTrace[1])) {
323:             return null;
324:         }
325: 
326:         // find last action
327:         $startOfBranchExecution = $flowTrace[1];
328:         $lastAction = $startOfBranchExecution[sizeof ($startOfBranchExecution) - 1];
329:         while (true) {
330:             if (is_subclass_of ($lastAction[0], 'MultiChildNode')){
331:                 $startOfBranchExecution = $lastAction[2];
332:                 if (sizeof ($startOfBranchExecution) > 0) {
333:                     $lastAction = $startOfBranchExecution[sizeof ($startOfBranchExecution) - 1];
334:                 } else {
335:                     return null;
336:                 }
337:             } else {
338:                 break;
339:             }
340:         }
341: 
342:         // if last action has return value, return it
343:         if (sizeof ($lastAction[1]) === 3) return $lastAction[1][2];
344:     }
345: 
346:     /**
347:      * Can be called to resume execution of flow that paused for the wait action. 
348:      * @param X2Flow &$flow the object representing the flow to run
349:      * @param array &$params an associative array of params, usually including 'model'=>$model,
350:      * @param mixed $flowPath an array of directions to a specific point in the flow. Defaults to
351:      *  null.
352:      */
353:     public static function resumeFlowExecution (
354:         &$flow, &$params, $actionId = null, $triggerLogId=null){
355: 
356:         if(self::$_triggerDepth > self::MAX_TRIGGER_DEPTH) // ...have we delved too deep?
357:             return;
358: 
359:         if(isset($params['model']) &&
360:            (!is_object($params['model']) || !($params['model'] instanceof X2Model))) {
361:             // Invalid model provided
362:             return false;
363:         }
364: 
365:         // increment stack depth before doing anything that might call X2Flow::trigger()
366:         self::$_triggerDepth++;
367:         $result = self::_executeFlow ($flow, $params, $actionId, $triggerLogId);
368:         self::$_triggerDepth--;  // this trigger call is done; decrement the stack depth
369:         return $result;
370:     }
371: 
372:     /**
373:      * Wrapper around _executeFlow which respects trigger depth restriction 
374:      */
375:     public static function executeFlow(&$flow, &$params, $actionId = null){
376:         if(self::$_triggerDepth > self::MAX_TRIGGER_DEPTH) // ...have we delved too deep?
377:             return;
378: 
379:         self::$_triggerDepth++;
380: 
381:         $triggerInfo = array(
382:             'triggerName' => Yii::t('studio', X2FlowItem::getTitle('MacroTrigger'))
383:         );
384:         if (isset($params['model']) && is_subclass_of($params['model'], 'X2Model') &&
385:                 $params['model']->asa('X2LinkableBehavior')) {
386:             $triggerInfo['modelLink'] = Yii::t('studio', 'View record: ') . 
387:                 $params['model']->getLink();
388:         }
389: 
390:         $triggerLog = new TriggerLog;
391:         $triggerLog->triggeredAt = time();
392:         $triggerLog->flowId = $flow->id;
393:         $triggerLog->save();
394:         $flowRetArr = self::_executeFlow ($flow, $params, $actionId, $triggerLog->id);
395:         $flowTrace = $flowRetArr['trace'];
396: 
397:         // save log for triggered flow
398:         $triggerLog->triggerLog = CJSON::encode(
399:             array_merge(array($triggerInfo), array($flowTrace)));
400:         $triggerLog->save();
401: 
402:         self::$_triggerDepth--;  // this trigger call is done; decrement the stack depth
403:         return $flowRetArr;
404:     }
405: 
406:     /**
407:      * Legacy method for cron events created in flows before 5.2. This could be removed if a 
408:      * migration script were written which migrated old x2_cron_event records containing flow
409:      * paths to the 5.2+ system which uses flow action ids.
410:      * 
411:      * Recursive method for traversing a flow tree using $flowPath, allowing us to
412:      * instantly skip to any point in a flow. This is used for delayed execution with X2CronAction.
413:      *
414:      * @param array $flowPath directions to the current position in the flow tree
415:      * @param array $flowItems the items in this branch
416:      * @param array &$params an associative array of params, usually including 'model'=>$model,
417:      * @param integer $pathIndex the position $flowPath to start at (for recursion), defaults to 0
418:      */
419:     private function traverseLegacy (
420:         $flowPath, &$flowItems, &$params, $pathIndex = 0, $triggerLogId=null) {
421: 
422:         // if it's true or false, skip directly to the next true/false fork
423:         if(is_bool($flowPath[$pathIndex])){
424:             foreach($flowItems as &$item){
425:                 if(is_subclass_of ($item['type'], 'MultiChildNode')){
426:                     $nodeClass = $item['type'];
427:                     if($flowPath[$pathIndex] && isset($item[$nodeClass::getRightChildName ()])){
428:                         if(isset($flowPath[$pathIndex + 1])) {
429:                             return $this->traverseLegacy(
430:                                 $flowPath, $item[$nodeClass::getRightChildName ()], $params, 
431:                                 $pathIndex + 1, $triggerLogId);
432:                         } else {
433:                             return array(
434:                                 $item['type'], true,
435:                                 $this->executeBranch(
436:                                     $item[$nodeClass::getRightChildName], $params, $triggerLogId)
437:                             );
438:                         }
439:                     } elseif(!$flowPath[$pathIndex] && 
440:                         isset($item[$nodeClass::getLeftChildName ()])){
441: 
442:                         if(isset($flowPath[$pathIndex + 1])) {
443:                             return $this->traverseLegacy(
444:                                 $flowPath, $item[$nodeClass::getLeftChildName ()], $params, 
445:                                 $pathIndex + 1, $triggerLogId);
446:                         } else {
447:                             return array(
448:                                 $item['type'], false,
449:                                 $this->executeBranch(
450:                                     $item[$nodeClass::getLeftChildName ()], $params, $triggerLogId)
451:                             );
452:                         }
453:                     }
454:                 }
455:             }
456:             return false;
457:         } else { // we're in the final branch, so just execute it starting at the specified index
458:             if(isset($flowPath[$pathIndex])) {
459:                 $sliced = array_slice ($flowItems, $flowPath[$pathIndex]);
460:                 return $this->executeBranch($sliced, $params, $triggerLogId);
461:             }
462:         }
463:     }
464: 
465:     /**
466:      * Jump to flow action with specified action id and resume flow from subsequent action
467:      * @param iint $actionId unique id of flow action
468:      * @param array $flowItems the items in this branch
469:      * @param array &$params an associative array of params, usually including 'model'=>$model,
470:      */
471:     private function traverse ($actionId, &$flowItems, &$params, $triggerLogId=null){
472:         if (is_array ($actionId)) {
473:             return $this->traverseLegacy (
474:                 $actionId, $flowItems, $params, 0, $triggerLogId);
475:         } else {
476:             $i = 0;
477:             // depth-first search for matching action id
478:             foreach ($flowItems as $item) {
479:                 if ($item['id'] === $actionId) {
480:                     $sliced = array_slice ($flowItems, $i + 1);
481:                     return $this->executeBranch ($sliced, $params, $triggerLogId);
482:                 }
483:                 if (is_subclass_of ($item['type'], 'MultiChildNode')) {
484:                     $nodeClass = $item['type'];
485:                     if (isset ($item[$nodeClass::getLeftChildName ()])) {
486:                         $ret = $this->traverse (
487:                             $actionId, $item[$nodeClass::getLeftChildName ()], $params, 
488:                             $triggerLogId);
489:                         if ($ret) return $ret;
490:                     }
491:                     if (isset ($item[$nodeClass::getRightChildName ()])) {
492:                         $ret = $this->traverse (
493:                             $actionId, $item[$nodeClass::getRightChildName ()], $params,
494:                             $triggerLogId);
495:                         if ($ret) return $ret;
496:                     }
497:                 } 
498:                 $i++;
499:             }
500:         }
501:         return false;
502:     }
503: 
504:     /**
505:      * Executes each action in a given branch, starting at $start.
506:      *
507:      * @param array $flowPath directions to the current position in the flow tree
508:      * @param array $flowItems the items in this branch
509:      * @param array &$params an associative array of params, usually including 'model'=>$model,
510:      * @param integer $start the position in the branch to start at, defaults to 0
511:      */
512:     public function executeBranch(&$flowItems, &$params, $triggerLogId=null){
513:         $results = array();
514: 
515:         for($i = 0; $i < count($flowItems); $i++){
516:             $item = &$flowItems[$i];
517:             if(!isset($item['type']) || !class_exists($item['type']))
518:                 continue;
519: 
520:             $node = X2FlowItem::create($item);
521:             if($item['type'] === 'X2FlowSwitch'){
522:                 $validateRetArr = $node->validate($params, $this->id);
523:                 if($validateRetArr[0]){
524: 
525:                     $checkRetArr = $node->check($params);
526:                     if($checkRetArr[0] && isset($item['trueBranch'])){
527:                         $results[] = array(
528:                             $item['type'], true,
529:                             $this->executeBranch(
530:                                 $item['trueBranch'], $params, $triggerLogId)
531:                         );
532:                     }elseif(isset($item['falseBranch'])){
533:                         $results[] = array(
534:                             $item['type'], false,
535:                             $this->executeBranch(
536:                                 $item['falseBranch'], $params, $triggerLogId)
537:                         );
538:                     }
539:                 }
540:             }elseif($item['type'] === 'X2FlowSplitter'){
541:                 $validateRetArr = $node->validate($params, $this->id);
542:                 if($validateRetArr[0]){
543:                     // right to left pre-order traversal
544:                     $branchVal = true;
545:                     if(isset($item[X2FlowSplitter::getRightChildName ()])){
546:                         $results[] = array(
547:                             $item['type'], true,
548:                             $this->executeBranch(
549:                                 $item[X2FlowSplitter::getRightChildName ()], $params, $triggerLogId)
550:                         );
551:                     }
552:                     if(isset($item[X2FlowSplitter::getLeftChildName ()])){
553:                         $results[] = array(
554:                             $item['type'], false,
555:                             $this->executeBranch(
556:                                 $item[X2FlowSplitter::getLeftChildName ()], $params, $triggerLogId)
557:                         );
558:                     }
559:                 }
560:             }else{
561:                 $flowAction = X2FlowAction::create($item);
562:                 if($item['type'] === 'X2FlowWait'){
563:                     $node->flowId = $this->id;
564:                     $results[] = $this->validateAndExecute (
565:                         $item, $node, $params, $triggerLogId);
566:                     break;
567:                 }else{
568:                     $results[] = $this->validateAndExecute (
569:                         $item, $node, $params, $triggerLogId);
570:                 }
571:             }
572:         }
573:         return $results;
574:     }
575: 
576:     public function validateAndExecute ($item, $flowAction, &$params, $triggerLogId=null) {
577:         $logEntry;
578:         $validationRetStatus = $flowAction->validate($params, $this->id);
579:         if ($validationRetStatus[0] === true) {
580:             $logEntry = array ($item['type'], $flowAction->execute ($params, $triggerLogId, $this));
581:         } else {
582:             $logEntry = array ($item['type'], $validationRetStatus);
583:         }
584:         return $logEntry;
585:     }
586: 
587:     /*
588:      * Parses variables in curly brackets and evaluates expressions
589:      *
590:      * @param mixed $value the value as specified by 'attributes' in {@link X2FlowAction::$config}
591:      * @param string $type the X2Fields type for this value
592:      * @return mixed the parsed value
593:      */
594:     public static function parseValue($value, $type, &$params = null, $renderFlag=true){
595:         if (is_string ($value)){
596:             if (strpos ($value, '=') === 0){
597:                 // It's a formula. Evaluate it.
598:                 $evald = X2FlowFormatter::parseFormula($value, $params);
599: 
600:                 // Fail silently because there's not yet a good way of reporting
601:                 // problems that occur in parseFormula --
602:                 $value = '';
603:                 if($evald[0])
604:                     $value = $evald[1];
605:             } else {
606:                 // Run token replacement:
607:                 $value = X2FlowFormatter::replaceVariables(
608:                     $value, $params, $type, $renderFlag, true);
609:             }
610:         }
611: 
612:         switch($type){
613:             case 'boolean':
614:                 return (bool) $value;
615:             case 'time':
616:             case 'date':
617:             case 'dateTime':
618:                 if(ctype_digit((string) $value))  // must already be a timestamp
619:                     return $value;
620:                 if($type === 'date')
621:                     $value = Formatter::parseDate($value);
622:                 elseif($type === 'dateTime')
623:                     $value = Formatter::parseDateTime($value);
624:                 else
625:                     $value = strtotime($value);
626:                 return $value === false ? null : $value;
627:             case 'link':
628:                 $pieces = explode('_', $value);
629:                 if(count($pieces) > 1)
630:                     return $pieces[0];
631:                 return $value;
632:             case 'tags':
633:                 return Tags::parseTags ($value);
634:             default:
635:                 return $value;
636:         }
637:     }
638: 
639: 
640:     public static function getModelTypes($assoc=false) {
641:         return array_diff_key (
642:             X2Model::getModelTypes ($assoc), 
643:             array_flip (array ('Fingerprint', 'Charts','EmailInboxes')));
644:     }
645: 
646:     /**
647:      * Executes a flow, starting by checking the trigger, passing params to each trigger/action,
648:      * and calling {@link X2Flow::executeBranch()}
649:      *
650:      * @param X2Flow &$flow the object representing the flow to run
651:      * @param array &$params an associative array of params, usually including 'model'=>$model,
652:      * @param mixed $actionId a unique id for the flow action where flow execution should start.
653:      *  This can also be an array of directions to the flow action (legacy option). 
654:      * Will skip checking the trigger conditions if not null, otherwise runs the entire flow.
655:      */
656:     private static function _executeFlow(&$flow, &$params, $actionId = null, $triggerLogId=null){
657:         $error = ''; //array($flow->name);
658: 
659:         $flowData = $flow->getFlow ();
660: 
661:         if($flowData !== false &&
662:            isset($flowData['trigger']['type'], $flowData['items'][0]['type'])){
663: 
664:             if($actionId === null){
665:                 $trigger = X2FlowTrigger::create($flowData['trigger']);
666:                 assert ($trigger !== null);
667:                 if($trigger === null) {
668:                     $error = array (
669:                         'trace' => array (false, 'failed to load trigger class'));
670:                 }
671:                 $validateRetArr = $trigger->validate($params, $flow->id);
672:                 if (!$validateRetArr[0]) {
673:                     $error = $validateRetArr;
674:                     return array ('trace' => $error);
675:                 } else if (sizeof ($validateRetArr) === 3) { // trigger has return value
676:                     return array (
677:                         'trace' => $validateRetArr,
678:                         'retVal' => $validateRetArr[2]
679:                     );
680:                 }
681:                 $checkRetArr = $trigger->check($params);
682:                 if (!$checkRetArr[0]) {
683:                     $error = $checkRetArr;
684:                 } 
685: 
686:                 if(empty($error)){
687:                     try{
688:                         $flowTrace = array (true, $flow->executeBranch (
689:                             $flowData['items'], $params, $triggerLogId));
690:                         $flowRetVal = self::extractRetValFromTrace ($flowTrace);
691:                         if (!$flowRetVal) {
692:                             $flowRetVal = $trigger->getDefaultReturnVal ($flow->id); 
693:                         }
694:                         return array (
695:                             'trace' => $flowTrace,
696:                             'retVal' => $flowRetVal,
697:                         );
698:                     }catch(Exception $e){
699:                         return array ('trace' => array (false, $e->getMessage()));
700:                         // whatever.
701:                     }
702:                 } else {
703:                     return array ('trace' => $error);
704:                 }
705:             }else{ // $actionId provided, skip to the specified position using X2Flow::traverse()
706:                 try{
707:                     return array (
708:                         'trace' => array (
709:                             true, $flow->traverse(
710:                                 $actionId, $flowData['items'], $params, $triggerLogId)));
711:                 } catch(Exception $e) {
712:                     return array (
713:                         'trace' => array (false, $e->getMessage())); // whatever.
714:                 }
715:             }
716:         }else{
717:             return array ('trace' => array (false, 'invalid flow data'));
718:         }
719:     }
720: 
721:     /**
722:      * Helper method for validateFlow. Used to recursively traverse flow while performing
723:      * validation on each item.
724:      * @param array $items 
725:      */
726:     private function validateFlowPrime ($items) {
727:         $valid = true;
728: 
729:         foreach ($items as $item) {
730:             if(!isset($item['type']) || !class_exists($item['type'])) {
731:                 continue;
732:             }
733:             if(is_subclass_of ($item['type'], 'MultiChildNode')){
734:                 $nodeClass = $item['type'];
735:                 if (isset ($item['type'][$nodeClass::getLeftChildName ()])) {
736:                     if (!$this->validateFlowPrime ($item[$nodeClass::getLeftChildName ()])) {
737:                         $valid = false;
738:                         break;
739:                     }
740:                 }
741:                 if (isset ($item['type'][$nodeClass::getRightChildName ()])) {
742:                     if (!$this->validateFlowPrime ($item[$nodeClass::getRightChildName ()])) {
743:                         $valid = false;
744:                         break;
745:                     }
746:                 }
747:             } else {
748:                 $valid = $this->validateFlowItem ($item);
749:                 if (!$valid) {
750:                     break;
751:                 }
752:             }
753:         }
754:         return $valid;
755:     }
756: 
757:     /**
758:      * Validates flow item (trigger or action) 
759:      * @return bool true if options are valid, false otherwise
760:      */
761:     private function validateFlowItem ($config, $action=true) {
762:         $class = $action ? 'X2FlowAction' : 'X2FlowTrigger';
763:         $flowItem = $class::create ($config);
764:         $paramRules = $flowItem->paramRules (); 
765:         list ($success, $message) = $flowItem->validateOptions ($paramRules, null, true);
766:         if ($success === false) {
767:             $this->addError ('flow', $flowItem->title.': '.$message);
768:             return false;
769:         } else if ($success === X2FlowItem::VALIDATION_WARNING) {
770:             Yii::app()->user->setFlash (
771:                 'notice', Yii::t('studio', $message));
772:         }
773:         return true;
774:     }
775: 
776: }
777: 
X2CRM Documentation API documentation generated by ApiGen 2.8.0