PageRenderTime 80ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/app/protected/core/models/RedBeanModel.php

https://bitbucket.org/mstowe/zurmo
PHP | 3070 lines | 2182 code | 151 blank | 737 comment | 270 complexity | 12bf324e583465a0ae4c636f75cb4bff MD5 | raw file
Possible License(s): GPL-3.0, BSD-3-Clause, LGPL-3.0, LGPL-2.1, BSD-2-Clause
  1. <?php
  2. /*********************************************************************************
  3. * Zurmo is a customer relationship management program developed by
  4. * Zurmo, Inc. Copyright (C) 2012 Zurmo Inc.
  5. *
  6. * Zurmo is free software; you can redistribute it and/or modify it under
  7. * the terms of the GNU 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 General Public License for more
  16. * details.
  17. *
  18. * You should have received a copy of the GNU 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 113 McHenry Road Suite 207,
  24. * Buffalo Grove, IL 60089, USA. or at email address contact@zurmo.com.
  25. ********************************************************************************/
  26. /**
  27. * Abstraction over the top of an application database accessed via
  28. * <a href="http://www.redbeanphp.com/">RedBean</a>. The base class for
  29. * an MVC model. Replaces the M part of MVC in Yii. Yii maps from the
  30. * database scheme to the objects, (good for database guys, not so good
  31. * for OO guys), this maps from objects to the database schema.
  32. *
  33. * A domain model is created by extending RedBeanModel and supplying
  34. * a getDefaultMetadata() method.
  35. *
  36. * Static getXxxx() methods can be supplied to query for the given domain
  37. * models, and instance methods should supply additional behaviour.
  38. *
  39. * getDefaultMetadata() returns an array of the class name mapped to
  40. * an array containing 'members' mapped to an array of member names,
  41. * (to be accessed as $model->memberName).
  42. *
  43. * It can then optionally have, 'relations' mapped
  44. * to an array of relation names, (to be accessed as $model->relationName),
  45. * mapped to its type, (the extending model class to which it relates).
  46. *
  47. * And it can then optionally have as well, 'rules' mapped to an array of
  48. * attribute names, (attributes are members and relations), a validator name,
  49. * and the parameters to the validator, if any, as per the Yii::CModel::rules()
  50. * method.See http://www.yiiframework.com/wiki/56/reference-model-rules-validation.
  51. *
  52. * These are used to automatically and dynamically create the database
  53. * schema on the fly as opposed to Yii's getting attributes from an
  54. * already existing schema.
  55. */
  56. abstract class RedBeanModel extends ObservableComponent implements Serializable
  57. {
  58. /**
  59. * Models that have not been saved yet have no id as far
  60. * as the database is concerned. Until they are saved they are
  61. * assigned a negative id, so that they have identity.
  62. * @var integer
  63. */
  64. private static $nextPseudoId = -1;
  65. /**
  66. * Array of static models. Used by Observers @see ObservableComponent to add events to a class.
  67. * @var array
  68. */
  69. private static $_models = array();
  70. /*
  71. * The id of an unsaved model.
  72. * @var integer
  73. */
  74. private $pseudoId;
  75. /**
  76. * When creating the class heirarchy for bean creation and maintenence, which class is the last class in the
  77. * lineage to create a bean for? Normally the RedBeanModel is the lastClass in the line, and therefore there
  78. * will not be a table redbeanmodel. Some classes that extend RedBeanModel might want the line to stop before
  79. * RedBeanModel since creating a table with just an 'id' would be pointless. @see OwnedModel
  80. * @var string
  81. */
  82. protected static $lastClassInBeanHeirarchy = 'RedBeanModel';
  83. // A model maps to one or more beans. If Person extends RedBeanModel
  84. // there is one bean, but if User then extends Person a User model
  85. // has two beans, the one holding the person data and the one holding
  86. // the extended User data. In this way in inheritance hierarchy from
  87. // model is normalized over several tables, one for each extending
  88. // class.
  89. private $modelClassNameToBean = array();
  90. private $attributeNameToBeanAndClassName = array();
  91. private $attributeNamesNotBelongsToOrManyMany = array();
  92. private $relationNameToRelationTypeModelClassNameAndOwns = array();
  93. private $relationNameToRelatedModel = array();
  94. private $unlinkedRelationNames = array();
  95. private $validators = array();
  96. private $attributeNameToErrors = array();
  97. private $scenarioName = '';
  98. // An object is automatcally savable if it is new or contains
  99. // modified members or related objects.
  100. // If it is newly created and has never had any data put into it
  101. // it can be saved explicitly but it wont be saved automatically
  102. // when it is a related model and will be redispensed next
  103. // time it is referenced.
  104. protected $modified = false;
  105. protected $deleted = false;
  106. protected $isInIsModified = false;
  107. protected $isInHasErrors = false;
  108. protected $isInGetErrors = false;
  109. protected $isValidating = false;
  110. protected $isSaving = false;
  111. protected $isNewModel = false;
  112. /**
  113. * Can this model be saved when save is called from a related model? True if it can, false if it cannot.
  114. * Setting this value to false can reduce unnecessary queries to the database. If the models of a class do
  115. * not change often then it can make sense to set this to false. An example is @see Currency.
  116. * @var boolean
  117. */
  118. protected $isSavableFromRelation = true;
  119. // Mapping of Yii validators to validators doing things that
  120. // are either required for RedBean, or that simply implement
  121. // The semantics that we want.
  122. private static $yiiValidatorsToRedBeanValidators = array(
  123. 'CDefaultValueValidator' => 'RedBeanModelDefaultValueValidator',
  124. 'CNumberValidator' => 'RedBeanModelNumberValidator',
  125. 'CTypeValidator' => 'RedBeanModelTypeValidator',
  126. 'CRequiredValidator' => 'RedBeanModelRequiredValidator',
  127. 'CUniqueValidator' => 'RedBeanModelUniqueValidator',
  128. 'defaultCalculatedDate' => 'RedBeanModelDefaultCalculatedDateValidator',
  129. 'readOnly' => 'RedBeanModelReadOnlyValidator',
  130. 'dateTimeDefault' => 'RedBeanModelDateTimeDefaultValueValidator',
  131. );
  132. /**
  133. * Used in an extending class's getDefaultMetadata() method to specify
  134. * that a relation is 1:1 and that the class on the side of the relationship where this is not a column in that
  135. * model's table. Example: model X HAS_ONE Y. There will be a y_id on the x table. But in Y you would have
  136. * HAS_ONE_BELONGS_TO X and there would be no column in the y table.
  137. */
  138. const HAS_ONE_BELONGS_TO = 0;
  139. /**
  140. * Used in an extending class's getDefaultMetadata() method to specify
  141. * that a relation is 1:M and that the class on the M side of the
  142. * relation.
  143. * Note: Currently if you have a relation that is set to HAS_MANY_BELONGS_TO, then that relation name
  144. * must be the strtolower() same as the related model class name. This is the current support for this
  145. * relation type. If something different is set, an exception will be thrown.
  146. */
  147. const HAS_MANY_BELONGS_TO = 1;
  148. /**
  149. * Used in an extending class's getDefaultMetadata() method to specify
  150. * that a relation is 1:1.
  151. */
  152. const HAS_ONE = 2;
  153. /**
  154. * Used in an extending class's getDefaultMetadata() method to specify
  155. * that a relation is 1:M and that the class is on the 1 side of the
  156. * relation.
  157. */
  158. const HAS_MANY = 3;
  159. /**
  160. * Used in an extending class's getDefaultMetadata() method to specify
  161. * that a relation is M:N and that the class on the either side of the
  162. * relation.
  163. */
  164. const MANY_MANY = 4;
  165. /**
  166. * Used in an extending class's getDefaultMetadata() method to specify
  167. * that a 1:1 or 1:M relation is one in which the left side of the relation
  168. * owns the model or models on the right side, meaning that if the model
  169. * is deleted it owns the related models and they are deleted along with it.
  170. * If not specified the related model is independent and is not deleted.
  171. */
  172. const OWNED = true;
  173. /**
  174. * @see const OWNED for more information.
  175. * @var boolean
  176. */
  177. const NOT_OWNED = false;
  178. /**
  179. * Returns the static model of the specified AR class.
  180. * The model returned is a static instance of the AR class.
  181. * It is provided for invoking class-level methods (something similar to static class methods.)
  182. *
  183. * EVERY derived AR class must override this method as follows,
  184. * <pre>
  185. * public static function model($className=__CLASS__)
  186. * {
  187. * return parent::model($className);
  188. * }
  189. * </pre>
  190. *
  191. * @param string $className active record class name.
  192. * @return CActiveRecord active record model instance.
  193. */
  194. public static function model($className = null)
  195. {
  196. if ($className == null)
  197. {
  198. $className = get_called_class();
  199. }
  200. if (isset(self::$_models[$className]))
  201. {
  202. return self::$_models[$className];
  203. }
  204. else
  205. {
  206. $model = self::$_models[$className] = new $className(false);
  207. return $model;
  208. }
  209. }
  210. /**
  211. * Gets all the models from the database of the named model type.
  212. * @param $orderBy TODO
  213. * @param $modelClassName Pass only when getting it at runtime
  214. * gets the wrong name.
  215. * @return An array of models of the type of the extending model.
  216. */
  217. public static function getAll($orderBy = null, $sortDescending = false, $modelClassName = null)
  218. {
  219. assert('$orderBy === null || is_string($orderBy) && $orderBy != ""');
  220. assert('is_bool($sortDescending)');
  221. assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
  222. $quote = DatabaseCompatibilityUtil::getQuote();
  223. $orderBySql = null;
  224. if ($orderBy !== null)
  225. {
  226. $orderBySql = "$quote$orderBy$quote";
  227. if ($sortDescending)
  228. {
  229. $orderBySql .= ' desc';
  230. }
  231. }
  232. return static::getSubset(null, null, null, null, $orderBySql, $modelClassName);
  233. }
  234. /**
  235. * Gets a range of models from the database of the named model type.
  236. * @param $modelClassName
  237. * @param $joinTablesAdapter null or instance of joinTablesAdapter.
  238. * @param $offset The zero based index of the first model to be returned.
  239. * @param $count The number of models to be returned.
  240. * @param $where
  241. * @param $orderBy - sql string. Example 'a desc' or 'a.b desc'. Currently only supports non-related attributes
  242. * @param $modelClassName Pass only when getting it at runtime gets the wrong name.
  243. * @return An array of models of the type of the extending model.
  244. */
  245. public static function getSubset(RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter = null,
  246. $offset = null, $count = null,
  247. $where = null, $orderBy = null,
  248. $modelClassName = null,
  249. $selectDistinct = false)
  250. {
  251. assert('$offset === null || is_integer($offset) && $offset >= 0');
  252. assert('$count === null || is_integer($count) && $count >= 1');
  253. assert('$where === null || is_string ($where) && $where != ""');
  254. assert('$orderBy === null || is_string ($orderBy) && $orderBy != ""');
  255. assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
  256. if ($modelClassName === null)
  257. {
  258. $modelClassName = get_called_class();
  259. }
  260. if ($joinTablesAdapter == null)
  261. {
  262. $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter($modelClassName);
  263. }
  264. $tableName = self::getTableName($modelClassName);
  265. $sql = static::makeSubsetOrCountSqlQuery($tableName, $joinTablesAdapter, $offset, $count, $where,
  266. $orderBy, false, $selectDistinct);
  267. $ids = R::getCol($sql);
  268. $tableName = self::getTableName($modelClassName);
  269. $beans = R::batch ($tableName, $ids);
  270. return self::makeModels($beans, $modelClassName);
  271. }
  272. /**
  273. * @param boolean $selectCount If true then make this a count query. If false, select ids from rows.
  274. * @param array $quotedExtraSelectColumnNameAndAliases - extra columns to select.
  275. * @return string - sql statement.
  276. */
  277. public static function makeSubsetOrCountSqlQuery($tableName,
  278. RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter,
  279. $offset = null, $count = null,
  280. $where = null, $orderBy = null,
  281. $selectCount = false,
  282. $selectDistinct = false,
  283. array $quotedExtraSelectColumnNameAndAliases = array())
  284. {
  285. assert('is_string($tableName) && $tableName != ""');
  286. assert('$offset === null || is_integer($offset) && $offset >= 0');
  287. assert('$count === null || is_integer($count) && $count >= 1');
  288. assert('$where === null || is_string ($where) && $where != ""');
  289. assert('$orderBy === null || is_string ($orderBy) && $orderBy != ""');
  290. assert('is_bool($selectCount)');
  291. assert('is_bool($selectDistinct)');
  292. $selectQueryAdapter = new RedBeanModelSelectQueryAdapter($selectDistinct);
  293. if ($selectCount)
  294. {
  295. $selectQueryAdapter->addCountClause($tableName);
  296. }
  297. else
  298. {
  299. $selectQueryAdapter->addClause($tableName, 'id', 'id');
  300. }
  301. foreach ($quotedExtraSelectColumnNameAndAliases as $columnName => $columnAlias)
  302. {
  303. $selectQueryAdapter->addClauseWithColumnNameOnlyAndNoEnclosure($columnName, $columnAlias);
  304. }
  305. return SQLQueryUtil::
  306. makeQuery($tableName, $selectQueryAdapter, $joinTablesAdapter, $offset, $count, $where, $orderBy);
  307. }
  308. /**
  309. * @param $modelClassName
  310. * @param $joinTablesAdapter null or instance of joinTablesAdapter.
  311. * @param $modelClassName Pass only when getting it at runtime gets the wrong name.
  312. */
  313. public static function getCount(RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter = null,
  314. $where = null, $modelClassName = null, $selectDistinct = false)
  315. {
  316. assert('$where === null || is_string($where)');
  317. assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
  318. if ($modelClassName === null)
  319. {
  320. $modelClassName = get_called_class();
  321. }
  322. if ($joinTablesAdapter == null)
  323. {
  324. $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter($modelClassName);
  325. }
  326. $tableName = self::getTableName($modelClassName);
  327. $sql = static::makeSubsetOrCountSqlQuery($tableName, $joinTablesAdapter, null, null, $where, null, true,
  328. $selectDistinct);
  329. $count = R::getCell($sql);
  330. if ($count === null || empty($count))
  331. {
  332. $count = 0;
  333. }
  334. return $count;
  335. }
  336. /**
  337. * Gets a model from the database by Id.
  338. * @param $id Integer Id.
  339. * @param $modelClassName Pass only when getting it at runtime
  340. * gets the wrong name.
  341. * @return A model of the type of the extending model.
  342. */
  343. public static function getById($id, $modelClassName = null)
  344. {
  345. assert('is_integer($id) && $id > 0');
  346. assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
  347. // I would have thought it was correct to user R::load() and get
  348. // a null, or error or something if the bean doesn't exist, but
  349. // it still returns a bean. So until I've investigated further
  350. // I'm using Finder.
  351. if ($modelClassName === null)
  352. {
  353. $modelClassName = get_called_class();
  354. }
  355. $tableName = self::getTableName($modelClassName);
  356. $beans = R::find($tableName, "id = '$id'");
  357. assert('count($beans) <= 1');
  358. if (count($beans) == 0)
  359. {
  360. throw new NotFoundException();
  361. }
  362. return RedBeanModel::makeModel(end($beans), $modelClassName);
  363. }
  364. public function getIsNewModel()
  365. {
  366. return $this->isNewModel;
  367. }
  368. /**
  369. * Constructs a new model.
  370. * Important:
  371. * Models are only constructed with beans by the RedBeanModel. Beans are
  372. * never used by the application directly.
  373. * The application can construct a new model object by constructing a
  374. * model without specifying a bean. In other words, if Php had
  375. * overloading a constructor with $setDefaults would be public, and
  376. * a constructor taking a $bean and $forceTreatAsCreation would be private.
  377. * @param $setDefaults. If false the default validators will not be run
  378. * on construction. The Yii way is that defaults are
  379. * filled in after the fact, which is counter the usual
  380. * for objects.
  381. * @param $bean A bean. Never specified by an application.
  382. * @param $forceTreatAsCreation. Never specified by an application.
  383. * @see getById()
  384. * @see makeModel()
  385. * @see makeModels()
  386. */
  387. public function __construct($setDefaults = true, RedBean_OODBBean $bean = null, $forceTreatAsCreation = false)
  388. {
  389. $this->pseudoId = self::$nextPseudoId--;
  390. $this->init();
  391. if ($bean === null)
  392. {
  393. foreach (array_reverse(RuntimeUtil::getClassHierarchy(get_class($this), static::$lastClassInBeanHeirarchy)) as $modelClassName)
  394. {
  395. $tableName = self::getTableName($modelClassName);
  396. $newBean = R::dispense($tableName);
  397. $this->modelClassNameToBean[$modelClassName] = $newBean;
  398. $this->mapAndCacheMetadataAndSetHints($modelClassName, $newBean);
  399. }
  400. // The yii way of doing defaults is the the default validator
  401. // fills in the defaults on attributes that don't have values
  402. // when you validator, or save. This weird, since when you get
  403. // a model the things with defaults have not been defaulted!
  404. // We want that semantic.
  405. if ($setDefaults)
  406. {
  407. $this->runDefaultValidators();
  408. }
  409. $forceTreatAsCreation = true;
  410. }
  411. else
  412. {
  413. assert('$bean->id > 0');
  414. $first = true;
  415. foreach (RuntimeUtil::getClassHierarchy(get_class($this), static::$lastClassInBeanHeirarchy) as $modelClassName)
  416. {
  417. if ($first)
  418. {
  419. $lastBean = $bean;
  420. $first = false;
  421. }
  422. else
  423. {
  424. $tableName = self::getTableName($modelClassName);
  425. $lastBean = ZurmoRedBeanLinkManager::getBean($lastBean, $tableName);
  426. if ($lastBean === null)
  427. {
  428. throw new MissingBeanException();
  429. }
  430. assert('$lastBean->id > 0');
  431. }
  432. $this->modelClassNameToBean[$modelClassName] = $lastBean;
  433. $this->mapAndCacheMetadataAndSetHints($modelClassName, $lastBean);
  434. }
  435. $this->modelClassNameToBean = array_reverse($this->modelClassNameToBean);
  436. }
  437. $this->constructDerived($bean, $setDefaults);
  438. if ($forceTreatAsCreation)
  439. {
  440. $this->onCreated();
  441. }
  442. else
  443. {
  444. $this->onLoaded();
  445. RedBeanModelsCache::cacheModel($this);
  446. }
  447. $this->modified = false;
  448. }
  449. // Derived classes can insert additional steps into the construction.
  450. protected function constructDerived($bean, $setDefaults)
  451. {
  452. assert('$bean === null || $bean instanceof RedBean_OODBBean');
  453. assert('is_bool($setDefaults)');
  454. }
  455. /**
  456. * Utilized when pieces of information need to be constructed on an existing model, that can potentially be
  457. * missing. For example, if a model is created, then a custom field is added, it is possible the cached model
  458. * is missing the custom field customFieldData.
  459. * @param unknown_type $bean
  460. */
  461. protected function constructIncomplete($bean)
  462. {
  463. assert('$bean === null || $bean instanceof RedBean_OODBBean');
  464. $this->init();
  465. }
  466. public function serialize()
  467. {
  468. return serialize(array(
  469. $this->pseudoId,
  470. $this->modelClassNameToBean,
  471. $this->attributeNameToBeanAndClassName,
  472. $this->attributeNamesNotBelongsToOrManyMany,
  473. $this->relationNameToRelationTypeModelClassNameAndOwns,
  474. $this->validators,
  475. ));
  476. }
  477. public function unserialize($data)
  478. {
  479. try
  480. {
  481. $data = unserialize($data);
  482. assert('is_array($data)');
  483. if (count($data) != 6)
  484. {
  485. return null;
  486. }
  487. $this->pseudoId = $data[0];
  488. $this->modelClassNameToBean = $data[1];
  489. $this->attributeNameToBeanAndClassName = $data[2];
  490. $this->attributeNamesNotBelongsToOrManyMany = $data[3];
  491. $this->relationNameToRelationTypeModelClassNameAndOwns = $data[4];
  492. $this->validators = $data[5];
  493. $this->relationNameToRelatedModel = array();
  494. $this->unlinkedRelationNames = array();
  495. $this->attributeNameToErrors = array();
  496. $this->scenarioName = '';
  497. $this->modified = false;
  498. $this->deleted = false;
  499. $this->isInIsModified = false;
  500. $this->isInHasErrors = false;
  501. $this->isInGetErrors = false;
  502. $this->isValidating = false;
  503. $this->isSaving = false;
  504. }
  505. catch (Exception $e)
  506. {
  507. return null;
  508. }
  509. }
  510. /**
  511. * Overriding constructors must call this function to ensure that
  512. * they leave the newly constructed instance not modified since
  513. * anything modifying the class during constructionm will set it
  514. * modified automatically.
  515. */
  516. protected function setNotModified()
  517. {
  518. $this->modified = false; // This sets this class to the right state.
  519. assert('!$this->isModified()'); // This tests that related classes are in the right state.
  520. }
  521. /**
  522. * By default the table name is the lowercased class name. If this
  523. * conflicts with a database keyword override to return true.
  524. * RedBean does not quote table names in most cases.
  525. */
  526. // Public for unit testing.
  527. public static function mangleTableName()
  528. {
  529. return false;
  530. }
  531. /**
  532. * Returns the table name for a class.
  533. * For use by RedBeanModelDataProvider. It will not
  534. * be of any use to an application. Applications
  535. * should not be doing anything table related.
  536. * Derived classes can refer directly to the
  537. * table name.
  538. */
  539. public static function getTableName($modelClassName)
  540. {
  541. assert('is_string($modelClassName) && $modelClassName != ""');
  542. $tableName = strtolower($modelClassName);
  543. if ($modelClassName::mangleTableName())
  544. {
  545. $tableName = '_' . $tableName;
  546. }
  547. return $tableName;
  548. }
  549. /**
  550. * Returns the table names for an array of classes.
  551. * For use by RedBeanModelDataProvider. It will not
  552. * be of any use to an application.
  553. */
  554. public static function getTableNames($classNames)
  555. {
  556. $tableNames = array();
  557. foreach ($classNames as $className)
  558. {
  559. $tableNames[] = self::getTableName($className);
  560. }
  561. return $tableNames;
  562. }
  563. /**
  564. * Used by classes such as containers which use sql to
  565. * optimize getting models from the database.
  566. */
  567. public static function getForeignKeyName($modelClassName, $relationName)
  568. {
  569. assert('is_string($modelClassName)');
  570. assert('$modelClassName != ""');
  571. $metadata = $modelClassName::getMetadata();
  572. foreach ($metadata as $modelClassName => $modelClassMetadata)
  573. {
  574. if (isset($metadata[$modelClassName]["relations"]) &&
  575. array_key_exists($relationName, $metadata[$modelClassName]["relations"]))
  576. {
  577. $relatedModelClassName = $metadata[$modelClassName]['relations'][$relationName][1];
  578. $relatedModelTableName = self::getTableName($relatedModelClassName);
  579. $columnName = '';
  580. if (strtolower($relationName) != strtolower($relatedModelClassName))
  581. {
  582. $columnName = strtolower($relationName) . '_';
  583. }
  584. $columnName .= $relatedModelTableName . '_id';
  585. return $columnName;
  586. }
  587. }
  588. throw new NotSupportedException;
  589. }
  590. /**
  591. * Called on construction when a new model is created.
  592. */
  593. protected function onCreated()
  594. {
  595. }
  596. /**
  597. * Called on construction when a model is loaded.
  598. */
  599. protected function onLoaded()
  600. {
  601. }
  602. /**
  603. * Called when a model is modified.
  604. */
  605. protected function onModified()
  606. {
  607. }
  608. /**
  609. * Used for mixins.
  610. */
  611. protected function mapAndCacheMetadataAndSetHints($modelClassName, RedBean_OODBBean $bean)
  612. {
  613. assert('is_string($modelClassName)');
  614. assert('$modelClassName != ""');
  615. $metadata = $this->getMetadata();
  616. if (isset($metadata[$modelClassName]))
  617. {
  618. $hints = array();
  619. if (isset($metadata[$modelClassName]['members']))
  620. {
  621. foreach ($metadata[$modelClassName]['members'] as $memberName)
  622. {
  623. $this->attributeNameToBeanAndClassName[$memberName] = array($bean, $modelClassName);
  624. $this->attributeNamesNotBelongsToOrManyMany[] = $memberName;
  625. if (substr($memberName, -2) == 'Id')
  626. {
  627. $columnName = strtolower($memberName);
  628. $hints[$columnName] = 'id';
  629. }
  630. }
  631. }
  632. if (isset($metadata[$modelClassName]['relations']))
  633. {
  634. foreach ($metadata[$modelClassName]['relations'] as $relationName => $relationTypeModelClassNameAndOwns)
  635. {
  636. assert('in_array(count($relationTypeModelClassNameAndOwns), array(2, 3, 4))');
  637. $relationType = $relationTypeModelClassNameAndOwns[0];
  638. $relationModelClassName = $relationTypeModelClassNameAndOwns[1];
  639. if ($relationType == self::HAS_MANY_BELONGS_TO &&
  640. strtolower($relationName) != strtolower($relationModelClassName))
  641. {
  642. $label = 'Relations of type HAS_MANY_BELONGS_TO must have the relation name ' .
  643. 'the same as the related model class name. Relation: {relationName} ' .
  644. 'Relation model class name: {relationModelClassName}';
  645. throw new NotSupportedException(Yii::t('Default', $label,
  646. array('{relationName}' => $relationName,
  647. '{relationModelClassName}' => $relationModelClassName)));
  648. }
  649. if (count($relationTypeModelClassNameAndOwns) >= 3 &&
  650. $relationTypeModelClassNameAndOwns[2] == self::OWNED)
  651. {
  652. $owns = true;
  653. }
  654. else
  655. {
  656. $owns = false;
  657. }
  658. if (count($relationTypeModelClassNameAndOwns) == 4 && $relationType != self::HAS_MANY)
  659. {
  660. throw new NotSupportedException();
  661. }
  662. if (count($relationTypeModelClassNameAndOwns) == 4)
  663. {
  664. $relationPolyOneToManyName = $relationTypeModelClassNameAndOwns[3];
  665. }
  666. else
  667. {
  668. $relationPolyOneToManyName = null;
  669. }
  670. assert('in_array($relationType, array(self::HAS_ONE_BELONGS_TO, self::HAS_MANY_BELONGS_TO, ' .
  671. 'self::HAS_ONE, self::HAS_MANY, self::MANY_MANY))');
  672. $this->attributeNameToBeanAndClassName[$relationName] = array($bean, $modelClassName);
  673. $this->relationNameToRelationTypeModelClassNameAndOwns[$relationName] = array($relationType,
  674. $relationModelClassName,
  675. $owns,
  676. $relationPolyOneToManyName);
  677. if (!in_array($relationType, array(self::HAS_ONE_BELONGS_TO, self::HAS_MANY_BELONGS_TO, self::MANY_MANY)))
  678. {
  679. $this->attributeNamesNotBelongsToOrManyMany[] = $relationName;
  680. }
  681. }
  682. }
  683. // Add model validators. Parent validators are already applied.
  684. if (isset($metadata[$modelClassName]['rules']))
  685. {
  686. foreach ($metadata[$modelClassName]['rules'] as $validatorMetadata)
  687. {
  688. assert('isset($validatorMetadata[0])');
  689. assert('isset($validatorMetadata[1])');
  690. $attributeName = $validatorMetadata[0];
  691. // Each rule in RedBeanModel must specify one attribute name.
  692. // This was just better style, now it is mandatory.
  693. assert('strpos($attributeName, " ") === false');
  694. $validatorName = $validatorMetadata[1];
  695. $validatorParameters = array_slice($validatorMetadata, 2);
  696. if (isset(CValidator::$builtInValidators[$validatorName]))
  697. {
  698. $validatorName = CValidator::$builtInValidators[$validatorName];
  699. }
  700. if (isset(self::$yiiValidatorsToRedBeanValidators[$validatorName]))
  701. {
  702. $validatorName = self::$yiiValidatorsToRedBeanValidators[$validatorName];
  703. }
  704. $validator = CValidator::createValidator($validatorName, $this, $attributeName, $validatorParameters);
  705. switch ($validatorName)
  706. {
  707. case 'RedBeanModelTypeValidator':
  708. case 'TypeValidator':
  709. $columnName = strtolower($attributeName);
  710. if (array_key_exists($columnName, $hints))
  711. {
  712. unset($hints[$columnName]);
  713. }
  714. if (in_array($validator->type, array('date', 'datetime', 'blob', 'longblob', 'string')))
  715. {
  716. $hints[$columnName] = $validator->type;
  717. }
  718. break;
  719. case 'CBooleanValidator':
  720. $columnName = strtolower($attributeName);
  721. $hints[$columnName] = 'boolean';
  722. break;
  723. case 'RedBeanModelUniqueValidator':
  724. if (!$this->isRelation($attributeName))
  725. {
  726. $bean->setMeta("buildcommand.unique", array(array($attributeName)));
  727. }
  728. else
  729. {
  730. $relatedModelClassName = $this->relationNameToRelationTypeModelClassNameAndOwns[$attributeName][1];
  731. $relatedModelTableName = self::getTableName($relatedModelClassName);
  732. $columnName = strtolower($attributeName);
  733. if ($columnName != $relatedModelTableName)
  734. {
  735. $columnName .= '_' . $relatedModelTableName;
  736. }
  737. $columnName .= '_id';
  738. $bean->setMeta("buildcommand.unique", array(array($columnName)));
  739. }
  740. break;
  741. }
  742. $this->validators[] = $validator;
  743. }
  744. // Check if we need to update string type to long string type, based on validators.
  745. if (isset($metadata[$modelClassName]['members']))
  746. {
  747. foreach ($metadata[$modelClassName]['members'] as $memberName)
  748. {
  749. $allValidators = $this->getValidators($memberName);
  750. foreach ($allValidators as $validator)
  751. {
  752. if ((get_class($validator) == 'RedBeanModelTypeValidator' ||
  753. get_class($validator) == 'TypeValidator') &&
  754. $validator->type == 'string')
  755. {
  756. $columnName = strtolower($validator->attributes[0]);
  757. if (count($allValidators) > 1)
  758. {
  759. $haveCStringValidator = false;
  760. foreach ($allValidators as $innerValidator)
  761. {
  762. if (get_class($innerValidator) == 'CStringValidator' &&
  763. isset($innerValidator->max) &&
  764. $innerValidator->max > 255)
  765. {
  766. if ($innerValidator->max > 65535)
  767. {
  768. $hints[$columnName] = 'longtext';
  769. }
  770. else
  771. {
  772. $hints[$columnName] = 'text';
  773. }
  774. }
  775. if (get_class($innerValidator) == 'CStringValidator')
  776. {
  777. $haveCStringValidator = true;
  778. }
  779. }
  780. if (!$haveCStringValidator)
  781. {
  782. $hints[$columnName] = 'text';
  783. }
  784. }
  785. else
  786. {
  787. $hints[$columnName] = 'text';
  788. }
  789. }
  790. }
  791. }
  792. }
  793. }
  794. $bean->setMeta('hint', $hints);
  795. }
  796. }
  797. /**
  798. * Used for mixins.
  799. */
  800. protected function runDefaultValidators()
  801. {
  802. foreach ($this->validators as $validator)
  803. {
  804. if ($validator instanceof CDefaultValueValidator)
  805. {
  806. $validator->validate($this);
  807. }
  808. }
  809. }
  810. /**
  811. * For use only by RedBeanModel and RedBeanModels. Beans are
  812. * never used by the application directly.
  813. */
  814. public function getPrimaryBean()
  815. {
  816. return end($this->modelClassNameToBean);
  817. }
  818. /**
  819. * Used for optimization.
  820. */
  821. public function getClassId($modelClassName)
  822. {
  823. assert('array_key_exists($modelClassName, $this->modelClassNameToBean)');
  824. return intval($this->getClassBean($modelClassName)->id); // Trying to combat the slop.
  825. }
  826. public function getClassBean($modelClassName)
  827. {
  828. assert('is_string($modelClassName)');
  829. assert('$modelClassName != ""');
  830. assert('array_key_exists($modelClassName, $this->modelClassNameToBean)');
  831. return $this->modelClassNameToBean[$modelClassName];
  832. }
  833. /**
  834. * Used for mixins.
  835. */
  836. protected function setClassBean($modelClassName, RedBean_OODBBean $bean)
  837. {
  838. assert('is_string($modelClassName)');
  839. assert('$modelClassName != ""');
  840. assert('!array_key_exists($modelClassName, $this->modelClassNameToBean)');
  841. $this->modelClassNameToBean = array_merge(array($modelClassName => $bean),
  842. $this->modelClassNameToBean);
  843. }
  844. public function getModelIdentifier()
  845. {
  846. return get_class($this) . strval($this->getPrimaryBean()->id);
  847. }
  848. /**
  849. * Returns metadata for the model. Attempts to cache metadata, if it is not already cached.
  850. * @see getDefaultMetadata()
  851. * @returns An array of metadata.
  852. */
  853. public static function getMetadata()
  854. {
  855. try
  856. {
  857. return GeneralCache::getEntry(get_called_class() . 'Metadata');
  858. }
  859. catch (NotFoundException $e)
  860. {
  861. $className = get_called_Class();
  862. $defaultMetadata = $className::getDefaultMetadata();
  863. $metadata = array();
  864. foreach (array_reverse(RuntimeUtil::getClassHierarchy($className, static::$lastClassInBeanHeirarchy)) as $modelClassName)
  865. {
  866. if ($modelClassName::canSaveMetadata())
  867. {
  868. try
  869. {
  870. $globalMetadata = GlobalMetadata::getByClassName($modelClassName);
  871. $metadata[$modelClassName] = unserialize($globalMetadata->serializedMetadata);
  872. }
  873. catch (NotFoundException $e)
  874. {
  875. if (isset($defaultMetadata[$modelClassName]))
  876. {
  877. $metadata[$modelClassName] = $defaultMetadata[$modelClassName];
  878. }
  879. }
  880. }
  881. else
  882. {
  883. if (isset($defaultMetadata[$modelClassName]))
  884. {
  885. $metadata[$modelClassName] = $defaultMetadata[$modelClassName];
  886. }
  887. }
  888. }
  889. if (YII_DEBUG)
  890. {
  891. self::assertMetadataIsValid($metadata);
  892. }
  893. GeneralCache::cacheEntry(get_called_class() . 'Metadata', $metadata);
  894. return $metadata;
  895. }
  896. }
  897. /**
  898. * By default models cannot save their metadata, allowing
  899. * them to be loaded quickly because the loading of of
  900. * metadata can be avoided as much as possible.
  901. * To make a model able to save its metadata override
  902. * this method to return true. PUT it before the
  903. * getDefaultMetadata in the derived class.
  904. */
  905. public static function canSaveMetadata()
  906. {
  907. return false;
  908. }
  909. /**
  910. * Sets metadata for the model.
  911. * @see getDefaultMetadata()
  912. * @returns An array of metadata.
  913. */
  914. public static function setMetadata(array $metadata)
  915. {
  916. if (YII_DEBUG)
  917. {
  918. self::assertMetadataIsValid($metadata);
  919. }
  920. $className = get_called_class();
  921. foreach (array_reverse(RuntimeUtil::getClassHierarchy($className, static::$lastClassInBeanHeirarchy)) as $modelClassName)
  922. {
  923. if ($modelClassName::canSaveMetadata())
  924. {
  925. if (isset($metadata[$modelClassName]))
  926. {
  927. try
  928. {
  929. $globalMetadata = GlobalMetadata::getByClassName($modelClassName);
  930. }
  931. catch (NotFoundException $e)
  932. {
  933. $globalMetadata = new GlobalMetadata();
  934. $globalMetadata->className = $modelClassName;
  935. }
  936. $globalMetadata->serializedMetadata = serialize($metadata[$modelClassName]);
  937. $saved = $globalMetadata->save();
  938. // TODO: decide how to deal with this properly if it fails.
  939. // ie: throw or return false, or something other than
  940. // this naughty assert.
  941. assert('$saved');
  942. }
  943. }
  944. }
  945. RedBeanModelsCache::forgetAllByModelType(get_called_class());
  946. GeneralCache::forgetEntry(get_called_class() . 'Metadata');
  947. }
  948. /**
  949. * Returns the default meta data for the class.
  950. * It must be appended to the meta data
  951. * from the parent model, if any.
  952. */
  953. public static function getDefaultMetadata()
  954. {
  955. return array();
  956. }
  957. protected static function assertMetadataIsValid(array $metadata)
  958. {
  959. $className = get_called_Class();
  960. foreach (RuntimeUtil::getClassHierarchy($className, static::$lastClassInBeanHeirarchy) as $modelClassName)
  961. {
  962. if (isset($metadata[$modelClassName]['members']))
  963. {
  964. assert('is_array($metadata[$modelClassName]["members"])');
  965. foreach ($metadata[$modelClassName]["members"] as $memberName)
  966. {
  967. assert('ctype_lower($memberName{0})');
  968. }
  969. }
  970. if (isset($metadata[$modelClassName]['relations']))
  971. {
  972. assert('is_array($metadata[$modelClassName]["relations"])');
  973. foreach ($metadata[$modelClassName]["relations"] as $relationName => $notUsed)
  974. {
  975. assert('ctype_lower($relationName{0})');
  976. }
  977. }
  978. if (isset($metadata[$modelClassName]['rules']))
  979. {
  980. assert('is_array($metadata[$modelClassName]["rules"])');
  981. }
  982. if (isset($metadata[$modelClassName]['defaultSortAttribute']))
  983. {
  984. assert('is_string($metadata[$modelClassName]["defaultSortAttribute"])');
  985. }
  986. if (isset($metadata[$modelClassName]['rollupRelations']))
  987. {
  988. assert('is_array($metadata[$modelClassName]["rollupRelations"])');
  989. }
  990. // Todo: add more rules here as I think of them.
  991. }
  992. }
  993. /**
  994. * Downcasting in general is a bad concept, but when pulling
  995. * a Person from the database it would require a lot of
  996. * jumping through hoops to make the RedBeanModel automatically
  997. * figure out if that person is really a User, Contact, Customer
  998. * or whatever might be derived from Person. So to avoid that
  999. * complication and performance hit where it is not necessary
  1000. * this method can be used to convert a model to one of
  1001. * a given set of derivatives. If model is not one
  1002. * of those NotFoundException is thrown.
  1003. */
  1004. public function castDown(array $derivedModelClassNames)
  1005. {
  1006. $bean = $this->getPrimaryBean();
  1007. $thisModelClassName = get_called_class();
  1008. $key = strtolower($thisModelClassName) . '_id';
  1009. foreach ($derivedModelClassNames as $modelClassNames)
  1010. {
  1011. if (is_string($modelClassNames))
  1012. {
  1013. $nextModelClassName = $modelClassNames;
  1014. if (get_class($this) == $nextModelClassName)
  1015. {
  1016. return $this;
  1017. }
  1018. $nextBean = self::findNextDerivativeBean($bean, $thisModelClassName, $nextModelClassName);
  1019. }
  1020. else
  1021. {
  1022. assert('is_array($modelClassNames)');
  1023. $targetModelClassName = end($modelClassNames);
  1024. if (get_class($this) == $targetModelClassName)
  1025. {
  1026. return $this;
  1027. }
  1028. $currentModelClassName = $thisModelClassName;
  1029. $nextBean = $bean;
  1030. foreach ($modelClassNames as $nextModelClassName)
  1031. {
  1032. $nextBean = self::findNextDerivativeBean($nextBean, $currentModelClassName, $nextModelClassName);
  1033. if ($nextBean === null)
  1034. {
  1035. break;
  1036. }
  1037. $currentModelClassName = $nextModelClassName;
  1038. }
  1039. }
  1040. if ($nextBean !== null)
  1041. {
  1042. return self::makeModel($nextBean, $nextModelClassName);
  1043. }
  1044. }
  1045. throw new NotFoundException();
  1046. }
  1047. private static function findNextDerivativeBean($bean, $modelClassName1, $modelClassName2)
  1048. {
  1049. $key = strtolower($modelClassName1) . '_id';
  1050. $tableName = self::getTableName($modelClassName2);
  1051. $beans = R::find($tableName, "$key = :id", array('id' => $bean->id));
  1052. if (count($beans) == 1)
  1053. {
  1054. return reset($beans);
  1055. }
  1056. return null;
  1057. }
  1058. /**
  1059. * Returns whether the given object is of the same type with the
  1060. * same id.
  1061. */
  1062. public function isSame(RedBeanModel $model)
  1063. {
  1064. // The two models are the same if they have the
  1065. // same root model, and if for that model they
  1066. // have the same id.
  1067. $rootId1 = reset($this ->modelClassNameToBean)->id;
  1068. $rootId2 = reset($model->modelClassNameToBean)->id;
  1069. if ($rootId1 == 0)
  1070. {
  1071. $rootId1 = $this->pseudoId;
  1072. }
  1073. if ($rootId2 == 0)
  1074. {
  1075. $rootId2 = $model->pseudoId;
  1076. }
  1077. return $rootId1 == $rootId2 && $rootId1 != 0 &&
  1078. key($this ->modelClassNameToBean) ==
  1079. key($model->modelClassNameToBean);
  1080. }
  1081. /**
  1082. * Returns the displayable string for the class. Should be
  1083. * overridden in any model that can provide a meaningful string
  1084. * representation of itself.
  1085. * @return A string.
  1086. */
  1087. public function __toString()
  1088. {
  1089. return Yii::t('Default', '(None)');
  1090. }
  1091. /**
  1092. * Exposes the members and relations of the model as if
  1093. * they were actual attributes of the model. See __set().
  1094. * @param $attributeName A non-empty string that is the name of a
  1095. * member or relation.
  1096. * @see attributeNames()
  1097. * @return A value or model of the type specified as valid for the
  1098. * member or relation by the meta data supplied by the extending
  1099. * class's getMetadata() method.
  1100. */
  1101. public function __get($attributeName)
  1102. {
  1103. return $this->unrestrictedGet($attributeName);
  1104. }
  1105. /**
  1106. * A protected version of __get() for models to talk to themselves
  1107. * to use their dynamically created members from 'members'
  1108. * and 'relations' in its metadata.
  1109. */
  1110. protected function unrestrictedGet($attributeName)
  1111. {
  1112. assert('is_string($attributeName)');
  1113. assert('$attributeName != ""');
  1114. assert("property_exists(\$this, '$attributeName') || \$this->isAttribute('$attributeName')");
  1115. if (property_exists($this, $attributeName))
  1116. {
  1117. return $this->$attributeName;
  1118. }
  1119. elseif ($attributeName == 'id')
  1120. {
  1121. $id = intval($this->getPrimaryBean()->id);
  1122. assert('$id >= 0');
  1123. if ($id == 0)
  1124. {
  1125. $id = $this->pseudoId;
  1126. }
  1127. return $id;
  1128. }
  1129. elseif ($this->isAttribute($attributeName))
  1130. {
  1131. list($bean, $attributeModelClassName) = $this->attributeNameToBeanAndClassName[$attributeName];
  1132. if (!$this->isRelation($attributeName))
  1133. {
  1134. $columnName = strtolower($attributeName);
  1135. return $bean->$columnName;
  1136. }
  1137. else
  1138. {
  1139. if (!array_key_exists($attributeName, $this->relationNameToRelatedModel))
  1140. {
  1141. list($relationType, $relatedModelClassName, $owns, $relationPolyOneToManyName) =
  1142. $this->relationNameToRelationTypeModelClassNameAndOwns[$attributeName];
  1143. $relatedTableName = self::getTableName($relatedModelClassName);
  1144. switch ($relationType)
  1145. {
  1146. case self::HAS_ONE_BELONGS_TO:
  1147. $linkName = strtolower(get_class($this));
  1148. $columnName = $linkName . '_id';
  1149. $relatedBeans = R::find($relatedTableName, $columnName . " = " . $bean->id);
  1150. if (count($relatedBeans) > 1)
  1151. {
  1152. throw new NotFoundException();
  1153. }
  1154. elseif (count($relatedBeans) == 0)
  1155. {
  1156. $relatedModel = new $relatedModelClassName();
  1157. }
  1158. else
  1159. {
  1160. $relatedModel = self::makeModel(end($relatedBeans), $relatedModelClassName);
  1161. }
  1162. $this->relationNameToRelatedModel[$attributeName] = $relatedModel;
  1163. break;
  1164. case self::HAS_ONE:
  1165. case self::HAS_MANY_BELONGS_TO:
  1166. if ($relationType == self::HAS_ONE)
  1167. {
  1168. $linkName = strtolower($attributeName);
  1169. if ($linkName == strtolower($relatedModelClassName))
  1170. {
  1171. $linkName = null;
  1172. }
  1173. }
  1174. else
  1175. {
  1176. $linkName = null;
  1177. }
  1178. if ($bean->id > 0 && !in_array($attributeName, $this->unlinkedRelationNames))
  1179. {
  1180. $linkFieldName = ZurmoRedBeanLinkManager::getLinkField($relatedTableName, $linkName);
  1181. if ((int)$bean->$linkFieldName > 0)
  1182. {
  1183. $beanIdentifier = $relatedTableName .(int)$bean->$linkFieldName;
  1184. try
  1185. {
  1186. $relatedBean = RedBeansCache::getBean($beanIdentifier);
  1187. }
  1188. catch (NotFoundException $e)
  1189. {
  1190. $relatedBean = ZurmoRedBeanLinkManager::getBean($bean, $relatedTableName, $linkName);
  1191. RedBeansCache::cacheBean($relatedBean, $beanIdentifier);
  1192. }
  1193. if ($relatedBean !== null && $relatedBean->id > 0)
  1194. {
  1195. $relatedModel = self::makeModel($relatedBean, $relatedModelClassName);
  1196. }
  1197. }
  1198. }
  1199. if (!isset($relatedModel))
  1200. {
  1201. $relatedModel = new $relatedModelClassName();
  1202. }
  1203. $this->relationNameToRelatedModel[$attributeName] = $relatedModel;
  1204. break;
  1205. case self::HAS_MANY:
  1206. $this->relationNameToRelatedModel[$attributeName] =
  1207. new RedBeanOneToManyRelatedModels($bean,
  1208. $relatedModelClassName,
  1209. $attributeModelClassName,
  1210. $owns,
  1211. $relationPolyOneToManyName);
  1212. break;
  1213. case self::MANY_MANY:
  1214. $this->relationNameToRelatedModel[$attributeName] = new RedBeanManyToManyRelatedModels($bean, $relatedModelClassName);
  1215. break;
  1216. default:
  1217. throw new NotSupportedException();
  1218. }
  1219. }
  1220. return $this->relationNameToRelatedModel[$attributeName];
  1221. }
  1222. }
  1223. else
  1224. {
  1225. throw new NotSupportedException('Invalid Attribute: ' . $attributeName);
  1226. }
  1227. }
  1228. /**
  1229. * Sets the members and relations of the model as if
  1230. * they were actual attributes of the model. For example, if Account
  1231. * extends RedBeanModel and its attributeNames() returns that one it has
  1232. * a member 'name' and a relation 'owner' they are simply
  1233. * accessed as:
  1234. * @code
  1235. * $account = new Account();
  1236. * $account->name = 'International Corp';
  1237. * $account->owner = User::getByUsername('bill');
  1238. * $account->save();
  1239. * @endcode
  1240. * @param $attributeName A non-empty string that is the name of a
  1241. * member or relation of the model.
  1242. * @param $value A value or model of the type specified as valid for the
  1243. * member or relation by the meta data supplied by the extending
  1244. * class's getMetadata() method.
  1245. */
  1246. public function __set($attributeName, $value)
  1247. {
  1248. if ($attributeName == 'id' ||
  1249. ($this->isAttributeReadOnly($attributeName) && !$this->isAllowedToSetReadOnlyAttribute($attributeName)))
  1250. {
  1251. throw new NotSupportedException();
  1252. }
  1253. else
  1254. {
  1255. if ($this->unrestrictedSet($attributeName, $value))
  1256. {
  1257. $this->modified = true;
  1258. $this->onModified();
  1259. }
  1260. }
  1261. }
  1262. /**
  1263. * A protected version of __set() for models to talk to themselves
  1264. * to use their dynamically created members from 'members'
  1265. * and 'relations' in its metadata.
  1266. */
  1267. protected function unrestrictedSet($attributeName, $value)
  1268. {
  1269. assert('is_string($attributeName)');
  1270. assert('$attributeName != ""');
  1271. assert("property_exists(\$this, '$attributeName') || \$this->isAttribute('$attributeName')");
  1272. if (property_exists($this, $attributeName))
  1273. {
  1274. $this->$attributeName = $value;
  1275. }
  1276. elseif ($this->isAttribute($attributeName))
  1277. {
  1278. $bean = $this->attributeNameToBeanAndClassName[$attributeName][0];
  1279. if (!$this->isRelation($attributeName))
  1280. {
  1281. $columnName = strtolower($attributeName);
  1282. if ($bean->$columnName !== $value)
  1283. {
  1284. $bean->$columnName = $value;
  1285. return true;
  1286. }
  1287. }
  1288. else
  1289. {
  1290. list($relationType, $relatedModelClassName, $owns, $relationPolyOneToManyName) =
  1291. $this->relationNameToRelationTypeModelClassNameAndOwns[$attributeName];
  1292. $relatedTableName = self::getTableName($relatedModelClassName);
  1293. $linkName = strtolower($attributeName);
  1294. if ($linkName == strtolower($relatedModelClassName))
  1295. {
  1296. $linkName = null;
  1297. }
  1298. switch ($relationType)
  1299. {
  1300. case self::HAS_MANY:
  1301. case self::MANY_MANY:
  1302. // The many sides of a relation cannot
  1303. // be assigned, they are changed by the using the
  1304. // RedBeanOneToManyRelatedModels or
  1305. // RedBeanManyToManyRelatedModels object
  1306. // on the 1 or other side of the relationship
  1307. // respectively.
  1308. throw new NotSupportedException();
  1309. }
  1310. // If the value is null we need to get the related model so that
  1311. // if there is none we can ignore the null and if there is one
  1312. // we can act on it.
  1313. if ($value === null &&
  1314. !in_array($attributeName, $this->unlinkedRelationNames) &&
  1315. !isset($this->relationNameToRelatedModel[$attributeName]))
  1316. {
  1317. $this->unrestrictedGet($attributeName);
  1318. }
  1319. if (isset($this->relationNameToRelatedModel[$attributeName]) &&
  1320. $value !== null &&
  1321. $this->relationNameToRelatedModel[$attributeName]->isSame($value))
  1322. {
  1323. // If there is a current related model and it is the same
  1324. // as the one being set then do nothing.
  1325. }
  1326. else
  1327. {
  1328. if (!in_array($attributeName, $this->unlinkedRelationNames) &&
  1329. isset($this->relationNameToRelatedModel[$attributeName]))
  1330. {
  1331. $this->unlinkedRelationNames[] = $attributeName;
  1332. }
  1333. if ($value === null)
  1334. {
  1335. unset($this->relationNameToRelatedModel[$attributeName]);
  1336. }
  1337. else
  1338. {
  1339. assert("\$value instanceof $relatedModelClassName");
  1340. $this->relationNameToRelatedModel[$attributeName] = $value;
  1341. }
  1342. }
  1343. return true;
  1344. }
  1345. }
  1346. else
  1347. {
  1348. throw new NotSupportedException();
  1349. }
  1350. return false;
  1351. }
  1352. /**
  1353. * Allows testing of the members and relations of the model as if
  1354. * they were actual attributes of the model.
  1355. */
  1356. public function __isset($attributeName)
  1357. {
  1358. assert('is_string($attributeName)');
  1359. assert('$attributeName != ""');
  1360. return $this->isAttribute($attributeName) &&
  1361. $this->$attributeName !== null ||
  1362. !$this->isAttribute($attributeName) &&
  1363. isset($this->$attributeName);
  1364. }
  1365. /**
  1366. * Allows unsetting of the members and relations of the model as if
  1367. * they were actual attributes of the model.
  1368. */
  1369. public function __unset($attributeName)
  1370. {
  1371. assert('is_string($attributeName)');
  1372. assert('$attributeName != ""');
  1373. $this->$attributeName = null;
  1374. }
  1375. /**
  1376. * Returns the member and relation names defined by the extending
  1377. * class's getMetadata() method.
  1378. */
  1379. public function attributeNames()
  1380. {
  1381. return array_keys($this->attributeNameToBeanAndClassName);
  1382. }
  1383. /**
  1384. * Returns true if the named attribute is one of the member or
  1385. * relation names defined by the extending
  1386. * class's getMetadata() method.
  1387. */
  1388. public function isAttribute($attributeName)
  1389. {
  1390. assert('is_string($attributeName)');
  1391. assert('$attributeName != ""');
  1392. return $attributeName == 'id' ||
  1393. array_key_exists($attributeName, $this->attributeNameToBeanAndClassName);
  1394. }
  1395. /**
  1396. * Returns true if the attribute is read-only.
  1397. */
  1398. public function isAttributeReadOnly($attributeName)
  1399. {
  1400. assert("\$this->isAttribute(\"$attributeName\")");
  1401. foreach ($this->validators as $validator)
  1402. {
  1403. if ($validator instanceof RedBeanModelReadOnlyValidator)
  1404. {
  1405. if (in_array($attributeName, $validator->attributes, true))
  1406. {
  1407. return true;
  1408. }
  1409. }
  1410. }
  1411. return false;
  1412. }
  1413. /**
  1414. * @param boolean $attributeName
  1415. * @return true/false whether the attributeName specified, it is allowed to be set externally even though it is
  1416. * a read-only attribute.
  1417. */
  1418. public function isAllowedToSetReadOnlyAttribute($attributeName)
  1419. {
  1420. return false;
  1421. }
  1422. /**
  1423. * Given an attribute return the column name.
  1424. * @param string $attributeName
  1425. */
  1426. public function getColumnNameByAttribute($attributeName)
  1427. {
  1428. assert('is_string($attributeName)');
  1429. if ($this->isRelation($attributeName))
  1430. {
  1431. $modelClassName = get_class($this);
  1432. $columnName = $modelClassName::getForeignKeyName($modelClassName, $attributeName);
  1433. }
  1434. else
  1435. {
  1436. $columnName = strtolower($attributeName);
  1437. }
  1438. return $columnName;
  1439. }
  1440. /**
  1441. * This method is needed to interpret when the attributeName is 'id'. Since id is not an attribute
  1442. * on the model, we manaully check for this and return the appropriate class name.
  1443. * @param string $attributeName
  1444. * @return the model class name for the attribute. This could be a casted up model class name.
  1445. */
  1446. public function resolveAttributeModelClassName($attributeName)
  1447. {
  1448. assert('is_string($attributeName)');
  1449. if ($attributeName == 'id')
  1450. {
  1451. return get_class($this);
  1452. }
  1453. return $this->getAttributeModelClassName($attributeName);
  1454. }
  1455. /**
  1456. * Returns the model class name for an
  1457. * attribute name defined by the extending class's getMetadata() method.
  1458. * For use by RedBeanModelDataProvider. Is unlikely to be of any
  1459. * use to an application.
  1460. */
  1461. public function getAttributeModelClassName($attributeName)
  1462. {
  1463. assert("\$this->isAttribute(\"$attributeName\")");
  1464. return $this->attributeNameToBeanAndClassName[$attributeName][1];
  1465. }
  1466. /**
  1467. * Returns true if the named attribute is one of the
  1468. * relation names defined by the extending
  1469. * class's getMetadata() method.
  1470. */
  1471. public function isRelation($attributeName)
  1472. {
  1473. assert("\$this->isAttribute('$attributeName')");
  1474. return array_key_exists($attributeName, $this->relationNameToRelationTypeModelClassNameAndOwns);
  1475. }
  1476. /**
  1477. * Returns true if the named attribute is one of the
  1478. * relation names defined by the extending
  1479. * class's getMetadata() method, and specifies RedBeanModel::OWNED.
  1480. */
  1481. public function isOwnedRelation($attributeName)
  1482. {
  1483. assert("\$this->isAttribute('$attributeName')");
  1484. return array_key_exists($attributeName, $this->relationNameToRelationTypeModelClassNameAndOwns) &&
  1485. $this->relationNameToRelationTypeModelClassNameAndOwns[$attributeName][2];
  1486. }
  1487. /**
  1488. * Returns the relation type
  1489. * relation name defined by the extending class's getMetadata() method.
  1490. */
  1491. public function getRelationType($relationName)
  1492. {
  1493. assert("\$this->isRelation('$relationName')");
  1494. return $this->relationNameToRelationTypeModelClassNameAndOwns[$relationName][0];
  1495. }
  1496. /**
  1497. * Returns the model class name for a
  1498. * relation name defined by the extending class's getMetadata() method.
  1499. * For use by RedBeanModelDataProvider. Is unlikely to be of any
  1500. * use to an application.
  1501. */
  1502. public function getRelationModelClassName($relationName)
  1503. {
  1504. assert("\$this->isRelation('$relationName')");
  1505. return $this->relationNameToRelationTypeModelClassNameAndOwns[$relationName][1];
  1506. }
  1507. /**
  1508. * See the yii documentation. Not used by RedBeanModel.
  1509. * @see getMetadata()
  1510. */
  1511. public function rules()
  1512. {
  1513. throw new NotImplementedException();
  1514. }
  1515. /**
  1516. * See the yii documentation.
  1517. */
  1518. public function behaviors()
  1519. {
  1520. return array();
  1521. }
  1522. /**
  1523. * See the yii documentation.
  1524. * RedBeanModels utilize untranslatedAttributeLabels to store any attribute information, which
  1525. * can then be translated in this method.
  1526. */
  1527. public function attributeLabels()
  1528. {
  1529. $attributeLabels = array();
  1530. foreach ($this->untranslatedAttributeLabels() as $attributeName => $label)
  1531. {
  1532. $attributeLabels[$attributeName] = Yii::t('Default', $label);
  1533. }
  1534. return $attributeLabels;
  1535. }
  1536. /**
  1537. * Array of untranslated attribute labels.
  1538. */
  1539. protected function untranslatedAttributeLabels()
  1540. {
  1541. return array();
  1542. }
  1543. /**
  1544. * Public for message checker only.
  1545. */
  1546. public function getUntranslatedAttributeLabels()
  1547. {
  1548. return $this->untranslatedAttributeLabels();
  1549. }
  1550. /**
  1551. * See the yii documentation.
  1552. * RedBeanModels utilize untranslatedAbbreviatedAttributeLabels to store any abbreviated attribute information, which
  1553. * can then be translated in this method.
  1554. */
  1555. public function abbreviatedAttributeLabels()
  1556. {
  1557. $abbreviatedAttributeLabels = array();
  1558. foreach ($this->untranslatedAbbreviatedAttributeLabels() as $attributeName => $label)
  1559. {
  1560. $abbreviatedAttributeLabels[$attributeName] = Yii::t('Default', $label);
  1561. }
  1562. return $abbreviatedAttributeLabels;
  1563. }
  1564. /**
  1565. * Array of untranslated abbreviated attribute labels.
  1566. */
  1567. protected function untranslatedAbbreviatedAttributeLabels()
  1568. {
  1569. return array();
  1570. }
  1571. /**
  1572. * Public for message checker only.
  1573. */
  1574. public function getUntranslatedAbbreviatedAttributeLabels()
  1575. {
  1576. return $this->untranslatedAbbreviatedAttributeLabels();
  1577. }
  1578. /**
  1579. * Performs validation using the validators specified in the 'rules'
  1580. * meta data by the extending class's getMetadata() method.
  1581. * Validation occurs on a new model or a modified model, but only
  1582. * proceeds to modified related models. Once validated a model
  1583. * will pass validation without revalidating until it is modified.
  1584. * Related models are only validated if the model validates.
  1585. * Cyclic relationships are prevented from causing problems by the
  1586. * validation either stopping at a non-validating model and only
  1587. * proceeding to non-validated models.
  1588. * @see RedBeanModel
  1589. * @param $ignoreRequiredValidator - set to true in scenarios where you want to validate everything but the
  1590. * the required validator. An example is a search form.
  1591. */
  1592. public function validate(array $attributeNames = null, $ignoreRequiredValidator = false)
  1593. {
  1594. if ($this->isValidating) // Prevent cycles.
  1595. {
  1596. return true;
  1597. }
  1598. $this->isValidating = true;
  1599. try
  1600. {
  1601. $this->clearErrors();
  1602. if ($this->beforeValidate())
  1603. {
  1604. $hasErrors = false;
  1605. if ($attributeNames === null)
  1606. {
  1607. $attributeNames = $this->attributeNamesNotBelongsToOrManyMany;
  1608. }
  1609. foreach ($this->getValidators() as $validator)
  1610. {
  1611. if ($validator instanceof RedBeanModelRequiredValidator && $validator->applyTo($this->scenarioName))
  1612. {
  1613. if (!$ignoreRequiredValidator)
  1614. {
  1615. $validator->validate($this, $attributeNames);
  1616. }
  1617. }
  1618. elseif (!$validator instanceof CDefaultValueValidator && $validator->applyTo($this->scenarioName))
  1619. {
  1620. $validator->validate($this, $attributeNames);
  1621. }
  1622. }
  1623. $relatedModelsHaveErrors = false;
  1624. foreach ($this->relationNameToRelatedModel as $relationName => $relatedModel)
  1625. {
  1626. if ((!$this->$relationName instanceof RedBeanModel) ||
  1627. !$this->$relationName->isSame($this))
  1628. {
  1629. if (in_array($relationName, $attributeNames) &&
  1630. ($this->$relationName->isModified() ||
  1631. ($this->isAttributeRequired($relationName) && !$ignoreRequiredValidator) &&
  1632. !$this->isSame($this->$relationName))) // Prevent cycles.
  1633. {
  1634. if (!$this->$relationName->validate(null, $ignoreRequiredValidator))
  1635. {
  1636. $hasErrors = true;
  1637. }
  1638. }
  1639. }
  1640. }
  1641. $this->afterValidate();
  1642. $hasErrors = $hasErrors || count($this->attributeNameToErrors) > 0;
  1643. // Put these asserts back if there are suspitions about validate/hasErrors/getErrors
  1644. // producing inconsistent results. But for now it is commented out because
  1645. // it makes too big an impact.
  1646. //assert('$hasErrors == (count($this->getErrors()) > 0)');
  1647. //assert('$hasErrors == $this->hasErrors()');
  1648. $this->isValidating = false;
  1649. return !$hasErrors;
  1650. }
  1651. $this->isValidating = false;
  1652. return false;
  1653. }
  1654. catch (Exception $e)
  1655. {
  1656. $this->isValidating = false;
  1657. throw $e;
  1658. }
  1659. }
  1660. /**
  1661. * See the yii documentation.
  1662. */
  1663. protected function beforeValidate()
  1664. {
  1665. $event = new CModelEvent($this);
  1666. $this->onBeforeValidate($event);
  1667. return $event->isValid;
  1668. }
  1669. /**
  1670. * See the yii documentation.
  1671. */
  1672. protected function afterValidate()
  1673. {
  1674. $this->onAfterValidate(new CEvent($this));
  1675. }
  1676. /**
  1677. * See the yii documentation.
  1678. */
  1679. public function onBeforeValidate(CModelEvent $event)
  1680. {
  1681. $this->raiseEvent('onBeforeValidate', $event);
  1682. }
  1683. /**
  1684. * See the yii documentation.
  1685. */
  1686. public function onAfterValidate($event)
  1687. {
  1688. $this->raiseEvent('onAfterValidate', $event);
  1689. }
  1690. /**
  1691. * See the yii documentation.
  1692. */
  1693. public function getValidatorList()
  1694. {
  1695. return $this->validators;
  1696. }
  1697. /**
  1698. * See the yii documentation.
  1699. */
  1700. public function getValidators($attributeName = null)
  1701. {
  1702. assert("\$attributeName === null || \$this->isAttribute('$attributeName')");
  1703. $validators = array();
  1704. $scenarioName = $this->scenarioName;
  1705. foreach ($this->validators as $validator)
  1706. {
  1707. if ($scenarioName === null || $validator->applyTo($scenarioName))
  1708. {
  1709. if ($attributeName === null || in_array($attributeName, $validator->attributes, true))
  1710. {
  1711. $validators[] = $validator;
  1712. }
  1713. }
  1714. }
  1715. return $validators;
  1716. }
  1717. /**
  1718. * See the yii documentation.
  1719. */
  1720. public function createValidators()
  1721. {
  1722. throw new NotImplementedException();
  1723. }
  1724. /**
  1725. * Returns true if the attribute value does not already exist in
  1726. * the database. This is used in the unique validator, but on saving
  1727. * RedBean can still throw because the unique constraint on the column
  1728. * has been violated because it was concurrently updated between the
  1729. * Yii validator being called and the save actually occuring.
  1730. */
  1731. public function isUniqueAttributeValue($attributeName, $value)
  1732. {
  1733. assert("\$this->isAttribute('$attributeName')");
  1734. assert('$value !== null');
  1735. if (!$this->isRelation($attributeName))
  1736. {
  1737. $modelClassName = $this->attributeNameToBeanAndClassName[$attributeName][1];
  1738. $tableName = self::getTableName($modelClassName);
  1739. $rows = R::getAll('select id from ' . $tableName . " where $attributeName = ?", array($value));
  1740. return count($rows) == 0 || count($rows) == 1 && $rows[0]['id'] == $this->id;
  1741. }
  1742. else
  1743. {
  1744. $model = $this->$attributeName;
  1745. if ($model->id == 0)
  1746. {
  1747. return true;
  1748. }
  1749. $modelClassName = $this->relationNameToRelationTypeModelClassNameAndOwns[$attributeName][1];
  1750. $tableName = self::getTableName($modelClassName);
  1751. $rows = R::getAll('select id from ' . $tableName . ' where id = ?', array($model->id));
  1752. return count($rows) == 0 || count($rows) == 1 && $rows[0]['id'] == $this->id;
  1753. }
  1754. }
  1755. /**
  1756. * Saves the model to the database. Models are only saved if they have been
  1757. * modified and related models are saved before this model. If a related model
  1758. * is modified and needs saving the deems the model to be modified and need
  1759. * saving, which ensures that keys are updated.
  1760. * Cyclic relationships are prevented from causing problems by the
  1761. * save only proceeding to non-saved models.
  1762. */
  1763. public function save($runValidation = true, array $attributeNames = null)
  1764. {
  1765. if ($attributeNames !== null)
  1766. {
  1767. throw new NotSupportedException();
  1768. }
  1769. if ($this->isSaving) // Prevent cycles.
  1770. {
  1771. return true;
  1772. }
  1773. $this->isSaving = true;
  1774. try
  1775. {
  1776. if (!$runValidation || $this->validate())
  1777. {
  1778. if ($this->beforeSave())
  1779. {
  1780. $beans = array_values($this->modelClassNameToBean);
  1781. $this->linkBeans();
  1782. // The breakLink/link is deferred until the save to avoid
  1783. // disconnecting or creating an empty row if the model was
  1784. // never actually saved.
  1785. foreach ($this->unlinkedRelationNames as $key => $relationName)
  1786. {
  1787. $bean = $this->attributeNameToBeanAndClassName [$relationName][0];
  1788. $relatedModelClassName = $this->relationNameToRelationTypeModelClassNameAndOwns[$relationName][1];
  1789. $relatedTableName = self::getTableName($relatedModelClassName);
  1790. $linkName = strtolower($relationName);
  1791. if ($linkName == strtolower($relatedModelClassName))
  1792. {
  1793. $linkName = null;
  1794. }
  1795. ZurmoRedBeanLinkManager::breakLink($bean, $relatedTableName, $linkName);
  1796. unset($this->unlinkedRelationNames[$key]);
  1797. }
  1798. assert('count($this->unlinkedRelationNames) == 0');
  1799. foreach ($this->relationNameToRelatedModel as $relationName => $relatedModel)
  1800. {
  1801. $relationType = $this->relationNameToRelationTypeModelClassNameAndOwns[$relationName][0];
  1802. if (!in_array($relationType, array(self::HAS_ONE_BELONGS_TO,
  1803. self::HAS_MANY_BELONGS_TO)))
  1804. {
  1805. if ($relatedModel->isModified() ||
  1806. ($this->isAttributeRequired($relationName)))
  1807. {
  1808. //If the attribute is required, but already exists and has not been modified we do
  1809. //not have to worry about saving it.
  1810. if ($this->isSavableFromRelation &&
  1811. !($this->isAttributeRequired($relationName) &&
  1812. !$relatedModel->isModified() &&
  1813. $relatedModel->id > 0))
  1814. {
  1815. if (!$relatedModel->save(false))
  1816. {
  1817. $this->isSaving = false;
  1818. return false;
  1819. }
  1820. }
  1821. elseif ($relatedModel->isModified())
  1822. {
  1823. throw new NotSuportedException();
  1824. }
  1825. }
  1826. }
  1827. if ($relatedModel instanceof RedBeanModel)
  1828. {
  1829. $bean = $this->attributeNameToBeanAndClassName [$relationName][0];
  1830. $relatedModelClassName = $this->relationNameToRelationTypeModelClassNameAndOwns[$relationName][1];
  1831. $linkName = strtolower($relationName);
  1832. if (strtolower($linkName) == strtolower($relatedModelClassName))
  1833. {
  1834. $linkName = null;
  1835. }
  1836. elseif ($relationType == RedBeanModel::HAS_MANY_BELONGS_TO ||
  1837. $relationType == RedBeanModel::HAS_ONE_BELONGS_TO)
  1838. {
  1839. $label = 'Relations of type HAS_MANY_BELONGS_TO OR HAS_ONE_BELONGS_TO must have the relation name ' .
  1840. 'the same as the related model class name. Relation: {relationName} ' .
  1841. 'Relation model class name: {relationModelClassName}';
  1842. throw new NotSupportedException(Yii::t('Default', $label,
  1843. array('{relationName}' => $linkName,
  1844. '{relationModelClassName}' => $relatedModelClassName)));
  1845. }
  1846. //Needed to exclude HAS_ONE_BELONGS_TO because an additional column was being created
  1847. //on the wrong side.
  1848. if ($relationType != RedBeanModel::HAS_ONE_BELONGS_TO && ($relatedModel->isModified() ||
  1849. $relatedModel->id > 0 ||
  1850. $this->isAttributeRequired($relationName)))
  1851. {
  1852. $relatedModel = $this->relationNameToRelatedModel[$relationName];
  1853. $relatedBean = $relatedModel->getClassBean($relatedModelClassName);
  1854. ZurmoRedBeanLinkManager::link($bean, $relatedBean, $linkName);
  1855. if (!RedBeanDatabase::isFrozen())
  1856. {
  1857. $tableName = self::getTableName($this->getAttributeModelClassName($relationName));
  1858. $columnName = self::getForeignKeyName(get_class($this), $relationName);
  1859. RedBeanColumnTypeOptimizer::optimize($tableName, $columnName, 'id');
  1860. }
  1861. }
  1862. }
  1863. }
  1864. $baseModelClassName = null;
  1865. foreach ($this->modelClassNameToBean as $modelClassName => $bean)
  1866. {
  1867. R::store($bean);
  1868. assert('$bean->id > 0');
  1869. if (!RedBeanDatabase::isFrozen())
  1870. {
  1871. static::resolveMixinsOnSaveForEnsuringColumnsAreCorrectlyFormed($baseModelClassName,
  1872. $modelClassName);
  1873. $baseModelClassName = $modelClassName;
  1874. }
  1875. }
  1876. $this->modified = false;
  1877. $this->afterSave();
  1878. RedBeanModelsCache::cacheModel($this);
  1879. $this->isSaving = false;
  1880. return true;
  1881. }
  1882. }
  1883. $this->isSaving = false;
  1884. return false;
  1885. }
  1886. catch (Exception $e)
  1887. {
  1888. $this->isSaving = false;
  1889. throw $e;
  1890. }
  1891. }
  1892. /**
  1893. * Resolve that the id columns are properly formed as integers.
  1894. * @param string or null $baseModelClassName
  1895. * @param string $modelClassName
  1896. */
  1897. protected static function resolveMixinsOnSaveForEnsuringColumnsAreCorrectlyFormed($baseModelClassName, $modelClassName)
  1898. {
  1899. assert('$baseModelClassName == null || is_string($baseModelClassName)');
  1900. assert('is_string($modelClassName)');
  1901. if ($baseModelClassName !== null)
  1902. {
  1903. $tableName = self::getTableName($modelClassName);
  1904. $columnName = self::getTableName($baseModelClassName) . '_id';
  1905. RedBeanColumnTypeOptimizer::optimize($tableName, $columnName, 'id');
  1906. }
  1907. }
  1908. /**
  1909. * This method is invoked before saving a record (after validation, if any).
  1910. * The default implementation raises the {@link onBeforeSave} event.
  1911. * You may override this method to do any preparation work for record saving.
  1912. * Use {@link isNewModel} to determine whether the saving is
  1913. * for inserting or updating record.
  1914. * Make sure you call the parent implementation so that the event is raised properly.
  1915. * @return boolean whether the saving should be executed. Defaults to true.
  1916. */
  1917. protected function beforeSave()
  1918. {
  1919. if ($this->hasEventHandler('onBeforeSave'))
  1920. {
  1921. $event = new CModelEvent($this);
  1922. $this->onBeforeSave($event);
  1923. return $event->isValid;
  1924. }
  1925. else
  1926. {
  1927. return true;
  1928. }
  1929. }
  1930. protected function afterSave()
  1931. {
  1932. $event = new CEvent($this);
  1933. $this->onAfterSave($event);
  1934. }
  1935. /**
  1936. * This event is raised before the record is saved.
  1937. * By setting {@link CModelEvent::isValid} to be false, the normal {@link save()} process will be stopped.
  1938. * @param CModelEvent $event the event parameter
  1939. * @since 1.0.2
  1940. */
  1941. public function onBeforeSave($event)
  1942. {
  1943. $this->raiseEvent('onBeforeSave', $event);
  1944. }
  1945. /**
  1946. * This event is raised after the record is saved.
  1947. * @param CEvent $event the event parameter
  1948. * @since 1.0.2
  1949. */
  1950. public function onAfterSave($event)
  1951. {
  1952. $this->raiseEvent('onAfterSave', $event);
  1953. }
  1954. /**
  1955. * This event is raised before the record is deleted.
  1956. * By setting {@link CModelEvent::isValid} to be false, the normal {@link delete()} process will be stopped.
  1957. * @param CModelEvent $event the event parameter
  1958. * @since 1.0.2
  1959. */
  1960. public function onBeforeDelete($event)
  1961. {
  1962. $this->raiseEvent('onBeforeDelete', $event);
  1963. }
  1964. /**
  1965. * This event is raised after the record is deleted.
  1966. * @param CEvent $event the event parameter
  1967. * @since 1.0.2
  1968. */
  1969. public function onAfterDelete($event)
  1970. {
  1971. $this->raiseEvent('onAfterDelete', $event);
  1972. }
  1973. protected function linkBeans()
  1974. {
  1975. $baseModelClassName = null;
  1976. $baseBean = null;
  1977. foreach ($this->modelClassNameToBean as $modelClassName => $bean)
  1978. {
  1979. if ($baseBean !== null)
  1980. {
  1981. ZurmoRedBeanLinkManager::link($bean, $baseBean);
  1982. if (!RedBeanDatabase::isFrozen())
  1983. {
  1984. $tableName = self::getTableName($modelClassName);
  1985. $columnName = self::getTableName($baseModelClassName) . '_id';
  1986. RedBeanColumnTypeOptimizer::optimize($tableName, $columnName, 'id');
  1987. }
  1988. }
  1989. $baseModelClassName = $modelClassName;
  1990. $baseBean = $bean;
  1991. }
  1992. }
  1993. /**
  1994. * Returns true if the model has been modified since it was saved
  1995. * or constructed.
  1996. */
  1997. public function isModified()
  1998. {
  1999. if ($this->modified)
  2000. {
  2001. return true;
  2002. }
  2003. if ($this->isInIsModified) // Prevent cycles.
  2004. {
  2005. return false;
  2006. }
  2007. $this->isInIsModified = true;
  2008. try
  2009. {
  2010. foreach ($this->relationNameToRelatedModel as $relationName => $relatedModel)
  2011. {
  2012. if ((!$this->$relationName instanceof RedBeanModel) ||
  2013. !$this->$relationName->isSame($this))
  2014. {
  2015. if (!in_array($this->relationNameToRelationTypeModelClassNameAndOwns[$relationName][0],
  2016. array(self::HAS_ONE_BELONGS_TO,
  2017. self::HAS_MANY_BELONGS_TO,
  2018. self::MANY_MANY)))
  2019. {
  2020. if ($this->$relationName->isModified() ||
  2021. $this->isAttributeRequired($relationName) &&
  2022. $this->$relationName->id <= 0)
  2023. {
  2024. $this->isInIsModified = false;
  2025. return true;
  2026. }
  2027. }
  2028. }
  2029. }
  2030. $this->isInIsModified = false;
  2031. return false;
  2032. }
  2033. catch (Exception $e)
  2034. {
  2035. $this->isInIsModified = false;
  2036. throw $e;
  2037. }
  2038. }
  2039. /**
  2040. * Deletes the model from the database.
  2041. */
  2042. public function delete()
  2043. {
  2044. if ($this->id < 0)
  2045. {
  2046. // If the model was never saved
  2047. // then it doesn't need to be deleted.
  2048. return false;
  2049. }
  2050. $modelClassName = get_called_class();
  2051. if (!$modelClassName::isTypeDeletable() ||
  2052. !$this->isDeletable())
  2053. {
  2054. // See comments below on isDeletable.
  2055. throw new NotSupportedException();
  2056. }
  2057. if ($this->beforeDelete())
  2058. {
  2059. $deleted = $this->unrestrictedDelete();
  2060. $this->afterDelete();
  2061. return $deleted;
  2062. }
  2063. else
  2064. {
  2065. return false;
  2066. }
  2067. }
  2068. /**
  2069. * This method is invoked before deleting a record.
  2070. * The default implementation raises the {@link onBeforeDelete} event.
  2071. * You may override this method to do any preparation work for record deletion.
  2072. * Make sure you call the parent implementation so that the event is raised properly.
  2073. * @return boolean whether the record should be deleted. Defaults to true.
  2074. */
  2075. protected function beforeDelete()
  2076. {
  2077. if ($this->hasEventHandler('onBeforeDelete'))
  2078. {
  2079. $event = new CModelEvent($this);
  2080. $this->onBeforeDelete($event);
  2081. return $event->isValid;
  2082. }
  2083. else
  2084. {
  2085. return true;
  2086. }
  2087. }
  2088. /**
  2089. * This method is invoked after deleting a record.
  2090. * The default implementation raises the {@link onAfterDelete} event.
  2091. * You may override this method to do postprocessing after the record is deleted.
  2092. * Make sure you call the parent implementation so that the event is raised properly.
  2093. */
  2094. protected function afterDelete()
  2095. {
  2096. if ($this->hasEventHandler('onAfterDelete'))
  2097. {
  2098. $this->onAfterDelete(new CEvent($this));
  2099. }
  2100. }
  2101. protected function unrestrictedDelete()
  2102. {
  2103. $this->forget();
  2104. // RedBeanModel only supports cascaded deletes on associations,
  2105. // not on links. So for now at least they are done the slow way.
  2106. foreach (RuntimeUtil::getClassHierarchy(get_class($this), static::$lastClassInBeanHeirarchy) as $modelClassName)
  2107. {
  2108. $this->deleteOwnedRelatedModels ($modelClassName);
  2109. $this->deleteForeignRelatedModels($modelClassName);
  2110. $this->deleteManyManyRelations ($modelClassName);
  2111. }
  2112. foreach ($this->modelClassNameToBean as $modelClassName => $bean)
  2113. {
  2114. R::trash($bean);
  2115. }
  2116. // The model cannot be used anymore.
  2117. $this->deleted = true;
  2118. return true;
  2119. }
  2120. public function isDeleted()
  2121. {
  2122. return $this->deleted;
  2123. }
  2124. protected function deleteOwnedRelatedModels($modelClassName)
  2125. {
  2126. foreach ($this->relationNameToRelationTypeModelClassNameAndOwns as $relationName => $relationTypeModelClassNameAndOwns)
  2127. {
  2128. assert('count($relationTypeModelClassNameAndOwns) == 3 || count($relationTypeModelClassNameAndOwns) == 4');
  2129. $relationType = $relationTypeModelClassNameAndOwns[0];
  2130. $owns = $relationTypeModelClassNameAndOwns[2];
  2131. if ($owns)
  2132. {
  2133. if ((!$this->$relationName instanceof RedBeanModel) ||
  2134. !$this->$relationName->isSame($this))
  2135. {
  2136. assert('in_array($relationType, array(self::HAS_ONE, self::HAS_MANY))');
  2137. if ($relationType == self::HAS_ONE)
  2138. {
  2139. if ($this->$relationName->id > 0)
  2140. {
  2141. $this->$relationName->unrestrictedDelete();
  2142. }
  2143. }
  2144. else
  2145. {
  2146. foreach ($this->$relationName as $model)
  2147. {
  2148. $model->unrestrictedDelete();
  2149. }
  2150. }
  2151. }
  2152. }
  2153. }
  2154. }
  2155. protected function deleteForeignRelatedModels($modelClassName)
  2156. {
  2157. $metadata = $this->getMetadata();
  2158. if (isset($metadata[$modelClassName]['foreignRelations']))
  2159. {
  2160. foreach ($metadata[$modelClassName]['foreignRelations'] as $relatedModelClassName)
  2161. {
  2162. $relatedModels = $relatedModelClassName::
  2163. getByRelatedClassId($relatedModelClassName,
  2164. $this->getClassId($modelClassName),
  2165. $modelClassName);
  2166. foreach ($relatedModels as $relatedModel)
  2167. {
  2168. $relatedModel->unrestrictedDelete();
  2169. }
  2170. }
  2171. }
  2172. }
  2173. protected static function getByRelatedClassId($relatedModelClassName, $id, $modelClassName = null)
  2174. {
  2175. assert('is_string($relatedModelClassName)');
  2176. assert('$relatedModelClassName != ""');
  2177. assert('is_int($id)');
  2178. assert('$id > 0');
  2179. assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
  2180. if ($modelClassName === null)
  2181. {
  2182. $modelClassName = get_called_class();
  2183. }
  2184. $tableName = self::getTableName($relatedModelClassName);
  2185. $foreignKeyName = strtolower($modelClassName) . '_id';
  2186. $beans = R::find($tableName, "$foreignKeyName = $id");
  2187. return self::makeModels($beans, $relatedModelClassName);
  2188. }
  2189. protected function deleteManyManyRelations($modelClassName)
  2190. {
  2191. $metadata = $this->getMetadata();
  2192. if (isset($metadata[$modelClassName]['relations']))
  2193. {
  2194. foreach ($metadata[$modelClassName]['relations'] as $relationName => $relationTypeModelClassNameAndOwns)
  2195. {
  2196. assert('in_array(count($relationTypeModelClassNameAndOwns), array(2, 3, 4))');
  2197. $relationType = $relationTypeModelClassNameAndOwns[0];
  2198. if ($relationType == self::MANY_MANY)
  2199. {
  2200. $this->{$relationName}->removeAll();
  2201. $this->{$relationName}->save();
  2202. }
  2203. }
  2204. }
  2205. }
  2206. /**
  2207. * To be overriden on intermediate derived classes
  2208. * to return false so that deletes are not done on
  2209. * intermediate classes because the object relational
  2210. * mapping will not clean up properly.
  2211. * For example if User is a Person, and Person is
  2212. * a RedBeanModel delete should be called only on User,
  2213. * not on Person. So User must override isDeletable
  2214. * to return false.
  2215. */
  2216. public static function isTypeDeletable()
  2217. {
  2218. return true;
  2219. }
  2220. /**
  2221. * To be overridden by derived classes to prevent
  2222. * deletion.
  2223. */
  2224. public function isDeletable()
  2225. {
  2226. return true;
  2227. }
  2228. /**
  2229. * Forgets about all of the objects so that when they are retrieved
  2230. * again they will be recreated from the database. For use in testing.
  2231. */
  2232. public static function forgetAll()
  2233. {
  2234. RedBeanModelsCache::forgetAll();
  2235. RedBeansCache::forgetAll();
  2236. }
  2237. /**
  2238. * Forgets about the object so that if it is retrieved
  2239. * again it will be recreated from the database. For use in testing.
  2240. */
  2241. public function forget()
  2242. {
  2243. RedBeanModelsCache::forgetModel($this);
  2244. RedBeansCache::forgetBean(self::getTableName(get_called_class()) . $this->id);
  2245. }
  2246. /**
  2247. * See the yii documentation.
  2248. */
  2249. public function isAttributeRequired($attributeName)
  2250. {
  2251. assert("\$this->isAttribute('$attributeName')");
  2252. foreach ($this->getValidators($attributeName) as $validator)
  2253. {
  2254. if ($validator instanceof CRequiredValidator)
  2255. {
  2256. return true;
  2257. }
  2258. }
  2259. return false;
  2260. }
  2261. /**
  2262. * See the yii documentation.
  2263. */
  2264. public function isAttributeSafe($attributeName)
  2265. {
  2266. $attributeNames = $this->getSafeAttributeNames();
  2267. return in_array($attributeName, $attributeNames);
  2268. }
  2269. public static function getModelLabelByTypeAndLanguage($type, $language = null)
  2270. {
  2271. assert('in_array($type, array("Singular", "SingularLowerCase", "Plural", "PluralLowerCase"))');
  2272. if ($type == 'Singular')
  2273. {
  2274. return Yii::t('Default', static::getLabel(),
  2275. LabelUtil::getTranslationParamsForAllModules(), null, $language);
  2276. }
  2277. if ($type == 'SingularLowerCase')
  2278. {
  2279. return strtolower(Yii::t('Default', static::getLabel(),
  2280. LabelUtil::getTranslationParamsForAllModules(), null, $language));
  2281. }
  2282. if ($type == 'Plural')
  2283. {
  2284. return Yii::t('Default', static::getPluralLabel(),
  2285. LabelUtil::getTranslationParamsForAllModules(), null, $language);
  2286. }
  2287. if ($type == 'PluralLowerCase')
  2288. {
  2289. return strtolower(Yii::t('Default', static::getPluralLabel(),
  2290. LabelUtil::getTranslationParamsForAllModules(), null, $language));
  2291. }
  2292. }
  2293. protected static function getLabel()
  2294. {
  2295. return get_called_class();
  2296. }
  2297. protected static function getPluralLabel()
  2298. {
  2299. return static::getLabel() . 's';
  2300. }
  2301. /**
  2302. * See the yii documentation.
  2303. */
  2304. public function getAbbreviatedAttributeLabel($attributeName)
  2305. {
  2306. return $this->getAbbreviatedAttributeLabelByLanguage($attributeName, Yii::app()->language);
  2307. }
  2308. /**
  2309. * Given an attributeName and a language, retrieve the translated attribute label. Attempts to find a customized
  2310. * label in the metadata first, before falling back on the standard attribute label for the specified attribute.
  2311. * @return string - translated attribute label
  2312. */
  2313. protected function getAbbreviatedAttributeLabelByLanguage($attributeName, $language)
  2314. {
  2315. assert('is_string($attributeName)');
  2316. assert('is_string($language)');
  2317. $labels = $this->untranslatedAbbreviatedAttributeLabels();
  2318. if (isset($labels[$attributeName]))
  2319. {
  2320. return ZurmoHtml::tag('span', array('title' => $this->generateAttributeLabel($attributeName)),
  2321. Yii::t('Default', $labels[$attributeName],
  2322. LabelUtil::getTranslationParamsForAllModules(), null, $language));
  2323. }
  2324. else
  2325. {
  2326. return null;
  2327. }
  2328. }
  2329. /**
  2330. * See the yii documentation.
  2331. */
  2332. public function getAttributeLabel($attributeName)
  2333. {
  2334. return $this->getAttributeLabelByLanguage($attributeName, Yii::app()->language);
  2335. }
  2336. /**
  2337. * Given an attributeName and a language, retrieve the translated attribute label. Attempts to find a customized
  2338. * label in the metadata first, before falling back on the standard attribute label for the specified attribute.
  2339. * @return string - translated attribute label
  2340. */
  2341. protected function getAttributeLabelByLanguage($attributeName, $language)
  2342. {
  2343. assert('is_string($attributeName)');
  2344. assert('is_string($language)');
  2345. $labels = $this->untranslatedAttributeLabels();
  2346. $customLabel = $this->getTranslatedCustomAttributeLabelByLanguage($attributeName, $language);
  2347. if ($customLabel != null)
  2348. {
  2349. return $customLabel;
  2350. }
  2351. elseif (isset($labels[$attributeName]))
  2352. {
  2353. return Yii::t('Default', $labels[$attributeName],
  2354. LabelUtil::getTranslationParamsForAllModules(), null, $language);
  2355. }
  2356. else
  2357. {
  2358. //should do a T:: wrapper here too.
  2359. return Yii::t('Default', $this->generateAttributeLabel($attributeName), array(), null, $language);
  2360. }
  2361. }
  2362. /**
  2363. * Given an attributeName, attempt to find in the metadata a custom attribute label for the given language.
  2364. * @return string - translated attribute label, if not found return null.
  2365. */
  2366. protected function getTranslatedCustomAttributeLabelByLanguage($attributeName, $language)
  2367. {
  2368. assert('is_string($attributeName)');
  2369. assert('is_string($language)');
  2370. $metadata = $this->getMetadata();
  2371. foreach ($metadata as $modelClassName => $modelClassMetadata)
  2372. {
  2373. if (isset($modelClassMetadata['labels']) &&
  2374. isset($modelClassMetadata['labels'][$attributeName]) &&
  2375. isset($modelClassMetadata['labels'][$attributeName][$language]))
  2376. {
  2377. return $modelClassMetadata['labels'][$attributeName][$language];
  2378. }
  2379. }
  2380. return null;
  2381. }
  2382. /**
  2383. * Given an attributeName, return an array of all attribute labels for each language available.
  2384. * @return array - attribute labels by language for the given attributeName.
  2385. */
  2386. public function getAttributeLabelsForAllSupportedLanguagesByAttributeName($attributeName)
  2387. {
  2388. assert('is_string($attributeName)');
  2389. $attirbuteLabelData = array();
  2390. foreach (Yii::app()->languageHelper->getSupportedLanguagesData() as $language => $name)
  2391. {
  2392. $attirbuteLabelData[$language] = $this->getAttributeLabelByLanguage($attributeName, $language);
  2393. }
  2394. return $attirbuteLabelData;
  2395. }
  2396. /**
  2397. * See the yii documentation. The yii hasErrors() takes an optional
  2398. * attribute name. RedBeanModel's hasErrors() takes an optional attribute
  2399. * name or array of attribute names. See getErrors() for an explanation
  2400. * of this difference.
  2401. */
  2402. public function hasErrors($attributeNameOrNames = null)
  2403. {
  2404. assert('$attributeNameOrNames === null || ' .
  2405. 'is_string($attributeNameOrNames) || ' .
  2406. 'is_array ($attributeNameOrNames) && AssertUtil::all($attributeNameOrNames, "is_string")');
  2407. if ($this->isInHasErrors) // Prevent cycles.
  2408. {
  2409. return false;
  2410. }
  2411. $this->isInHasErrors = true;
  2412. try
  2413. {
  2414. if (is_string($attributeNameOrNames))
  2415. {
  2416. $attributeName = $attributeNameOrNames;
  2417. $relatedAttributeNames = null;
  2418. }
  2419. elseif (is_array($attributeNameOrNames))
  2420. {
  2421. $attributeName = $attributeNameOrNames[0];
  2422. if (count($attributeNameOrNames) > 1)
  2423. {
  2424. $relatedAttributeNames = array_slice($attributeNameOrNames, 1);
  2425. }
  2426. else
  2427. {
  2428. $relatedAttributeNames = null;
  2429. }
  2430. }
  2431. else
  2432. {
  2433. $attributeName = null;
  2434. $relatedAttributeNames = null;
  2435. }
  2436. assert("\$attributeName === null || is_string('$attributeName')");
  2437. assert('$relatedAttributeNames === null || is_array($relatedAttributeNames)');
  2438. assert('!($attributeName === null && $relatedAttributeNames !== null)');
  2439. if ($attributeName === null)
  2440. {
  2441. if (count($this->attributeNameToErrors) > 0)
  2442. {
  2443. $this->isInHasErrors = false;
  2444. return true;
  2445. }
  2446. foreach ($this->relationNameToRelatedModel as $relationName => $relatedModelOrModels)
  2447. {
  2448. if ((!$this->$relationName instanceof RedBeanModel) ||
  2449. !$this->$relationName->isSame($this))
  2450. {
  2451. if (in_array($relationName, $this->attributeNamesNotBelongsToOrManyMany))
  2452. {
  2453. if ($relatedModelOrModels->hasErrors($relatedAttributeNames))
  2454. {
  2455. $this->isInHasErrors = false;
  2456. return true;
  2457. }
  2458. }
  2459. }
  2460. }
  2461. $this->isInHasErrors = false;
  2462. return false;
  2463. }
  2464. else
  2465. {
  2466. if (!$this->isRelation($attributeName))
  2467. {
  2468. $this->isInHasErrors = false;
  2469. return array_key_exists($attributeName, $this->attributeNameToErrors);
  2470. }
  2471. else
  2472. {
  2473. if (in_array($attributeName, $this->attributeNamesNotBelongsToOrManyMany))
  2474. {
  2475. $this->isInHasErrors = false;
  2476. return isset($this->relationNameToRelatedModel[$attributeName]) &&
  2477. count($this->relationNameToRelatedModel[$attributeName]->getErrors($relatedAttributeNames)) > 0;
  2478. }
  2479. }
  2480. }
  2481. $this->isInHasErrors = false;
  2482. return false;
  2483. }
  2484. catch (Exception $e)
  2485. {
  2486. $this->isInHasErrors = false;
  2487. throw $e;
  2488. }
  2489. }
  2490. /**
  2491. * See the yii documentation. The yii getErrors() takes an optional
  2492. * attribute name. RedBeanModel's getErrors() takes an optional attribute
  2493. * name or array of attribute names.
  2494. * @param @attributeNameOrNames Either null, return all errors on the
  2495. * model and its related models, an attribute name on the model, return
  2496. * errors on that attribute, or an array of relation and attribute names,
  2497. * return errors on a related model's attribute.
  2498. */
  2499. public function getErrors($attributeNameOrNames = null)
  2500. {
  2501. assert('$attributeNameOrNames === null || ' .
  2502. 'is_string($attributeNameOrNames) || ' .
  2503. 'is_array ($attributeNameOrNames) && AssertUtil::all($attributeNameOrNames, "is_string")');
  2504. if ($this->isInGetErrors) // Prevent cycles.
  2505. {
  2506. return array();
  2507. }
  2508. $this->isInGetErrors = true;
  2509. try
  2510. {
  2511. if (is_string($attributeNameOrNames))
  2512. {
  2513. $attributeName = $attributeNameOrNames;
  2514. $relatedAttributeNames = null;
  2515. }
  2516. elseif (is_array($attributeNameOrNames))
  2517. {
  2518. $attributeName = $attributeNameOrNames[0];
  2519. if (count($attributeNameOrNames) > 1)
  2520. {
  2521. $relatedAttributeNames = array_slice($attributeNameOrNames, 1);
  2522. }
  2523. else
  2524. {
  2525. $relatedAttributeNames = null;
  2526. }
  2527. }
  2528. else
  2529. {
  2530. $attributeName = null;
  2531. $relatedAttributeNames = null;
  2532. }
  2533. assert("\$attributeName === null || is_string('$attributeName')");
  2534. assert('$relatedAttributeNames === null || is_array($relatedAttributeNames)');
  2535. assert('!($attributeName === null && $relatedAttributeNames !== null)');
  2536. if ($attributeName === null)
  2537. {
  2538. $errors = $this->attributeNameToErrors;
  2539. foreach ($this->relationNameToRelatedModel as $relationName => $relatedModelOrModels)
  2540. {
  2541. if ((!$this->$relationName instanceof RedBeanModel) ||
  2542. !$this->$relationName->isSame($this))
  2543. {
  2544. if (!in_array($this->relationNameToRelationTypeModelClassNameAndOwns[$relationName][0],
  2545. array(self::HAS_ONE_BELONGS_TO,
  2546. self::HAS_MANY_BELONGS_TO,
  2547. self::MANY_MANY)))
  2548. {
  2549. $relatedErrors = $relatedModelOrModels->getErrors($relatedAttributeNames);
  2550. if (count($relatedErrors) > 0)
  2551. {
  2552. $errors[$relationName] = $relatedErrors;
  2553. }
  2554. }
  2555. }
  2556. }
  2557. $this->isInGetErrors = false;
  2558. return $errors;
  2559. }
  2560. else
  2561. {
  2562. if (isset($this->attributeNameToErrors[$attributeName]))
  2563. {
  2564. $this->isInGetErrors = false;
  2565. return $this->attributeNameToErrors[$attributeName];
  2566. }
  2567. elseif (isset($this->relationNameToRelatedModel[$attributeName]))
  2568. {
  2569. if (!in_array($this->relationNameToRelationTypeModelClassNameAndOwns[$attributeName][0],
  2570. array(self::HAS_ONE_BELONGS_TO, self::HAS_MANY_BELONGS_TO)))
  2571. {
  2572. $this->isInGetErrors = false;
  2573. return $this->relationNameToRelatedModel[$attributeName]->getErrors($relatedAttributeNames);
  2574. }
  2575. }
  2576. }
  2577. $this->isInGetErrors = false;
  2578. return array();
  2579. }
  2580. catch (Exception $e)
  2581. {
  2582. $this->isInGetErrors = false;
  2583. throw $e;
  2584. }
  2585. }
  2586. /**
  2587. * See the yii documentation.
  2588. */
  2589. public function getError($attributeName)
  2590. {
  2591. assert("\$this->isAttribute('$attributeName')");
  2592. return isset($this->attributeNameToErrors[$attributeName]) ? reset($this->attributeNameToErrors[$attributeName]) : null;
  2593. }
  2594. /**
  2595. * See the yii documentation.
  2596. */
  2597. public function addError($attributeName, $errorMessage)
  2598. {
  2599. assert("\$this->isAttribute('$attributeName')");
  2600. if (!isset($this->attributeNameToErrors[$attributeName]))
  2601. {
  2602. $this->attributeNameToErrors[$attributeName] = array();
  2603. }
  2604. $this->attributeNameToErrors[$attributeName][] = $errorMessage;
  2605. }
  2606. /**
  2607. * See the yii documentation.
  2608. */
  2609. public function addErrors(array $errors)
  2610. {
  2611. foreach ($errors as $attributeName => $error)
  2612. {
  2613. assert("\$this->isAttribute('$attributeName')");
  2614. assert('is_array($error) || is_string($error)');
  2615. if (is_array($error))
  2616. {
  2617. if (!isset($this->attributeNameToErrors[$attributeName]))
  2618. {
  2619. $this->attributeNameToErrors[$attributeName] = array();
  2620. }
  2621. $this->attributeNameToErrors[$attributeName] =
  2622. array_merge($this->attributeNameToErrors[$attributeName], $error);
  2623. }
  2624. else
  2625. {
  2626. $this->attributeNameToErrors[$attributeName][] = $error;
  2627. }
  2628. }
  2629. }
  2630. /**
  2631. * See the yii documentation.
  2632. */
  2633. public function clearErrors($attributeName = null)
  2634. {
  2635. assert("\$attributeName === null || \$this->isAttribute('$attributeName')");
  2636. if ($attributeName === null)
  2637. {
  2638. $this->attributeNameToErrors = array();
  2639. }
  2640. else
  2641. {
  2642. unset($this->attributeNameToErrors[$attributeName]);
  2643. }
  2644. }
  2645. /**
  2646. * See the yii documentation.
  2647. */
  2648. public function generateAttributeLabel($attributeName)
  2649. {
  2650. assert("\$this->isAttribute('$attributeName')");
  2651. return ucfirst(preg_replace('/([A-Z0-9])/', ' \1', $attributeName));
  2652. }
  2653. /**
  2654. * See the yii documentation.
  2655. */
  2656. public function getAttributes(array $attributeNames = null)
  2657. {
  2658. $values = array();
  2659. if (is_array($attributeNames))
  2660. {
  2661. $values2 = array();
  2662. $allModelAttributeNames = $this->attributeNames();
  2663. foreach ($attributeNames as $attributeName)
  2664. {
  2665. if (in_array($attributeName, $allModelAttributeNames))
  2666. {
  2667. $values2[$attributeName] = $this->$attributeName;
  2668. }
  2669. }
  2670. return $values2;
  2671. }
  2672. else
  2673. {
  2674. foreach ($this->attributeNames() as $attributeName)
  2675. {
  2676. $values[$attributeName] = $this->$attributeName;
  2677. }
  2678. return $values;
  2679. }
  2680. }
  2681. /**
  2682. * See the yii documentation.
  2683. */
  2684. public function setAttributes(array $values, $safeOnly = true)
  2685. {
  2686. assert('is_bool($safeOnly)');
  2687. $attributeNames = array_flip($safeOnly ? $this->getSafeAttributeNames() : $this->attributeNames());
  2688. foreach ($values as $attributeName => $value)
  2689. {
  2690. if ($value !== null)
  2691. {
  2692. if (!is_array($value))
  2693. {
  2694. assert('$attributeName != "id"');
  2695. if ($attributeName != 'id' && $this->isAttribute($attributeName))
  2696. {
  2697. if ($this->isAttributeSafe($attributeName) || !$safeOnly)
  2698. {
  2699. $this->$attributeName = $value;
  2700. }
  2701. else
  2702. {
  2703. $this->onUnsafeAttribute($attributeName, $value);
  2704. }
  2705. }
  2706. }
  2707. else
  2708. {
  2709. if ($this->isRelation($attributeName))
  2710. {
  2711. if (count($value) == 1 && array_key_exists('id', $value))
  2712. {
  2713. if (empty($value['id']))
  2714. {
  2715. $this->$attributeName = null;
  2716. }
  2717. else
  2718. {
  2719. $relatedModelClassName = $this->relationNameToRelationTypeModelClassNameAndOwns[$attributeName][1];
  2720. $this->$attributeName = $relatedModelClassName::getById(intval($value['id']), $relatedModelClassName);
  2721. }
  2722. }
  2723. else
  2724. {
  2725. $setAttributeMethodName = 'set' . ucfirst($attributeName);
  2726. if ($this->$attributeName instanceof RedBeanOneToManyRelatedModels &&
  2727. method_exists($this, $setAttributeMethodName))
  2728. {
  2729. $this->$setAttributeMethodName($value);
  2730. }
  2731. else
  2732. {
  2733. $this->$attributeName->setAttributes($value);
  2734. }
  2735. }
  2736. }
  2737. }
  2738. }
  2739. }
  2740. }
  2741. /**
  2742. * See the yii documentation.
  2743. */
  2744. public function unsetAttributes($attributeNames = null)
  2745. {
  2746. if ($attributeNames === null)
  2747. {
  2748. $attributeNames = $this->attributeNames();
  2749. }
  2750. foreach ($attributeNames as $attributeName)
  2751. {
  2752. $this->$attributeNames = null;
  2753. }
  2754. }
  2755. /**
  2756. * See the yii documentation.
  2757. */
  2758. public function onUnsafeAttribute($name, $value)
  2759. {
  2760. if (YII_DEBUG)
  2761. {
  2762. Yii::log(Yii::t('yii', 'Failed to set unsafe attribute "{attribute}".', array('{attribute}' => $name)), CLogger::LEVEL_WARNING);
  2763. }
  2764. }
  2765. /**
  2766. * See the yii documentation.
  2767. */
  2768. public function getScenario()
  2769. {
  2770. return $this->scenarioName;
  2771. }
  2772. /**
  2773. * See the yii documentation.
  2774. */
  2775. public function setScenario($scenarioName)
  2776. {
  2777. assert('is_string($scenarioName)');
  2778. $this->scenarioName = $scenarioName;
  2779. }
  2780. /**
  2781. * See the yii documentation.
  2782. */
  2783. public function getSafeAttributeNames()
  2784. {
  2785. $attributeNamesToIsSafe = array();
  2786. $unsafeAttributeNames = array();
  2787. foreach ($this->getValidators() as $validator)
  2788. {
  2789. if (!$validator->safe)
  2790. {
  2791. foreach ($validator->attributes as $attributeName)
  2792. {
  2793. $unsafeAttributeNames[] = $attributeName;
  2794. }
  2795. }
  2796. else
  2797. {
  2798. foreach ($validator->attributes as $attributeName)
  2799. {
  2800. $attributeNamesToIsSafe[$attributeName] = true;
  2801. }
  2802. }
  2803. }
  2804. foreach ($unsafeAttributeNames as $attributeName)
  2805. {
  2806. unset($attributeNamesToIsSafe[$attributeName]);
  2807. }
  2808. return array_keys($attributeNamesToIsSafe);
  2809. }
  2810. /**
  2811. * See the yii documentation.
  2812. */
  2813. public function getIterator()
  2814. {
  2815. throw new NotImplementedException();
  2816. }
  2817. /**
  2818. * See the yii documentation.
  2819. */
  2820. public function offsetExists($offset)
  2821. {
  2822. throw new NotImplementedException();
  2823. }
  2824. /**
  2825. * See the yii documentation.
  2826. */
  2827. public function offsetGet($offset)
  2828. {
  2829. throw new NotImplementedException();
  2830. }
  2831. /**
  2832. * See the yii documentation.
  2833. */
  2834. public function offsetSet($offset, $item)
  2835. {
  2836. throw new NotImplementedException();
  2837. }
  2838. /**
  2839. * See the yii documentation.
  2840. */
  2841. public function offsetUnset($offset)
  2842. {
  2843. throw new NotImplementedException();
  2844. }
  2845. /**
  2846. * Creates an instance of the extending model wrapping the given
  2847. * bean. For use only by models. Beans are never used by the
  2848. * application directly.
  2849. * @param $bean A <a href="http://www.redbeanphp.com/">RedBean</a>
  2850. * bean.
  2851. * @param $modelClassName Pass only when getting it at runtime
  2852. * gets the wrong name.
  2853. * @return An instance of the type of the extending model.
  2854. */
  2855. public static function makeModel(RedBean_OODBBean $bean, $modelClassName = null, $forceTreatAsCreation = false)
  2856. {
  2857. assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
  2858. if ($modelClassName === null)
  2859. {
  2860. $modelClassName = get_called_class();
  2861. }
  2862. $modelIdentifier = $modelClassName . strval($bean->id);
  2863. try
  2864. {
  2865. $model = RedBeanModelsCache::getModel($modelIdentifier);
  2866. $model->constructIncomplete($bean, false);
  2867. return $model;
  2868. }
  2869. catch (NotFoundException $e)
  2870. {
  2871. return new $modelClassName(true, $bean, $forceTreatAsCreation);
  2872. }
  2873. }
  2874. /**
  2875. * Creates an array of instances of the named model type wrapping the
  2876. * given beans. For use only by models. Beans are never used by the
  2877. * application directly.
  2878. * @param $beans An array of <a href="http://www.redbeanphp.com/">RedBean</a>
  2879. * beans.
  2880. * @param $modelClassName Pass only when getting it at runtime
  2881. * gets the wrong name.
  2882. * @return An array of instances of the type of the extending model.
  2883. */
  2884. public static function makeModels(array $beans, $modelClassName = null)
  2885. {
  2886. if ($modelClassName === null)
  2887. {
  2888. $modelClassName = get_called_class();
  2889. }
  2890. $models = array();
  2891. foreach ($beans as $bean)
  2892. {
  2893. assert('$bean instanceof RedBean_OODBBean');
  2894. try
  2895. {
  2896. $models[] = self::makeModel($bean, $modelClassName);
  2897. }
  2898. catch (MissingBeanException $e)
  2899. {
  2900. }
  2901. }
  2902. return $models;
  2903. }
  2904. public static function getModuleClassName()
  2905. {
  2906. return null;
  2907. }
  2908. /**
  2909. * Given an array of data, create stringified content.
  2910. * @param array $values
  2911. */
  2912. public function stringifyOneToManyRelatedModelsValues($values)
  2913. {
  2914. assert('is_array($values)');
  2915. return ArrayUtil::stringify($values);
  2916. }
  2917. }
  2918. ?>