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: * Provides utility methods for handling quick creation of records and relationships.
39: * This class involves the use of two models:
40: * The model associated with the owner of this behavior (referred to as 'the first model') and
41: * the model associated with the view from which the quick create ajax request was made
42: * (referred to as 'the second model').
43: *
44: * @package application.components
45: */
46: class QuickCreateRelationshipBehavior extends QuickCRUDBehavior {
47:
48: /**
49: * Used to specify which attributes (for a given model type) should be updated to match
50: * the first model's attribute values.
51: * @var array (<model type> => <array of attributes in second model indexed by attributes in
52: * the first model>)
53: */
54: public $attributesOfNewRecordToUpdate = array ();
55:
56: protected $inlineFormPathAlias = 'application.components.views._form';
57:
58: private static $_modelsWhichSupportQuickCreate;
59:
60: /**
61: * Returns an array of all model classes (associated with some module) which have this
62: * behavior
63: *
64: * @return <array of strings>
65: */
66: public static function getModelsWhichSupportQuickCreate ($includeActions=false) {
67: if (!isset (self::$_modelsWhichSupportQuickCreate)) {
68: self::$_modelsWhichSupportQuickCreate = array_diff (
69: array_keys (X2Model::getModelNames()),
70: array ('Docs', 'Groups', 'Campaign', 'Media', 'Quote',
71: 'BugReports'));
72: self::$_modelsWhichSupportQuickCreate[] = 'Actions';
73: }
74: $modelNames = self::$_modelsWhichSupportQuickCreate;
75: if (!$includeActions) {
76: array_pop ($modelNames);
77: }
78: return $modelNames;
79: }
80:
81: /**
82: * @param array $models
83: * @return array of urls for create actions of each model in $models
84: */
85: public static function getCreateUrlsForModels ($models) {
86: return parent::getUrlsForModels ($models, 'create');
87: }
88:
89: /**
90: * Returns array of dialog titles to be used for quick create dialogs for each model
91: * @param array $models
92: * @return array
93: */
94: public static function getDialogTitlesForModels ($models) {
95: // get create relationship dialog titles for each linkable model
96: $dialogTitles = array_flip ($models);
97: array_walk (
98: $dialogTitles,
99: function (&$val, $key) {
100: $val = Yii::t('app',
101: 'Create {relatedModelClass}',
102: array ('{relatedModelClass}' => ucfirst (X2Model::getRecordName ($key))));
103: });
104: return $dialogTitles;
105: }
106:
107: /**
108: * Returns array of tooltips to be applied to quick create buttons for each model
109: * @param array $models
110: * @param string $modelName
111: * @return array
112: */
113: public static function getDialogTooltipsForModels ($models, $modelName) {
114: $tooltips = array_flip ($models);
115: array_walk (
116: $tooltips,
117: function (&$val, $key) use ($modelName) {
118: $val = Yii::t('app',
119: 'Create a new {relatedModelClass} associated with this {modelClass}',
120: array (
121: '{relatedModelClass}' => X2Model::getRecordName ($key),
122: '{modelClass}' =>
123: X2Model::getRecordName (X2Model::getModelName ($modelName))
124: )
125: );
126: });
127: return $tooltips;
128: }
129:
130: /**
131: * For controllers implementing this behavior, this method should be called if the GET parameter
132: * 'x2ajax' is set to '1' after the model is created and fields are set.
133: *
134: * If called from the record create page:
135: * No record exists yet for the second model. An array is echoed containing values of the
136: * first model which should be used to populate fields in the create form of the second model.
137: *
138: * If called from the record view page:
139: * Attempts to create a new relationship between first and second models.
140: * If creation of new record is successful and if the second model has been updated,
141: * an updated detailView of the second model is returned.
142: *
143: * If the first record could not be created, the create form is rendered again with errors.
144: *
145: * @return bool true if errors were encountered, false otherwise
146: */
147: public function quickCreate ($model) {
148: Yii::app()->clientScript->scriptMap['*.css'] = false;
149:
150: $errors = false;
151:
152: if (isset ($_POST['validateOnly'])) return;
153:
154: if ($model->save ()) {
155: if (isset ($_POST['ModelName'])) {
156: $secondModelName = $_POST['ModelName'];
157: }
158: if (!empty ($_POST['ModelId'])) {
159: $secondModelId = $_POST['ModelId'];
160: }
161:
162: if (isset ($secondModelName) && !empty ($secondModelId)) {
163: $secondModel = $this->quickCreateRelationship (
164: $model, $secondModelName, $secondModelId);
165: echo CJSON::encode (
166: array (
167: 'status' => 'success',
168: 'data' => ($secondModel ? $this->owner->getDetailView ($secondModel) : ''),
169: 'name' => $model->name,
170: 'id' => $model->id,
171: 'attributes' => $model->getVisibleAttributes (),
172: ));
173: } else if (isset ($secondModelName)) {
174: $data = $this->getValuesOfNewRecordToUpdate ($model, $secondModelName);
175: echo CJSON::encode (
176: array (
177: 'status' => 'success',
178: 'data' => $data,
179: 'name' => $model->name,
180: 'id' => $model->id,
181: 'attributes' => $model->getVisibleAttributes (),
182: ));
183: } else {
184: $model->refresh ();
185: $modelClass = get_class ($model);
186: $modelLink = ($modelClass === 'Actions' ?
187: $model->getLink (30, false) : $model->getLink());
188: if (!isset ($_POST['saveOnly'])) {
189: echo CJSON::encode (
190: array (
191: 'status' => 'success',
192: 'message' => Yii::t('app', '{recordType} created: {link}', array (
193: '{recordType}' => $modelClass,
194: '{link}' => $modelLink,
195: )),
196: 'attributes' => $model->getVisibleAttributes (),
197: ));
198: }
199: }
200:
201: if (!isset ($_POST['saveOnly'])) Yii::app()->end();
202: } else {
203: $errors = true;
204: }
205:
206: return $errors;
207: }
208:
209: /**
210: * Renders an inline record create/update form
211: * @param object $model
212: * @param bool $hasErrors
213: */
214: public function renderInlineForm ($model, array $viewParams = array ()) {
215: //@FORMVIEW
216: $that = $this;
217: echo CJSON::encode (
218: array (
219: 'status' => $model->hasErrors () ? 'userError' : 'success',
220: 'page' =>
221: X2Widget::ajaxRender (function () use ($model, $that, $viewParams) {
222: echo $that->owner->widget(
223: 'FormView',
224: array_merge (array(
225: 'model' => $model,
226: 'suppressQuickCreate' => true,
227: 'formSettings' => array ()
228: ), $viewParams), true, true);
229: }, true),
230: ));
231: }
232:
233: /**
234: * Returns an associative array of values of the first model indexed by attribute
235: * names in the second model.
236: * @return array (<name of attribute to modify => <value of attribute in new record>)
237: */
238: private function getValuesOfNewRecordToUpdate ($firstModel, $secondModelName) {
239: $attributesToUpdate = (isset ($this->attributesOfNewRecordToUpdate[$secondModelName]) ?
240: $this->attributesOfNewRecordToUpdate[$secondModelName] : array ());
241:
242: $data = array ();
243: foreach ($attributesToUpdate as $firstModelAttr => $secondModelAttr) {
244: if (isset ($firstModel->$firstModelAttr)) {
245: $data[$secondModelAttr] = $firstModel->$firstModelAttr;
246: }
247: }
248:
249: return $data;
250: }
251:
252: /**
253: * Creates a new relationship and then, based on the value of attributesOfNewRecordToUpdate,
254: * sets values of the second model using values of the first model.
255: * Returns an array of the values that were changed indexed by the attribute name.
256: * @param object $firstModel
257: * @param string $firstModelNamethe class name of the first model
258: * @param string $firstModelId the id of the first model
259: * @param string $secondModelName the class name of the second model
260: * @param string $secondModelId the id of the second model
261: * @return mixed false if the second model isn't updated, the second model otherwise
262: */
263: private function quickCreateRelationship (
264: $firstModel, $secondModelName, $secondModelId) {
265:
266: $secondModel = $secondModelName::model ()->findByPk ($secondModelId);
267: $firstModel->createRelationship($secondModel);
268:
269: $attributesToUpdate = (isset ($this->attributesOfNewRecordToUpdate[$secondModelName]) ?
270: $this->attributesOfNewRecordToUpdate[$secondModelName] : array ());
271:
272: if ($secondModel) {
273: $changed = false;
274:
275: /*
276: Set values of existing record to values of newly created record based on mapping
277: configured in $attributesOfNewRecordToUpdate
278: */
279: foreach ($attributesToUpdate as $firstModelAttr => $secondModelAttr) {
280:
281: if (isset ($firstModel->$firstModelAttr) &&
282: (!isset ($secondModel->$secondModelAttr) ||
283: $secondModel->$secondModelAttr === '')) {
284:
285: $secondModel->$secondModelAttr = $firstModel->$firstModelAttr;
286:
287: $changed = true;
288: }
289: }
290:
291: if ($changed) {
292: $secondModel->update ();
293: }
294: }
295:
296: if ($secondModel && $changed) return $secondModel;
297: else return false;
298: }
299:
300: /**
301: * Alias for {@link renderInlineForm} preserved for backwards compatibility with
302: * TemplatesController.
303: * @deprecated
304: */
305: public function renderInlineCreateForm ($model, $hasErrors) {
306: $this->renderInlineForm ($model);
307: }
308:
309: }
310: ?>
311: