PageRenderTime 67ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 1ms

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

https://bitbucket.org/zurmo/zurmo/
PHP | 3320 lines | 2369 code | 158 blank | 793 comment | 324 complexity | 2d617da78d49a14c2fa04b349b3fa674 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, GPL-2.0, LGPL-3.0, LGPL-2.1, BSD-2-Clause

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*********************************************************************************
  3. * Zurmo is a customer relationship management program developed by
  4. * Zurmo, Inc. Copyright (C) 2015 Zurmo Inc.
  5. *
  6. * Zurmo is free software; you can redistribute it and/or modify it under
  7. * the terms of the GNU Affero General Public License version 3 as published by the
  8. * Free Software Foundation with the addition of the following permission added
  9. * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
  10. * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
  11. * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
  12. *
  13. * Zurmo is distributed in the hope that it will be useful, but WITHOUT
  14. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  15. * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  16. * details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License along with
  19. * this program; if not, see http://www.gnu.org/licenses or write to the Free
  20. * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  21. * 02110-1301 USA.
  22. *
  23. * You can contact Zurmo, Inc. with a mailing address at 27 North Wacker Drive
  24. * Suite 370 Chicago, IL 60606. or at email address contact@zurmo.com.
  25. *
  26. * The interactive user interfaces in original and modified versions
  27. * of this program must display Appropriate Legal Notices, as required under
  28. * Section 5 of the GNU Affero General Public License version 3.
  29. *
  30. * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
  31. * these Appropriate Legal Notices must retain the display of the Zurmo
  32. * logo and Zurmo copyright notice. If the display of the logo is not reasonably
  33. * feasible for technical reasons, the Appropriate Legal Notices must display the words
  34. * "Copyright Zurmo Inc. 2015. All rights reserved".
  35. ********************************************************************************/
  36. /**
  37. * Abstraction over the top of an application database accessed via
  38. * <a href="http://www.redbeanphp.com/">RedBean</a>. The base class for
  39. * an MVC model. Replaces the M part of MVC in Yii. Yii maps from the
  40. * database scheme to the objects, (good for database guys, not so good
  41. * for OO guys), this maps from objects to the database schema.
  42. *
  43. * A domain model is created by extending RedBeanModel and supplying
  44. * a getDefaultMetadata() method.
  45. *
  46. * Static getXxxx() methods can be supplied to query for the given domain
  47. * models, and instance methods should supply additional behaviour.
  48. *
  49. * getDefaultMetadata() returns an array of the class name mapped to
  50. * an array containing 'members' mapped to an array of member names,
  51. * (to be accessed as $model->memberName).
  52. *
  53. * It can then optionally have, 'relations' mapped
  54. * to an array of relation names, (to be accessed as $model->relationName),
  55. * mapped to its type, (the extending model class to which it relates).
  56. *
  57. * And it can then optionally have as well, 'rules' mapped to an array of
  58. * attribute names, (attributes are members and relations), a validator name,
  59. * and the parameters to the validator, if any, as per the Yii::CModel::rules()
  60. * method.See http://www.yiiframework.com/wiki/56/reference-model-rules-validation.
  61. *
  62. * These are used to automatically and dynamically create the database
  63. * schema on the fly as opposed to Yii's getting attributes from an
  64. * already existing schema.
  65. */
  66. abstract class RedBeanModel extends BeanModel implements Serializable
  67. {
  68. /**
  69. * Character used as delimiter when generating model identifiers
  70. */
  71. const MODEL_IDENTIFIER_DELIMITER = '_';
  72. /**
  73. * Models that have not been saved yet have no id as far
  74. * as the database is concerned. Until they are saved they are
  75. * assigned a negative id, so that they have identity.
  76. * @var integer
  77. */
  78. private static $nextPseudoId = -1;
  79. /**
  80. * Array of static models. Used by Observers @see ObservableComponent to add events to a class.
  81. * @var array
  82. */
  83. private static $_models = array();
  84. /*
  85. * The id of an unsaved model.
  86. * @var integer
  87. */
  88. private $pseudoId;
  89. /**
  90. * When creating the class heirarchy for bean creation and maintenence, which class is the last class in the
  91. * lineage to create a bean for? Normally the RedBeanModel is the lastClass in the line, and therefore there
  92. * will not be a table redbeanmodel. Some classes that extend RedBeanModel might want the line to stop before
  93. * RedBeanModel since creating a table with just an 'id' would be pointless. @see OwnedModel
  94. * @var string
  95. */
  96. protected static $lastClassInBeanHeirarchy = 'RedBeanModel';
  97. // A model maps to one or more beans. If Person extends RedBeanModel
  98. // there is one bean, but if User then extends Person a User model
  99. // has two beans, the one holding the person data and the one holding
  100. // the extended User data. In this way in inheritance hierarchy from
  101. // model is normalized over several tables, one for each extending
  102. // class.
  103. protected $modelClassNameToBean = array();
  104. protected $attributeNameToBeanAndClassName = array();
  105. protected $relationNameToRelatedModel = array();
  106. protected $unlinkedRelationNames = array();
  107. protected $unlinkedOwnedRelatedModelsToRemove = array();
  108. protected $validators = array();
  109. protected $attributeNameToErrors = array();
  110. protected $scenarioName = '';
  111. // An object is automatically savable if it is new or contains
  112. // modified members or related objects.
  113. // If it is newly created and has never had any data put into it
  114. // it can be saved explicitly but it wont be saved automatically
  115. // when it is a related model and will be redispensed next
  116. // time it is referenced.
  117. protected $modified = false;
  118. protected $deleted = false;
  119. protected $isInIsModified = false;
  120. protected $isInHasErrors = false;
  121. protected $isInGetErrors = false;
  122. protected $isValidating = false;
  123. protected $isSaving = false;
  124. protected $isDeleting = false;
  125. protected $isNewModel = false;
  126. protected $isCopied = false;
  127. /**
  128. * Can this model be saved when save is called from a related model? True if it can, false if it cannot.
  129. * Setting this value to false can reduce unnecessary queries to the database. If the models of a class do
  130. * not change often then it can make sense to set this to false. An example is @see Currency.
  131. * @var boolean
  132. */
  133. protected $isSavableFromRelation = true;
  134. // Mapping of Yii validators to validators doing things that
  135. // are either required for RedBean, or that simply implement
  136. // The semantics that we want.
  137. private static $yiiValidatorsToRedBeanValidators = array(
  138. 'CDefaultValueValidator' => 'RedBeanModelDefaultValueValidator',
  139. 'CNumberValidator' => 'RedBeanModelNumberValidator',
  140. 'CTypeValidator' => 'RedBeanModelTypeValidator',
  141. 'CRequiredValidator' => 'RedBeanModelRequiredValidator',
  142. 'CUniqueValidator' => 'RedBeanModelUniqueValidator',
  143. 'defaultCalculatedDate' => 'RedBeanModelDefaultCalculatedDateValidator',
  144. 'readOnly' => 'RedBeanModelReadOnlyValidator',
  145. 'dateTimeDefault' => 'RedBeanModelDateTimeDefaultValueValidator',
  146. 'probability' => 'RedBeanModelProbabilityValidator',
  147. );
  148. /**
  149. * Stores the attributeLabelsByLanguage
  150. * @var array
  151. */
  152. protected static $attributeLabelsByLanguage = array();
  153. /**
  154. * Returns the static model of the specified AR class.
  155. * The model returned is a static instance of the AR class.
  156. * It is provided for invoking class-level methods (something similar to static class methods.)
  157. *
  158. * EVERY derived AR class must override this method as follows,
  159. * <pre>
  160. * public static function model($className=__CLASS__)
  161. * {
  162. * return parent::model($className);
  163. * }
  164. * </pre>
  165. *
  166. * This method will make a model that does not run construction
  167. *
  168. * @param string $className active record class name.
  169. * @return CActiveRecord active record model instance.
  170. */
  171. public static function model($className = null)
  172. {
  173. if ($className == null)
  174. {
  175. $className = get_called_class();
  176. }
  177. if (isset(self::$_models[$className]))
  178. {
  179. return self::$_models[$className];
  180. }
  181. else
  182. {
  183. $model = self::$_models[$className] = new $className(false, null, false, false);
  184. return $model;
  185. }
  186. }
  187. /**
  188. * Gets all the models from the database of the named model type.
  189. * @param $orderBy TODO
  190. * @param $modelClassName Pass only when getting it at runtime
  191. * gets the wrong name.
  192. * @return An array of models of the type of the extending model.
  193. */
  194. public static function getAll($orderBy = null, $sortDescending = false, $modelClassName = null)
  195. {
  196. assert('$orderBy === null || is_string($orderBy) && $orderBy != ""');
  197. assert('is_bool($sortDescending)');
  198. assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
  199. $quote = DatabaseCompatibilityUtil::getQuote();
  200. $orderBySql = null;
  201. if ($orderBy !== null)
  202. {
  203. $orderBySql = "$quote$orderBy$quote";
  204. if ($sortDescending)
  205. {
  206. $orderBySql .= ' desc';
  207. }
  208. }
  209. return static::getSubset(null, null, null, null, $orderBySql, $modelClassName);
  210. }
  211. /**
  212. * Gets a range of models from the database of the named model type.
  213. * @param $modelClassName
  214. * @param $joinTablesAdapter null or instance of joinTablesAdapter.
  215. * @param $offset The zero based index of the first model to be returned.
  216. * @param $count The number of models to be returned.
  217. * @param $where
  218. * @param $orderBy - sql string. Example 'a desc' or 'a.b desc'. Currently only supports non-related attributes
  219. * @param $modelClassName Pass only when getting it at runtime gets the wrong name.
  220. * @return An array of models of the type of the extending model.
  221. */
  222. public static function getSubset(RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter = null,
  223. $offset = null, $count = null,
  224. $where = null, $orderBy = null,
  225. $modelClassName = null,
  226. $selectDistinct = false)
  227. {
  228. assert('$offset === null || is_integer($offset) && $offset >= 0');
  229. assert('$count === null || is_integer($count) && $count >= 1');
  230. assert('$where === null || is_string ($where) && $where != ""');
  231. assert('$orderBy === null || is_string ($orderBy) && $orderBy != ""');
  232. assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
  233. if ($modelClassName === null)
  234. {
  235. $modelClassName = get_called_class();
  236. }
  237. $ids = self::getSubsetIds($joinTablesAdapter, $offset, $count, $where,
  238. $orderBy, $modelClassName, $selectDistinct);
  239. $tableName = $modelClassName::getTableName();
  240. $beans = ZurmoRedBean::batch ($tableName, $ids);
  241. return self::makeModels($beans, $modelClassName);
  242. }
  243. /**
  244. * Gets a range of model ids from the database of the named model type.
  245. * @param $modelClassName
  246. * @param $joinTablesAdapter null or instance of joinTablesAdapter.
  247. * @param $offset The zero based index of the first model to be returned.
  248. * @param $count The number of models to be returned.
  249. * @param $where
  250. * @param $orderBy - sql string. Example 'a desc' or 'a.b desc'. Currently only supports non-related attributes
  251. * @param $modelClassName Pass only when getting it at runtime gets the wrong name.
  252. * @return An array of models ids
  253. */
  254. public static function getSubsetIds(RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter = null,
  255. $offset = null, $count = null,
  256. $where = null, $orderBy = null,
  257. $modelClassName = null,
  258. $selectDistinct = false)
  259. {
  260. assert('$offset === null || is_integer($offset) && $offset >= 0');
  261. assert('$count === null || is_integer($count) && $count >= 1');
  262. assert('$where === null || is_string ($where) && $where != ""');
  263. assert('$orderBy === null || is_string ($orderBy) && $orderBy != ""');
  264. assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
  265. if ($modelClassName === null)
  266. {
  267. $modelClassName = get_called_class();
  268. }
  269. if ($joinTablesAdapter == null)
  270. {
  271. $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter($modelClassName);
  272. }
  273. $tableName = $modelClassName::getTableName();
  274. $sql = static::makeSubsetOrCountSqlQuery($tableName, $joinTablesAdapter, $offset, $count, $where,
  275. $orderBy, false, $selectDistinct);
  276. $ids = ZurmoRedBean::getCol($sql);
  277. return $ids;
  278. }
  279. /**
  280. * @param boolean $selectCount If true then make this a count query. If false, select ids from rows.
  281. * @param array $quotedExtraSelectColumnNameAndAliases - extra columns to select.
  282. * @return string - sql statement.
  283. */
  284. public static function makeSubsetOrCountSqlQuery($tableName,
  285. RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter,
  286. $offset = null, $count = null,
  287. $where = null, $orderBy = null,
  288. $selectCount = false,
  289. $selectDistinct = false,
  290. array $quotedExtraSelectColumnNameAndAliases = array())
  291. {
  292. assert('is_string($tableName) && $tableName != ""');
  293. assert('$offset === null || is_integer($offset) && $offset >= 0');
  294. assert('$count === null || is_integer($count) && $count >= 1');
  295. assert('$where === null || is_string ($where) && $where != ""');
  296. assert('$orderBy === null || is_string ($orderBy) && $orderBy != ""');
  297. assert('is_bool($selectCount)');
  298. assert('is_bool($selectDistinct)');
  299. $selectQueryAdapter = new RedBeanModelSelectQueryAdapter($selectDistinct);
  300. if ($selectCount)
  301. {
  302. $selectQueryAdapter->addCountClause($tableName);
  303. }
  304. else
  305. {
  306. $selectQueryAdapter->addClause($tableName, 'id', 'id');
  307. }
  308. foreach ($quotedExtraSelectColumnNameAndAliases as $columnName => $columnAlias)
  309. {
  310. $selectQueryAdapter->addClauseWithColumnNameOnlyAndNoEnclosure($columnName, $columnAlias);
  311. }
  312. return SQLQueryUtil::
  313. makeQuery($tableName, $selectQueryAdapter, $joinTablesAdapter, $offset, $count, $where, $orderBy);
  314. }
  315. /**
  316. * @param $modelClassName
  317. * @param $joinTablesAdapter null or instance of joinTablesAdapter.
  318. * @param $modelClassName Pass only when getting it at runtime gets the wrong name.
  319. */
  320. public static function getCount(RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter = null,
  321. $where = null, $modelClassName = null, $selectDistinct = false)
  322. {
  323. assert('$where === null || is_string($where)');
  324. assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
  325. if ($modelClassName === null)
  326. {
  327. $modelClassName = get_called_class();
  328. }
  329. if ($joinTablesAdapter == null)
  330. {
  331. $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter($modelClassName);
  332. }
  333. $tableName = $modelClassName::getTableName();
  334. $sql = static::makeSubsetOrCountSqlQuery($tableName, $joinTablesAdapter, null, null, $where, null, true,
  335. $selectDistinct);
  336. $count = ZurmoRedBean::getCell($sql);
  337. if ($count === null || empty($count))
  338. {
  339. $count = 0;
  340. }
  341. return intval($count);
  342. }
  343. /**
  344. * Gets a model from the database by Id.
  345. * @param $id Integer Id.
  346. * @param $modelClassName Pass only when getting it at runtime
  347. * gets the wrong name.
  348. * @return A model of the type of the extending model.
  349. */
  350. public static function getById($id, $modelClassName = null)
  351. {
  352. assert('is_integer($id) && $id > 0');
  353. assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
  354. // I would have thought it was correct to user ZurmoRedBean::load() and get
  355. // a null, or error or something if the bean doesn't exist, but
  356. // it still returns a bean. So until I've investigated further
  357. // I'm using Finder.
  358. if ($modelClassName === null)
  359. {
  360. $modelClassName = get_called_class();
  361. }
  362. $tableName = $modelClassName::getTableName();
  363. $beans = ZurmoRedBean::find($tableName, "id = '$id'");
  364. assert('count($beans) <= 1');
  365. if (count($beans) == 0)
  366. {
  367. throw new NotFoundException();
  368. }
  369. return static::makeModel(end($beans), $modelClassName);
  370. }
  371. public function getIsNewModel()
  372. {
  373. return $this->isNewModel;
  374. }
  375. /**
  376. * Constructs a new model.
  377. * Important:
  378. * Models are only constructed with beans by the RedBeanModel. Beans are
  379. * never used by the application directly.
  380. * The application can construct a new model object by constructing a
  381. * model without specifying a bean. In other words, if Php had
  382. * overloading a constructor with $setDefaults would be public, and
  383. * a constructor taking a $bean and $forceTreatAsCreation would be private.
  384. * @param $setDefaults. If false the default validators will not be run
  385. * on construction. The Yii way is that defaults are
  386. * filled in after the fact, which is counter the usual
  387. * for objects.
  388. * @param $bean A bean. Never specified by an application.
  389. * @param $forceTreatAsCreation. Never specified by an application.
  390. * @param $runConstruction. Sometimes a model is needed to hook in events and the construction logic
  391. * does not necessarily need to be run.
  392. * @see getById()
  393. * @see makeModel()
  394. * @see makeModels()
  395. */
  396. public function __construct($setDefaults = true, RedBean_OODBBean $bean = null, $forceTreatAsCreation = false,
  397. $runConstruction = true)
  398. {
  399. $this->pseudoId = self::$nextPseudoId--;
  400. $this->init();
  401. if (!$runConstruction)
  402. {
  403. return;
  404. }
  405. if ($bean === null)
  406. {
  407. foreach (array_reverse(RuntimeUtil::getClassHierarchy(get_class($this), static::$lastClassInBeanHeirarchy)) as $modelClassName)
  408. {
  409. if ($modelClassName::getCanHaveBean())
  410. {
  411. $tableName = $modelClassName::getTableName();
  412. $newBean = ZurmoRedBean::dispense($tableName);
  413. $this->modelClassNameToBean[$modelClassName] = $newBean;
  414. $this->mapAndCacheMetadataAndSetHints($modelClassName, $newBean);
  415. }
  416. }
  417. // The yii way of doing defaults is the the default validator
  418. // fills in the defaults on attributes that don't have values
  419. // when you validator, or save. This weird, since when you get
  420. // a model the things with defaults have not been defaulted!
  421. // We want that semantic.
  422. if ($setDefaults)
  423. {
  424. $this->runDefaultValidators();
  425. }
  426. $forceTreatAsCreation = true;
  427. }
  428. else
  429. {
  430. assert('$bean->id > 0');
  431. $first = true;
  432. foreach (RuntimeUtil::getClassHierarchy(get_class($this), static::$lastClassInBeanHeirarchy) as $modelClassName)
  433. {
  434. if ($modelClassName::getCanHaveBean())
  435. {
  436. if ($first)
  437. {
  438. $lastBean = $bean;
  439. $first = false;
  440. }
  441. else
  442. {
  443. $tableName = $modelClassName::getTableName();
  444. $lastBean = ZurmoRedBeanLinkManager::getBean($lastBean, $tableName);
  445. if ($lastBean === null)
  446. {
  447. throw new MissingBeanException();
  448. }
  449. assert('$lastBean->id > 0');
  450. }
  451. $this->modelClassNameToBean[$modelClassName] = $lastBean;
  452. $this->mapAndCacheMetadataAndSetHints($modelClassName, $lastBean);
  453. }
  454. }
  455. $this->modelClassNameToBean = array_reverse($this->modelClassNameToBean);
  456. }
  457. $this->constructDerived($bean, $setDefaults);
  458. if ($forceTreatAsCreation)
  459. {
  460. $this->onCreated();
  461. }
  462. else
  463. {
  464. $this->onLoaded();
  465. $modelClassName = get_called_class();
  466. if ($modelClassName::isCacheable())
  467. {
  468. RedBeanModelsCache::cacheModel($this);
  469. }
  470. }
  471. $this->modified = false;
  472. }
  473. /**
  474. * Delete all models
  475. */
  476. public static function deleteAll()
  477. {
  478. if (static::getCanHaveBean() && static::isTypeDeletable())
  479. {
  480. foreach (static::getAll() as $model)
  481. {
  482. if (!$model->delete())
  483. {
  484. throw new NotSupportedException("Unable to delete id# " . $model->id);
  485. }
  486. }
  487. // we could have used ZurmoRedBean::$writer->wipe() but that won't fire events related to delete.
  488. }
  489. else
  490. {
  491. throw new NotSupportedException(get_called_class() . "Can either not have bean or is not deletable");
  492. }
  493. }
  494. // Derived classes can insert additional steps into the construction.
  495. protected function constructDerived($bean, $setDefaults)
  496. {
  497. assert('$bean === null || $bean instanceof RedBean_OODBBean');
  498. assert('is_bool($setDefaults)');
  499. }
  500. /**
  501. * Utilized when pieces of information need to be constructed on an existing model, that can potentially be
  502. * missing. For example, if a model is created, then a custom field is added, it is possible the cached model
  503. * is missing the custom field customFieldData.
  504. * @param unknown_type $bean
  505. */
  506. protected function constructIncomplete($bean)
  507. {
  508. assert('$bean === null || $bean instanceof RedBean_OODBBean');
  509. $this->init();
  510. }
  511. public function serialize()
  512. {
  513. return serialize(array(
  514. $this->pseudoId,
  515. $this->modelClassNameToBean,
  516. $this->attributeNameToBeanAndClassName,
  517. $this->validators,
  518. ));
  519. }
  520. public function unserialize($data)
  521. {
  522. try
  523. {
  524. $data = unserialize($data);
  525. assert('is_array($data)');
  526. if (count($data) != 4)
  527. {
  528. return null;
  529. }
  530. $this->pseudoId = $data[0];
  531. $this->modelClassNameToBean = $data[1];
  532. $this->attributeNameToBeanAndClassName = $data[2];
  533. $this->validators = $data[3];
  534. $this->relationNameToRelatedModel = array();
  535. $this->unlinkedRelationNames = array();
  536. $this->unlinkedOwnedRelatedModelsToRemove = array();
  537. $this->attributeNameToErrors = array();
  538. $this->scenarioName = '';
  539. $this->modified = false;
  540. $this->deleted = false;
  541. $this->isInIsModified = false;
  542. $this->isInHasErrors = false;
  543. $this->isInGetErrors = false;
  544. $this->isValidating = false;
  545. $this->isSaving = false;
  546. $this->isDeleting = false;
  547. }
  548. catch (Exception $e)
  549. {
  550. return null;
  551. }
  552. }
  553. /**
  554. * Overriding constructors must call this function to ensure that
  555. * they leave the newly constructed instance not modified since
  556. * anything modifying the class during constructionm will set it
  557. * modified automatically.
  558. */
  559. protected function setNotModified()
  560. {
  561. $this->modified = false; // This sets this class to the right state.
  562. assert('!$this->isModified()'); // This tests that related classes are in the right state.
  563. }
  564. /**
  565. * By default the table name is the lowercased class name. If this
  566. * conflicts with a database keyword override to return true.
  567. * RedBean does not quote table names in most cases.
  568. */
  569. // Public for unit testing.
  570. public static function mangleTableName()
  571. {
  572. return false;
  573. }
  574. /**
  575. * Returns the table name for a class.
  576. * For use by RedBeanModelDataProvider. It will not
  577. * be of any use to an application. Applications
  578. * should not be doing anything table related.
  579. * Derived classes can refer directly to the
  580. * table name.
  581. */
  582. public static function getTableName()
  583. {
  584. // You could also call it on objects like: $model::getTableName() or
  585. // $model->getTableName() but it would be preferable to keep it accessed through class, statically.
  586. $modelClassName = get_called_class();
  587. $tableName = strtolower($modelClassName);
  588. if ($modelClassName::mangleTableName())
  589. {
  590. $tableName = '_' . $tableName;
  591. }
  592. return $tableName;
  593. }
  594. /**
  595. * Returns the table names for an array of classes.
  596. * For use by RedBeanModelDataProvider. It will not
  597. * be of any use to an application.
  598. */
  599. public static function getTableNames($classNames)
  600. {
  601. $tableNames = array();
  602. foreach ($classNames as $className)
  603. {
  604. $tableNames[] = $className::getTableName();
  605. }
  606. return $tableNames;
  607. }
  608. /**
  609. * Used by classes such as containers which use sql to
  610. * optimize getting models from the database.
  611. */
  612. public static function getForeignKeyName($modelClassName, $relationName)
  613. {
  614. assert('is_string($modelClassName)');
  615. assert('$modelClassName != ""');
  616. $metadata = $modelClassName::getMetadata();
  617. foreach ($metadata as $modelClassName => $modelClassMetadata)
  618. {
  619. if (isset($metadata[$modelClassName]["relations"]) &&
  620. array_key_exists($relationName, $metadata[$modelClassName]["relations"]))
  621. {
  622. $relatedModelClassName = $metadata[$modelClassName]['relations'][$relationName][1];
  623. self::resolveModelClassNameForClassesWithoutBeans($relatedModelClassName);
  624. $relatedModelTableName = $relatedModelClassName::getTableName();
  625. $linkType = null;
  626. $relationLinkName = null;
  627. self::resolveLinkTypeAndRelationLinkName($metadata[$modelClassName]['relations'][$relationName],
  628. $linkType,
  629. $relationLinkName);
  630. $linkName = self::makeCasedLinkName($metadata[$modelClassName]['relations'][$relationName][0],
  631. $linkType, $relationLinkName);
  632. if ($metadata[$modelClassName]['relations'][$relationName][0] == self::HAS_MANY ||
  633. $metadata[$modelClassName]['relations'][$relationName][0] == self::HAS_ONE_BELONGS_TO)
  634. {
  635. $columnName = $modelClassName::getTableName() . '_id';
  636. }
  637. else
  638. {
  639. $columnName = $relatedModelTableName . '_id';
  640. }
  641. $columnName = ZurmoRedBeanLinkManager::resolveColumnPrefix($linkName) . $columnName;
  642. return $columnName;
  643. }
  644. }
  645. throw new NotSupportedException;
  646. }
  647. /**
  648. * Called on construction when a new model is created.
  649. */
  650. protected function onCreated()
  651. {
  652. if ($this->hasEventHandler('onCreated'))
  653. {
  654. $event = new CModelEvent($this);
  655. $this->onCreated($event);
  656. }
  657. }
  658. /**
  659. * Called on construction when a model is loaded.
  660. */
  661. protected function onLoaded()
  662. {
  663. if ($this->hasEventHandler('onLoaded'))
  664. {
  665. $event = new CModelEvent($this);
  666. $this->onLoaded($event);
  667. }
  668. }
  669. /**
  670. * Called when a model is modified.
  671. */
  672. protected function onModified()
  673. {
  674. }
  675. protected static function makeCasedLinkName($relationType, $linkType, $relationLinkName)
  676. {
  677. assert('is_int($relationType)');
  678. assert('is_int($linkType)');
  679. assert('is_string($relationLinkName) || $relationLinkName == null');
  680. if (($relationType == self::HAS_ONE || $relationType == self::HAS_MANY ||
  681. $relationType == self::HAS_ONE_BELONGS_TO) && $linkType == self::LINK_TYPE_SPECIFIC)
  682. {
  683. return strtolower($relationLinkName);
  684. }
  685. }
  686. /**
  687. * Used for mixins.
  688. */
  689. protected function mapAndCacheMetadataAndSetHints($modelClassName, RedBean_OODBBean $bean)
  690. {
  691. assert('is_string($modelClassName)');
  692. assert('$modelClassName != ""');
  693. $metadata = $this->getMetadata();
  694. if (isset($metadata[$modelClassName]))
  695. {
  696. $hints = array();
  697. if (isset($metadata[$modelClassName]['members']))
  698. {
  699. foreach ($metadata[$modelClassName]['members'] as $memberName)
  700. {
  701. $this->attributeNameToBeanAndClassName[$memberName] = array($bean, $modelClassName);
  702. //$this->attributeNamesNotBelongsToOrManyMany[] = $memberName;
  703. if (substr($memberName, -2) == 'Id')
  704. {
  705. $columnName = strtolower($memberName);
  706. $hints[$columnName] = 'id';
  707. }
  708. }
  709. }
  710. if (isset($metadata[$modelClassName]['relations']))
  711. {
  712. foreach ($metadata[$modelClassName]['relations'] as $relationName => $relationTypeModelClassNameAndOwns)
  713. {
  714. assert('in_array(count($relationTypeModelClassNameAndOwns), array(2, 3, 4, 5))');
  715. $relationType = $relationTypeModelClassNameAndOwns[0];
  716. $relationModelClassName = $relationTypeModelClassNameAndOwns[1];
  717. if ($relationType == self::HAS_MANY_BELONGS_TO &&
  718. strtolower($relationName) != strtolower($relationModelClassName))
  719. {
  720. throw new NotSupportedException(Zurmo::t('Core', 'Relations of type HAS_MANY_BELONGS_TO must have the relation name ' .
  721. 'the same as the related model class name. Relation: {relationName} ' .
  722. 'Relation model class name: {relationModelClassName}',
  723. array('{relationName}' => $relationName,
  724. '{relationModelClassName}' => $relationModelClassName)));
  725. }
  726. if (count($relationTypeModelClassNameAndOwns) >= 3 &&
  727. $relationTypeModelClassNameAndOwns[2] == self::OWNED)
  728. {
  729. $owns = true;
  730. }
  731. else
  732. {
  733. $owns = false;
  734. }
  735. // $linkType = null;
  736. // $relationLinkName = null;
  737. // self::resolveLinkTypeAndRelationLinkName($relationTypeModelClassNameAndOwns, $linkType,
  738. // $relationLinkName);
  739. assert('in_array($relationType, array(self::HAS_ONE_BELONGS_TO, self::HAS_MANY_BELONGS_TO, ' .
  740. 'self::HAS_ONE, self::HAS_MANY, self::MANY_MANY))');
  741. $this->attributeNameToBeanAndClassName[$relationName] = array($bean, $modelClassName);
  742. /**
  743. $this->relationNameToRelationTypeModelClassNameAndOwns[$relationName] = array($relationType,
  744. $relationModelClassName,
  745. $owns,
  746. $linkType,
  747. $relationLinkName);
  748. * **/
  749. if (!in_array($relationType, array(self::HAS_ONE_BELONGS_TO, self::HAS_MANY_BELONGS_TO, self::MANY_MANY)))
  750. {
  751. //$this->attributeNamesNotBelongsToOrManyMany[] = $relationName;
  752. }
  753. }
  754. }
  755. // Add model validators. Parent validators are already applied.
  756. if (isset($metadata[$modelClassName]['rules']))
  757. {
  758. foreach ($metadata[$modelClassName]['rules'] as $validatorMetadata)
  759. {
  760. assert('isset($validatorMetadata[0])');
  761. assert('isset($validatorMetadata[1])');
  762. $attributeName = $validatorMetadata[0];
  763. // Each rule in RedBeanModel must specify one attribute name.
  764. // This was just better style, now it is mandatory.
  765. assert('strpos($attributeName, " ") === false');
  766. $validatorName = $validatorMetadata[1];
  767. $validatorParameters = array_slice($validatorMetadata, 2);
  768. if (isset(CValidator::$builtInValidators[$validatorName]))
  769. {
  770. $validatorName = CValidator::$builtInValidators[$validatorName];
  771. }
  772. if (isset(self::$yiiValidatorsToRedBeanValidators[$validatorName]))
  773. {
  774. $validatorName = self::$yiiValidatorsToRedBeanValidators[$validatorName];
  775. }
  776. $validator = CValidator::createValidator($validatorName, $this, $attributeName, $validatorParameters);
  777. switch ($validatorName)
  778. {
  779. case 'RedBeanModelTypeValidator':
  780. case 'TypeValidator':
  781. $columnName = strtolower($attributeName);
  782. if (array_key_exists($columnName, $hints))
  783. {
  784. unset($hints[$columnName]);
  785. }
  786. if (in_array($validator->type, array('date', 'datetime', 'blob', 'longblob', 'string', 'text', 'longtext')))
  787. {
  788. $hints[$columnName] = $validator->type;
  789. }
  790. break;
  791. case 'CBooleanValidator':
  792. $columnName = strtolower($attributeName);
  793. $hints[$columnName] = 'boolean';
  794. break;
  795. case 'RedBeanModelUniqueValidator':
  796. if (!static::isRelation($attributeName))
  797. {
  798. $bean->setMeta("buildcommand.unique", array(array($attributeName)));
  799. }
  800. else
  801. {
  802. $relationAndOwns = static::getRelationNameToRelationTypeModelClassNameAndOwnsForModel();
  803. $relatedModelClassName = $relationAndOwns[$attributeName][1];
  804. $relatedModelTableName = $relatedModelClassName::getTableName();
  805. $columnName = strtolower($attributeName);
  806. if ($columnName != $relatedModelTableName)
  807. {
  808. $columnName .= '_' . $relatedModelTableName;
  809. }
  810. $columnName .= '_id';
  811. $bean->setMeta("buildcommand.unique", array(array($columnName)));
  812. }
  813. break;
  814. }
  815. $this->validators[] = $validator;
  816. }
  817. // Check if we need to update string type to long string type, based on validators.
  818. if (isset($metadata[$modelClassName]['members']))
  819. {
  820. foreach ($metadata[$modelClassName]['members'] as $memberName)
  821. {
  822. $allValidators = $this->getValidators($memberName);
  823. if (!empty($allValidators))
  824. {
  825. foreach ($allValidators as $validator)
  826. {
  827. if ((get_class($validator) == 'RedBeanModelTypeValidator' ||
  828. get_class($validator) == 'TypeValidator') &&
  829. $validator->type == 'string')
  830. {
  831. $columnName = strtolower($validator->attributes[0]);
  832. if (count($allValidators) > 1)
  833. {
  834. $haveCStringValidator = false;
  835. foreach ($allValidators as $innerValidator)
  836. {
  837. if (get_class($innerValidator) == 'CStringValidator' &&
  838. isset($innerValidator->max) &&
  839. $innerValidator->max > 0)
  840. {
  841. if ($innerValidator->max > 65535)
  842. {
  843. $hints[$columnName] = 'longtext';
  844. }
  845. elseif ($innerValidator->max < 255)
  846. {
  847. $hints[$columnName] = "string({$innerValidator->max})";
  848. }
  849. else
  850. {
  851. $hints[$columnName] = 'text';
  852. }
  853. }
  854. if (get_class($innerValidator) == 'CStringValidator')
  855. {
  856. $haveCStringValidator = true;
  857. }
  858. }
  859. if (!$haveCStringValidator)
  860. {
  861. $hints[$columnName] = 'text';
  862. }
  863. }
  864. else
  865. {
  866. $hints[$columnName] = 'text';
  867. }
  868. }
  869. }
  870. }
  871. }
  872. }
  873. }
  874. $bean->setMeta('hint', $hints);
  875. }
  876. }
  877. /**
  878. * Used for mixins.
  879. */
  880. protected function runDefaultValidators()
  881. {
  882. foreach ($this->validators as $validator)
  883. {
  884. if ($validator instanceof CDefaultValueValidator)
  885. {
  886. $validator->validate($this);
  887. }
  888. }
  889. }
  890. /**
  891. * For use only by RedBeanModel and RedBeanModels. Beans are
  892. * never used by the application directly.
  893. */
  894. public function getPrimaryBean()
  895. {
  896. return end($this->modelClassNameToBean);
  897. }
  898. /**
  899. * Used for optimization.
  900. */
  901. public function getClassId($modelClassName)
  902. {
  903. assert('array_key_exists($modelClassName, $this->modelClassNameToBean)');
  904. return intval($this->getClassBean($modelClassName)->id); // Trying to combat the slop.
  905. }
  906. public function getClassBean($modelClassName)
  907. {
  908. assert('is_string($modelClassName)');
  909. assert('$modelClassName != ""');
  910. self::resolveModelClassNameForClassesWithoutBeans($modelClassName);
  911. assert('array_key_exists($modelClassName, $this->modelClassNameToBean)');
  912. return $this->modelClassNameToBean[$modelClassName];
  913. }
  914. /**
  915. * Used for mixins.
  916. */
  917. protected function setClassBean($modelClassName, RedBean_OODBBean $bean)
  918. {
  919. assert('is_string($modelClassName)');
  920. assert('$modelClassName != ""');
  921. assert('!array_key_exists($modelClassName, $this->modelClassNameToBean)');
  922. $this->modelClassNameToBean = array_merge(array($modelClassName => $bean),
  923. $this->modelClassNameToBean);
  924. }
  925. public function getModelIdentifier()
  926. {
  927. $className = get_class($this);
  928. $beanId = strval($this->getPrimaryBean()->id);
  929. $modelIdentifier = static::getModelIdentifierByClassNameAndBeanId($className, $beanId);
  930. return $modelIdentifier;
  931. }
  932. protected static function getModelIdentifierByClassNameAndBeanId($modelClassName, $beanId)
  933. {
  934. return $modelClassName . static::MODEL_IDENTIFIER_DELIMITER . $beanId;
  935. }
  936. public static function getModelClassNameByIdentifier($identifier)
  937. {
  938. $identifierTokens = explode(static::MODEL_IDENTIFIER_DELIMITER, $identifier);
  939. return $identifierTokens[0];
  940. }
  941. /**
  942. * Returns metadata for the model. Attempts to cache metadata, if it is not already cached.
  943. * @see getDefaultMetadata()
  944. * @returns An array of metadata.
  945. */
  946. public static function getMetadata()
  947. {
  948. try
  949. {
  950. // not using default value to save cpu cycles on requests that follow the first exception.
  951. return GeneralCache::getEntry(get_called_class() . 'Metadata');
  952. }
  953. catch (NotFoundException $e)
  954. {
  955. $className = get_called_Class();
  956. $defaultMetadata = $className::getDefaultMetadata();
  957. $metadata = array();
  958. foreach (array_reverse(RuntimeUtil::getClassHierarchy($className, static::$lastClassInBeanHeirarchy)) as $modelClassName)
  959. {
  960. if ($modelClassName::getCanHaveBean())
  961. {
  962. if ($modelClassName::canSaveMetadata())
  963. {
  964. try
  965. {
  966. $globalMetadata = GlobalMetadata::getByClassName($modelClassName);
  967. $metadata[$modelClassName] = unserialize($globalMetadata->serializedMetadata);
  968. }
  969. catch (NotFoundException $e)
  970. {
  971. if (isset($defaultMetadata[$modelClassName]))
  972. {
  973. $metadata[$modelClassName] = $defaultMetadata[$modelClassName];
  974. }

Large files files are truncated, but you can click here to view the full file