1: <?php
  2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35: 
 36: 
 37:  38:  39: 
 40: 
 41: class X2ModelConversionBehavior extends CActiveRecordBehavior {
 42: 
 43:      44:  45: 
 46:     private $_fieldMappings = array (
 47:         'X2Leads' => array (
 48:             'Contacts' => array (
 49:                 'accountName' => 'company',
 50:                 'quoteAmount' => 'dealvalue',
 51:                 'salesStage' => 'dealstatus',
 52:                 'probability' => null,
 53:                 'description' => 'backgroundInfo',
 54:             ),
 55:             'Opportunity' => array (
 56:                 'firstName' => null, 
 57:                 'lastName' => null,
 58:             ),
 59:         )
 60:     );
 61: 
 62:     public $deleteConvertedRecord = true;
 63: 
 64:      65:  66: 
 67:     public $convertedField; 
 68: 
 69:      70:  71: 
 72:     public $conversionDateField; 
 73: 
 74:      75:  76: 
 77:     public $convertedToTypeField;
 78: 
 79:      80:  81: 
 82:     public $convertedToIdField;
 83: 
 84:      85:  86: 
 87:     public $conversionFailed = false; 
 88: 
 89:      90:  91:  92: 
 93:     public $errorModel = null; 
 94: 
 95:     public static function getActions () {
 96:         return array (
 97:             'convert' => 
 98:                 'application.components.recordConversion.X2ModelConversionAction',
 99:         );
100:     }
101:         
102:    103: 104: 105: 
106:     public function getFieldMap ($targetClass, $flip=false) {
107:         $modelClass = get_class ($this->owner);
108:         if (!isset ($this->_fieldMappings[$modelClass]) || 
109:             !isset ($this->_fieldMappings[$modelClass][$targetClass])) {
110: 
111:             return array ();
112:         }
113:         $fieldMappings = $this->_fieldMappings[$modelClass][$targetClass];
114:         if ($flip) {
115:             foreach ($fieldMappings as $key => $val) {
116:                 if ($val === null) { 
117:                     unset ($fieldMappings[$key]);
118:                 }
119:             }
120:             $fieldMappings = array_flip ($fieldMappings);
121:         }
122:         return $fieldMappings;
123:     }
124: 
125:     126: 127: 128: 129: 
130:     public function mapFields (array $attributes, $targetClass, $associative=false) {
131:         $modelClass = get_class ($this->owner);
132:         $fieldMappings = $this->getFieldMap ($targetClass);
133:         if (!count ($fieldMappings)) {
134:             return $attributes;
135:         }
136:         $attributeNames = array_flip ($associative ? array_keys ($attributes) : $attributes);
137:         foreach ($fieldMappings as $source => $target) {
138:             if (isset ($attributeNames[$source])) {
139:                 if ($target !== null) {
140:                     if ($associative) {
141:                         $attributes[$target] = $attributes[$source];
142:                     } else {
143:                         $attributes[] = $target;
144:                     }
145:                 }
146:                 $key = $associative ? $source : array_search ($source, $attributes);
147:                 unset ($attributes[$key]);
148:             }
149:         }
150:         return $attributes;
151:     }
152: 
153:     154: 155: 
156:     public function attributeNames ($model) {
157:         $conversionAttrs = array ();
158:         if ($this->conversionDateField) $conversionAttrs[] = $this->conversionDateField;
159:         if ($this->convertedField) $conversionAttrs[] = $this->convertedField;
160:         if ($this->convertedToTypeField) $conversionAttrs[] = $this->convertedToTypeField;
161:         if ($this->convertedToIdField) $conversionAttrs[] = $this->convertedToIdField;
162:         return array_diff ($model->attributeNames (), $conversionAttrs);
163:     }
164: 
165:     166: 167: 
168:     public function checkConversionCompatibility ($targetClass) {
169:         $targetModel = new $targetClass ();
170:         $fieldMap = $this->getFieldMap ($targetClass, true);
171: 
172:         
173:         $fieldDiff = array_diff (
174:             $this->mapFields ($this->attributeNames ($this->owner), $targetClass), 
175:             $targetClass::model ()->attributeNames ());
176:         if (count ($fieldDiff) > 0) {
177:             $potentialDataLoss = false;
178:             foreach ($fieldDiff as $name) {
179:                 $name = isset ($fieldMap[$name]) ? $fieldMap[$name] : $name;
180:                 if (isset ($this->owner->$name)) {
181:                     $potentialDataLoss = true;
182:                 }
183:             }
184:             if ($potentialDataLoss) return false;
185:         }
186: 
187:         
188:         
189:         $sharedAttrs = array_intersect (
190:             $this->mapFields ($this->attributeNames ($this->owner), $targetClass), 
191:             $targetModel->attributeNames ());
192: 
193:         foreach ($sharedAttrs as $name) {
194:             
195:             $sourceFieldName = isset ($fieldMap[$name]) ? $fieldMap[$name] : $name;
196: 
197:             $sourceField = $this->owner->getField ($sourceFieldName);
198:             $targetModelField = $targetModel->getField ($name);
199: 
200:             if (!$sourceField instanceof Fields || !$targetModelField instanceof Fields) {
201:                 continue;
202:             }
203: 
204:             if ($sourceField->type !== $targetModelField->type) {
205:                 return false;
206:             }
207:         }
208: 
209:         return true;
210:     }
211: 
212:     213: 214: 215: 216: 217: 
218:     public function convert ($targetClass, $force=false) {
219:         $attributes = $this->mapFields ($this->owner->getAttributes (), $targetClass, true);
220:         unset ($attributes['id']);
221:         unset ($attributes['nameId']);
222:         unset ($attributes['createDate']);
223:         if ($this->convertedField)
224:             unset ($attributes[$this->convertedField]);
225:         if ($this->conversionDateField)
226:             unset ($attributes[$this->conversionDateField]);
227:         if ($this->convertedToTypeField)
228:             unset ($attributes[$this->convertedToTypeField]);
229:         if ($this->convertedToIdField)
230:             unset ($attributes[$this->convertedToIdField]);
231: 
232:         $targetModel = new $targetClass ();
233: 
234:         if (!$force && !$this->checkConversionCompatibility ($targetClass)) { 
235:             return false;
236:         }
237: 
238:         $targetModel->setAttributes ($attributes, false);
239: 
240:         
241:         $targetModel->disableBehavior('changelog'); 
242:         if ($targetModel->save ()) {
243:             $targetModel->mergeRelatedRecords ($this->owner);
244:             $changeLogBehavior = $this->owner->asa ('changelog');
245:             $changeLogBehavior->createEvent = false; 
246:             if ($this->deleteConvertedRecord) {
247:                 $this->owner->delete ();
248:             } else {
249:                 $updated = array ();
250:                 if ($this->convertedField) {
251:                     $convertedField = $this->convertedField;
252:                     $this->owner->$convertedField = true;
253:                     $updated[] = $convertedField;
254:                 }
255:                 if ($this->conversionDateField) {
256:                     $conversionDateField = $this->conversionDateField;
257:                     $this->owner->$conversionDateField = time ();
258:                     $updated[] = $conversionDateField;
259:                 }
260:                 if ($this->convertedToTypeField) {
261:                     $convertedToTypeField = $this->convertedToTypeField;
262:                     $this->owner->$convertedToTypeField = get_class ($targetModel);
263:                     $updated[] = $convertedToTypeField;
264:                 }
265:                 if ($this->convertedToIdField) {
266:                     $convertedToIdField = $this->convertedToIdField;
267:                     $this->owner->$convertedToIdField = $targetModel->id;
268:                     $updated[] = $convertedToIdField;
269:                 }
270:                 if ($updated)
271:                     $this->owner->update ($updated);
272: 
273:             }
274:             return $targetModel;
275:         }
276:         $this->errorModel = $targetModel;
277:         return $targetModel;
278:     }
279: 
280:     281: 282: 283: 
284:     public function getConversionIncompatibilityWarnings ($targetClass) {
285:         $warnings = array ();
286:         $targetModel = $targetClass::model ();
287:         $attributeNames = $this->mapFields ($this->attributeNames ($this->owner), $targetClass);
288:         $leadsAttrs = array_diff (
289:             $attributeNames, $targetClass::model()->attributeNames ());
290:         $fieldMap = $this->getFieldMap ($targetClass, true);
291: 
292:         
293:         foreach ($leadsAttrs as $name) {
294:             $name = isset ($fieldMap[$name]) ? $fieldMap[$name] : $name;
295:             
296:             if (!isset ($this->owner->$name)) continue;
297:             $warnings[] = 
298:                 Yii::t('app', 
299:                     'A field {fieldName} is in Leads but not in {targetModel}.',
300:                     array (
301:                         '{fieldName}' => $name,
302:                         '{targetModel}' => X2Model::getModelTitle ($targetClass),
303:                     )
304:                 );
305:         }
306: 
307:         
308:         $sharedAttrs = array_intersect (
309:             $attributeNames, $targetModel->attributeNames ());
310:         foreach ($sharedAttrs as $name) {
311:             $originalName = $name;
312:             $sourceFieldName = isset ($fieldMap[$name]) ? $fieldMap[$name] : $name;
313:             $sourceField = $this->owner->getField ($sourceFieldName);
314:             $targetModelField = $targetModel->getField ($name);
315: 
316:             if (!$sourceField instanceof Fields || !$targetModelField instanceof Fields) {
317:                 continue;
318:             }
319: 
320:             if ($sourceField->type !== $targetModelField->type) {
321:                 if (isset ($fieldMap[$originalName])) {
322:                     $warnings[] = 
323:                         Yii::t('app', 
324:                             'The {model} field {fieldName} maps to the {targetModel} field '.
325:                             '{targetField} but the fields have different types.', 
326:                             array (
327:                                 '{fieldName}' => $sourceFieldName,
328:                                 '{targetField}' => $name,
329:                                 '{model}' => X2Model::getModelTitle (get_class ($this->owner)),
330:                                 '{targetModel}' => X2Model::getModelTitle ($targetClass),
331:                             )
332:                         );
333:                 } else {
334:                     $warnings[] = 
335:                         Yii::t('app', 
336:                             'A field {fieldName} is in both {model} and {targetModel} but the fields
337:                              have different types.', 
338:                             array (
339:                                 '{fieldName}' => $name,
340:                                 '{model}' => X2Model::getModelTitle (get_class ($this->owner)),
341:                                 '{targetModel}' => X2Model::getModelTitle ($targetClass),
342:                             )
343:                         );
344:                 }
345:             }
346:         }
347: 
348:         return $warnings;
349:     }
350: 
351:     352: 353: 354: 
355:     public function errorSummary ($targetModelClass, $convertMultiple=false) {
356:         $sourceModelClass = get_class ($this->owner);
357:         $sourceModelTitle = X2Model::getModelTitle ($sourceModelClass, true);
358:         $targetModelTitle = X2Model::getModelTitle ($targetModelClass, true);
359:         $sourceModelTitlePlural = X2Model::getModelTitle ($sourceModelClass, false);
360:         $targetModelTitlePlural = X2Model::getModelTitle ($targetModelClass, false);
361: 
362:         $html = '<p>';
363:         $html .= CHtml::encode (!$convertMultiple ?
364:             Yii::t('app', 'Converting this {model} to {article} {targetModel} could result in data 
365:                 from your {model} being lost. '.
366:                 'The following field incompatibilities have been detected: ',
367:                 array (
368:                     '{model}' => $sourceModelTitle,
369:                     '{targetModel}' => $targetModelTitle,
370:                     '{article}' => preg_match ('/^[aeiouAEIOU]/', $targetModelTitle) ? 'an' : 'a',
371:                 )) : 
372:             Yii::t('app', 'Converting these {model} to {targetModel} could result in data 
373:                 from your {model} being lost. '.
374:                 'The following field incompatibilities have been detected: ',
375:                 array (
376:                     '{model}' => $sourceModelTitlePlural,
377:                     '{targetModel}' => $targetModelTitlePlural,
378:                 )));
379:         $html .= "
380:         </p>
381:         <ul class='errorSummary'>
382:         ";
383:         foreach ($this->owner->getConversionIncompatibilityWarnings ($targetModelClass) as 
384:             $message) {
385:             
386:             $html .= '<li>'.CHtml::encode ($message).'</li>';
387:         }
388:         $html .= "
389:         </ul>
390:         <p>
391:         ";
392:         $html .= CHtml::encode (
393:             Yii::t('app', 'To resolve these incompatibilities, make sure that every '.
394:             '{model} field has a corresponding {targetModel} field of the same name and type.', 
395:             array (
396:                 '{model}' => $sourceModelTitlePlural,
397:                 '{targetModel}' => $targetModelTitlePlural,
398:             )));
399:         $html .= '</p>';
400:         return $html;
401:     }
402: }
403: 
404: ?>
405: