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

  • X2FlowAction
  • X2FlowItem
  • X2FlowTrigger
  • X2FlowTriggerBehavior
  • 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:  * Color utilities (unused)
 39:  *
 40:  * @package application.components.x2flow
 41:  */
 42: abstract class X2FlowTrigger extends X2FlowItem {
 43: 
 44:     /**
 45:      * $var string the type of notification to create
 46:      */
 47:     public $notifType = '';
 48:     /**
 49:      * $var string the type of event to create
 50:      */
 51:     public $eventType = '';
 52: 
 53:     /**
 54:      * @return array all standard comparison operators
 55:      */
 56:     public static function getFieldComparisonOptions () {
 57:         return array(
 58:             '=' => Yii::t('app','equals'),
 59:             '>' => Yii::t('app','greater than'),
 60:             '<' => Yii::t('app','less than'),
 61:             '>=' => Yii::t('app','greater than or equal to'),
 62:             '<=' => Yii::t('app','less than or equal to'),
 63:             '<>' => Yii::t('app','not equal to'),
 64:             'list' => Yii::t('app','in list'),
 65:             'notList' => Yii::t('app','not in list'),
 66:             'empty' => Yii::t('app','empty'),
 67:             'notEmpty' => Yii::t('app','not empty'),
 68:             'contains' => Yii::t('app','contains'),
 69:             'noContains' => Yii::t('app','does not contain'),
 70:             'changed' => Yii::t('app','changed'),
 71:             'before' => Yii::t('app','before'),
 72:             'after' => Yii::t('app','after'),
 73:         );
 74:     }
 75: 
 76: /*     public static function getGenericConditions() {
 77:         return array(
 78:             'attribute'            => Yii::t('studio','Compare Attribute'),
 79:             'current_user'        => Yii::t('studio','Current User'),
 80:             'month'                => Yii::t('studio','Current Month'),
 81:             'day_of_week'        => Yii::t('studio','Day of Week'),
 82:             'time_of_day'        => Yii::t('studio','Time of Day'),
 83:             'current_time'        => Yii::t('studio','Current Timestamp'),
 84:             'user_active'        => Yii::t('studio','User Active?'),
 85:             'on_list'            => Yii::t('studio','On List'),
 86:             'workflow_status'    => Yii::t('studio','Workflow Status'),
 87:             // 'current_local_time' => Yii::t('studio',''),
 88:         );
 89:     } */
 90: 
 91:     public static $genericConditions = array(
 92:         'attribute' => 'Compare Attribute',
 93:         'workflow_status' => 'Process Status',
 94:         'current_user' => 'Current User',
 95:         'month' => 'Current Month',
 96:         'day_of_week' => 'Day of Week',
 97:         'day_of_month' => 'Day of Month',
 98:         'time_of_day' => 'Time of Day',
 99:         'current_time' => 'Current Time',
100:         'user_active' => 'User Logged In',
101:         'on_list' => 'On List',
102:         'has_tags' => 'Has Tags',
103:         'email_open' => 'Email Opened',
104:         // 'workflow_status'    => 'Workflow Status',
105:         // 'current_local_time' => ''),
106:     );
107: 
108:     public static function getGenericConditions() {
109:         return array_map(function($term){
110:             return Yii::t('studio',$term);
111:         },self::$genericConditions);
112:     }
113: 
114:     public static function getGenericCondition($type) {
115:         switch($type) {
116:             case 'current_user':
117:                 return array(
118:                     'name' => 'user',
119:                     'label' => Yii::t('studio','Current User'),
120:                     'type' => 'dropdown',
121:                     'multiple' => 1,
122:                     'options' => X2Model::getAssignmentOptions(false,false),
123:                     'operators'=>array('=','<>','list','notList')
124:                 );
125: 
126:             case 'month':
127:                 return array(
128:                     'label'=>Yii::t('studio','Current Month'),
129:                     'type' => 'dropdown',
130:                     'multiple' => 1,
131:                     'options' => Yii::app()->locale->monthNames,
132:                     'operators'=>array('=','<>','list','notList')
133:                 );
134: 
135:             case 'day_of_week':
136:                 return array(
137:                     'label' => Yii::t('studio','Day of Week'),
138:                     'type' => 'dropdown',
139:                     'multiple' => 1,
140:                     'options' => Yii::app()->locale->weekDayNames,
141:                     'operators'=>array('=','<>','list','notList')
142:                 );
143: 
144:             case 'day_of_month':
145:                 $days = array_keys(array_fill(1,31,1));
146:                 return array(
147:                     'label' => Yii::t('studio','Day of Month'),
148:                     'type' => 'dropdown',
149:                     'multiple' => 1,
150:                     'options' => array_combine($days,$days),
151:                     'operators'=>array('=','<>','list','notList')
152:                 );
153: 
154:             case 'time_of_day':
155:                 return array(
156:                     'label' => Yii::t('studio','Time of Day'),
157:                     'type' => 'time',
158:                     'operators'=>array('before','after')
159:                 );
160: 
161:             // case 'current_local_time':
162: 
163:             case 'current_time':
164:                 return array(
165:                     'label' => Yii::t('studio','Current Time'),
166:                     'type' => 'dateTime',
167:                     'operators'=>array('before','after')
168:                 );
169: 
170:             case 'user_active':
171:                 return array(
172:                     'label' => Yii::t('studio','User Logged In'),
173:                     'type' => 'dropdown',
174:                     'options' => X2Model::getAssignmentOptions(false,false)
175:                 );
176: 
177:             case 'on_list':
178:                 return array(
179:                     'label' => Yii::t('studio','On List'),
180:                     'type' => 'link',
181:                     'linkType'=>'X2List',
182:                     'linkSource'=>Yii::app()->controller->createUrl(
183:                         CActiveRecord::model('X2List')->autoCompleteSource)
184:                 );
185:             case 'has_tags':
186:                 return array(
187:                     'label' => Yii::t('studio','Has Tags'),
188:                     'type' => 'tags',
189:                 );
190:             case 'email_open':
191:                 return array(
192:                     'label' => Yii::t('studio', 'Email Opened'),
193:                     'type' => 'dropdown',
194:                     'options' => array(),
195:                 );
196:             default:
197:                 return false;
198:             // case 'workflow_status':
199:                 // return array(
200:                     // 'label'=>Yii::t('studio','Workflow Status'),
201: 
202: 
203:                 // 'workflowId',
204:                 // 'stageNumber',
205: 
206:                     // 'started_workflow',
207:                     // 'started_stage',
208:                     // 'completed_stage',
209:                     // 'completed_workflow', */
210:         }
211:     }
212: 
213:     /**
214:      * Can be overriden in child class to give flow a default return value
215:      */
216:     public function getDefaultReturnVal ($flowId) { return null; }
217: 
218:     /**
219:      * Can be overriden in child class to extend behavior of validate method
220:      */
221:     public function afterValidate (&$params, $defaultErrMsg='', $flowId) { 
222:         return array (false, Yii::t('studio', $defaultErrMsg)); 
223:     }
224: 
225:     /**
226:      * Checks if all all the params are ship-shape
227:      */
228:     public function validate(&$params=array(), $flowId=null) {
229:         $paramRules = $this->paramRules();
230:         if(!isset($paramRules['options'],$this->config['options'])) {
231:             return $this->afterValidate (
232:                 $params, YII_DEBUG ? 
233:                     'invalid rules/params: trigger passed options when it specifies none' :
234:                     'invalid rules/params', $flowId);
235:         }
236:         $config = &$this->config['options'];
237: 
238:         if(isset($paramRules['modelClass'])) {
239:             $modelClass = $paramRules['modelClass'];
240:             if($modelClass === 'modelClass') {
241:                 if(isset($config['modelClass'],$config['modelClass']['value'])) {
242:                     $modelClass = $config['modelClass']['value'];
243:                 } else {
244:                     return $this->afterValidate (
245:                         $params, YII_DEBUG ? 
246:                             'invalid rules/params: '.
247:                             'trigger requires model class option but given none' : 
248:                             'invalid rules/params', $flowId);
249:                 }
250:             }
251:             if(!isset($params['model'])) {
252:                 return $this->afterValidate (
253:                     $params, YII_DEBUG ? 
254:                         'invalid rules/params: trigger requires a model but passed none' :
255:                         'invalid rules/params', $flowId);
256:             }
257:             if($modelClass !== get_class($params['model'])) {
258:                 return $this->afterValidate (
259:                     $params, YII_DEBUG ? 
260:                         'invalid rules/params: required model class does not match model passed ' .
261:                         'to trigger' :
262:                         'invalid rules/params', $flowId);
263:             }
264:         }
265:         return $this->validateOptions($paramRules,$params);
266:     }
267: 
268:     /**
269:      * Default condition processor for main config panel. Checks each option against the key in 
270:      * $params of the same name, using an operator if provided (defaults to "=")
271:      * @return array (error status, message)
272:      */
273:     public function check(&$params) {
274:         foreach($this->config['options'] as $name => &$option) {
275:             // modelClass is a special case, ignore it
276:             if($name === 'modelClass')    
277:                 continue;
278: 
279:             // if it's optional and blank, forget about it
280:             if($option['optional'] && ($option['value'] === null || $option['value'] === ''))    
281:                 continue;
282: 
283:             $value = $option['value'];
284: 
285:             if(isset($option['type']))
286:                 $value = X2Flow::parseValue($value,$option['type'],$params);
287: 
288:             if (isset ($option['comparison']) && !$option['comparison']) {
289:                 continue;
290:             }
291: 
292:             if(!static::evalComparison($params[$name], $option['operator'], $value)) {
293:                 if (is_string ($value) && is_string ($params[$name]) && 
294:                     is_string ($option['operator'])) {
295: 
296:                     return array (
297:                         false, 
298:                         Yii::t('studio', 'The following condition did not pass: ' .
299:                             '{name} {operator} {value}', array (
300:                                 '{name}' => $params[$name],
301:                                 '{operator}' => $option['operator'],
302:                                 '{value}' => (string) $value,
303:                             ))
304:                     );
305:                 } else {
306:                     return array (
307:                         false, 
308:                         Yii::t('studio', 'Condition failed')
309:                     );
310:                 }
311:             }
312:         }
313: 
314:         return $this->checkConditions($params);
315:     }
316:     /**
317:      * Tests this trigger's conditions against the provided params.
318:      * @return array (error status, message)
319:      */
320:     public function checkConditions(&$params) {
321:         if(isset($this->config['conditions'])){
322:             foreach($this->config['conditions'] as &$condition) {
323:                 if(!isset($condition['type']))
324:                     $condition['type'] = '';
325:                     // continue;
326:                 $required = isset($condition['required']) && $condition['required'];
327: 
328:                 // required param missing
329:                 if(isset($condition['name']) && $required && !isset($params[$condition['name']])) {
330:                     if (YII_DEBUG) {
331:                         return array (false, Yii::t('studio', 'a required parameter is missing'));
332:                     } else {
333:                         return array (false, Yii::t('studio', 'conditions not passed'));
334:                     }
335:                 }
336: 
337:                 if(array_key_exists($condition['type'],self::$genericConditions)) {
338:                     if(!self::checkCondition($condition,$params))
339:                         return array (
340:                             false, 
341:                             Yii::t('studio', 'conditions not passed')
342:                         );
343:                 }
344:             }
345:         }
346:         return array (true, '');
347:     }
348: 
349:     /**
350:      * Used to check workflow status condition
351:      * @param Array $condition
352:      * @param Array $params
353:      * @return bool true for success, false otherwise
354:      */
355:     public static function checkWorkflowStatusCondition ($condition, &$params) {
356:         if (!isset ($params['model']) || 
357:             !isset ($condition['workflowId']) ||
358:             !isset ($condition['stageNumber']) ||
359:             !isset ($condition['stageState'])) {
360:             
361:             return false;
362:         }
363: 
364:         $model = $params['model'];
365:         $workflowId = $condition['workflowId'];
366:         $stageNumber = $condition['stageNumber'];
367:         $stageState = $condition['stageState'];
368:         $modelId = $model->id;
369:         $type = lcfirst (X2Model::getModuleName (get_class ($model)));
370: 
371:         $workflowStatus = Workflow::getWorkflowStatus($workflowId,$modelId,$type);
372:         $stages = $workflowStatus['stages'];
373:         if (!isset ($workflowStatus['stages'][$stageNumber])) return false;
374: 
375:         $stage = $workflowStatus['stages'][$stageNumber];
376: 
377:         $passed = false;
378:         switch ($stageState) {
379:             case 'completed':
380:                 $passed = Workflow::isCompleted ($workflowStatus, $stageNumber);
381:                 break;
382:             case 'started':
383:                 $passed = Workflow::isStarted ($workflowStatus, $stageNumber);
384:                 break;
385:             case 'notCompleted':
386:                 $passed = !Workflow::isCompleted ($workflowStatus, $stageNumber);
387:                 break;
388:             case 'notStarted':
389:                 $passed = !Workflow::isStarted ($workflowStatus, $stageNumber);
390:                 break;
391:             default: 
392:                 return false;
393:         }
394:         return $passed;
395:     }
396: 
397:     /**
398:      * @param Array $condition
399:      * @param Array $params
400:      * @return bool true for success, false otherwise
401:      */
402:     public static function checkCondition($condition,&$params) {
403:         if ($condition['type'] === 'workflow_status') 
404:             return self::checkWorkflowStatusCondition ($condition, $params);
405: 
406:         $model = isset($params['model'])? $params['model'] : null;
407:         $operator = isset($condition['operator'])? $condition['operator'] : '=';
408:         // $type = isset($condition['type'])? $condition['type'] : null;
409:         $value = isset($condition['value'])? $condition['value'] : null;
410: 
411:         // default to a doing basic value comparison
412:         if(isset($condition['name']) && $condition['type'] === '') {    
413:             if(!isset($params[$condition['name']]))
414:                 return false;
415: 
416:             return self::evalComparison($params[$condition['name']],$operator,$value);
417:         }
418: 
419:         switch($condition['type']) {
420:             case 'attribute':
421:                 if(!isset($condition['name'],$model))
422:                     return false;
423:                 $attr = &$condition['name'];
424:                 if(null === $field = $model->getField($attr))
425:                     return false;
426: 
427:                 if($operator === 'changed') {
428:                     return $model->attributeChanged ($attr);
429:                 }
430: 
431:                 if ($field->type === 'link') {
432:                     list ($attrVal, $id) = Fields::nameAndId ($model->getAttribute ($attr));
433:                 } else {
434:                     $attrVal = $model->getAttribute ($attr);
435:                 }
436: 
437:                 return self::evalComparison(
438:                     $attrVal,$operator, X2Flow::parseValue($value,$field->type,$params), $field);
439: 
440:             case 'current_user':
441:                 return self::evalComparison(
442:                     Yii::app()->user->getName(),$operator,
443:                     X2Flow::parseValue($value,'assignment',$params));
444: 
445:             case 'month':
446:                 return self::evalComparison((int)date('n'),$operator,$value);    // jan = 1, dec = 12
447: 
448:             case 'day_of_month':
449:                 return self::evalComparison((int)date('j'),$operator,$value);    // 1 through 31
450: 
451:             case 'day_of_week':
452:                 return self::evalComparison((int)date('N'),$operator,$value);    // monday = 1, sunday = 7
453: 
454:             case 'time_of_day':    // - mktime(0,0,0)
455:                 return self::evalComparison(time(),$operator,X2Flow::parseValue($value,'time',$params));    // seconds since midnight
456: 
457:             // case 'current_local_time':
458: 
459:             case 'current_time':
460:                 return self::evalComparison(time(),$operator,X2Flow::parseValue($value,'dateTime',$params));
461: 
462:             case 'user_active':
463:                 return CActiveRecord::model('Session')->exists('user=:user AND status=1',array(':user'=>X2Flow::parseValue($value,'assignment',$params)));
464: 
465: 
466:             case 'on_list':
467:                 if(!isset($model,$value))
468:                     return false;
469:                 $value = X2Flow::parseValue ($value, 'link');
470: 
471:                 // look up specified list
472:                 if(is_numeric($value)){
473:                     $list = CActiveRecord::model('X2List')->findByPk($value);
474:                 }else{
475:                     $list = CActiveRecord::model('X2List')->findByAttributes(
476:                         array('name'=>$value));
477:                 }
478: 
479:                 return ($list !== null && $list->hasRecord ($model));
480:             case 'has_tags':
481:                 if(!isset($model,$value))
482:                     return false;
483:                 $tags = X2Flow::parseValue ($value, 'tags');
484:                 return $model->hasTags ($tags, 'AND');
485:             case 'workflow_status':
486:                 if(!isset($model,$condition['workflowId'],$condition['stageNumber']))
487:                     return false;
488: 
489:                 switch($operator) {
490:                     case 'started_workflow':
491:                         return CActiveRecord::model('Actions')->exists(
492:                             'associationType=:type AND associationId=:modelId AND type="workflow" AND workflowId=:workflow',array(
493:                                 ':type' => get_class($model),
494:                                 ':modelId' => $model->id,
495:                                 ':workflow' => $condition['workflowId'],
496:                             ));
497:                     case 'started_stage':
498:                         return CActiveRecord::model('Actions')->exists(
499:                             'associationType=:type AND associationId=:modelId AND type="workflow" AND workflowId=:workflow AND stageNumber=:stage AND (completeDate IS NULL OR completeDate=0)',array(
500:                                 ':type' => get_class($model),
501:                                 ':modelId' => $model->id,
502:                                 ':workflow' => $condition['workflowId'],
503:                                 ':stageNumber' => $condition['stageNumber'],
504:                             ));
505:                     case 'completed_stage':
506:                         return CActiveRecord::model('Actions')->exists(
507:                             'associationType=:type AND associationId=:modelId AND type="workflow" AND workflowId=:workflow AND stageNumber=:stage AND completeDate > 0',array(
508:                                 ':type' => get_class($model),
509:                                 ':modelId' => $model->id,
510:                                 ':workflow' => $condition['workflowId'],
511:                                 ':stageNumber' => $condition['stageNumber'],
512:                             ));
513:                     case 'completed_workflow':
514:                         $stageCount = CActiveRecord::model('WorkflowStage')->count('workflowId=:id',array(':id'=>$condition['workflowId']));
515:                         $actionCount = CActiveRecord::model('Actions')->count(
516:                             'associationType=:type AND associationId=:modelId AND type="workflow" AND workflowId=:workflow',array(
517:                                 ':type' => get_class($model),
518:                                 ':modelId' => $model->id,
519:                                 ':workflow' => $condition['workflowId'],
520:                             ));
521:                         return $actionCount >= $stageCount;
522:                 }
523:                 return false;
524:             case 'email_open':
525:                 if (isset($params['sentEmails'], $params['sentEmails'][$value])) {
526:                     $trackEmail = TrackEmail::model()->findByAttributes(array('uniqueId' => $params['sentEmails'][$value]));
527:                     return $trackEmail && !is_null($trackEmail->opened);
528:                 }
529:                 return false;
530:         }
531:         return false;
532: 
533:         // foreach($condition as $key = >$value) {
534: 
535: 
536:             // Record attribute (=, <, >, <>, in list, not in list, empty, not empty, contains)
537:             // Linked record attribute (eg. a contact's account has > 30 employees)
538:             // Current user
539:             // Current time (day of week, hours, etc)
540:             // Current time in record's timezone
541:             // Is user X logged in
542:             // Workflow status (in workflow X, started stage Y, completed Y, completed all)
543: 
544:         // }
545:     }
546: 
547:     protected static function parseArray ($operator, $value) {
548:         $expectsArray = array ('list', 'notList', 'between');
549: 
550:         // $value needs to be a comma separated list
551:         if(in_array($operator, $expectsArray, true) && !is_array($value)) {    
552:             $value = explode(',',$value);
553: 
554:             $len = count($value);
555:             for($i=0;$i<$len; $i++) {
556:                 // loop through the values, trim and remove empty strings
557:                 if(($value[$i] = trim($value[$i])) === '')        
558:                     unset($value[$i]);
559:             }
560:         }
561:         return $value;
562:     }
563: 
564:     /**
565:      * @param mixed $subject if applicable, the value to compare $subject with (value of model 
566:      *  attribute)
567:      * @param string $operator the type of comparison to be used
568:      * @param mixed $value the value being analyzed (specified in config menu)
569:      * @return boolean
570:      */
571:     public static function evalComparison($subject,$operator,$value=null, Fields $field = null) {
572:         $value = self::parseArray ($operator, $value);
573: //        if (!in_array ($operator, $expectsArray, true) && is_array ($value)) {
574: //            if (count ($value) > 1) {
575: //                return false;
576: //            } else {
577: //                $value = array_pop ($value);
578: //            }
579: //        }
580: 
581:         switch($operator) {
582:             case '=':
583:                 // check for multiselect dropdown
584:                 if ($field && $field->type === 'dropdown') {
585:                     $dropdown = $field->getDropdown (); 
586:                     if ($dropdown && $dropdown->multi) {
587:                         $subject = StringUtil::jsonDecode ($subject, false);
588:                         AuxLib::coerceToArray ($subject);
589:                         AuxLib::coerceToArray ($value);
590:                         return $subject === $value;
591:                     }
592:                 // check for muti-assignment field
593:                 } else if ($field && $field->type === 'assignment' && 
594:                     $field->linkType === 'multiple') {
595: 
596:                     $subject = explode (Fields::MULTI_ASSIGNMENT_DELIM, $subject); 
597:                     AuxLib::coerceToArray ($subject);
598:                     AuxLib::coerceToArray ($value);
599:                     return $subject === $value;
600:                 } 
601: 
602:                 // this case occurs when dropdown or assignment fields are changed from multiple
603:                 // to single selection, and flow conditions are left over from before the change 
604:                 // was made
605:                 if (is_array ($value)) { 
606:                     AuxLib::coerceToArray ($subject);
607:                 }
608:                 return $subject == $value;
609: 
610:             case '>':
611:                 return $subject > $value;
612: 
613:             case '<':
614:                 return $subject < $value;
615: 
616:             case '>=':
617:                 return $subject >= $value;
618: 
619:             case '<=':
620:                 return $subject <= $value;
621: 
622:             case 'between':
623:                 if(count($value) !== 2)
624:                     return false;
625:                 return $subject >= min($value) && $subject <= max($value);
626: 
627:             case '<>':
628:             case '!=':
629:                 return $subject != $value;
630: 
631:             case 'notEmpty':
632:                 return $subject !== null && $subject !== '';
633: 
634:             case 'empty':
635:                 return $subject === null || trim($subject) === '';
636: 
637:             case 'list':
638:                 if(count($value) === 0)    // if the list is empty,
639:                     return false;                                // A isn't in it
640:                 foreach($value as &$val)
641:                     if($subject == $val)
642:                         return true;
643: 
644:                 return false;
645: 
646:             case 'notList':
647:                 if(count($value) === 0)    // if the list is empty,
648:                     return true;                                // A isn't *not* in it
649:                 foreach($value as &$val)
650:                     if($subject == $val)
651:                         return false;
652: 
653:                 return true;
654: 
655:             case 'noContains':
656:                 return stripos($subject,$value) === false;
657: 
658:             case 'contains':
659:             default:
660:                 return stripos($subject,$value) !== false;
661:         }
662:     }
663: 
664:     // const T_VAR = 0;
665:     // const T_SPACE = 1;
666:     // const T_COMMA = 2;
667:     // const T_OPEN_BRACKET = 3;
668:     // const T_CLOSE_BRACKET = 4;
669:     // const T_PLUS = 5;
670:     // const T_MINUS = 6;
671:     // const T_TIMES = 7;
672:     // const T_DIVIDE = 8;
673:     // const T_OPEN_PAREN = 9;
674:     // const T_CLOSE_PAREN = 10;
675:     // const T_NUMBER = 11;
676:     // const T_ERROR = 12;
677: 
678:     protected static $_tokenChars = array(
679:         ',' => 'COMMA',
680:         '{' => 'OPEN_BRACKET',
681:         '}' => 'CLOSE_BRACKET',
682:         '+' => 'ADD',
683:         '-' => 'SUBTRACT',
684:         '*' => 'MULTIPLY',
685:         '/' => 'DIVIDE',
686:         '%' => 'MOD',
687:         // '(' => 'OPEN_PAREN',
688:         // ')' => 'CLOSE_PAREN',
689:     );
690:     protected static $_tokenRegex = array(
691:         '\d+\.\d+\b|^\.?\d+\b' => 'NUMBER',
692:         '[a-zA-Z]\w*\.[a-zA-Z]\w*' => 'VAR_COMPLEX',
693:         '[a-zA-Z]\w*' => 'VAR',
694:         '\s+' => 'SPACE',
695:         '.' => 'UNKNOWN',
696:     );
697: 
698:     /**
699:      * Breaks a string expression into an array of 2-element arrays (type, value)
700:      * using {@link $_tokenChars} and {@link $_tokenRegex} to identify tokens
701:      * @param string $str the input expression
702:      * @return array a flat array of tokens
703:      */
704:     protected static function tokenize($str) {
705:         $tokens = array();
706:         $offset = 0;
707:         while($offset < mb_strlen($str)) {
708:             $token = array();
709: 
710:             $substr = mb_substr($str,$offset);    // remaining string starting at $offset
711: 
712:             foreach(self::$_tokenChars as $char => &$name) {    // scan single-character patterns first
713:                 if(mb_substr($substr,0,1) === $char) {
714:                     $tokens[] = array($name);    // add it to $tokens
715:                     $offset++;
716:                     continue 2;
717:                 }
718:             }
719:             foreach(self::$_tokenRegex as $regex => &$name) {    // now loop through regex patterns
720:                 $matches = array();
721:                 if(preg_match('/^'.$regex.'/u',$substr,$matches) === 1) {
722:                     $tokens[] = array($name,$matches[0]);    // add it to $tokens
723:                     $offset += mb_strlen($matches[0]);
724:                     continue 2;
725:                 }
726:             }
727:             $offset++;    // no infinite looping, yo
728:         }
729:         return $tokens;
730:     }
731: 
732:     /**
733:      * Adds a new node at the end of the specified branch
734:      * @param array &$tree the tree object
735:      * @param array $nodePath array of branch indeces leading to the target branch
736:      * @value array an array containing the new node's type and value
737:      */
738:     protected static function addNode(&$tree,$nodePath,$value) {
739:         if(count($nodePath) > 0)
740:             return self::addNode($tree[array_shift($nodePath)],$nodePath,$value);
741: 
742:         $tree[] = $value;
743:         return count($tree) - 1;
744:     }
745: 
746:     /**
747:      * Checks if this branch has only one node and eliminates it by moving the child node up one level
748:      * @param array &$tree the tree object
749:      * @param array $nodePath array of branch indeces leading to the target node
750:      */
751:     protected static function simplifyNode(&$tree,$nodePath) {
752:         if(count($nodePath) > 0)                                                    // before doing anything, recurse down the tree using $nodePath
753:             return self::simplifyNode($tree[array_shift($nodePath)],$nodePath);        // to get to the targeted node
754: 
755:         $last = count($tree) - 1;
756: 
757:         if(empty($tree[$last][1]))
758:             array_pop($tree);
759:         elseif(count($tree[$last][1]) === 1)
760:             $tree[$last] = $tree[$last][1][0];
761:     }
762: 
763:     /**
764:      * Processes the expression tree and attempts to evaluate it
765:      * @param array &$tree the tree object
766:      * @param boolean $expression
767:      * @return mixed the value, or false if the tree was invalid
768:      */
769: /*     protected static function parseExpression(&$tree,$expression=false) {
770: 
771:         $answer = 0;
772: 
773:         // echo '1';
774:         for($i=0;$i<count($tree);$i++) {
775:             $prev = isset($tree[$i+1])? $tree[$i+1] : false;
776:             $next = isset($tree[$i+1])? $tree[$i+1] : false;
777: 
778: 
779:             switch($tree[$i][0]) {
780: 
781:                 case 'VAR':
782:                 case 'VAR_COMPLEX':
783:                     continue 2;
784: 
785:                 case 'EXPRESSION':    // please
786:                     $subresult = self::parseExpression($tree[$i][1],true);    // the expression itself must be valid
787:                     if($subresult === false)
788:                         return $subresult;
789: 
790:                     // if($next !== false)
791:                     break;
792: 
793:                 case 'EXPONENT':    // excuse
794:                     break;
795: 
796:                 case 'MULTIPLY':    // my
797:                     break;
798: 
799:                 case 'DIVIDE':    // dear
800:                     break;
801: 
802:                 case 'MOD':
803:                     break;
804: 
805: 
806:                 case 'ADD':    // aunt
807:                     break;
808: 
809:                 case 'SUBTRACT':    // sally
810:                     break;
811: 
812:                 case 'COMMA':
813: 
814:                     break;
815:                 case 'NUMBER':
816:                     break;
817: 
818: 
819:                 case 'SPACE':
820: 
821:                 case 'UNKNOWN':
822:                     return 'Unrecognized entity: "'.$tree[$i][1].'"';
823: 
824:                 default:
825:                     return 'Unknown entity type: "'.$tree[$i][0].'"';
826:             }
827:         }
828:         return true;
829:     } */
830: 
831:     /**
832:      * @param String $str string to be parsed into an expression tree
833:      * @return mixed a variable depth array containing pairs of entity
834:      * types and values, or a string containing an error message
835:      */
836:     public static function parseExpressionTree($str) {
837: 
838:         $tokens = self::tokenize($str);
839: 
840:         $tree = array();
841:         $nodePath = array();
842:         $error = false;
843: 
844:         for($i=0;$i<count($tokens);$i++) {
845:             switch($tokens[$i][0]) {
846:                 case 'OPEN_BRACKET':
847:                     $nodePath[] = self::addNode($tree,$nodePath,array('EXPRESSION',array()));    // add a new expression node, get its offset in the current branch,
848:                     $nodePath[] = 1;    // then move down to its 2nd element (1st element is the type, i.e. 'EXPRESSION')
849:                     break;
850:                 case 'CLOSE_BRACKET':
851:                     if(count($nodePath) > 1) {
852:                         $nodePath = array_slice($nodePath,0,-2);    // set node path to one level higher
853:                         self::simplifyNode($tree,$nodePath);        // we're closing an expression node; check to see if its empty or only contains one thing
854: 
855:                     } else {
856:                         $error = 'unbalanced brackets';
857:                     }
858:                     break;
859: 
860:                 case 'SPACE': break;
861:                 default:
862:                     self::addNode($tree,$nodePath,$tokens[$i]);
863:             }
864:         }
865: 
866:         if(count($nodePath) !== 0)
867:             $error = 'unbalanced brackets';
868: 
869:         if($error !== false)
870:             return 'ERROR: '.$error;
871:         else
872:             return $tree;
873:     }
874: 
875:     /**
876:      * Gets all X2Flow trigger types.
877:      *
878:      * Optionally constrains the list to those with a property matching a value.
879:      * @param string $queryProperty The property of each trigger to test
880:      * @param mixed $queryValue The value to match trigger against
881:      */
882:     public static function getTriggerTypes($queryProperty = False,$queryValue = False) {
883:         $types = array();
884:         foreach(self::getTriggerInstances() as $class) {
885:             $include = true;
886:             if($queryProperty)
887:                 $include = $class->$queryProperty == $queryValue;
888:             if($include)
889:               $types[get_class($class)] = Yii::t('studio',$class->title);
890:         }
891:         return $types;
892:     }
893: 
894:     /**
895:      * Gets X2Flow trigger title.
896:      * 
897:      * @param string $triggerType The trigger class name
898:      * @return string the empty string or the title of the trigger with the given class name
899:      */
900:     public static function getTriggerTitle ($triggerType) {
901:         foreach(self::getTriggerInstances() as $class) {
902:             if (get_class ($class) === $triggerType) {
903:                 return Yii::t('studio', $class->title);
904:             }
905:         }
906:         return '';
907:     }
908: 
909:     public static function getTriggerInstances(){
910:         return self::getInstances('triggers',array(__CLASS__,'X2FlowSwitch','X2FlowSplitter','BaseTagTrigger','BaseWorkflowStageTrigger', 'BaseWorkflowTrigger', 'BaseUserTrigger', 'MultiChildNode'));
911:     }
912: }
913: 
X2CRM Documentation API documentation generated by ApiGen 2.8.0