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.components.x2flow
39: */
40: abstract class X2FlowItem extends CComponent {
41:
42: const VALIDATION_WARNING = 2;
43:
44: /**
45: * "Cache" of instantiated triggers, for reference purposes
46: */
47: protected static $_instances;
48:
49: /**
50: * $var string the text label for this action
51: */
52: public $label = '';
53:
54: /**
55: * $var string the description of this action
56: */
57: public $info = '';
58:
59: /**
60: * $var array the config parameters for this action
61: */
62: public $config = '';
63:
64: /**
65: * $var bool distinguishes whether cron is required for running the action properly
66: */
67: public $requiresCron = false;
68:
69: /**
70: * @return array the param rules.
71: */
72: abstract public function paramRules();
73:
74: /**
75: * Checks if all all the params are ship-shape
76: */
77: abstract public function validate(&$params=array(), $flowId=null);
78:
79: /**
80: * Checks if all the config variables and runtime params are ship-shape
81: * Ignores param requirements if $params isn't provided
82: * @param bool $staticValidation If true, validation will include checks for warnings
83: */
84: public function validateOptions(&$paramRules,$params=null,$staticValidation=false) {
85: $configOptions = &$this->config['options'];
86:
87: // loop through options defined in paramRules() and make sure they're all set in $config
88: foreach($paramRules['options'] as &$optRule) {
89: if(!isset($optRule['name'])) // don't worry about unnamed params
90: continue;
91: $optName = &$optRule['name'];
92:
93: // each option must be present in $this->config and $params
94: if(!isset($configOptions[$optName])) {
95: if (isset ($optRule['defaultVal'])) {
96: $configOptions[$optName] = array ();
97: } else {
98: continue; // but just ignore them for future proofing
99: }
100: }
101: // if params are provided, check them for this option name
102: // if($params !== null && !isset($params[$optName]))
103: // return false;
104: // this is disabled because it doesn't work if every option in $options doesn't
105: // correspond to a $param. the ultimate solution is to separate params and options
106: // completely. if a trigger/action is going to require params, it should define this
107: // separately. the reason for the way it is now is that you can set up an action with
108: // very little code. by assuming $params corresponds to $options, check() can
109: // treat each option like a condition and compare it to the param.
110:
111: $option = &$configOptions[$optName];
112:
113: // set optional flag
114: $option['optional'] = isset($optRule['optional']) && $optRule['optional'];
115: $option['comparison'] = isset($optRule['comparison']) ? $optRule['comparison'] : true;
116:
117: // operator defaults to "=" if not set
118: $option['operator'] = isset($option['operator']) ? $option['operator'] : '=';
119:
120: // if there's a type setting, set that in the config data
121: if(isset($optRule['type']))
122: $option['type'] = $optRule['type'];
123:
124: // if there's an operator setting, it must be valid
125: if(isset($optRule['operators']) &&
126: !in_array($option['operator'], $optRule['operators'])) {
127:
128: return array (
129: false,
130: Yii::t('studio', 'Flow item validation error: Invalid operator'));
131: }
132:
133: // value must not be empty, unless it's an optional setting
134: if(!isset($option['value']) || $option['value'] === null || $option['value'] === '') {
135: if(isset($optRule['defaultVal'])) {
136:
137: // use the default value
138: $option['value'] = $optRule['defaultVal'];
139: } elseif(!$option['optional']) {
140:
141: // if not, fail if it was required
142: if (YII_DEBUG) {
143: return array (
144: false,
145: Yii::t('studio',
146: 'Required flow item input missing: {optName} was left blank.',
147: array ('{optName}' => $optName)));
148: } else {
149: return array (
150: false,
151: Yii::t('studio', 'Required flow item input missing'));
152: }
153: }
154: }
155:
156: if (isset ($option['type']) && isset ($option['value'])) {
157: switch ($option['type']) {
158: case 'dropdown':
159: list ($success, $message) = $this->validateDropdown ($option, $optRule);
160: if (!$success) return array (false, $message);
161: break;
162: case 'email':
163: list ($success, $message) = $this->validateEmail ($option, $optRule);
164: if (!$success) return array (false, $message);
165: break;
166: }
167: }
168: }
169:
170: return array (true, '');
171: }
172:
173: public function validateEmail ($option, $optRule) {
174: if (isset ($option['value']) &&
175: !Formatter::isFormula ($option['value']) &&
176: !Formatter::isShortcode ($option['value'])) {
177: try {
178: EmailDeliveryBehavior::addressHeaderToArray ($option['value']);
179: } catch (CException $e) {
180: return array (false, $e->getMessage ());
181: }
182: }
183: return array (true, '');
184: }
185:
186: public function validateDropdown (&$option, $optRule) {
187: $name = $optRule['name'];
188: if (!((isset ($option['operator']) &&
189: in_array ($option['operator'], array ('list', 'notList', 'between'))) ||
190: (isset ($optRule['multiple']) && $optRule['multiple'])) &&
191: is_array ($option['value'])) {
192:
193: if (count ($option['value']) === 1 &&
194: isset ($option['value'][0])) { // repair value if possible
195:
196: $option['value'] = $option['value'][0];
197: return array (true, '');
198: }
199:
200: return array (false, Yii::t('studio', 'Invalid option value for {optionName}. '.
201: 'Multiple values specified but only one is allowed.', array (
202: '{optionName}' => $name,
203: )));
204: }
205: return array (true, '');
206: }
207:
208: /**
209: * Gets the param rules for the specified flow item
210: * @param string $type name of action class
211: * @return mixed an array of param rules, or false if the action doesn't exist
212: */
213: public static function getParamRules($type) {
214: $item = self::create(array('type'=>$type));
215: if($item !== null) {
216: $paramRules = $item->paramRules();
217: $paramRules['class'] = get_class ($item);
218: return $paramRules;
219: }
220: return false;
221: }
222:
223: /**
224: * Gets the title property of the specified flow item
225: * @param string $type name of action class
226: * @return string the title property, or '' if the type is invalid of if the class
227: * associated with the type doesn't have a title property
228: */
229: public static function getTitle ($type) {
230: $item = self::create(array('type'=>$type));
231: $title = '';
232: if ($item !== null && property_exists ($item, 'title')) {
233: $title = $item->title;
234: }
235: return $title;
236: }
237:
238: /**
239: * Creates a flow item with the provided config data
240: * @return mixed a class extending X2FlowAction with the specified name
241: */
242: public static function create($config) {
243: if(isset($config['type']) && class_exists($config['type'])) {
244: $item = new $config['type'];
245: $item->config = $config;
246: return $item;
247: }
248: return null;
249: }
250:
251: /**
252: * Calculates a time offset from a number and a unit
253: * @param int $time the number of time units to add
254: * @param string $unit the unit of time
255: * @return mixed the calculated timestamp, or false if the $unit is invalid
256: */
257: public static function calculateTimeOffset($time,$unit) {
258: switch($unit) {
259: case 'secs':
260: return $time;
261: case 'mins':
262: return $time * 60;
263: case 'hours':
264: return $time * 3600;
265: case 'days':
266: return $time * 86400;
267: case 'months':
268: return $time * 2629743; // average seconds in a month
269: default:
270: return false;
271: }
272: }
273:
274: /**
275: * @param string $name the name of the option
276: * @param array $params the parameters passed to trigger ()
277: * @return mixed null if the option was not set by the user, the parsed value otherwise
278: */
279: public function parseOption($name,&$params) {
280: $options = &$this->config['options'];
281: if(!isset($options[$name]['value']))
282: return null;
283:
284: $type = isset($options[$name]['type'])? $options[$name]['type'] : '';
285:
286: return X2Flow::parseValue($options[$name]['value'],$type,$params);
287: }
288:
289: /**
290: * Generalized mass-instantiation method.
291: *
292: * Loads and instantiates all X2Flow items of a given type (i.e. actions,
293: * triggers).
294: *
295: * @param type $type
296: * @param type $excludeClasses
297: * @return type
298: */
299: public static function getInstances($type,$excludeClasses=array()) {
300: if(!isset(self::$_instances))
301: self::$_instances = array();
302: if(!isset(self::$_instances[$type])) {
303: $excludeFiles = array();
304: foreach($excludeClasses as $class) {
305: $excludedFiles[] = "$class.php";
306: }
307: $excludedFiles[] = '.';
308: $excludedFiles[] = '..';
309:
310: self::$_instances[$type] = array();
311: foreach(scandir(Yii::getPathOfAlias('application.components.x2flow.'.$type)) as $file) {
312: if(!preg_match ('/\.php$/', $file) || in_array($file,$excludedFiles)) {
313: continue;
314: }
315: $class = self::create(array('type'=>substr($file,0,-4)));
316: if($class !== null)
317: self::$_instances[$type][] = $class;
318: }
319: }
320: return self::$_instances[$type];
321: }
322:
323:
324: }
325: