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
39: */
40: class X2FlowFormatter extends Formatter {
41:
42: /**
43: * Parses a "short code" as a part of variable replacement.
44: *
45: * Short codes are defined in the file protected/components/x2flow/shortcodes.php
46: * and are a list of manually defined pieces of code to be run in variable replacement.
47: * Because they are stored in a protected directory, validation on allowed
48: * functions is not performed, as it is the user's responsibility to edit this file.
49: *
50: * @param String $key The key of the short code to be used
51: * @param X2Model $model The model having variables replaced, some short codes
52: * use a model
53: * @return mixed Returns the result of code evaluation if a short code
54: * existed for the index $key, otherwise null
55: */
56: private static function parseShortCode($key, array $params){
57: if (isset ($params['model'])) {
58: $model = $params['model'];
59: }
60: $path = implode(DIRECTORY_SEPARATOR,
61: array(Yii::app()->basePath,'components','x2flow','shortcodes.php'));
62: if(file_exists($path)){
63: $shortCodes = include(Yii::getCustomPath ($path));
64: if(isset($shortCodes[$key])){
65: return eval($shortCodes[$key]);
66: }
67: }
68: return null;
69: }
70:
71: /**
72: * Parses text for short codes and returns an associative array of them.
73: * Overrides parent method to add support for shortcodes, missing model param, and insertable
74: * attributes referencing param names
75: *
76: * @param string $value The value to parse
77: * @param X2Model $model The model on which to operate with attribute replacement
78: * @param bool $renderFlag The render flag to pass to {@link X2Model::getAttribute()}
79: * @param bool $makeLinks If the render flag is set, determines whether to render attributes
80: * as links
81: */
82: protected static function getReplacementTokens(
83: $value, array $params, $renderFlag, $makeLinks) {
84:
85: if (isset ($params['model'])) {
86: $model = $params['model'];
87: } else {
88: $model = null;
89: }
90:
91: // Pattern will match {attr}, {attr1.attr2}, {attr1.attr2.attr3}, etc.
92: $codes = array();
93: // Types of each value for the short codes:
94: $codeTypes = array();
95: $fieldTypes = array_map(function($f){return $f['phpType'];},Fields::getFieldTypes());
96: if ($model)
97: $fields = $model->getFields(true);
98: else
99: $fields = array ();
100: // check for variables
101: preg_match_all('/{([a-z]\w*)(\.[a-z]\w*)*?}/i', trim($value), $matches);
102:
103: $isRenderException = function ($match) use ($fields) {
104: return isset ($fields[$match]) && $fields[$match]->fieldName === 'id';
105: };
106:
107: if(!empty($matches[0])){
108: foreach($matches[0] as $match){
109: $match = substr($match, 1, -1); // Remove the "{" and "}" characters
110: $attr = $match;
111: if(strpos($match, '.') !== false){
112: // We found a link attribute (i.e. {company.name})
113: $newModel = $model;
114: $newModelFields = $fields;
115:
116: $pieces = explode('.',$match);
117: $first = array_shift($pieces);
118:
119: // First check if the first piece is part of a short code, like "user"
120: $tmpModel = self::parseShortCode(
121: $first, array_merge ($params, array ('model' => $newModel)));
122:
123: if(isset($tmpModel) && $tmpModel instanceof CActiveRecord){
124: // If we got a model from our short code, use that
125: $newModel = $tmpModel;
126: // Also, set the attribute to have the first item removed.
127: $attr = implode('.',$pieces);
128: if ($newModel instanceof X2Model) {
129: $newModelFields = $newModel->getFields (true);
130: } else {
131: $newModelFields = array ();
132: }
133: }
134:
135: if ($newModel) {
136: $codes['{'.$match.'}'] = $newModel->getAttribute(
137: $attr,
138: $isRenderException ($match) ? true : $renderFlag,
139: $makeLinks);
140: $codeTypes[$match] = isset($newModelFields[$attr])
141: && isset($fieldTypes[$newModelFields[$attr]->type])
142: ? $fieldTypes[$newModelFields[$attr]->type]
143: : 'string';
144: }
145:
146: }else{ // Standard attribute
147: // First check if we provided a value for this attribute
148: if(isset($params[$match]) && is_scalar ($params[$match])){
149: $codes['{'.$match.'}'] = $params[$match];
150: $codeTypes[$match] = gettype($params[$match]);
151: // Next check if the attribute exists on the model
152: }elseif($model && $model->hasAttribute($match)){
153: $codes['{'.$match.'}'] = $model->getAttribute(
154: $match,
155: $isRenderException ($match) ? true : $renderFlag,
156: $makeLinks);
157: $codeTypes[$match] = isset($fields[$match])
158: && isset($fieldTypes[$fields[$match]->type])
159: ? $fieldTypes[$fields[$match]->type]
160: : 'string';
161:
162: } else {
163: // Finally, try to parse it as a short code if nothing else worked
164: $shortCodeValue = self::parseShortCode($match, $params);
165: if(!is_null($shortCodeValue) && is_scalar ($shortCodeValue)){
166: $codes['{'.$match.'}'] = $shortCodeValue;
167: $codeTypes[$match] = gettype($shortCodeValue);
168: }
169: }
170: }
171: }
172: }
173:
174: $codes = self::castReplacementTokenTypes ($codes, $codeTypes);
175: return $codes;
176: }
177:
178: }
179:
180: ?>
181: