Account Suspended
Account Suspended
This Account has been suspended.
Contact your hosting provider for more information.
 All Data Structures Functions Variables Pages
RedBeanModel.php
1 <?php
2  /*********************************************************************************
3  * Zurmo is a customer relationship management program developed by
4  * Zurmo, Inc. Copyright (C) 2017 Zurmo Inc.
5  *
6  * Zurmo 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 ZURMO, ZURMO DISCLAIMS THE WARRANTY
11  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
12  *
13  * Zurmo 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 Zurmo, Inc. with a mailing address at 27 North Wacker Drive
24  * Suite 370 Chicago, IL 60606. or at email address contact@zurmo.com.
25  *
26  * The interactive user interfaces in original and modified 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 Zurmo
32  * logo and Zurmo copyright notice. If the display of the logo is not reasonably
33  * feasible for technical reasons, the Appropriate Legal Notices must display the words
34  * "Copyright Zurmo Inc. 2017. All rights reserved".
35  ********************************************************************************/
36 
67  abstract class RedBeanModel extends BeanModel implements Serializable
68  {
73 
80  private static $nextPseudoId = -1;
81 
86  private static $_models = array();
87 
88  /*
89  * The id of an unsaved model.
90  * @var integer
91  */
92  private $pseudoId;
93 
101  protected static $lastClassInBeanHeirarchy = 'RedBeanModel';
102 
103  // A model maps to one or more beans. If Person extends RedBeanModel
104  // there is one bean, but if User then extends Person a User model
105  // has two beans, the one holding the person data and the one holding
106  // the extended User data. In this way in inheritance hierarchy from
107  // model is normalized over several tables, one for each extending
108  // class.
109  protected $modelClassNameToBean = array();
110  protected $attributeNameToBeanAndClassName = array();
111  protected $relationNameToRelatedModel = array();
112  protected $unlinkedRelationNames = array();
113  protected $unlinkedOwnedRelatedModelsToRemove = array();
114  protected $validators = array();
115  protected $attributeNameToErrors = array();
116  protected $scenarioName = '';
117  // An object is automatically savable if it is new or contains
118  // modified members or related objects.
119  // If it is newly created and has never had any data put into it
120  // it can be saved explicitly but it wont be saved automatically
121  // when it is a related model and will be redispensed next
122  // time it is referenced.
123  protected $modified = false;
124  protected $deleted = false;
125  protected $isInIsModified = false;
126  protected $isInHasErrors = false;
127  protected $isInGetErrors = false;
128  protected $isValidating = false;
129  protected $isSaving = false;
130  protected $isDeleting = false;
131  protected $isNewModel = false;
132  protected $isCopied = false;
133 
140  protected $isSavableFromRelation = true;
141 
142  // Mapping of Yii validators to validators doing things that
143  // are either required for RedBean, or that simply implement
144  // The semantics that we want.
145  private static $yiiValidatorsToRedBeanValidators = array(
146  'CDefaultValueValidator' => 'RedBeanModelDefaultValueValidator',
147  'CNumberValidator' => 'RedBeanModelNumberValidator',
148  'CTypeValidator' => 'RedBeanModelTypeValidator',
149  'CRequiredValidator' => 'RedBeanModelRequiredValidator',
150  'CUniqueValidator' => 'RedBeanModelUniqueValidator',
151  'defaultCalculatedDate' => 'RedBeanModelDefaultCalculatedDateValidator',
152  'readOnly' => 'RedBeanModelReadOnlyValidator',
153  'dateTimeDefault' => 'RedBeanModelDateTimeDefaultValueValidator',
154  'probability' => 'RedBeanModelProbabilityValidator',
155  );
156 
161  protected static $attributeLabelsByLanguage = array();
162 
181  public static function model($className = null)
182  {
183  if ($className == null)
184  {
185  $className = get_called_class();
186  }
187  if (isset(self::$_models[$className]))
188  {
189  return self::$_models[$className];
190  }
191  else
192  {
193  $model = self::$_models[$className] = new $className(false, null, false, false);
194  return $model;
195  }
196  }
197 
205  public static function getAll($orderBy = null, $sortDescending = false, $modelClassName = null)
206  {
207  assert('$orderBy === null || is_string($orderBy) && $orderBy != ""');
208  assert('is_bool($sortDescending)');
209  assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
211  $orderBySql = null;
212  if ($orderBy !== null)
213  {
214  $orderBySql = "$quote$orderBy$quote";
215  if ($sortDescending)
216  {
217  $orderBySql .= ' desc';
218  }
219  }
220  return static::getSubset(null, null, null, null, $orderBySql, $modelClassName);
221  }
222 
234  public static function getSubset(RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter = null,
235  $offset = null, $count = null,
236  $where = null, $orderBy = null,
237  $modelClassName = null,
238  $selectDistinct = false)
239  {
240  assert('$offset === null || is_integer($offset) && $offset >= 0');
241  assert('$count === null || is_integer($count) && $count >= 1');
242  assert('$where === null || is_string ($where) && $where != ""');
243  assert('$orderBy === null || is_string ($orderBy) && $orderBy != ""');
244  assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
245 
246  if ($modelClassName === null)
247  {
248  $modelClassName = get_called_class();
249  }
250  $ids = self::getSubsetIds($joinTablesAdapter, $offset, $count, $where,
251  $orderBy, $modelClassName, $selectDistinct);
252  $tableName = $modelClassName::getTableName();
253  $beans = ZurmoRedBean::batch ($tableName, $ids);
254  return self::makeModels($beans, $modelClassName);
255  }
256 
268  public static function getSubsetIds(RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter = null,
269  $offset = null, $count = null,
270  $where = null, $orderBy = null,
271  $modelClassName = null,
272  $selectDistinct = false)
273  {
274  assert('$offset === null || is_integer($offset) && $offset >= 0');
275  assert('$count === null || is_integer($count) && $count >= 1');
276  assert('$where === null || is_string ($where) && $where != ""');
277  assert('$orderBy === null || is_string ($orderBy) && $orderBy != ""');
278  assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
279 
280  if ($modelClassName === null)
281  {
282  $modelClassName = get_called_class();
283  }
284  if ($joinTablesAdapter == null)
285  {
286  $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter($modelClassName);
287  }
288  $tableName = $modelClassName::getTableName();
289  $sql = static::makeSubsetOrCountSqlQuery($tableName, $joinTablesAdapter, $offset, $count, $where,
290  $orderBy, false, $selectDistinct);
291  $ids = ZurmoRedBean::getCol($sql);
292  return $ids;
293  }
294 
300  public static function makeSubsetOrCountSqlQuery($tableName,
301  RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter,
302  $offset = null, $count = null,
303  $where = null, $orderBy = null,
304  $selectCount = false,
305  $selectDistinct = false,
306  array $quotedExtraSelectColumnNameAndAliases = array())
307  {
308  assert('is_string($tableName) && $tableName != ""');
309  assert('$offset === null || is_integer($offset) && $offset >= 0');
310  assert('$count === null || is_integer($count) && $count >= 1');
311  assert('$where === null || is_string ($where) && $where != ""');
312  assert('$orderBy === null || is_string ($orderBy) && $orderBy != ""');
313  assert('is_bool($selectCount)');
314  assert('is_bool($selectDistinct)');
315  $selectQueryAdapter = new RedBeanModelSelectQueryAdapter($selectDistinct);
316  if ($selectCount)
317  {
318  $selectQueryAdapter->addCountClause($tableName);
319  }
320  else
321  {
322  $selectQueryAdapter->addClause($tableName, 'id', 'id');
323  }
324  foreach ($quotedExtraSelectColumnNameAndAliases as $columnName => $columnAlias)
325  {
326  $selectQueryAdapter->addClauseWithColumnNameOnlyAndNoEnclosure($columnName, $columnAlias);
327  }
328  return SQLQueryUtil::
329  makeQuery($tableName, $selectQueryAdapter, $joinTablesAdapter, $offset, $count, $where, $orderBy);
330  }
331 
337  public static function getCount(RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter = null,
338  $where = null, $modelClassName = null, $selectDistinct = false)
339  {
340  assert('$where === null || is_string($where)');
341  assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
342  if ($modelClassName === null)
343  {
344  $modelClassName = get_called_class();
345  }
346  if ($joinTablesAdapter == null)
347  {
348  $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter($modelClassName);
349  }
350  $tableName = $modelClassName::getTableName();
351  $sql = static::makeSubsetOrCountSqlQuery($tableName, $joinTablesAdapter, null, null, $where, null, true,
352  $selectDistinct);
353  $count = ZurmoRedBean::getCell($sql);
354  if ($count === null || empty($count))
355  {
356  $count = 0;
357  }
358  return intval($count);
359  }
360 
368  public static function getById($id, $modelClassName = null)
369  {
370  assert('is_integer($id) && $id > 0');
371  assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
372  // I would have thought it was correct to user ZurmoRedBean::load() and get
373  // a null, or error or something if the bean doesn't exist, but
374  // it still returns a bean. So until I've investigated further
375  // I'm using Finder.
376  if ($modelClassName === null)
377  {
378  $modelClassName = get_called_class();
379  }
380  $tableName = $modelClassName::getTableName();
381  $beans = ZurmoRedBean::find($tableName, "id = '$id'");
382  assert('count($beans) <= 1');
383  if (count($beans) == 0)
384  {
385  throw new NotFoundException();
386  }
387  return static::makeModel(end($beans), $modelClassName);
388  }
389 
390  public function getIsNewModel()
391  {
392  return $this->isNewModel;
393  }
394 
416  public function __construct($setDefaults = true, RedBean_OODBBean $bean = null, $forceTreatAsCreation = false,
417  $runConstruction = true)
418  {
419  $this->pseudoId = self::$nextPseudoId--;
420  $this->init();
421  if (!$runConstruction)
422  {
423  return;
424  }
425  if ($bean === null)
426  {
427  foreach (array_reverse(RuntimeUtil::getClassHierarchy(get_class($this), static::$lastClassInBeanHeirarchy)) as $modelClassName)
428  {
429  if ($modelClassName::getCanHaveBean())
430  {
431  $tableName = $modelClassName::getTableName();
432  $newBean = ZurmoRedBean::dispense($tableName);
433  $this->modelClassNameToBean[$modelClassName] = $newBean;
434  $this->mapAndCacheMetadataAndSetHints($modelClassName, $newBean);
435  }
436  }
437  // The yii way of doing defaults is the the default validator
438  // fills in the defaults on attributes that don't have values
439  // when you validator, or save. This weird, since when you get
440  // a model the things with defaults have not been defaulted!
441  // We want that semantic.
442  if ($setDefaults)
443  {
444  $this->runDefaultValidators();
445  }
446  $forceTreatAsCreation = true;
447  }
448  else
449  {
450  assert('$bean->id > 0');
451  $first = true;
452  foreach (RuntimeUtil::getClassHierarchy(get_class($this), static::$lastClassInBeanHeirarchy) as $modelClassName)
453  {
454  if ($modelClassName::getCanHaveBean())
455  {
456  if ($first)
457  {
458  $lastBean = $bean;
459  $first = false;
460  }
461  else
462  {
463  $tableName = $modelClassName::getTableName();
464  $lastBean = ZurmoRedBeanLinkManager::getBean($lastBean, $tableName);
465  if ($lastBean === null)
466  {
467  throw new MissingBeanException();
468  }
469  assert('$lastBean->id > 0');
470  }
471  $this->modelClassNameToBean[$modelClassName] = $lastBean;
472  $this->mapAndCacheMetadataAndSetHints($modelClassName, $lastBean);
473  }
474  }
475  $this->modelClassNameToBean = array_reverse($this->modelClassNameToBean);
476  }
477  $this->constructDerived($bean, $setDefaults);
478  if ($forceTreatAsCreation)
479  {
480  $this->onCreated();
481  }
482  else
483  {
484  $this->onLoaded();
485  $modelClassName = get_called_class();
486  if ($modelClassName::isCacheable())
487  {
489  }
490  }
491  $this->modified = false;
492  }
493 
497  public static function deleteAll()
498  {
499  if (static::getCanHaveBean() && static::isTypeDeletable())
500  {
501  foreach (static::getAll() as $model)
502  {
503  if (!$model->delete())
504  {
505  throw new NotSupportedException("Unable to delete id# " . $model->id);
506  }
507  }
508  // we could have used ZurmoRedBean::$writer->wipe() but that won't fire events related to delete.
509  }
510  else
511  {
512  throw new NotSupportedException(get_called_class() . "Can either not have bean or is not deletable");
513  }
514  }
515 
516  // Derived classes can insert additional steps into the construction.
517  protected function constructDerived($bean, $setDefaults)
518  {
519  assert('$bean === null || $bean instanceof RedBean_OODBBean');
520  assert('is_bool($setDefaults)');
521  }
522 
529  protected function constructIncomplete($bean)
530  {
531  assert('$bean === null || $bean instanceof RedBean_OODBBean');
532  $this->init();
533  }
534 
535  public function serialize()
536  {
537  return serialize(array(
538  $this->pseudoId,
539  $this->modelClassNameToBean,
540  $this->attributeNameToBeanAndClassName,
541  $this->validators,
542  ));
543  }
544 
545  public function unserialize($data)
546  {
547  try
548  {
549  $data = unserialize($data);
550  assert('is_array($data)');
551  if (count($data) != 4)
552  {
553  return null;
554  }
555  $this->pseudoId = $data[0];
556  $this->modelClassNameToBean = $data[1];
557  $this->attributeNameToBeanAndClassName = $data[2];
558  $this->validators = $data[3];
559 
560  $this->relationNameToRelatedModel = array();
561  $this->unlinkedRelationNames = array();
562  $this->unlinkedOwnedRelatedModelsToRemove = array();
563  $this->attributeNameToErrors = array();
564  $this->scenarioName = '';
565  $this->modified = false;
566  $this->deleted = false;
567  $this->isInIsModified = false;
568  $this->isInHasErrors = false;
569  $this->isInGetErrors = false;
570  $this->isValidating = false;
571  $this->isSaving = false;
572  $this->isDeleting = false;
573  }
574  catch (Exception $e)
575  {
576  return null;
577  }
578  }
579 
586  protected function setNotModified()
587  {
588  $this->modified = false; // This sets this class to the right state.
589  assert('!$this->isModified()'); // This tests that related classes are in the right state.
590  }
591 
597  // Public for unit testing.
598  public static function mangleTableName()
599  {
600  return false;
601  }
602 
611  public static function getTableName()
612  {
613  // You could also call it on objects like: $model::getTableName() or
614  // $model->getTableName() but it would be preferable to keep it accessed through class, statically.
615  $modelClassName = get_called_class();
616  $tableName = strtolower($modelClassName);
617  if ($modelClassName::mangleTableName())
618  {
619  $tableName = '_' . $tableName;
620  }
621  return $tableName;
622  }
623 
629  public static function getTableNames($classNames)
630  {
631  $tableNames = array();
632  foreach ($classNames as $className)
633  {
634  $tableNames[] = $className::getTableName();
635  }
636  return $tableNames;
637  }
638 
643  public static function getForeignKeyName($modelClassName, $relationName)
644  {
645  assert('is_string($modelClassName)');
646  assert('$modelClassName != ""');
647  $metadata = $modelClassName::getMetadata();
648  foreach ($metadata as $modelClassName => $modelClassMetadata)
649  {
650  if (isset($metadata[$modelClassName]["relations"]) &&
651  array_key_exists($relationName, $metadata[$modelClassName]["relations"]))
652  {
653  $relatedModelClassName = $metadata[$modelClassName]['relations'][$relationName][1];
654  self::resolveModelClassNameForClassesWithoutBeans($relatedModelClassName);
655  $relatedModelTableName = $relatedModelClassName::getTableName();
656  $linkType = null;
657  $relationLinkName = null;
658  self::resolveLinkTypeAndRelationLinkName($metadata[$modelClassName]['relations'][$relationName],
659  $linkType,
660  $relationLinkName);
661  $linkName = self::makeCasedLinkName($metadata[$modelClassName]['relations'][$relationName][0],
662  $linkType, $relationLinkName);
663  if ($metadata[$modelClassName]['relations'][$relationName][0] == self::HAS_MANY ||
664  $metadata[$modelClassName]['relations'][$relationName][0] == self::HAS_ONE_BELONGS_TO)
665  {
666  $columnName = $modelClassName::getTableName() . '_id';
667  }
668  else
669  {
670  $columnName = $relatedModelTableName . '_id';
671  }
672  $columnName = ZurmoRedBeanLinkManager::resolveColumnPrefix($linkName) . $columnName;
673 
674  return $columnName;
675  }
676  }
677  throw new NotSupportedException;
678  }
679 
683  protected function onCreated()
684  {
685  if ($this->hasEventHandler('onCreated'))
686  {
687  $event = new CModelEvent($this);
688  $this->onCreated($event);
689  }
690  }
691 
695  protected function onLoaded()
696  {
697  if ($this->hasEventHandler('onLoaded'))
698  {
699  $event = new CModelEvent($this);
700  $this->onLoaded($event);
701  }
702  }
703 
707  protected function onModified()
708  {
709  }
710 
711  protected static function makeCasedLinkName($relationType, $linkType, $relationLinkName)
712  {
713  assert('is_int($relationType)');
714  assert('is_int($linkType)');
715  assert('is_string($relationLinkName) || $relationLinkName == null');
716  if (($relationType == self::HAS_ONE || $relationType == self::HAS_MANY ||
717  $relationType == self::HAS_ONE_BELONGS_TO) && $linkType == self::LINK_TYPE_SPECIFIC)
718  {
719  return strtolower($relationLinkName);
720  }
721  }
722 
726  protected function mapAndCacheMetadataAndSetHints($modelClassName, RedBean_OODBBean $bean)
727  {
728  assert('is_string($modelClassName)');
729  assert('$modelClassName != ""');
730  $metadata = $this->getMetadata();
731  if (isset($metadata[$modelClassName]))
732  {
733  $hints = array();
734  if (isset($metadata[$modelClassName]['members']))
735  {
736  foreach ($metadata[$modelClassName]['members'] as $memberName)
737  {
738  $this->attributeNameToBeanAndClassName[$memberName] = array($bean, $modelClassName);
739  //$this->attributeNamesNotBelongsToOrManyMany[] = $memberName;
740  if (substr($memberName, -2) == 'Id')
741  {
742  $columnName = strtolower($memberName);
743  $hints[$columnName] = 'id';
744  }
745  }
746  }
747  if (isset($metadata[$modelClassName]['relations']))
748  {
749  foreach ($metadata[$modelClassName]['relations'] as $relationName => $relationTypeModelClassNameAndOwns)
750  {
751  assert('in_array(count($relationTypeModelClassNameAndOwns), array(2, 3, 4, 5))');
752 
753  $relationType = $relationTypeModelClassNameAndOwns[0];
754  $relationModelClassName = $relationTypeModelClassNameAndOwns[1];
755  if ($relationType == self::HAS_MANY_BELONGS_TO &&
756  strtolower($relationName) != strtolower($relationModelClassName))
757  {
758  throw new NotSupportedException(Zurmo::t('Core', 'Relations of type HAS_MANY_BELONGS_TO must have the relation name ' .
759  'the same as the related model class name. Relation: {relationName} ' .
760  'Relation model class name: {relationModelClassName}',
761  array('{relationName}' => $relationName,
762  '{relationModelClassName}' => $relationModelClassName)));
763  }
764  if (count($relationTypeModelClassNameAndOwns) >= 3 &&
765  $relationTypeModelClassNameAndOwns[2] == self::OWNED)
766  {
767  $owns = true;
768  }
769  else
770  {
771  $owns = false;
772  }
773  // $linkType = null;
774  // $relationLinkName = null;
775  // self::resolveLinkTypeAndRelationLinkName($relationTypeModelClassNameAndOwns, $linkType,
776  // $relationLinkName);
777  assert('in_array($relationType, array(self::HAS_ONE_BELONGS_TO, self::HAS_MANY_BELONGS_TO, ' .
778  'self::HAS_ONE, self::HAS_MANY, self::MANY_MANY))');
779  $this->attributeNameToBeanAndClassName[$relationName] = array($bean, $modelClassName);
780 
788  if (!in_array($relationType, array(self::HAS_ONE_BELONGS_TO, self::HAS_MANY_BELONGS_TO, self::MANY_MANY)))
789  {
790  //$this->attributeNamesNotBelongsToOrManyMany[] = $relationName;
791  }
792  }
793  }
794  // Add model validators. Parent validators are already applied.
795  if (isset($metadata[$modelClassName]['rules']))
796  {
797  foreach ($metadata[$modelClassName]['rules'] as $validatorMetadata)
798  {
799  assert('isset($validatorMetadata[0])');
800  assert('isset($validatorMetadata[1])');
801  $attributeName = $validatorMetadata[0];
802  // Each rule in RedBeanModel must specify one attribute name.
803  // This was just better style, now it is mandatory.
804  assert('strpos($attributeName, " ") === false');
805  $validatorName = $validatorMetadata[1];
806  $validatorParameters = array_slice($validatorMetadata, 2);
807  if (isset(CValidator::$builtInValidators[$validatorName]))
808  {
809  $validatorName = CValidator::$builtInValidators[$validatorName];
810  }
811  if (isset(self::$yiiValidatorsToRedBeanValidators[$validatorName]))
812  {
813  $validatorName = self::$yiiValidatorsToRedBeanValidators[$validatorName];
814  }
815  $validator = CValidator::createValidator($validatorName, $this, $attributeName, $validatorParameters);
816 
817  switch ($validatorName)
818  {
819  case 'RedBeanModelTypeValidator':
820  case 'TypeValidator':
821  $columnName = strtolower($attributeName);
822  if (array_key_exists($columnName, $hints))
823  {
824  unset($hints[$columnName]);
825  }
826  if (in_array($validator->type, array('date', 'datetime', 'blob', 'longblob', 'string', 'text', 'longtext')))
827  {
828  $hints[$columnName] = $validator->type;
829  }
830  break;
831  case 'CBooleanValidator':
832  $columnName = strtolower($attributeName);
833  $hints[$columnName] = 'boolean';
834  break;
835  case 'RedBeanModelUniqueValidator':
836  if (!static::isRelation($attributeName))
837  {
838  $bean->setMeta("buildcommand.unique", array(array($attributeName)));
839  }
840  else
841  {
842  $relationAndOwns = static::getRelationNameToRelationTypeModelClassNameAndOwnsForModel();
843  $relatedModelClassName = $relationAndOwns[$attributeName][1];
844  $relatedModelTableName = $relatedModelClassName::getTableName();
845  $columnName = strtolower($attributeName);
846  if ($columnName != $relatedModelTableName)
847  {
848  $columnName .= '_' . $relatedModelTableName;
849  }
850  $columnName .= '_id';
851  $bean->setMeta("buildcommand.unique", array(array($columnName)));
852  }
853  break;
854  }
855  $this->validators[] = $validator;
856  }
857 
858  // Check if we need to update string type to long string type, based on validators.
859  if (isset($metadata[$modelClassName]['members']))
860  {
861  foreach ($metadata[$modelClassName]['members'] as $memberName)
862  {
863  $allValidators = $this->getValidators($memberName);
864  if (!empty($allValidators))
865  {
866  foreach ($allValidators as $validator)
867  {
868  if ((get_class($validator) == 'RedBeanModelTypeValidator' ||
869  get_class($validator) == 'TypeValidator') &&
870  $validator->type == 'string')
871  {
872  $columnName = strtolower($validator->attributes[0]);
873  if (count($allValidators) > 1)
874  {
875  $haveCStringValidator = false;
876  foreach ($allValidators as $innerValidator)
877  {
878  if (get_class($innerValidator) == 'CStringValidator' &&
879  isset($innerValidator->max) &&
880  $innerValidator->max > 0)
881  {
882  if ($innerValidator->max > 65535)
883  {
884  $hints[$columnName] = 'longtext';
885  }
886  elseif ($innerValidator->max < 255)
887  {
888  $hints[$columnName] = "string({$innerValidator->max})";
889  }
890  else
891  {
892  $hints[$columnName] = 'text';
893  }
894  }
895  if (get_class($innerValidator) == 'CStringValidator')
896  {
897  $haveCStringValidator = true;
898  }
899  }
900  if (!$haveCStringValidator)
901  {
902  $hints[$columnName] = 'text';
903  }
904  }
905  else
906  {
907  $hints[$columnName] = 'text';
908  }
909  }
910  }
911  }
912  }
913  }
914  }
915  $bean->setMeta('hint', $hints);
916  }
917  }
918 
922  protected function runDefaultValidators()
923  {
924  foreach ($this->validators as $validator)
925  {
926  if ($validator instanceof CDefaultValueValidator)
927  {
928  $validator->validate($this);
929  }
930  }
931  }
932 
937  public function getPrimaryBean()
938  {
939  return end($this->modelClassNameToBean);
940  }
941 
945  public function getClassId($modelClassName)
946  {
947  assert('array_key_exists($modelClassName, $this->modelClassNameToBean)');
948  return intval($this->getClassBean($modelClassName)->id); // Trying to combat the slop.
949  }
950 
951  public function getClassBean($modelClassName)
952  {
953  assert('is_string($modelClassName)');
954  assert('$modelClassName != ""');
955  self::resolveModelClassNameForClassesWithoutBeans($modelClassName);
956  assert('array_key_exists($modelClassName, $this->modelClassNameToBean)');
957  return $this->modelClassNameToBean[$modelClassName];
958  }
959 
963  protected function setClassBean($modelClassName, RedBean_OODBBean $bean)
964  {
965  assert('is_string($modelClassName)');
966  assert('$modelClassName != ""');
967  assert('!array_key_exists($modelClassName, $this->modelClassNameToBean)');
968  $this->modelClassNameToBean = array_merge(array($modelClassName => $bean),
969  $this->modelClassNameToBean);
970  }
971 
972  public function getModelIdentifier()
973  {
974  $className = get_class($this);
975  $beanId = strval($this->getPrimaryBean()->id);
976  $modelIdentifier = static::getModelIdentifierByClassNameAndBeanId($className, $beanId);
977  return $modelIdentifier;
978  }
979 
980  protected static function getModelIdentifierByClassNameAndBeanId($modelClassName, $beanId)
981  {
982  return $modelClassName . static::MODEL_IDENTIFIER_DELIMITER . $beanId;
983  }
984 
985  public static function getModelClassNameByIdentifier($identifier)
986  {
987  $identifierTokens = explode(static::MODEL_IDENTIFIER_DELIMITER, $identifier);
988  return $identifierTokens[0];
989  }
990 
996  public static function getMetadata()
997  {
998  try
999  {
1000  // not using default value to save cpu cycles on requests that follow the first exception.
1001  return GeneralCache::getEntry(get_called_class() . 'Metadata');
1002  }
1003  catch (NotFoundException $e)
1004  {
1005  $className = get_called_Class();
1006  $defaultMetadata = $className::getDefaultMetadata();
1007  $metadata = array();
1008  foreach (array_reverse(RuntimeUtil::getClassHierarchy($className, static::$lastClassInBeanHeirarchy)) as $modelClassName)
1009  {
1010  if ($modelClassName::getCanHaveBean())
1011  {
1012  if ($modelClassName::canSaveMetadata())
1013  {
1014  try
1015  {
1016  $globalMetadata = GlobalMetadata::getByClassName($modelClassName);
1017  $metadata[$modelClassName] = unserialize($globalMetadata->serializedMetadata);
1018  }
1019  catch (NotFoundException $e)
1020  {
1021  if (isset($defaultMetadata[$modelClassName]))
1022  {
1023  $metadata[$modelClassName] = $defaultMetadata[$modelClassName];
1024  }
1025  }
1026  }
1027  else
1028  {
1029  if (isset($defaultMetadata[$modelClassName]))
1030  {
1031  $metadata[$modelClassName] = $defaultMetadata[$modelClassName];
1032  }
1033  }
1034  }
1035  }
1036  if (YII_DEBUG)
1037  {
1038  self::assertMetadataIsValid($metadata);
1039  }
1040  GeneralCache::cacheEntry(get_called_class() . 'Metadata', $metadata);
1041  return $metadata;
1042  }
1043  }
1044 
1053  public static function canSaveMetadata()
1054  {
1055  return false;
1056  }
1057 
1063  public static function setMetadata(array $metadata)
1064  {
1065  if (YII_DEBUG)
1066  {
1067  self::assertMetadataIsValid($metadata);
1068  }
1069  $className = get_called_class();
1070  foreach (array_reverse(RuntimeUtil::getClassHierarchy($className, static::$lastClassInBeanHeirarchy)) as $modelClassName)
1071  {
1072  if ($modelClassName::getCanHaveBean())
1073  {
1074  if ($modelClassName::canSaveMetadata())
1075  {
1076  if (isset($metadata[$modelClassName]))
1077  {
1078  try
1079  {
1080  $globalMetadata = GlobalMetadata::getByClassName($modelClassName);
1081  }
1082  catch (NotFoundException $e)
1083  {
1084  $globalMetadata = new GlobalMetadata();
1085  $globalMetadata->className = $modelClassName;
1086  }
1087  $globalMetadata->serializedMetadata = serialize($metadata[$modelClassName]);
1088  $saved = $globalMetadata->save();
1089  // TODO: decide how to deal with this properly if it fails.
1090  // ie: throw or return false, or something other than
1091  // this naughty assert.
1092  assert('$saved'); // Not Coding Standard
1093  }
1094  }
1095  }
1096  }
1097  self::forgetBeanModel(get_called_class());
1098  RedBeanModelsCache::forgetAllByModelType(get_called_class());
1099  GeneralCache::forgetEntry(get_called_class() . 'Metadata');
1100  }
1101 
1107  public static function getDefaultMetadata()
1108  {
1109  return array();
1110  }
1111 
1112  protected static function assertMetadataIsValid(array $metadata)
1113  {
1114  $className = get_called_Class();
1115  foreach (RuntimeUtil::getClassHierarchy($className, static::$lastClassInBeanHeirarchy) as $modelClassName)
1116  {
1117  if ($modelClassName::getCanHaveBean())
1118  {
1119  if (isset($metadata[$modelClassName]['members']))
1120  {
1121  assert('is_array($metadata[$modelClassName]["members"])');
1122  foreach ($metadata[$modelClassName]["members"] as $memberName)
1123  {
1124  assert('ctype_lower($memberName{0})');
1125  }
1126  }
1127  if (isset($metadata[$modelClassName]['relations']))
1128  {
1129  assert('is_array($metadata[$modelClassName]["relations"])');
1130  foreach ($metadata[$modelClassName]["relations"] as $relationName => $notUsed)
1131  {
1132  assert('ctype_lower($relationName{0})');
1133  }
1134  }
1135  if (isset($metadata[$modelClassName]['indexes']))
1136  {
1137  assert('is_array($metadata[$modelClassName]["indexes"])');
1138  foreach ($metadata[$modelClassName]["indexes"] as $indexName => $notUsed)
1139  {
1140  assert('ctype_lower($indexName{0})');
1141  }
1142  }
1143  if (isset($metadata[$modelClassName]['rules']))
1144  {
1145  assert('is_array($metadata[$modelClassName]["rules"])');
1146  }
1147  if (isset($metadata[$modelClassName]['defaultSortAttribute']))
1148  {
1149  assert('is_string($metadata[$modelClassName]["defaultSortAttribute"])');
1150  }
1151  if (isset($metadata[$modelClassName]['rollupRelations']))
1152  {
1153  assert('is_array($metadata[$modelClassName]["rollupRelations"])');
1154  }
1155  }
1156  }
1157  }
1158 
1170  public function castDown(array $derivedModelClassNames)
1171  {
1172  $bean = $this->getPrimaryBean();
1173  $thisModelClassName = get_called_class();
1174  $key = strtolower($thisModelClassName) . '_id';
1175  foreach ($derivedModelClassNames as $modelClassNames)
1176  {
1177  if (is_string($modelClassNames))
1178  {
1179  $nextModelClassName = $modelClassNames;
1180  if (get_class($this) == $nextModelClassName)
1181  {
1182  return $this;
1183  }
1184  $nextBean = self::findNextDerivativeBean($bean, $thisModelClassName, $nextModelClassName);
1185  }
1186  else
1187  {
1188  assert('is_array($modelClassNames)');
1189  $targetModelClassName = end($modelClassNames);
1190  if (get_class($this) == $targetModelClassName)
1191  {
1192  return $this;
1193  }
1194  $currentModelClassName = $thisModelClassName;
1195  $nextBean = $bean;
1196  foreach ($modelClassNames as $nextModelClassName)
1197  {
1198  if ($nextModelClassName::getCanHaveBean())
1199  {
1200  $nextBean = self::findNextDerivativeBean($nextBean, $currentModelClassName, $nextModelClassName);
1201  if ($nextBean === null)
1202  {
1203  break;
1204  }
1205  $currentModelClassName = $nextModelClassName;
1206  }
1207  }
1208  }
1209  if ($nextBean !== null)
1210  {
1211  return self::makeModel($nextBean, $nextModelClassName);
1212  }
1213  }
1214  throw new NotFoundException();
1215  }
1216 
1217  private static function findNextDerivativeBean($bean, $modelClassName1, $modelClassName2)
1218  {
1219  if ($bean->id > 0)
1220  {
1221  $key = strtolower($modelClassName1) . '_id';
1222  $tableName = $modelClassName2::getTableName();
1223  $beans = ZurmoRedBean::find($tableName, "$key = :id", array('id' => $bean->id));
1224  if (count($beans) == 1)
1225  {
1226  return reset($beans);
1227  }
1228  }
1229  return null;
1230  }
1231 
1236  public function isSame(RedBeanModel $model)
1237  {
1238  // The two models are the same if they have the
1239  // same root model, and if for that model they
1240  // have the same id.
1241  $rootId1 = reset($this ->modelClassNameToBean)->id;
1242  $rootId2 = reset($model->modelClassNameToBean)->id;
1243  if ($rootId1 == 0)
1244  {
1245  $rootId1 = $this->pseudoId;
1246  }
1247  if ($rootId2 == 0)
1248  {
1249  $rootId2 = $model->pseudoId;
1250  }
1251  return $rootId1 == $rootId2 && $rootId1 != 0 &&
1252  key($this ->modelClassNameToBean) ==
1253  key($model->modelClassNameToBean);
1254  }
1255 
1262  public function __toString()
1263  {
1264  return Zurmo::t('Core', '(None)');
1265  }
1266 
1277  public function __get($attributeName)
1278  {
1279  return $this->unrestrictedGet($attributeName);
1280  }
1281 
1287  protected function unrestrictedGet($attributeName)
1288  {
1289  assert('is_string($attributeName)');
1290  assert('$attributeName != ""');
1291  assert("property_exists(\$this, '$attributeName') || \$this->isAttribute('$attributeName')");
1292  if (property_exists($this, $attributeName))
1293  {
1294  return $this->$attributeName;
1295  }
1296  elseif ($attributeName == 'id')
1297  {
1298  $id = intval($this->getPrimaryBean()->id);
1299  assert('$id >= 0');
1300  if ($id == 0)
1301  {
1302  $id = $this->pseudoId;
1303  }
1304  return $id;
1305  }
1306  elseif ($this->isAttribute($attributeName))
1307  {
1308  list($bean, $attributeModelClassName) = $this->attributeNameToBeanAndClassName[$attributeName];
1309  if (!static::isRelation($attributeName))
1310  {
1311  $columnName = strtolower($attributeName);
1312  return $bean->$columnName;
1313  }
1314  else
1315  {
1316  if (!array_key_exists($attributeName, $this->relationNameToRelatedModel))
1317  {
1318  $relationAndOwns = static::getRelationNameToRelationTypeModelClassNameAndOwnsForModel();
1319  list($relationType, $relatedModelClassName, $owns, $linkType, $relationLinkName) =
1320  $relationAndOwns[$attributeName];
1321 
1322  $tempRelatedModelClassName = $relatedModelClassName;
1323  self::resolveModelClassNameForClassesWithoutBeans($tempRelatedModelClassName);
1324  $relatedTableName = $tempRelatedModelClassName::getTableName();
1325  switch ($relationType)
1326  {
1327  case self::HAS_ONE_BELONGS_TO:
1328  $linkName = strtolower(get_class($this));
1329  $columnName = $linkName . '_id';
1330  $relatedBeans = ZurmoRedBean::find($relatedTableName, $columnName . " = " . $bean->id);
1331  if (count($relatedBeans) > 1)
1332  {
1333  throw new NotFoundException();
1334  }
1335  elseif (count($relatedBeans) == 0)
1336  {
1337  $relatedModel = new $relatedModelClassName();
1338  }
1339  else
1340  {
1341  $relatedModel = self::makeModel(end($relatedBeans), $relatedModelClassName);
1342  }
1343  $this->relationNameToRelatedModel[$attributeName] = $relatedModel;
1344  break;
1345  case self::HAS_ONE:
1346  case self::HAS_MANY_BELONGS_TO:
1347  $linkName = self::makeCasedLinkName($relationType, $linkType, $relationLinkName);
1348  if ($bean->id > 0 && !in_array($attributeName, $this->unlinkedRelationNames))
1349  {
1350  $linkFieldName = ZurmoRedBeanLinkManager::getLinkField($relatedTableName, $linkName);
1351 
1352  if ((int)$bean->$linkFieldName > 0)
1353  {
1354  $beanIdentifier = $relatedTableName .(int)$bean->$linkFieldName;
1355  try
1356  {
1357  $relatedBean = RedBeansCache::getBean($beanIdentifier);
1358  }
1359  catch (NotFoundException $e)
1360  {
1361  $relatedBean = ZurmoRedBeanLinkManager::getBean($bean, $relatedTableName, $linkName);
1362  RedBeansCache::cacheBean($relatedBean, $beanIdentifier);
1363  }
1364  if ($relatedBean !== null && $relatedBean->id > 0)
1365  {
1366  $relatedModel = self::makeModel($relatedBean, $relatedModelClassName);
1367  }
1368  }
1369  }
1370  if (!isset($relatedModel))
1371  {
1372  $relatedModel = new $relatedModelClassName();
1373  }
1374  $this->relationNameToRelatedModel[$attributeName] = $relatedModel;
1375  break;
1376 
1377  case self::HAS_MANY:
1378  $this->relationNameToRelatedModel[$attributeName] =
1380  $bean,
1381  $relatedModelClassName,
1382  $attributeModelClassName,
1383  $attributeName,
1384  $owns,
1385  $linkType,
1386  $relationLinkName);
1387  break;
1388 
1389  case self::MANY_MANY:
1390  $this->relationNameToRelatedModel[$attributeName] =
1391  new RedBeanManyToManyRelatedModels($bean, $relatedModelClassName,
1392  $linkType, $relationLinkName);
1393  break;
1394 
1395  default:
1396  throw new NotSupportedException();
1397  }
1398  }
1399  return $this->relationNameToRelatedModel[$attributeName];
1400  }
1401  }
1402  else
1403  {
1404  throw new NotSupportedException('Invalid Attribute: ' . $attributeName);
1405  }
1406  }
1407 
1426  public function __set($attributeName, $value)
1427  {
1428  if ($attributeName == 'id' ||
1429  ($this->isAttributeReadOnly($attributeName) && !$this->isAllowedToSetReadOnlyAttribute($attributeName)))
1430  {
1431  $message = Zurmo::t('Core', 'It is not allowed to set read only attribute: {attributeName}.',
1432  array('{attributeName}' => $attributeName));
1433  throw new NotSupportedException($message);
1434  }
1435  else
1436  {
1437  if ($this->unrestrictedSet($attributeName, $value))
1438  {
1439  $this->modified = true;
1440  $this->onModified();
1441  }
1442  }
1443  }
1444 
1450  protected function unrestrictedSet($attributeName, $value)
1451  {
1452  assert('is_string($attributeName)');
1453  assert('$attributeName != ""');
1454  assert("property_exists(\$this, '$attributeName') || \$this->isAttribute('$attributeName')");
1455  if (property_exists($this, $attributeName))
1456  {
1457  $this->$attributeName = $value;
1458  }
1459  elseif (static::isAnAttribute($attributeName))
1460  {
1461  if (!static::isRelation($attributeName))
1462  {
1463  $bean = $this->attributeNameToBeanAndClassName[$attributeName][0];
1464  $columnName = strtolower($attributeName);
1465  if ($bean->$columnName !== $value)
1466  {
1467  $bean->$columnName = $value;
1468  return true;
1469  }
1470  }
1471  else
1472  {
1473  $relationAndOwns = static::getRelationNameToRelationTypeModelClassNameAndOwnsForModel();
1474  list($relationType, $relatedModelClassName, $owns, $linkType, $relationLinkName) =
1475  $relationAndOwns[$attributeName];
1476  $relatedTableName = $relatedModelClassName::getTableName();
1477  switch ($relationType)
1478  {
1479  case self::HAS_MANY:
1480  case self::MANY_MANY:
1481  // The many sides of a relation cannot
1482  // be assigned, they are changed by the using the
1483  // RedBeanOneToManyRelatedModels or
1484  // RedBeanManyToManyRelatedModels object
1485  // on the 1 or other side of the relationship
1486  // respectively.
1487  throw new NotSupportedException();
1488  }
1489  // If the value is null we need to get the related model so that
1490  // if there is none we can ignore the null and if there is one
1491  // we can act on it.
1492  if ($value === null &&
1493  !in_array($attributeName, $this->unlinkedRelationNames) &&
1494  !isset($this->relationNameToRelatedModel[$attributeName]))
1495  {
1496  $this->unrestrictedGet($attributeName);
1497  }
1498  elseif ($value !== null && $owns == static::OWNED &&
1499  !in_array($attributeName, $this->unlinkedRelationNames) &&
1500  !isset($this->relationNameToRelatedModel[$attributeName]))
1501  {
1502  $this->unrestrictedGet($attributeName);
1503  }
1504  if (isset($this->relationNameToRelatedModel[$attributeName]) &&
1505  $value !== null &&
1506  $this->relationNameToRelatedModel[$attributeName]->isSame($value))
1507  {
1508  // If there is a current related model and it is the same
1509  // as the one being set then do nothing.
1510  }
1511  else
1512  {
1513  if (!in_array($attributeName, $this->unlinkedRelationNames) &&
1514  isset($this->relationNameToRelatedModel[$attributeName]))
1515  {
1516  $this->unlinkedRelationNames[] = $attributeName;
1517  if ($owns == static::OWNED)
1518  {
1519  $this->unlinkedOwnedRelatedModelsToRemove[$attributeName] =
1520  $this->relationNameToRelatedModel[$attributeName];
1521  }
1522  }
1523  if ($value === null)
1524  {
1525  unset($this->relationNameToRelatedModel[$attributeName]);
1526  }
1527  else
1528  {
1529  assert("\$value instanceof $relatedModelClassName");
1530  $this->relationNameToRelatedModel[$attributeName] = $value;
1531  }
1532  }
1533  return true;
1534  }
1535  }
1536  else
1537  {
1538  throw new NotSupportedException($attributeName);
1539  }
1540  return false;
1541  }
1542 
1547  public function __isset($attributeName)
1548  {
1549  assert('is_string($attributeName)');
1550  assert('$attributeName != ""');
1551  return $this->isAttribute($attributeName) &&
1552  $this->$attributeName !== null ||
1553  !$this->isAttribute($attributeName) &&
1554  isset($this->$attributeName);
1555  }
1556 
1561  public function __unset($attributeName)
1562  {
1563  assert('is_string($attributeName)');
1564  assert('$attributeName != ""');
1565  $this->$attributeName = null;
1566  }
1567 
1572  public function attributeNames()
1573  {
1574  return static::getAttributeNames();
1575  }
1576 
1582  public function isAttribute($attributeName)
1583  {
1584  return static::isAnAttribute($attributeName);
1585  }
1586 
1595  public function isAttributeWithLowerCaseConversion($attributeName)
1596  {
1597  $existingAttributeNames = static::getAttributeNames();
1598  $existingAttributeNames = array_map('strtolower', $existingAttributeNames);
1599  if (in_array(strtolower($attributeName), $existingAttributeNames))
1600  {
1601  return true;
1602  }
1603  }
1604 
1608  public function isAttributeReadOnly($attributeName)
1609  {
1610  assert("\$this->isAttribute(\"$attributeName\")");
1611  foreach ($this->validators as $validator)
1612  {
1613  if ($validator instanceof RedBeanModelReadOnlyValidator)
1614  {
1615  if (in_array($attributeName, $validator->attributes, true))
1616  {
1617  return true;
1618  }
1619  }
1620  }
1621  return false;
1622  }
1623 
1627  public function isAttributeFormattedAsProbability($attributeName)
1628  {
1629  assert("\$this->isAttribute(\"$attributeName\")");
1630  foreach ($this->validators as $validator)
1631  {
1632  if ($validator instanceof RedBeanModelProbabilityValidator)
1633  {
1634  if (in_array($attributeName, $validator->attributes, true))
1635  {
1636  return true;
1637  }
1638  }
1639  }
1640  return false;
1641  }
1642 
1648  public function isAllowedToSetReadOnlyAttribute($attributeName)
1649  {
1650  return false;
1651  }
1652 
1657  public function rules()
1658  {
1659  throw new NotImplementedException();
1660  }
1661 
1665  public function behaviors()
1666  {
1667  return array();
1668  }
1669 
1674  public function attributeLabels()
1675  {
1676  return static::translatedAttributeLabels(Yii::app()->language);
1677  }
1678 
1682  public function abbreviatedAttributeLabels()
1683  {
1684  return static::translatedAbbreviatedAttributeLabels(Yii::app()->language);
1685  }
1686 
1701  public function validate(array $attributeNames = null, $ignoreRequiredValidator = false)
1702  {
1703  if ($this->isValidating) // Prevent cycles.
1704  {
1705  return true;
1706  }
1707  $this->isValidating = true;
1708  try
1709  {
1710  $this->clearErrors();
1711  if ($this->beforeValidate())
1712  {
1713  $hasErrors = false;
1714  if ($attributeNames === null)
1715  {
1716  $attributeNames = static::getAttributeNamesNotBelongsToOrManyManyForModel();
1717  }
1718  foreach ($this->getValidators() as $validator)
1719  {
1720  if ($validator instanceof RedBeanModelRequiredValidator && $validator->applyTo($this->scenarioName))
1721  {
1722  if (!$ignoreRequiredValidator)
1723  {
1724  $validator->validate($this, $attributeNames);
1725  }
1726  }
1727  elseif (!$validator instanceof CDefaultValueValidator && $validator->applyTo($this->scenarioName))
1728  {
1729  $validator->validate($this, $attributeNames);
1730  }
1731  }
1732  $relatedModelsHaveErrors = false;
1733  foreach ($this->relationNameToRelatedModel as $relationName => $relatedModel)
1734  {
1735  if ((!$this->$relationName instanceof RedBeanModel) ||
1736  !$this->$relationName->isSame($this))
1737  {
1738  if (in_array($relationName, $attributeNames) &&
1739  ($this->$relationName->isModified() ||
1740  ($this->isAttributeRequired($relationName) && !$ignoreRequiredValidator) &&
1741  !$this->isSame($this->$relationName))) // Prevent cycles.
1742  {
1743  if (!$this->$relationName->validate(null, $ignoreRequiredValidator))
1744  {
1745  $hasErrors = true;
1746  }
1747  }
1748  }
1749  }
1750  $this->afterValidate();
1751  $hasErrors = $hasErrors || count($this->attributeNameToErrors) > 0;
1752  // Put these asserts back if there are suspitions about validate/hasErrors/getErrors
1753  // producing inconsistent results. But for now it is commented out because
1754  // it makes too big an impact.
1755  //assert('$hasErrors == (count($this->getErrors()) > 0)');
1756  //assert('$hasErrors == $this->hasErrors()');
1757  $this->isValidating = false;
1758  return !$hasErrors;
1759  }
1760  $this->isValidating = false;
1761  return false;
1762  }
1763  catch (Exception $e)
1764  {
1765  $this->isValidating = false;
1766  throw $e;
1767  }
1768  }
1769 
1773  protected function beforeValidate()
1774  {
1775  $event = new CModelEvent($this);
1776  $this->onBeforeValidate($event);
1777  return $event->isValid;
1778  }
1779 
1783  protected function afterValidate()
1784  {
1785  $this->onAfterValidate(new CEvent($this));
1786  }
1787 
1791  public function onBeforeValidate(CModelEvent $event)
1792  {
1793  $this->raiseEvent('onBeforeValidate', $event);
1794  }
1795 
1799  public function onAfterValidate($event)
1800  {
1801  $this->raiseEvent('onAfterValidate', $event);
1802  }
1803 
1807  public function getValidatorList()
1808  {
1809  return $this->validators;
1810  }
1811 
1815  public function getValidators($attributeName = null)
1816  {
1817  assert("\$attributeName === null || \$this->isAttribute('$attributeName')");
1818  $validators = array();
1819  $scenarioName = $this->scenarioName;
1820  foreach ($this->validators as $validator)
1821  {
1822  if ($scenarioName === null || $validator->applyTo($scenarioName))
1823  {
1824  if ($attributeName === null || in_array($attributeName, $validator->attributes, true))
1825  {
1826  $validators[] = $validator;
1827  }
1828  }
1829  }
1830  return $validators;
1831  }
1832 
1837  public function forgetValidators()
1838  {
1839  if (!empty($this->validators))
1840  {
1841  $this->validators = array();
1842  }
1843  }
1844 
1848  public function createValidators()
1849  {
1850  throw new NotImplementedException();
1851  }
1852 
1860  public function isUniqueAttributeValue($attributeName, $value)
1861  {
1862  assert("\$this->isAttribute('$attributeName')");
1863  assert('$value !== null');
1864  if (!static::isRelation($attributeName))
1865  {
1866  $modelClassName = $this->attributeNameToBeanAndClassName[$attributeName][1];
1867  $tableName = $modelClassName::getTableName();
1868  $rows = ZurmoRedBean::getAll('select id from ' . $tableName . " where $attributeName = ?", array($value));
1869  return count($rows) == 0 || count($rows) == 1 && $rows[0]['id'] == $this->id;
1870  }
1871  else
1872  {
1873  $model = $this->$attributeName;
1874  if ($model->id == 0)
1875  {
1876  return true;
1877  }
1878  $relationAndOwns = static::getRelationNameToRelationTypeModelClassNameAndOwnsForModel();
1879  $modelClassName = $relationAndOwns[$attributeName][1];
1880  $tableName = $modelClassName::getTableName();
1881  $rows = ZurmoRedBean::getAll('select id from ' . $tableName . ' where id = ?', array($model->id));
1882  return count($rows) == 0 || count($rows) == 1 && $rows[0]['id'] == $this->id;
1883  }
1884  }
1885 
1894  public function save($runValidation = true, array $attributeNames = null)
1895  {
1896  if ($attributeNames !== null)
1897  {
1898  throw new NotSupportedException();
1899  }
1900  if ($this->isSaving) // Prevent cycles.
1901  {
1902  return true;
1903  }
1904  $this->isSaving = true;
1905  try
1906  {
1907  if (!$runValidation || $this->validate())
1908  {
1909  if ($this->beforeSave())
1910  {
1911  $beans = array_values($this->modelClassNameToBean);
1912  $this->linkBeans();
1913  // The breakLink/link is deferred until the save to avoid
1914  // disconnecting or creating an empty row if the model was
1915  // never actually saved.
1916  foreach ($this->unlinkedRelationNames as $key => $relationName)
1917  {
1918  $bean = $this->attributeNameToBeanAndClassName[$relationName][0];
1919  $relationAndOwns = static::getRelationNameToRelationTypeModelClassNameAndOwnsForModel();
1920  $relatedModelClassName = $relationAndOwns[$relationName][1];
1921  $tempRelatedModelClassName = $relatedModelClassName;
1922  self::resolveModelClassNameForClassesWithoutBeans($tempRelatedModelClassName);
1923  $relatedTableName = $tempRelatedModelClassName::getTableName();
1924  $linkName = strtolower($relationName);
1925  if (static::getRelationType($relationName) == self::HAS_ONE &&
1926  static::getRelationLinkType($relationName) == self::LINK_TYPE_SPECIFIC)
1927  {
1928  $linkName = strtolower(static::getRelationLinkName($relationName));
1929  }
1930  elseif ($linkName == strtolower($relatedModelClassName))
1931  {
1932  $linkName = null;
1933  }
1934  ZurmoRedBeanLinkManager::breakLink($bean, $relatedTableName, $linkName);
1935  //Check the $this->{$relationName} second in the if clause to avoid accidentially getting
1936  //a relation to now save. //todo: this needs to be properly handled.
1937  if (isset($this->unlinkedOwnedRelatedModelsToRemove[$relationName]) && $this->{$relationName} !== null)
1938  {
1939  //Remove hasOne owned related models that are no longer needed because they have
1940  //been replaced with another hasOne owned model.
1941  if ($this->unlinkedOwnedRelatedModelsToRemove[$relationName]->id > 0)
1942  {
1943  $this->unlinkedOwnedRelatedModelsToRemove[$relationName]->unrestrictedDelete();
1944  }
1945  unset($this->unlinkedOwnedRelatedModelsToRemove[$relationName]);
1946  }
1947  unset($this->unlinkedRelationNames[$key]);
1948  }
1949  assert('count($this->unlinkedRelationNames) == 0');
1950  foreach ($this->relationNameToRelatedModel as $relationName => $relatedModel)
1951  {
1952  $relationAndOwns = static::getRelationNameToRelationTypeModelClassNameAndOwnsForModel();
1953  $relationType = $relationAndOwns[$relationName][0];
1954  if (!in_array($relationType, array(self::HAS_ONE_BELONGS_TO,
1955  self::HAS_MANY_BELONGS_TO)))
1956  {
1957  if ($this->isRelatedModelReallyModified($relatedModel, $relationAndOwns[$relationName][0],
1958  $relationAndOwns[$relationName][2]) || $this->isAttributeRequired($relationName))
1959  {
1960  //If the attribute is required, but already exists and has not been modified we do
1961  //not have to worry about saving it.
1962  if ($this->isSavableFromRelation &&
1963  !($this->isAttributeRequired($relationName) &&
1964  !$relatedModel->isModified() &&
1965  $relatedModel->id > 0))
1966  {
1967  if (!$relatedModel->save(false))
1968  {
1969  $this->isSaving = false;
1970  return false;
1971  }
1972  }
1973  elseif ($relatedModel->isModified())
1974  {
1975  throw new NotSupportedException();
1976  }
1977  }
1978  }
1979  if ($relatedModel instanceof RedBeanModel)
1980  {
1981  $bean = $this->attributeNameToBeanAndClassName [$relationName][0];
1982  $relationAndOwns = static::getRelationNameToRelationTypeModelClassNameAndOwnsForModel();
1983  $relatedModelClassName = $relationAndOwns[$relationName][1];
1984  $linkName = strtolower($relationName);
1985  if ($relationType == self::HAS_ONE &&
1986  static::getRelationLinkType($relationName) == self::LINK_TYPE_SPECIFIC)
1987  {
1988  $linkName = strtolower(static::getRelationLinkName($relationName));
1989  }
1990  elseif (strtolower($linkName) == strtolower($relatedModelClassName) ||
1991  static::getRelationLinkType($relationName) == self::LINK_TYPE_ASSUMPTIVE)
1992  {
1993  $linkName = null;
1994  }
1995  elseif ($relationType == static::HAS_MANY_BELONGS_TO ||
1996  $relationType == static::HAS_ONE_BELONGS_TO)
1997  {
1998  throw new NotSupportedException(Zurmo::t('Core', 'Relations of type HAS_MANY_BELONGS_TO OR HAS_ONE_BELONGS_TO must have the relation name ' .
1999  'the same as the related model class name. Relation: {relationName} ' .
2000  'Relation model class name: {relationModelClassName}',
2001  array('{relationName}' => $linkName,
2002  '{relationModelClassName}' => $relatedModelClassName)));
2003  }
2004  //Needed to exclude HAS_ONE_BELONGS_TO because an additional column was being created
2005  //on the wrong side.
2006  if ($relationType != static::HAS_ONE_BELONGS_TO &&
2007  ($this->isRelatedModelReallyModified($relatedModel, $relationAndOwns[$relationName][0], $relationAndOwns[$relationName][2]) ||
2008  $relatedModel->id > 0 ||
2009  $this->isAttributeRequired($relationName)))
2010  {
2011  $relatedModel = $this->relationNameToRelatedModel[$relationName];
2012  $relatedBean = $relatedModel->getClassBean($relatedModelClassName);
2013  //Exclude HAS_MANY_BELONGS_TO because if the existing relation is unlinked, then
2014  //this link should not be reactivated, because it will improperly create the bean
2015  //in the database.
2016  if (!($relationType == static::HAS_MANY_BELONGS_TO && $this->{$relationName}->id < 0))
2017  {
2018  ZurmoRedBeanLinkManager::link($bean, $relatedBean, $linkName);
2019  }
2020  }
2021  }
2022  }
2023  $baseModelClassName = null;
2024  foreach ($this->modelClassNameToBean as $modelClassName => $bean)
2025  {
2026  ZurmoRedBean::store($bean);
2027  assert('$bean->id > 0');
2028  }
2029  $this->modified = false;
2030  $this->afterSave();
2031  $calledModelClassName = get_called_class();
2032  if ($calledModelClassName::isCacheable())
2033  {
2035  }
2036  $this->isSaving = false;
2037  return true;
2038  }
2039  }
2040  $this->isSaving = false;
2041  return false;
2042  }
2043  catch (Exception $e)
2044  {
2045  $this->isSaving = false;
2046  throw $e;
2047  }
2048  }
2049 
2058  public function isRelatedModelReallyModified($relatedModel, $relationType, $isOwned)
2059  {
2060  assert('is_int($relationType)');
2061  assert('is_bool($isOwned)');
2062  if ($relatedModel instanceof RedBeanModel)
2063  {
2064  return $relatedModel->isReallyModified($relationType, $isOwned);
2065  }
2066  else
2067  {
2068  return $relatedModel->isModified();
2069  }
2070  }
2071 
2084  public function isReallyModified($relationType, $isOwned)
2085  {
2086  assert('is_int($relationType)');
2087  assert('is_bool($isOwned)');
2088  return $this->isModified();
2089  }
2090 
2100  protected function beforeSave()
2101  {
2102  if ($this->hasEventHandler('onBeforeSave'))
2103  {
2104  $event = new CModelEvent($this);
2105  $this->onBeforeSave($event);
2106  return $event->isValid;
2107  }
2108  else
2109  {
2110  return true;
2111  }
2112  }
2113 
2114  protected function afterSave()
2115  {
2116  $event = new CEvent($this);
2117  $this->onAfterSave($event);
2118  }
2119 
2126  public function onBeforeSave($event)
2127  {
2128  $this->raiseEvent('onBeforeSave', $event);
2129  }
2130 
2136  public function onAfterSave($event)
2137  {
2138  $this->raiseEvent('onAfterSave', $event);
2139  }
2140 
2146  {
2147  $this->raiseEvent('onRedBeanOneToManyRelatedModelsChange', $event);
2148  }
2149 
2156  public function onBeforeDelete($event)
2157  {
2158  $this->raiseEvent('onBeforeDelete', $event);
2159  }
2160 
2166  public function onAfterDelete($event)
2167  {
2168  $this->raiseEvent('onAfterDelete', $event);
2169  }
2170 
2171  protected function linkBeans()
2172  {
2173  $baseBean = null;
2174  foreach ($this->modelClassNameToBean as $modelClassName => $bean)
2175  {
2176  if ($baseBean !== null)
2177  {
2178  ZurmoRedBeanLinkManager::link($bean, $baseBean);
2179  }
2180  $baseBean = $bean;
2181  }
2182  }
2183 
2188  public function isModified()
2189  {
2190  if ($this->modified)
2191  {
2192  return true;
2193  }
2194  if ($this->isInIsModified) // Prevent cycles.
2195  {
2196  return false;
2197  }
2198  $this->isInIsModified = true;
2199  try
2200  {
2201  foreach ($this->relationNameToRelatedModel as $relationName => $relatedModel)
2202  {
2203  if ((!$this->$relationName instanceof RedBeanModel) ||
2204  !$this->$relationName->isSame($this))
2205  {
2206  $relationAndOwns = static::getRelationNameToRelationTypeModelClassNameAndOwnsForModel();
2207  if (!in_array($relationAndOwns[$relationName][0],
2208  array(self::HAS_ONE_BELONGS_TO,
2209  self::HAS_MANY_BELONGS_TO)))
2210  {
2211  if ($this->$relationName->isModified())
2212  {
2213  $this->isInIsModified = false;
2214  return true;
2215  }
2216  }
2217  }
2218  }
2219  $this->isInIsModified = false;
2220  return false;
2221  }
2222  catch (Exception $e)
2223  {
2224  $this->isInIsModified = false;
2225  throw $e;
2226  }
2227  }
2228 
2232  public function delete()
2233  {
2234  if ($this->id < 0)
2235  {
2236  // If the model was never saved
2237  // then it doesn't need to be deleted.
2238  return false;
2239  }
2240  $modelClassName = get_called_class();
2241  if (!$modelClassName::isTypeDeletable() ||
2242  !$this->isDeletable())
2243  {
2244  // See comments below on isDeletable.
2245  throw new NotSupportedException();
2246  }
2247  if ($this->beforeDelete())
2248  {
2249  $this->isDeleting = true;
2250  $deleted = $this->unrestrictedDelete();
2251  $this->isDeleting = false;
2252  $this->afterDelete();
2253  return $deleted;
2254  }
2255  else
2256  {
2257  return false;
2258  }
2259  }
2260 
2268  protected function beforeDelete()
2269  {
2270  if ($this->hasEventHandler('onBeforeDelete'))
2271  {
2272  $event = new CModelEvent($this);
2273  $this->onBeforeDelete($event);
2274  return $event->isValid;
2275  }
2276  else
2277  {
2278  return true;
2279  }
2280  }
2281 
2288  protected function afterDelete()
2289  {
2290  if ($this->hasEventHandler('onAfterDelete'))
2291  {
2292  $this->onAfterDelete(new CEvent($this));
2293  }
2294  }
2295 
2296  protected function unrestrictedDelete()
2297  {
2298  $this->forget();
2299  // RedBeanModel only supports cascaded deletes on associations,
2300  // not on links. So for now at least they are done the slow way.
2301  foreach (RuntimeUtil::getClassHierarchy(get_class($this), static::$lastClassInBeanHeirarchy) as $modelClassName)
2302  {
2303  if ($modelClassName::getCanHaveBean())
2304  {
2305  $this->deleteOwnedRelatedModels ($modelClassName);
2306  $this->deleteForeignRelatedModels($modelClassName);
2307  $this->deleteManyManyRelations ($modelClassName);
2308  }
2309  }
2310  foreach ($this->modelClassNameToBean as $modelClassName => $bean)
2311  {
2312  ZurmoRedBean::trash($bean);
2313  }
2314  // The model cannot be used anymore.
2315  $this->deleted = true;
2316  return true;
2317  }
2318 
2319  public function isDeleted()
2320  {
2321  return $this->deleted;
2322  }
2323 
2324  protected function deleteOwnedRelatedModels($modelClassName)
2325  {
2326  foreach (static::getRelationNameToRelationTypeModelClassNameAndOwnsForModel() as $relationName => $relationTypeModelClassNameAndOwns)
2327  {
2328  assert('count($relationTypeModelClassNameAndOwns) > 2 && count($relationTypeModelClassNameAndOwns) < 6');
2329  $relationType = $relationTypeModelClassNameAndOwns[0];
2330  $owns = $relationTypeModelClassNameAndOwns[2];
2331  if ($owns)
2332  {
2333  if ((!$this->$relationName instanceof RedBeanModel) ||
2334  !$this->$relationName->isSame($this))
2335  {
2336  assert('in_array($relationType, array(self::HAS_ONE, self::HAS_MANY))');
2337  if ($relationType == self::HAS_ONE)
2338  {
2339  if ($this->$relationName->id > 0)
2340  {
2341  // we can't change these to delete() because its Owned, it can't be deleted from outside
2342  $this->$relationName->unrestrictedDelete();
2343  }
2344  }
2345  else
2346  {
2347  foreach ($this->$relationName as $model)
2348  {
2349  // we can't change this to delete() because its Owned, it can't be deleted from outside
2350  $model->unrestrictedDelete();
2351  }
2352  }
2353  }
2354  }
2355  }
2356  }
2357 
2358  protected function deleteForeignRelatedModels($modelClassName)
2359  {
2360  $metadata = $this->getMetadata();
2361  if (isset($metadata[$modelClassName]['foreignRelations']))
2362  {
2363  foreach ($metadata[$modelClassName]['foreignRelations'] as $relatedModelClassName)
2364  {
2365  $relatedModels = $relatedModelClassName::
2366  getByRelatedClassId($relatedModelClassName,
2367  $this->getClassId($modelClassName),
2368  $modelClassName);
2369  foreach ($relatedModels as $relatedModel)
2370  {
2371  $relatedModel->unrestrictedDelete();
2372  }
2373  }
2374  }
2375  }
2376 
2377  protected static function getByRelatedClassId($relatedModelClassName, $id, $modelClassName = null)
2378  {
2379  assert('is_string($relatedModelClassName)');
2380  assert('$relatedModelClassName != ""');
2381  assert('is_int($id)');
2382  //assert('$id > 0');
2383  assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
2384  if ($modelClassName === null)
2385  {
2386  $modelClassName = get_called_class();
2387  }
2388  $tableName = $relatedModelClassName::getTableName();
2389  $foreignKeyName = strtolower($modelClassName) . '_id';
2390  $beans = ZurmoRedBean::find($tableName, "$foreignKeyName = $id");
2391  return self::makeModels($beans, $relatedModelClassName);
2392  }
2393 
2394  protected function deleteManyManyRelations($modelClassName)
2395  {
2396  $metadata = $this->getMetadata();
2397  if (isset($metadata[$modelClassName]['relations']))
2398  {
2399  foreach ($metadata[$modelClassName]['relations'] as $relationName => $relationTypeModelClassNameAndOwns)
2400  {
2401  assert('in_array(count($relationTypeModelClassNameAndOwns), array(2, 3, 4, 5))');
2402  $relationType = $relationTypeModelClassNameAndOwns[0];
2403  if ($relationType == self::MANY_MANY)
2404  {
2405  $this->{$relationName}->removeAll();
2406  $this->{$relationName}->save();
2407  }
2408  }
2409  }
2410  }
2411 
2422  public static function isTypeDeletable()
2423  {
2424  return true;
2425  }
2426 
2431  public function isDeletable()
2432  {
2433  return true;
2434  }
2435 
2440  public static function forgetAll()
2441  {
2442  self::forgetAllBeanModels();
2443  self::$attributeLabelsByLanguage = array();
2446  }
2447 
2452  public function forget()
2453  {
2454  self::forgetBeanModel(get_called_class());
2456  RedBeansCache::forgetBean(static::getTableName() . $this->id);
2457  }
2458 
2462  public function isAttributeRequired($attributeName)
2463  {
2464  assert("\$this->isAttribute('$attributeName')");
2465  foreach ($this->getValidators($attributeName) as $validator)
2466  {
2467  if ($validator instanceof CRequiredValidator)
2468  {
2469  return true;
2470  }
2471  }
2472  return false;
2473  }
2474 
2478  public function isAttributeSafe($attributeName)
2479  {
2480  $attributeNames = $this->getSafeAttributeNames();
2481  return in_array($attributeName, $attributeNames);
2482  }
2483 
2484  public static function getModelLabelByTypeAndLanguage($type, $language = null)
2485  {
2486  assert('in_array($type, array("Singular", "SingularLowerCase", "Plural", "PluralLowerCase"))');
2487  if ($type == 'Singular')
2488  {
2489  return static::getLabel($language);
2490  }
2491  if ($type == 'SingularLowerCase')
2492  {
2493  return TextUtil::strToLowerWithDefaultEncoding(static::getLabel($language));
2494  }
2495  if ($type == 'Plural')
2496  {
2497  return static::getPluralLabel($language);
2498  }
2499  if ($type == 'PluralLowerCase')
2500  {
2501  return TextUtil::strToLowerWithDefaultEncoding(static::getPluralLabel($language));
2502  }
2503  }
2504 
2512  protected static function getLabel($language = null)
2513  {
2514  if (null != $moduleClassName = static::getModuleClassName())
2515  {
2516  return $moduleClassName::getModuleLabelByTypeAndLanguage('Singular', $language);
2517  }
2518  return get_called_class();
2519  }
2520 
2527  protected static function getPluralLabel($language = null)
2528  {
2529  if (null != $moduleClassName = static::getModuleClassName())
2530  {
2531  return $moduleClassName::getModuleLabelByTypeAndLanguage('Plural', $language);
2532  }
2533  return static::getLabel($language) . 's';
2534  }
2535 
2539  public function getAttributeLabel($attributeName)
2540  {
2541  return static::getAnAttributeLabel($attributeName);
2542  }
2543 
2547  public static function getAnAttributeLabel($attributeName)
2548  {
2549  return static::getAttributeLabelByLanguage($attributeName, Yii::app()->language);
2550  }
2551 
2557  protected static function getAttributeLabelByLanguage($attributeName, $language)
2558  {
2559  assert('is_string($attributeName)');
2560  assert('is_string($language)');
2561  if (isset(static::$attributeLabelsByLanguage[$language][get_called_class()][$attributeName]))
2562  {
2563  return static::$attributeLabelsByLanguage[$language][get_called_class()][$attributeName];
2564  }
2565  $labels = static::translatedAttributeLabels($language);
2566  $customLabel = static::getTranslatedCustomAttributeLabelByLanguage($attributeName, $language);
2567  if ($customLabel != null)
2568  {
2569  $label = $customLabel;
2570  }
2571  elseif (isset($labels[$attributeName]))
2572  {
2573  $label = $labels[$attributeName];
2574  }
2575  else
2576  {
2577  //This is a last resort if the translated attribute was not located. Make sure to define all
2578  //attributes in translatedAttributeLabels($language)
2579  $label = Zurmo::t('Core', static::generateAnAttributeLabel($attributeName), array(), null, $language);
2580  }
2581  static::$attributeLabelsByLanguage[$language][get_called_class()][$attributeName] = $label;
2582  return $label;
2583  }
2584 
2589  protected static function getTranslatedCustomAttributeLabelByLanguage($attributeName, $languageCode)
2590  {
2591  assert('is_string($attributeName)');
2592  assert('is_string($languageCode)');
2593  $metadata = static::getMetadata();
2594  foreach ($metadata as $notUsed => $modelClassMetadata)
2595  {
2596  if (isset($modelClassMetadata['labels']) &&
2597  isset($modelClassMetadata['labels'][$attributeName]) &&
2598  isset($modelClassMetadata['labels'][$attributeName][$languageCode]))
2599  {
2600  return $modelClassMetadata['labels'][$attributeName][$languageCode];
2601  }
2602  }
2603  return null;
2604  }
2605 
2611  {
2612  assert('is_string($attributeName)');
2613  $attirbuteLabelData = array();
2614  foreach (Yii::app()->languageHelper->getActiveLanguagesData() as $languageCode => $languageData)
2615  {
2616  $attirbuteLabelData[$languageCode] = $this->getAttributeLabelByLanguage($attributeName, $languageCode);
2617  }
2618  return $attirbuteLabelData;
2619  }
2620 
2627  public function hasErrors($attributeNameOrNames = null)
2628  {
2629  assert('$attributeNameOrNames === null || ' .
2630  'is_string($attributeNameOrNames) || ' .
2631  'is_array ($attributeNameOrNames) && AssertUtil::all($attributeNameOrNames, "is_string")');
2632  if ($this->isInHasErrors) // Prevent cycles.
2633  {
2634  return false;
2635  }
2636  $this->isInHasErrors = true;
2637  try
2638  {
2639  if (is_string($attributeNameOrNames))
2640  {
2641  $attributeName = $attributeNameOrNames;
2642  $relatedAttributeNames = null;
2643  }
2644  elseif (is_array($attributeNameOrNames))
2645  {
2646  $attributeName = $attributeNameOrNames[0];
2647  if (count($attributeNameOrNames) > 1)
2648  {
2649  $relatedAttributeNames = array_slice($attributeNameOrNames, 1);
2650  }
2651  else
2652  {
2653  $relatedAttributeNames = null;
2654  }
2655  }
2656  else
2657  {
2658  $attributeName = null;
2659  $relatedAttributeNames = null;
2660  }
2661  assert("\$attributeName === null || is_string('$attributeName')");
2662  assert('$relatedAttributeNames === null || is_array($relatedAttributeNames)');
2663  assert('!($attributeName === null && $relatedAttributeNames !== null)');
2664  if ($attributeName === null)
2665  {
2666  if (count($this->attributeNameToErrors) > 0)
2667  {
2668  $this->isInHasErrors = false;
2669  return true;
2670  }
2671  foreach ($this->relationNameToRelatedModel as $relationName => $relatedModelOrModels)
2672  {
2673  if ((!$this->$relationName instanceof RedBeanModel) ||
2674  !$this->$relationName->isSame($this))
2675  {
2676  if (in_array($relationName, static::getAttributeNamesNotBelongsToOrManyManyForModel()))
2677  {
2678  if ($relatedModelOrModels->hasErrors($relatedAttributeNames))
2679  {
2680  $this->isInHasErrors = false;
2681  return true;
2682  }
2683  }
2684  }
2685  }
2686  $this->isInHasErrors = false;
2687  return false;
2688  }
2689  else
2690  {
2691  if (!static::isRelation($attributeName))
2692  {
2693  $this->isInHasErrors = false;
2694  return array_key_exists($attributeName, $this->attributeNameToErrors);
2695  }
2696  else
2697  {
2698  if (in_array($attributeName, static::getAttributeNamesNotBelongsToOrManyManyForModel()))
2699  {
2700  $this->isInHasErrors = false;
2701  return isset($this->relationNameToRelatedModel[$attributeName]) &&
2702  count($this->relationNameToRelatedModel[$attributeName]->getErrors($relatedAttributeNames)) > 0;
2703  }
2704  }
2705  }
2706  $this->isInHasErrors = false;
2707  return false;
2708  }
2709  catch (Exception $e)
2710  {
2711  $this->isInHasErrors = false;
2712  throw $e;
2713  }
2714  }
2715 
2726  public function getErrors($attributeNameOrNames = null)
2727  {
2728  assert('$attributeNameOrNames === null || ' .
2729  'is_string($attributeNameOrNames) || ' .
2730  'is_array ($attributeNameOrNames) && AssertUtil::all($attributeNameOrNames, "is_string")');
2731  if ($this->isInGetErrors) // Prevent cycles.
2732  {
2733  return array();
2734  }
2735  $this->isInGetErrors = true;
2736  try
2737  {
2738  if (is_string($attributeNameOrNames))
2739  {
2740  $attributeName = $attributeNameOrNames;
2741  $relatedAttributeNames = null;
2742  }
2743  elseif (is_array($attributeNameOrNames))
2744  {
2745  $attributeName = $attributeNameOrNames[0];
2746  if (count($attributeNameOrNames) > 1)
2747  {
2748  $relatedAttributeNames = array_slice($attributeNameOrNames, 1);
2749  }
2750  else
2751  {
2752  $relatedAttributeNames = null;
2753  }
2754  }
2755  else
2756  {
2757  $attributeName = null;
2758  $relatedAttributeNames = null;
2759  }
2760  assert("\$attributeName === null || is_string('$attributeName')");
2761  assert('$relatedAttributeNames === null || is_array($relatedAttributeNames)');
2762  assert('!($attributeName === null && $relatedAttributeNames !== null)');
2763  if ($attributeName === null)
2764  {
2765  $errors = $this->attributeNameToErrors;
2766  foreach ($this->relationNameToRelatedModel as $relationName => $relatedModelOrModels)
2767  {
2768  if ((!$this->$relationName instanceof RedBeanModel) ||
2769  !$this->$relationName->isSame($this))
2770  {
2771  $relationAndOwns = static::getRelationNameToRelationTypeModelClassNameAndOwnsForModel();
2772  if (!in_array($relationAndOwns[$relationName][0],
2773  array(self::HAS_ONE_BELONGS_TO,
2774  self::HAS_MANY_BELONGS_TO,
2775  self::MANY_MANY)))
2776  {
2777  $relatedErrors = $relatedModelOrModels->getErrors($relatedAttributeNames);
2778  if (count($relatedErrors) > 0)
2779  {
2780  $errors[$relationName] = $relatedErrors;
2781  }
2782  }
2783  }
2784  }
2785  $this->isInGetErrors = false;
2786  return $errors;
2787  }
2788  else
2789  {
2790  if (isset($this->attributeNameToErrors[$attributeName]))
2791  {
2792  $this->isInGetErrors = false;
2793  return $this->attributeNameToErrors[$attributeName];
2794  }
2795  elseif (isset($this->relationNameToRelatedModel[$attributeName]))
2796  {
2797  $relationAndOwns = static::getRelationNameToRelationTypeModelClassNameAndOwnsForModel();
2798  if (!in_array($relationAndOwns[$attributeName][0],
2799  array(self::HAS_ONE_BELONGS_TO, self::HAS_MANY_BELONGS_TO)))
2800  {
2801  $this->isInGetErrors = false;
2802  return $this->relationNameToRelatedModel[$attributeName]->getErrors($relatedAttributeNames);
2803  }
2804  }
2805  }
2806  $this->isInGetErrors = false;
2807  return array();
2808  }
2809  catch (Exception $e)
2810  {
2811  $this->isInGetErrors = false;
2812  throw $e;
2813  }
2814  }
2815 
2819  public function getError($attributeName)
2820  {
2821  assert("\$this->isAttribute('$attributeName')");
2822  return isset($this->attributeNameToErrors[$attributeName]) ? reset($this->attributeNameToErrors[$attributeName]) : null;
2823  }
2824 
2828  public function addError($attributeName, $errorMessage)
2829  {
2830  assert("\$this->isAttribute('$attributeName')");
2831  if (!isset($this->attributeNameToErrors[$attributeName]))
2832  {
2833  $this->attributeNameToErrors[$attributeName] = array();
2834  }
2835  $this->attributeNameToErrors[$attributeName][] = $errorMessage;
2836  }
2837 
2841  public function addErrors(array $errors)
2842  {
2843  foreach ($errors as $attributeName => $error)
2844  {
2845  assert("\$this->isAttribute('$attributeName')");
2846  assert('is_array($error) || is_string($error)');
2847  if (is_array($error))
2848  {
2849  if (!isset($this->attributeNameToErrors[$attributeName]))
2850  {
2851  $this->attributeNameToErrors[$attributeName] = array();
2852  }
2853  $this->attributeNameToErrors[$attributeName] =
2854  array_merge($this->attributeNameToErrors[$attributeName], $error);
2855  }
2856  else
2857  {
2858  $this->attributeNameToErrors[$attributeName][] = $error;
2859  }
2860  }
2861  }
2862 
2866  public function clearErrors($attributeName = null)
2867  {
2868  assert("\$attributeName === null || \$this->isAttribute('$attributeName')");
2869  if ($attributeName === null)
2870  {
2871  $this->attributeNameToErrors = array();
2872  }
2873  else
2874  {
2875  unset($this->attributeNameToErrors[$attributeName]);
2876  }
2877  }
2878 
2882  public function generateAttributeLabel($attributeName)
2883  {
2884  $modelClassName = get_called_class();
2885  return $modelClassName::generateAnAttributeLabel($attributeName);
2886  }
2887 
2891  public function getAttributes(array $attributeNames = null)
2892  {
2893  $values = array();
2894  if (is_array($attributeNames))
2895  {
2896  $values2 = array();
2897  $allModelAttributeNames = $this->attributeNames();
2898  foreach ($attributeNames as $attributeName)
2899  {
2900  if (in_array($attributeName, $allModelAttributeNames))
2901  {
2902  $values2[$attributeName] = $this->$attributeName;
2903  }
2904  }
2905  return $values2;
2906  }
2907  else
2908  {
2909  foreach ($this->attributeNames() as $attributeName)
2910  {
2911  $values[$attributeName] = $this->$attributeName;
2912  }
2913  return $values;
2914  }
2915  }
2916 
2920  public function setAttributes(array $values, $safeOnly = true)
2921  {
2922  assert('is_bool($safeOnly)');
2923  $attributeNames = array_flip($safeOnly ? $this->getSafeAttributeNames() : $this->attributeNames());
2924  foreach ($values as $attributeName => $value)
2925  {
2926  if ($value !== null)
2927  {
2928  if (!is_array($value))
2929  {
2930  assert('$attributeName != "id"');
2931  if ($attributeName != 'id' && $this->isAttribute($attributeName))
2932  {
2933  if ($this->isAttributeSafe($attributeName) || !$safeOnly)
2934  {
2935  $this->$attributeName = $value;
2936  }
2937  else
2938  {
2939  $this->onUnsafeAttribute($attributeName, $value);
2940  }
2941  }
2942  }
2943  else
2944  {
2945  if (static::isRelation($attributeName))
2946  {
2947  if (count($value) == 1 && array_key_exists('id', $value))
2948  {
2949  if (empty($value['id']))
2950  {
2951  $this->$attributeName = null;
2952  }
2953  else
2954  {
2955  $relationAndOwns = static::getRelationNameToRelationTypeModelClassNameAndOwnsForModel();
2956  $relatedModelClassName = $relationAndOwns[$attributeName][1];
2957  $this->$attributeName = $relatedModelClassName::getById(intval($value['id']), $relatedModelClassName);
2958  }
2959  }
2960  else
2961  {
2962  $setAttributeMethodName = 'set' . ucfirst($attributeName);
2963  if ($this->$attributeName instanceof RedBeanOneToManyRelatedModels &&
2964  method_exists($this, $setAttributeMethodName))
2965  {
2966  $this->$setAttributeMethodName($value);
2967  }
2968  else
2969  {
2970  $this->$attributeName->setAttributes($value);
2971  }
2972  }
2973  }
2974  }
2975  }
2976  }
2977  }
2978 
2982  public function unsetAttributes($attributeNames = null)
2983  {
2984  if ($attributeNames === null)
2985  {
2986  $attributeNames = $this->attributeNames();
2987  }
2988  foreach ($attributeNames as $attributeName)
2989  {
2990  $this->$attributeNames = null;
2991  }
2992  }
2993 
2997  public function onUnsafeAttribute($name, $value)
2998  {
2999  if (YII_DEBUG)
3000  {
3001  Yii::log(Zurmo::t('Core', 'Failed to set unsafe attribute "{attribute}".', array('{attribute}' => $name)),
3002  CLogger::LEVEL_WARNING);
3003  }
3004  }
3005 
3009  public function getScenario()
3010  {
3011  return $this->scenarioName;
3012  }
3013 
3017  public function setScenario($scenarioName)
3018  {
3019  assert('is_string($scenarioName)');
3020  $this->scenarioName = $scenarioName;
3021  }
3022 
3026  public function getSafeAttributeNames()
3027  {
3028  $attributeNamesToIsSafe = array();
3029  $unsafeAttributeNames = array();
3030  foreach ($this->getValidators() as $validator)
3031  {
3032  if (!$validator->safe)
3033  {
3034  foreach ($validator->attributes as $attributeName)
3035  {
3036  $unsafeAttributeNames[] = $attributeName;
3037  }
3038  }
3039  else
3040  {
3041  foreach ($validator->attributes as $attributeName)
3042  {
3043  $attributeNamesToIsSafe[$attributeName] = true;
3044  }
3045  }
3046  }
3047  foreach ($unsafeAttributeNames as $attributeName)
3048  {
3049  unset($attributeNamesToIsSafe[$attributeName]);
3050  }
3051  return array_keys($attributeNamesToIsSafe);
3052  }
3053 
3057  public function getIterator()
3058  {
3059  throw new NotImplementedException();
3060  }
3061 
3065  public function offsetExists($offset)
3066  {
3067  throw new NotImplementedException();
3068  }
3069 
3073  public function offsetGet($offset)
3074  {
3075  throw new NotImplementedException();
3076  }
3077 
3081  public function offsetSet($offset, $item)
3082  {
3083  throw new NotImplementedException();
3084  }
3085 
3089  public function offsetUnset($offset)
3090  {
3091  throw new NotImplementedException();
3092  }
3093 
3104  public static function makeModel(RedBean_OODBBean $bean, $modelClassName = null)
3105  {
3106  assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
3107  if ($modelClassName === null)
3108  {
3109  $modelClassName = get_called_class();
3110  }
3111  $modelIdentifier = static::getModelIdentifierByClassNameAndBeanId($modelClassName, strval($bean->id));
3112  try
3113  {
3114  $model = RedBeanModelsCache::getModel($modelIdentifier);
3115  $model->constructIncomplete($bean, false);
3116  return $model;
3117  }
3118  catch (NotFoundException $e)
3119  {
3120  return new $modelClassName(false, $bean);
3121  //return new $modelClassName(true, $bean, $forceTreatAsCreation); //no need to set defaults here and force creation since it is always false and the bean already exists
3122  }
3123  }
3124 
3135  public static function makeModels(array $beans, $modelClassName = null)
3136  {
3137  if ($modelClassName === null)
3138  {
3139  $modelClassName = get_called_class();
3140  }
3141  $models = array();
3142  foreach ($beans as $bean)
3143  {
3144  assert('$bean instanceof RedBean_OODBBean');
3145  try
3146  {
3147  $models[] = self::makeModel($bean, $modelClassName);
3148  }
3149  catch (MissingBeanException $e)
3150  {
3151  }
3152  }
3153  return $models;
3154  }
3155 
3156  public static function getModuleClassName()
3157  {
3158  return null;
3159  }
3160 
3165  public function stringifyOneToManyRelatedModelsValues($values)
3166  {
3167  assert('is_array($values)');
3168  return ArrayUtil::stringify($values);
3169  }
3170 
3176  protected static function resolveModelClassNameForClassesWithoutBeans(& $modelClassName)
3177  {
3178  assert('is_string($modelClassName)');
3179  if (!$modelClassName::getCanHaveBean())
3180  {
3181  $modelClassName = get_parent_class($modelClassName);
3182  if (!$modelClassName::getCanHaveBean())
3183  {
3184  //For the moment, only support a single class in a chain of classes not having a bean.
3185  //Expand this support as needed.
3186  throw new NotSupportedException();
3187  }
3188  }
3189  return $modelClassName;
3190  }
3191 
3192  public static function getLastClassInBeanHeirarchy()
3193  {
3194  return static::$lastClassInBeanHeirarchy;
3195  }
3196 
3202  public static function getSortAttributesByAttribute($attribute)
3203  {
3204  return array($attribute);
3205  }
3206 
3210  public function setIsCopied()
3211  {
3212  $this->isCopied = true;
3213  }
3214 
3218  public function isCopied()
3219  {
3220  return $this->isCopied;
3221  }
3222 
3226  public static function isCacheable()
3227  {
3228  return true;
3229  }
3230 
3231  public static function getYiiValidatorsToRedBeanValidators()
3232  {
3233  return static::$yiiValidatorsToRedBeanValidators;
3234  }
3235 
3242  public function addValidator($attribute, $validator, $params = array())
3243  {
3244  if ($attribute != null && $validator != null)
3245  {
3246  $this->validators[] = CValidator::createValidator($validator, $this, $attribute, $params);
3247  }
3248  }
3249 
3250  public static function getHasManyOpposingRelationName(RedBeanModel $model, $precedingModelClassName, $precedingRelation)
3251  {
3252  assert('is_string($precedingModelClassName)');
3253  assert('is_string($precedingRelation)');
3254  foreach ($model->attributeNames() as $attributeName)
3255  {
3256  if ($model->isRelation($attributeName) &&
3257  ($model->getRelationType($attributeName) == RedBeanModel::HAS_ONE ||
3258  $model->getRelationType($attributeName) == RedBeanModel::HAS_MANY_BELONGS_TO) &&
3259  static::relationLinksToPrecedingRelation(get_class($model), $attributeName,
3260  $precedingModelClassName, $precedingRelation))
3261  {
3262  return $attributeName;
3263  }
3264  }
3265  }
3266 
3274  public static function relationLinksToPrecedingRelation($modelClassName, $relation,
3275  $precedingModelClassName = null,
3276  $precedingRelation = null)
3277  {
3278  assert('is_string($modelClassName)');
3279  assert('is_string($relation)');
3280  assert('is_string($precedingModelClassName) || $precedingModelClassName == null');
3281  assert('is_string($precedingRelation) || $precedingRelation == null');
3282  if ($precedingModelClassName == null || $precedingRelation == null)
3283  {
3284  return false;
3285  }
3286  //Check if the relation is a derived relation in which case return false because it is handled by
3287  //@see self::inferredRelationLinksToPrecedingRelation
3288  if (!$precedingModelClassName::isAnAttribute($precedingRelation))
3289  {
3290  return false;
3291  }
3292  if ($precedingModelClassName != $modelClassName::getRelationModelClassName($relation))
3293  {
3294  return false;
3295  }
3296  if ( $precedingModelClassName::getRelationLinkType($precedingRelation) == RedBeanModel::LINK_TYPE_ASSUMPTIVE &&
3297  $modelClassName::getRelationLinkType($relation) == RedBeanModel::LINK_TYPE_ASSUMPTIVE)
3298  {
3299  return true;
3300  }
3301  //Check for LINK_TYPE_SPECIFIC
3302  if ( $precedingModelClassName::getRelationLinkType($precedingRelation) == RedBeanModel::LINK_TYPE_SPECIFIC &&
3303  $modelClassName::getRelationLinkType($relation) == RedBeanModel::LINK_TYPE_SPECIFIC &&
3304  $precedingModelClassName::getRelationLinkName($precedingRelation) == $modelClassName::getRelationLinkName($relation))
3305  {
3306  return true;
3307  }
3308  return false;
3309  }
3310 
3315  public static function allowMemcacheCache()
3316  {
3317  return true;
3318  }
3319  }
3320 ?>
static forgetModel(RedBeanModel $model)
isAttributeWithLowerCaseConversion($attributeName)
setScenario($scenarioName)
isAttributeFormattedAsProbability($attributeName)
static getModel($modelIdentifier)
static canSaveMetadata()
unrestrictedGet($attributeName)
stringifyOneToManyRelatedModelsValues($values)
isReallyModified($relationType, $isOwned)
addErrors(array $errors)
clearErrors($attributeName=null)
static model($className=null)
onRedBeanOneToManyRelatedModelsChange($event)
static mangleTableName()
__isset($attributeName)
static resolveModelClassNameForClassesWithoutBeans(&$modelClassName)
static getAnAttributeLabel($attributeName)
getAttributes(array $attributeNames=null)
offsetSet($offset, $item)
static forgetAll($onlyForgetPhpCache=false)
constructIncomplete($bean)
static forgetAll()
static getForeignKeyName($modelClassName, $relationName)
static allowMemcacheCache()
static cacheModel(RedBeanModel $model)
getClassId($modelClassName)
static getByClassName($className)
onAfterDelete($event)
static forgetAll()
isAttribute($attributeName)
const HAS_ONE
Definition: BeanModel.php:64
static getBean($beanIdentifier)
onUnsafeAttribute($name, $value)
__get($attributeName)
static isTypeDeletable()
static isCacheable()
onAfterSave($event)
offsetExists($offset)
static makeModels(array $beans, $modelClassName=null)
isRelatedModelReallyModified($relatedModel, $relationType, $isOwned)
mapAndCacheMetadataAndSetHints($modelClassName, RedBean_OODBBean $bean)
offsetGet($offset)
static forgetAllByModelType($modelClassName)
onBeforeSave($event)
static stringify($data)
Definition: ArrayUtil.php:102
static getDefaultMetadata()
onBeforeDelete($event)
isAttributeReadOnly($attributeName)
__unset($attributeName)
__construct($setDefaults=true, RedBean_OODBBean $bean=null, $forceTreatAsCreation=false, $runConstruction=true)
static getTableNames($classNames)
static getSubset(RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter=null, $offset=null, $count=null, $where=null, $orderBy=null, $modelClassName=null, $selectDistinct=false)
__set($attributeName, $value)
static getAttributeLabelByLanguage($attributeName, $language)
static makeSubsetOrCountSqlQuery($tableName, RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter, $offset=null, $count=null, $where=null, $orderBy=null, $selectCount=false, $selectDistinct=false, array $quotedExtraSelectColumnNameAndAliases=array())
setClassBean($modelClassName, RedBean_OODBBean $bean)
validate(array $attributeNames=null, $ignoreRequiredValidator=false)
static deleteAll()
static strToLowerWithDefaultEncoding($string)
Definition: TextUtil.php:47
static getCount(RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter=null, $where=null, $modelClassName=null, $selectDistinct=false)
unrestrictedSet($attributeName, $value)
static getClassHierarchy($className, $upToAndNotIncludingClassName)
Definition: RuntimeUtil.php:54
static forgetBean($beanIdentifier)
isAttributeSafe($attributeName)
isAllowedToSetReadOnlyAttribute($attributeName)
const HAS_MANY_BELONGS_TO
Definition: BeanModel.php:58
static getSortAttributesByAttribute($attribute)
isAttributeRequired($attributeName)
static getById($id, $modelClassName=null)
getError($attributeName)
static relationLinksToPrecedingRelation($modelClassName, $relation, $precedingModelClassName=null, $precedingRelation=null)
castDown(array $derivedModelClassNames)
getAttributeLabel($attributeName)
offsetUnset($offset)
addValidator($attribute, $validator, $params=array())
static isRelation($attributeName)
Definition: BeanModel.php:220
setAttributes(array $values, $safeOnly=true)
static cacheBean(RedBean_OODBBean $bean, $beanIdentifier)
static getPluralLabel($language=null)
onBeforeValidate(CModelEvent $event)
static getAll($orderBy=null, $sortDescending=false, $modelClassName=null)
getErrors($attributeNameOrNames=null)
addError($attributeName, $errorMessage)
static getTableName()
static getSubsetIds(RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter=null, $offset=null, $count=null, $where=null, $orderBy=null, $modelClassName=null, $selectDistinct=false)
hasErrors($attributeNameOrNames=null)
onAfterValidate($event)
static getMetadata()
generateAttributeLabel($attributeName)
static setMetadata(array $metadata)
save($runValidation=true, array $attributeNames=null)
isSame(RedBeanModel $model)
getValidators($attributeName=null)
isUniqueAttributeValue($attributeName, $value)
getAttributeLabelsForAllActiveLanguagesByAttributeName($attributeName)
static getTranslatedCustomAttributeLabelByLanguage($attributeName, $languageCode)
const MODEL_IDENTIFIER_DELIMITER
unsetAttributes($attributeNames=null)
abbreviatedAttributeLabels()
static makeModel(RedBean_OODBBean $bean, $modelClassName=null)
static getRelationType($relationName)
Definition: BeanModel.php:243
static getLabel($language=null)
Generated on Sun Aug 9 2020 07:10:26
Account Suspended
Account Suspended
This Account has been suspended.
Contact your hosting provider for more information.