PageRenderTime 54ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/library/XenForo/DataWriter.php

https://github.com/hanguyenhuu/DTUI_201105
PHP | 1855 lines | 935 code | 195 blank | 725 comment | 143 complexity | 6f78923ee88cc8ec745198477255435f MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0, BSD-3-Clause
  1. <?php
  2. //TODO: Set raw
  3. /**
  4. * Abstract handler for data writing. Data writers focus on writing a unit of data
  5. * to the database, including verifying all data to the application rules (including
  6. * those set by the owner) and doing denormalized updates as necessary.
  7. *
  8. * The writer may also interact with the cache, if required.
  9. *
  10. * @package XenForo_Core
  11. */
  12. abstract class XenForo_DataWriter
  13. {
  14. /**
  15. * Constant for error handling. Use this to trigger an exception when an error occurs.
  16. *
  17. * @var integer
  18. */
  19. const ERROR_EXCEPTION = 1;
  20. /**
  21. * Constant for error handling. Use this to push errors onto an array. If you try
  22. * to save while there are errors, an exception will be thrown.
  23. *
  24. * @var integer
  25. */
  26. const ERROR_ARRAY = 2;
  27. /**
  28. * Constant for error handling. Use this to push errors onto an array. If you try
  29. * to save while there are errors, the save will silently fail. Use this in places
  30. * where you aren't interested in handling errors.
  31. *
  32. * @var integer
  33. */
  34. const ERROR_SILENT = 3;
  35. /**
  36. * Constant for data fields. Use this for 0/1 boolean integer fields.
  37. *
  38. * @var string
  39. */
  40. const TYPE_BOOLEAN = 'boolean';
  41. /**
  42. * Constant for data fields. Use this for string fields. String fields are assumed
  43. * to be UTF-8 and limits will refer to characters.
  44. *
  45. * @var string
  46. */
  47. const TYPE_STRING = 'string';
  48. /**
  49. * Constant for data fields. Use this for binary or ASCII-only fields. Limits
  50. * will refer to bytes.
  51. *
  52. * @var string
  53. */
  54. const TYPE_BINARY = 'binary';
  55. /**
  56. * Constant for data fields. Use this for integer fields. Limits can be applied
  57. * on the range of valid values.
  58. *
  59. * @var string
  60. */
  61. const TYPE_INT = 'int';
  62. /**
  63. * Constant for data fields. Use this for unsigned integer fields. Negative values
  64. * will always fail to be valid. Limits can be applied on the range of valid values.
  65. *
  66. * @var string
  67. */
  68. const TYPE_UINT = 'uint';
  69. /**
  70. * Constant for data fields. Use this for unsigned integer fields. This differs from
  71. * TYPE_UINT in that negative values will be silently cast to 0. Limits can be
  72. * applied on the range of valid values.
  73. *
  74. * @var string
  75. */
  76. const TYPE_UINT_FORCED = 'uint_forced';
  77. /**
  78. * Constant for data fields. Use this for float fields. Limits can be applied
  79. * on the range of valid values.
  80. *
  81. * @var string
  82. */
  83. const TYPE_FLOAT = 'float';
  84. /**
  85. * Data is serialized. Ensures that if the data is not a string, it is serialized to ne.
  86. *
  87. * @var string
  88. */
  89. const TYPE_SERIALIZED = 'serialized';
  90. /**
  91. * Constant for data fields. Use this for fields that have a type that cannot be
  92. * known statically. Use this sparingly, as you must write code to ensure that
  93. * the value is a scalar before it is inserted into the DB. The behavior if you
  94. * don't do this is not defined!
  95. *
  96. * @var string
  97. */
  98. const TYPE_UNKNOWN = 'unknown';
  99. /**
  100. * Cache object
  101. *
  102. * @var Zend_Cache_Core|Zend_Cache_Frontend
  103. */
  104. protected $_cache = null;
  105. /**
  106. * Database object
  107. *
  108. * @var Zend_Db_Adapter_Abstract
  109. */
  110. protected $_db = null;
  111. /**
  112. * Array of valid fields in the table. See {@link _getFields()} for more info.
  113. *
  114. * @var array
  115. */
  116. protected $_fields = array();
  117. /**
  118. * Options that change the behavior of the data writer. All options that a
  119. * data writer supports must be documented in this array, preferably
  120. * by the use of constants.
  121. *
  122. * @var array
  123. */
  124. protected $_options = array();
  125. /**
  126. * Extra data that the data writer can use. The DW should usually function without
  127. * any data in this array. Any data that DW supports should be documented in
  128. * this array, preferably by the use of constants.
  129. *
  130. * Required data (for example, a related phrase) can be included here if necessary.
  131. *
  132. * @var array
  133. */
  134. protected $_extraData = array();
  135. /**
  136. * Data that has just been set an is about to be saved.
  137. *
  138. * @var array
  139. */
  140. protected $_newData = array();
  141. /**
  142. * Existing data in the database. This is only populated when updating a record.
  143. *
  144. * @var array
  145. */
  146. protected $_existingData = array();
  147. /**
  148. * When enabled, preSave, postSave, and verification functions are disabled. To be used
  149. * when using the DW for importing data in bulk. Note that you are responsible for manually
  150. * replicating the post save effects.
  151. *
  152. * @var boolean
  153. */
  154. protected $_importMode = false;
  155. /**
  156. * Type of error handler. See {@link ERROR_EXCEPTION} and related.
  157. *
  158. * @var integer
  159. */
  160. protected $_errorHandler = 0;
  161. /**
  162. * Array of errors. This is always populated when an error occurs, though
  163. * an exception may be thrown as well.
  164. *
  165. * @var array
  166. */
  167. protected $_errors = array();
  168. /**
  169. * Tracks whether {@link _preSave()} was called. It can only be called once.
  170. *
  171. * @var boolean
  172. */
  173. protected $_preSaveCalled = false;
  174. /**
  175. * Tracks whether {@link _preDelete()} was called. It can only be called once.
  176. *
  177. * @var boolean
  178. */
  179. protected $_preDeleteCalled = false;
  180. /**
  181. * Options that can be passed into {@link set()}. Available options are:
  182. * (default value is listed in parentheses)
  183. * * ignoreInvalidFields (false) - when true, an invalid field is simply ignored; otherwise an error is triggered
  184. * * replaceInvalidWithDefault (false) - when true, invalid values are replaced with the default value (if there is one)
  185. * * runVerificationCallback (true) - when true, verification callbacks are run when setting
  186. * * setAfterPreSave (false) - when true, you may set data after preSave is called. This has very specific uses.
  187. *
  188. * @var array
  189. */
  190. protected $_setOptions = array(
  191. 'ignoreInvalidFields' => false,
  192. 'replaceInvalidWithDefault' => false,
  193. 'runVerificationCallback' => true,
  194. 'setAfterPreSave' => false
  195. );
  196. /**
  197. * Title of the phrase that will be created when a call to set the
  198. * existing data fails (when the data doesn't exist).
  199. *
  200. * @var string
  201. */
  202. protected $_existingDataErrorPhrase = 'existing_data_required_data_writer_not_found';
  203. /**
  204. * Standard approach to caching model objects for the lifetime of the data writer.
  205. * This is now a static cache to allow data to be reused between data writers, as they
  206. * are often used in bulk.
  207. *
  208. * @var array
  209. */
  210. protected static $_modelCache = array();
  211. /**
  212. * Constructor.
  213. *
  214. * @param constant Error handler. See {@link ERROR_EXCEPTION} and related.
  215. * @param array|null Dependency injector. Array keys available: db, cache.
  216. */
  217. public function __construct($errorHandler = self::ERROR_ARRAY, array $inject = null)
  218. {
  219. $this->_db = (isset($inject['db']) ? $inject['db'] : XenForo_Application::get('db'));
  220. if (isset($inject['cache']))
  221. {
  222. $this->_cache = $inject['cache'];
  223. }
  224. $this->setErrorHandler($errorHandler);
  225. $fields = $this->_getFields();
  226. if (is_array($fields))
  227. {
  228. $this->_fields = $fields;
  229. }
  230. $options = $this->_getDefaultOptions();
  231. if (is_array($options))
  232. {
  233. $this->_options = $options;
  234. }
  235. }
  236. /**
  237. * Gets the fields that are defined for the table. This should return an array with
  238. * each key being a table name and a further array with each key being a field in database.
  239. * The value of each entry should be an array, which
  240. * may have the following keys:
  241. * * type - one of the TYPE_* constants, this controls the type of data in the field
  242. * * autoIncrement - set to true when the field is an auto_increment field. Used to populate this field after an insert
  243. * * required - if set, inserts will be prevented if this field is not set or an empty string (0 is accepted)
  244. * * requiredError - the phrase title that should be used if the field is not set (only if required)
  245. * * default - the default value of the field; used if the field isn't set or if the set value is outside the constraints (if a {@link $_setOption} is set)
  246. * * maxLength - for string/binary types only, the maximum length of the data. For strings, in characters; for binary, in bytes.
  247. * * min - for numeric types only, the minimum value allowed (inclusive)
  248. * * max - for numeric types only, the maximum value allowed (inclusive)
  249. * * allowedValues - an array of allowed values for this field (commonly for enums)
  250. * * verification - a callback to do more advanced verification. Callback function will take params for $value and $this.
  251. *
  252. * @return array
  253. */
  254. abstract protected function _getFields();
  255. /**
  256. * Gets the actual existing data out of data that was passed in. This data
  257. * may be a scalar or an array. If it's a scalar, assume that it is the primary
  258. * key (if there is one); if it is an array, attempt to extract the primary key
  259. * (or some other unique identifier). Then fetch the correct data from a model.
  260. *
  261. * @param mixed Data that can uniquely ID this item
  262. *
  263. * @return array|false
  264. */
  265. abstract protected function _getExistingData($data);
  266. /**
  267. * Gets SQL condition to update the existing record. Should read from {@link _existingData}.
  268. *
  269. * @param string Table name
  270. *
  271. * @return string
  272. */
  273. abstract protected function _getUpdateCondition($tableName);
  274. /**
  275. * Get the primary key from either an array of data or just the scalar value
  276. *
  277. * @param mixed Array of data containing the primary field or a scalar ID
  278. * @param string Primary key field, if data is an array
  279. * @param string Table name, if empty the first table defined in fields is used
  280. *
  281. * @return string|false
  282. */
  283. protected function _getExistingPrimaryKey($data, $primaryKeyField = '', $tableName = '')
  284. {
  285. if (!$tableName)
  286. {
  287. $tableName = $this->_getPrimaryTable();
  288. }
  289. if (!$primaryKeyField)
  290. {
  291. $primaryKeyField = $this->_getAutoIncrementField($tableName);
  292. }
  293. if (!isset($this->_fields[$tableName][$primaryKeyField]))
  294. {
  295. return false;
  296. }
  297. if (is_array($data))
  298. {
  299. if (isset($data[$primaryKeyField]))
  300. {
  301. return $data[$primaryKeyField];
  302. }
  303. else
  304. {
  305. return false;
  306. }
  307. }
  308. else
  309. {
  310. return $data;
  311. }
  312. }
  313. /**
  314. * Splits a (complete) data record array into its constituent tables using the _fields array for use in _getExistingData
  315. *
  316. * In order for this to work, the following must be true:
  317. * 1) The input array must provide all data that this datawriter requires
  318. * 2) There can be no overlapping field names in the tables that define this data, unless those fields all store the same value
  319. * 3) IMPORTANT: If the input array does *not* include all the required data fields, it is your responsibility to provide it after this function returns.
  320. *
  321. * @param array Complete data record
  322. *
  323. * @return array
  324. */
  325. public function getTablesDataFromArray(array $dataArray)
  326. {
  327. $data = array();
  328. /**
  329. * there are no dupe fieldnames between the tables,
  330. * so we might as well populate the existing data using the existing _fields array
  331. */
  332. foreach ($this->_fields AS $tableName => $field)
  333. {
  334. $data[$tableName] = array();
  335. foreach ($field AS $fieldName => $fieldInfo)
  336. {
  337. /**
  338. * don't attempt to set fields that are not provided by $dataArray,
  339. * it is your own responsibility to resolve this afterwards the function returns
  340. */
  341. if (isset($dataArray[$fieldName]))
  342. {
  343. $data[$tableName][$fieldName] = $dataArray[$fieldName];
  344. }
  345. }
  346. }
  347. return $data;
  348. }
  349. /**
  350. * Gets the default set of options for this data writer. This is automatically
  351. * called in the constructor. If you wish to set options in your data writer,
  352. * override this function. As this is handled at run time rather than compile time,
  353. * you may use dynamic behaviors to set the option defaults.
  354. *
  355. * @return array
  356. */
  357. protected function _getDefaultOptions()
  358. {
  359. return array();
  360. }
  361. /**
  362. * Sets the default options for {@link set()}. See {@link $_setOptions} for
  363. * the available options.
  364. *
  365. * @param array
  366. */
  367. public function setDefaultSetOptions(array $setOptions)
  368. {
  369. $this->_setOptions = array_merge($this->_setOptions, $setOptions);
  370. }
  371. /**
  372. * Sets an option. The meaning of this option is completely domain specific.
  373. * If an unknown option specified, an exception will be triggered.
  374. *
  375. * @param string Name of the option to set
  376. * @param mixed Value of the option
  377. */
  378. public function setOption($name, $value)
  379. {
  380. if (!isset($this->_options[$name]))
  381. {
  382. throw new XenForo_Exception('Cannot set unknown option');
  383. }
  384. $this->_options[$name] = $value;
  385. }
  386. /**
  387. * Gets an option. The meaning of this option is completely domain specific.
  388. * If an unknown option specified, an exception will be triggered.
  389. *
  390. * @param string Name of the option to get
  391. *
  392. * @return mixed Value of the option
  393. */
  394. public function getOption($name)
  395. {
  396. if (!array_key_exists($name, $this->_options))
  397. {
  398. throw new XenForo_Exception('Cannot get unknown option ' . $name);
  399. }
  400. return $this->_options[$name];
  401. }
  402. /**
  403. * Sets extra data that the DW can use to help it. The DW should be able
  404. * to function without this data.
  405. *
  406. * @param string Name of the data
  407. * @param mixed Value of the data
  408. */
  409. public function setExtraData($name, $value)
  410. {
  411. $this->_extraData[$name] = $value;
  412. }
  413. /**
  414. * Gets the named piece of extra data.
  415. *
  416. * @param string $name
  417. *
  418. * @return mixed
  419. */
  420. public function getExtraData($name)
  421. {
  422. return isset($this->_extraData[$name]) ? $this->_extraData[$name] : null;
  423. }
  424. /**
  425. * Sets the import mode. When enabled, preSave, postSave, and verification functions are disabled.
  426. *
  427. * @param boolean$mode
  428. */
  429. public function setImportMode($mode)
  430. {
  431. $this->_importMode = $mode;
  432. }
  433. /**
  434. * Sets the existing data. This causes the system to do an update instead of an insert.
  435. * This function triggers an error if no data can be fetched from the provided data.
  436. *
  437. * @param mixed Data that can uniquely ID this item
  438. * @param boolean If true, trust the passed data to be based on data in DB; if false, the data is used as is (if it's an array)
  439. *
  440. * @return boolean
  441. */
  442. public function setExistingData($data, $trustInputAsPrimary = false)
  443. {
  444. if ($trustInputAsPrimary && is_array($data) && sizeof(array_keys($this->_fields)) == 1)
  445. {
  446. // don't force standardization and given an array, so use it as is
  447. $existing = array($this->_getPrimaryTable() => $data);
  448. }
  449. else
  450. {
  451. $existing = $this->_getExistingData($data);
  452. if (is_array($existing) && !array_key_exists($this->_getPrimaryTable(), $existing))
  453. {
  454. throw new XenForo_Exception('_getExistingData returned an array but did not include data for the primary table');
  455. }
  456. }
  457. // data is only valid if the data is an array and every entry (table) is an array as well
  458. $validData = true;
  459. if (!is_array($existing))
  460. {
  461. $validData = false;
  462. }
  463. else
  464. {
  465. foreach ($existing AS $table => $tableData)
  466. {
  467. if (!is_array($tableData))
  468. {
  469. $validData = false;
  470. break;
  471. }
  472. }
  473. }
  474. if ($validData)
  475. {
  476. $this->_existingData = $existing;
  477. return true;
  478. }
  479. else
  480. {
  481. $this->_triggerInvalidExistingDataError();
  482. return false;
  483. }
  484. }
  485. /**
  486. * Triggers the error for when invalid existing data was requested. This can (and generally
  487. * should) be extended by concrete DWs to give an error that is more specific to
  488. * their content type.
  489. */
  490. protected function _triggerInvalidExistingDataError()
  491. {
  492. $this->error(new XenForo_Phrase($this->_existingDataErrorPhrase));
  493. }
  494. /**
  495. * Sets a field. This value will be updated when save is called.
  496. *
  497. * @param string Name of field to update
  498. * @param string Value to update with
  499. * @param string Table name, if empty then all tables with that column
  500. * @param array Options. See {@link $_setOptions).
  501. *
  502. * @return boolean
  503. */
  504. public function set($field, $value, $tableName = '', array $options = null)
  505. {
  506. $options = (is_array($options) ? array_merge($this->_setOptions, $options) : $this->_setOptions);
  507. if ($this->_preSaveCalled && empty($options['setAfterPreSave']))
  508. {
  509. throw new XenForo_Exception('Set cannot be called after preSave has been called.');
  510. }
  511. $validField = false;
  512. $dataSet = false;
  513. foreach ($this->_fields AS $table => $fields)
  514. {
  515. if ($tableName && $tableName != $table)
  516. {
  517. continue;
  518. }
  519. if (isset($fields[$field]) && is_array($fields[$field]))
  520. {
  521. $validField = true;
  522. $newValue = $value;
  523. if ($this->_isFieldValueValid($field, $fields[$field], $newValue, $options))
  524. {
  525. $this->_setInternal($table, $field, $newValue);
  526. $dataSet = true;
  527. }
  528. }
  529. }
  530. if (!$validField && empty($options['ignoreInvalidFields']))
  531. {
  532. $this->error("The field '$field' was not recognised.", $field, false);
  533. }
  534. return $validField;
  535. }
  536. /**
  537. * Internal function to set a field without any validation or type casting.
  538. * Use only when you're sure validation, etc isn't needed. The field will only
  539. * be set if the value has changed.
  540. *
  541. * @param string $table Table the field belongs to
  542. * @param string $field Name of the field
  543. * @param mixed $newValue Value for the field
  544. * @param boolean $forceSet If true, the set always goes through
  545. */
  546. protected function _setInternal($table, $field, $newValue, $forceSet = false)
  547. {
  548. $existingValue = $this->get($field, $table);
  549. if ($forceSet
  550. || $existingValue === null
  551. || !is_scalar($newValue)
  552. || !is_scalar($existingValue)
  553. || strval($newValue) != strval($existingValue)
  554. )
  555. {
  556. if ($newValue === $this->getExisting($field, $table))
  557. {
  558. unset($this->_newData[$table][$field]);
  559. }
  560. else
  561. {
  562. $this->_newData[$table][$field] = $newValue;
  563. }
  564. }
  565. }
  566. /**
  567. * Internal helper for calling set after save and not triggering an error.
  568. *
  569. * @param string Name of field to update
  570. * @param string Value to update with
  571. * @param string Table name, if empty then all tables with that column
  572. * @param array Options. See {@link $_setOptions).
  573. *
  574. * @return boolean
  575. */
  576. protected function _setPostSave($field, $newValue, $tableName = '', array $options = array())
  577. {
  578. $options['setAfterPreSave'] = true;
  579. return $this->set($field, $newValue, $tableName, $options);
  580. }
  581. /**
  582. * Determines if the provided value for a field is valid. The value may be
  583. * modified based on type, contraints, or other verification methods.
  584. *
  585. * @param string Name of the field.
  586. * @param array Data about the field (includes type, contraints, callback, etc)
  587. * @param mixed Value for the field
  588. * @param array Options. Uses {@link $_setOptions}.
  589. *
  590. * @return boolean
  591. */
  592. protected function _isFieldValueValid($fieldName, array $fieldData, &$value, array $options = array())
  593. {
  594. $fieldType = isset($fieldData['type']) ? $fieldData['type'] : self::TYPE_BINARY;
  595. $value = $this->_castValueToType($fieldType, $value, $fieldName, $fieldData);
  596. if (!empty($options['runVerificationCallback']) && !empty($fieldData['verification']))
  597. {
  598. if (!$this->_runVerificationCallback($fieldData['verification'], $value, $fieldData, $fieldName))
  599. {
  600. // verification callbacks are responsible for throwing errors
  601. return false;
  602. }
  603. }
  604. $checkLimits = $this->_applyFieldValueLimits($fieldType, $value, $fieldData);
  605. if ($checkLimits !== true)
  606. {
  607. if (empty($options['replaceInvalidWithDefault']) || !isset($fieldData['default']))
  608. {
  609. $this->error($checkLimits, $fieldName, false);
  610. return false;
  611. }
  612. else
  613. {
  614. $value = $fieldData['default'];
  615. }
  616. }
  617. return true;
  618. }
  619. /**
  620. * Casts the field value based on the specified type (TYPE_* constants).
  621. *
  622. * @param string $fieldType Type to cast to
  623. * @param mixed $value Value to cast
  624. * @param string $fieldName Name of the field being cast
  625. * @param array Array of all field data information, for extra options
  626. *
  627. * @return mixed
  628. */
  629. protected function _castValueToType($fieldType, $value, $fieldName, array $fieldData)
  630. {
  631. switch ($fieldType)
  632. {
  633. case self::TYPE_STRING:
  634. if (isset($fieldData['noTrim']))
  635. {
  636. return strval($value);
  637. }
  638. else
  639. {
  640. return trim(strval($value));
  641. }
  642. case self::TYPE_BINARY:
  643. return strval($value);
  644. case self::TYPE_UINT_FORCED:
  645. $value = intval($value);
  646. return ($value < 0 ? 0 : $value);
  647. case self::TYPE_UINT:
  648. case self::TYPE_INT:
  649. return intval($value);
  650. case self::TYPE_FLOAT:
  651. return strval($value) + 0;
  652. case self::TYPE_BOOLEAN:
  653. return ($value ? 1 : 0);
  654. case self::TYPE_SERIALIZED:
  655. if (!is_string($value))
  656. {
  657. return serialize($value);
  658. }
  659. if (@unserialize($value) === false && $value != serialize(false))
  660. {
  661. throw new XenForo_Exception('Value is not unserializable');
  662. }
  663. return $value;
  664. case self::TYPE_UNKNOWN:
  665. return $value; // unmodified
  666. default:
  667. throw new XenForo_Exception((
  668. ($fieldName === false)
  669. ? "There is no field type '$fieldType'."
  670. : "The field type specified for '$fieldName' is not valid ($fieldType)."
  671. ));
  672. }
  673. }
  674. /**
  675. * Applies value limits to a field based on type and other constraints.
  676. * Returns true if the field meets the constraints. The passed in value will
  677. * be modified by reference.
  678. *
  679. * @param string Type of the field. See the TYPE_* constants.
  680. * @param mixed Value for the field.
  681. * @param array Extra constraints
  682. *
  683. * @return boolean|string Either TRUE or an error message
  684. */
  685. protected function _applyFieldValueLimits($fieldType, &$value, array $extraLimits = array())
  686. {
  687. // constraints
  688. switch ($fieldType)
  689. {
  690. case self::TYPE_STRING:
  691. case self::TYPE_BINARY:
  692. $strlenFunc = ($fieldType == self::TYPE_STRING ? 'utf8_strlen' : 'strlen');
  693. if (isset($extraLimits['maxLength']) && $strlenFunc($value) > $extraLimits['maxLength'])
  694. {
  695. if ($this->_importMode)
  696. {
  697. if ($strlenFunc == 'utf8_strlen')
  698. {
  699. $value = utf8_substr($value, 0, $extraLimits['maxLength']);
  700. }
  701. else
  702. {
  703. $value = substr($value, 0, $extraLimits['maxLength']);
  704. }
  705. }
  706. else
  707. {
  708. return new XenForo_Phrase('please_enter_value_using_x_characters_or_fewer', array('count' => $extraLimits['maxLength']));
  709. }
  710. }
  711. break;
  712. case self::TYPE_UINT_FORCED:
  713. case self::TYPE_UINT:
  714. if ($value < 0)
  715. {
  716. if ($this->_importMode)
  717. {
  718. $value = 0;
  719. }
  720. else
  721. {
  722. return new XenForo_Phrase('please_enter_positive_whole_number');
  723. }
  724. }
  725. else if ($this->_importMode && $value > 4294967295)
  726. {
  727. $value = 4294967295;
  728. }
  729. break;
  730. case self::TYPE_INT:
  731. if ($this->_importMode)
  732. {
  733. if ($value > 2147483647)
  734. {
  735. $value = 2147483647;
  736. }
  737. else if ($value < -2147483648)
  738. {
  739. $value = -2147483648;
  740. }
  741. }
  742. break;
  743. }
  744. switch ($fieldType)
  745. {
  746. case self::TYPE_UINT_FORCED:
  747. case self::TYPE_UINT:
  748. case self::TYPE_INT:
  749. case self::TYPE_FLOAT:
  750. if (isset($extraLimits['min']) && $value < $extraLimits['min'])
  751. {
  752. if ($this->_importMode)
  753. {
  754. $value = $extraLimits['min'];
  755. }
  756. else
  757. {
  758. return new XenForo_Phrase('please_enter_number_that_is_at_least_x', array('min' => $extraLimits['min']));
  759. }
  760. }
  761. if (isset($extraLimits['max']) && $value > $extraLimits['max'])
  762. {
  763. if ($this->_importMode)
  764. {
  765. $value = $extraLimits['max'];
  766. }
  767. else
  768. {
  769. return new XenForo_Phrase('please_enter_number_that_is_no_more_than_x', array('max' => $extraLimits['max']));
  770. }
  771. }
  772. break;
  773. }
  774. if (isset($extraLimits['allowedValues']) && is_array($extraLimits['allowedValues']) && !in_array($value, $extraLimits['allowedValues']))
  775. {
  776. return new XenForo_Phrase('please_enter_valid_value');
  777. }
  778. return true;
  779. }
  780. /**
  781. * Runs the verification callback. This callback may modify the value if it chooses to.
  782. * Callback receives 2 params: the value and this object. The callback must return true
  783. * if the value was valid.
  784. *
  785. * Returns true if the verification was successful.
  786. *
  787. * @param callback Callback to run. Use an array with a string '$this' to callback to this object.
  788. * @param mixed Value to verify
  789. * @param array Information about the field, including all constraints to be applied
  790. *
  791. * @return boolean
  792. */
  793. protected function _runVerificationCallback($callback, &$value, array $fieldData, $fieldName = false)
  794. {
  795. if ($this->_importMode)
  796. {
  797. return true;
  798. }
  799. if (is_array($callback) && isset($callback[0]) && $callback[0] == '$this')
  800. {
  801. $callback[0] = $this;
  802. }
  803. return (boolean)call_user_func_array($callback,
  804. array(&$value, $this, $fieldName, $fieldData)
  805. );
  806. }
  807. /**
  808. * Helper method to bulk set values from an array.
  809. *
  810. * @param array Key-value pairs of fields and values.
  811. * @param array Options to pass into {@link set()}. See {@link $_setOptions}.
  812. */
  813. public function bulkSet(array $fields, array $options = null)
  814. {
  815. foreach ($fields AS $field => $value)
  816. {
  817. $this->set($field, $value, '', $options);
  818. }
  819. }
  820. /**
  821. * Sets the error handler type.
  822. *
  823. * @param integer $errorHandler
  824. */
  825. public function setErrorHandler($errorHandler)
  826. {
  827. $this->_errorHandler = intval($errorHandler);
  828. }
  829. /**
  830. * Gets data related to this object regardless of where it is defined (new or old).
  831. *
  832. * @param string Field name
  833. * @param string Table name, if empty loops through tables until first match
  834. *
  835. * @return mixed Returns null if the specified field could not be found.
  836. */
  837. public function get($field, $tableName = '')
  838. {
  839. $tables = $this->_getTableList($tableName);
  840. foreach ($tables AS $tableName)
  841. {
  842. if (isset($this->_newData[$tableName][$field]))
  843. {
  844. return $this->_newData[$tableName][$field];
  845. }
  846. else if (isset($this->_existingData[$tableName][$field]))
  847. {
  848. return $this->_existingData[$tableName][$field];
  849. }
  850. }
  851. return null;
  852. }
  853. /**
  854. * Explictly gets data from the new data array. Returns null if not set.
  855. *
  856. * @param string Field name
  857. * @param string Table name, if empty loops through tables until first match
  858. *
  859. * @return mixed
  860. */
  861. public function getNew($field, $tableName = '')
  862. {
  863. $tables = $this->_getTableList($tableName);
  864. foreach ($tables AS $tableName)
  865. {
  866. if (isset($this->_newData[$tableName][$field]))
  867. {
  868. return $this->_newData[$tableName][$field];
  869. }
  870. }
  871. return null;
  872. }
  873. /**
  874. * Returns true if changes have been made to this data.
  875. *
  876. * @return boolean
  877. */
  878. public function hasChanges()
  879. {
  880. return (!empty($this->_newData));
  881. }
  882. /**
  883. * Determines whether the named field has been changed/set.
  884. *
  885. * @param string Field name
  886. * @param string Table name, if empty loops through tables until first match
  887. *
  888. * @return boolean
  889. */
  890. public function isChanged($field, $tableName = '')
  891. {
  892. return ($this->getNew($field, $tableName) !== null);
  893. }
  894. /**
  895. * Explictly gets data from the existing data array. Returns null if not set.
  896. *
  897. * @param string Field name
  898. * @param string Table name, if empty loops through tables until first match
  899. *
  900. * @return mixed
  901. */
  902. public function getExisting($field, $tableName = '')
  903. {
  904. $tables = $this->_getTableList($tableName);
  905. foreach ($tables AS $tableName)
  906. {
  907. if (isset($this->_existingData[$tableName][$field]))
  908. {
  909. return $this->_existingData[$tableName][$field];
  910. }
  911. }
  912. return null;
  913. }
  914. /**
  915. * Merges the new and existing data to show a "final" view of the data. This will
  916. * generally reflect what is in the database.
  917. *
  918. * If no table is specified, all data will be flattened into one array. New data
  919. * takes priority over existing data, and earlier tables in the list take priority
  920. * over later tables.
  921. *
  922. * @param string $tableName
  923. *
  924. * @return array
  925. */
  926. public function getMergedData($tableName = '')
  927. {
  928. $tables = $this->_getTableList($tableName);
  929. $output = array();
  930. // loop through all tables and use the first value that comes up for a field.
  931. // this assumes that the more "primary" tables come first
  932. foreach ($tables AS $tableName)
  933. {
  934. if (isset($this->_newData[$tableName]))
  935. {
  936. $output += $this->_newData[$tableName];
  937. }
  938. if (isset($this->_existingData[$tableName]))
  939. {
  940. $output += $this->_existingData[$tableName];
  941. }
  942. }
  943. return $output;
  944. }
  945. /**
  946. * Gets the existing data only into a flat view of the data.
  947. *
  948. * @param string $tableName
  949. *
  950. * @return array
  951. */
  952. public function getMergedExistingData($tableName = '')
  953. {
  954. $tables = $this->_getTableList($tableName);
  955. $output = array();
  956. // loop through all tables and use the first value that comes up for a field.
  957. // this assumes that the more "primary" tables come first
  958. foreach ($tables AS $tableName)
  959. {
  960. if (isset($this->_existingData[$tableName]))
  961. {
  962. $output += $this->_existingData[$tableName];
  963. }
  964. }
  965. return $output;
  966. }
  967. /**
  968. * Trigger an error with the specified string (or phrase object). Depending on the
  969. * type of error handler chosen, this may throw an exception.
  970. *
  971. * @param string|XenForo_Phrase $error Error message
  972. * @param string|false $errorKey Unique key for the error. Used to prevent multiple errors from the same field being displayed.
  973. * @param boolean $specificError If true and error key specified, overwrites an existing error with this name
  974. */
  975. public function error($error, $errorKey = false, $specificError = true)
  976. {
  977. if ($errorKey !== false)
  978. {
  979. if ($specificError || !isset($this->_errors[strval($errorKey)]))
  980. {
  981. $this->_errors[strval($errorKey)] = $error;
  982. }
  983. }
  984. else
  985. {
  986. $this->_errors[] = $error;
  987. }
  988. if ($this->_errorHandler == self::ERROR_EXCEPTION)
  989. {
  990. throw new XenForo_Exception($error, true);
  991. }
  992. }
  993. /**
  994. * Merges a set of errors into this DW.
  995. *
  996. * @param array $errors
  997. */
  998. public function mergeErrors(array $errors)
  999. {
  1000. foreach ($errors AS $errorKey => $error)
  1001. {
  1002. if (is_integer($errorKey))
  1003. {
  1004. $errorKey = false;
  1005. }
  1006. $this->error($error, $errorKey);
  1007. }
  1008. }
  1009. /**
  1010. * Gets all errors that have been triggered. If {@link preSave()} has been
  1011. * called and this returns an empty array, a {@link save()} should go through.
  1012. *
  1013. * @return array
  1014. */
  1015. public function getErrors()
  1016. {
  1017. return $this->_errors;
  1018. }
  1019. /**
  1020. * Determines if this DW has errors.
  1021. *
  1022. * @return boolean
  1023. */
  1024. public function hasErrors()
  1025. {
  1026. return (count($this->_errors) > 0);
  1027. }
  1028. /**
  1029. * Gets the specific named error if reported.
  1030. *
  1031. * @param string $errorKey
  1032. *
  1033. * @return string
  1034. */
  1035. public function getError($errorKey)
  1036. {
  1037. return (isset($this->_errors[$errorKey]) ? $this->_errors[$errorKey] : false);
  1038. }
  1039. /**
  1040. * Gets all new data that has been set to date
  1041. *
  1042. * @return array
  1043. */
  1044. public function getNewData()
  1045. {
  1046. return $this->_newData;
  1047. }
  1048. /**
  1049. * Updates the version ID field for the specified record. Th add-on ID
  1050. * is determined from a previously set value, so it should not be updated
  1051. * after this is called.
  1052. *
  1053. * @param string $versionIdField Name of the field the version ID will be written to
  1054. * @param string $versionStringField Name of the field where the version string will be written to
  1055. * @param string $addOnIdField Name of the field the add-on ID is kept in
  1056. *
  1057. * @return integer Version ID to use
  1058. */
  1059. public function updateVersionId($versionIdField = 'version_id', $versionStringField = 'version_string', $addOnIdField = 'addon_id')
  1060. {
  1061. $addOnId = $this->get($addOnIdField);
  1062. if ($addOnId)
  1063. {
  1064. $version = $this->getModelFromCache('XenForo_Model_AddOn')->getAddOnVersion($addOnId);
  1065. if (!$version)
  1066. {
  1067. $this->set($addOnIdField, ''); // no add-on found, make it custom
  1068. $versionId = 0;
  1069. $versionString = '';
  1070. }
  1071. else
  1072. {
  1073. $versionId = $version['version_id'];
  1074. $versionString = $version['version_string'];
  1075. }
  1076. }
  1077. else
  1078. {
  1079. $versionId = 0;
  1080. $versionString = '';
  1081. }
  1082. $this->set($versionIdField, $versionId);
  1083. $this->set($versionStringField, $versionString);
  1084. return $versionId;
  1085. }
  1086. /**
  1087. * Determines if we have errors that would prevent a save. If the silent error
  1088. * handler is used, errors simply trigger a return of true (yes, we have errors);
  1089. * otherwise, errors trigger an exception. Generally, if errors are to be handled,
  1090. * save shouldn't be called.
  1091. *
  1092. * @return boolean True if there are errors
  1093. */
  1094. protected function _haveErrorsPreventSave()
  1095. {
  1096. if ($this->_errors)
  1097. {
  1098. if ($this->_errorHandler == self::ERROR_SILENT)
  1099. {
  1100. return true;
  1101. }
  1102. else
  1103. {
  1104. throw new XenForo_Exception($this->_errors, true);
  1105. }
  1106. }
  1107. return false;
  1108. }
  1109. /**
  1110. * External handler to get the SQL update/delete condition for this data. If there is no
  1111. * existing data, this always returns an empty string, otherwise it proxies
  1112. * to {@link _getUpdateCondition()}, which is an abstract function.
  1113. *
  1114. * @param string Name of the table to fetch the condition for
  1115. *
  1116. * @return string
  1117. */
  1118. public function getUpdateCondition($tableName)
  1119. {
  1120. if (!$this->_existingData || !isset($this->_existingData[$tableName]))
  1121. {
  1122. return '';
  1123. }
  1124. else
  1125. {
  1126. return $this->_getUpdateCondition($tableName);
  1127. }
  1128. }
  1129. /**
  1130. * Saves the changes to the data. This either updates an existing record or inserts
  1131. * a new one, depending whether {@link setExistingData} was called.
  1132. *
  1133. * After a successful insert with an auto_increment field, the value will be stored
  1134. * into the new data array with a field marked as autoIncrement. Use {@link get()}
  1135. * to get access to it.
  1136. *
  1137. * @return boolean True on success
  1138. */
  1139. public function save()
  1140. {
  1141. $this->preSave();
  1142. if ($this->_haveErrorsPreventSave())
  1143. {
  1144. return false;
  1145. }
  1146. if (!$this->_newData)
  1147. {
  1148. // nothing to change; error if insert, act as if everything is ok on update
  1149. if (!$this->_existingData)
  1150. {
  1151. throw new XenForo_Exception('Cannot save item when no data has been set');
  1152. }
  1153. }
  1154. $this->_beginDbTransaction();
  1155. try
  1156. {
  1157. $this->_save();
  1158. if (!$this->_importMode)
  1159. {
  1160. $this->_postSave();
  1161. }
  1162. }
  1163. catch (Exception $e)
  1164. {
  1165. $this->_rollbackDbTransaction();
  1166. throw $e;
  1167. }
  1168. $this->_commitDbTransaction();
  1169. if (!$this->_importMode)
  1170. {
  1171. $this->_postSaveAfterTransaction();
  1172. }
  1173. return true;
  1174. }
  1175. /**
  1176. * Public facing handler for final verification before a save. Any verification
  1177. * or complex defaults may be set by this.
  1178. *
  1179. * It is generally not advisable to override this function. If you just want to
  1180. * add pre-save behaviors, override {@link _preSave()}.
  1181. */
  1182. public function preSave()
  1183. {
  1184. if ($this->_preSaveCalled)
  1185. {
  1186. return;
  1187. }
  1188. $this->_preSaveDefaults();
  1189. if (!$this->_importMode)
  1190. {
  1191. $this->_preSave();
  1192. if (!$this->_existingData)
  1193. {
  1194. $this->_resolveDefaultsAndRequiredFieldsForInsert();
  1195. }
  1196. else
  1197. {
  1198. $this->_checkRequiredFieldsForUpdate();
  1199. }
  1200. }
  1201. else
  1202. {
  1203. $this->_resolveDefaultsAndRequiredFieldsForInsert(false);
  1204. }
  1205. $this->_preSaveCalled = true;
  1206. }
  1207. /**
  1208. * Method designed to be overridden by child classes to add pre-save behaviors that
  1209. * set dynamic defaults. This is still called in import mode.
  1210. */
  1211. protected function _preSaveDefaults()
  1212. {
  1213. }
  1214. /**
  1215. * Method designed to be overridden by child classes to add pre-save behaviors. This
  1216. * is not callbed in import mode.
  1217. */
  1218. protected function _preSave()
  1219. {
  1220. }
  1221. /**
  1222. * This resolves unset fields to their defaults (if available) and the checks
  1223. * for required fields that are unset or empty. If a required field is not
  1224. * set properly, an error is thrown.
  1225. *
  1226. * @param boolean $checkRequired If true, checks required fields
  1227. */
  1228. protected function _resolveDefaultsAndRequiredFieldsForInsert($checkRequired = true)
  1229. {
  1230. foreach ($this->_fields AS $tableName => $fields)
  1231. {
  1232. foreach ($fields AS $field => $fieldData)
  1233. {
  1234. // when default is an array it references another column in an earlier table
  1235. if (!isset($this->_newData[$tableName][$field]) && isset($fieldData['default']) && !is_array($fieldData['default']))
  1236. {
  1237. $this->_setInternal($tableName, $field, $fieldData['default']);
  1238. }
  1239. if ($checkRequired && !empty($fieldData['required']) && (!isset($this->_newData[$tableName][$field]) || $this->_newData[$tableName][$field] === ''))
  1240. {
  1241. // references an externalID, we can't resolve this
  1242. if (isset($fieldData['default']) && is_array($fieldData['default']))
  1243. {
  1244. continue;
  1245. }
  1246. $this->_triggerRequiredFieldError($tableName, $field);
  1247. }
  1248. }
  1249. }
  1250. }
  1251. /**
  1252. * Checks that required field values are still maintained on updates.
  1253. */
  1254. protected function _checkRequiredFieldsForUpdate()
  1255. {
  1256. foreach ($this->_fields AS $tableName => $fields)
  1257. {
  1258. foreach ($fields AS $field => $fieldData)
  1259. {
  1260. if (!isset($this->_newData[$tableName][$field]))
  1261. {
  1262. continue;
  1263. }
  1264. if (!empty($fieldData['required']) && $this->_newData[$tableName][$field] === '')
  1265. {
  1266. $this->_triggerRequiredFieldError($tableName, $field);
  1267. }
  1268. }
  1269. }
  1270. }
  1271. /**
  1272. * Triggers the error for a required field not being specified.
  1273. *
  1274. * @param string $tableName
  1275. * @param string $field
  1276. */
  1277. protected function _triggerRequiredFieldError($tableName, $field)
  1278. {
  1279. $errorText = $this->_getSpecificRequiredFieldErrorText($tableName, $field);
  1280. if ($errorText)
  1281. {
  1282. $this->error($errorText, $field, false);
  1283. }
  1284. else if (isset($this->_fields[$tableName][$field]['requiredError']))
  1285. {
  1286. $this->error(new XenForo_Phrase($this->_fields[$tableName][$field]['requiredError']), $field, false);
  1287. }
  1288. else
  1289. {
  1290. $this->error(new XenForo_Phrase('please_enter_value_for_required_field_x', array('field' => $field)), $field, false);
  1291. }
  1292. }
  1293. /**
  1294. * Gets the error text (or phrase) for a specific required field. Concrete DWs
  1295. * may override this to get nicer error messages for specific fields.
  1296. *
  1297. * @param string $tableName
  1298. * @param string $field
  1299. *
  1300. * @return false|string|XenForo_Phrase
  1301. */
  1302. protected function _getSpecificRequiredFieldErrorText($tableName, $field)
  1303. {
  1304. return false;
  1305. }
  1306. /**
  1307. * Returns true if this DW is updating a record, rather than inserting one.
  1308. *
  1309. * @return boolean
  1310. */
  1311. public function isUpdate()
  1312. {
  1313. return !empty($this->_existingData);
  1314. }
  1315. /**
  1316. * Returns true if this DW is inserting a record, rather than updating one.
  1317. *
  1318. * @return boolean
  1319. */
  1320. public function isInsert()
  1321. {
  1322. return !$this->isUpdate();
  1323. }
  1324. /**
  1325. * Internal save handler. Deals with both updates and inserts.
  1326. */
  1327. protected function _save()
  1328. {
  1329. if ($this->isUpdate())
  1330. {
  1331. $this->_update();
  1332. }
  1333. else
  1334. {
  1335. $this->_insert();
  1336. }
  1337. }
  1338. /**
  1339. * Internal save handler.
  1340. */
  1341. protected function _insert()
  1342. {
  1343. foreach ($this->_getTableList() AS $tableName)
  1344. {
  1345. $this->_db->insert($tableName, $this->_newData[$tableName]);
  1346. $this->_setAutoIncrementValue($this->_db->lastInsertId(), $tableName, true);
  1347. }
  1348. }
  1349. /**
  1350. * Internal update handler.
  1351. */
  1352. protected function _update()
  1353. {
  1354. foreach ($this->_getTableList() AS $tableName)
  1355. {
  1356. if (!($update = $this->getUpdateCondition($tableName)) || empty($this->_newData[$tableName]))
  1357. {
  1358. continue;
  1359. }
  1360. $this->_db->update($tableName, $this->_newData[$tableName], $update);
  1361. }
  1362. }
  1363. /**
  1364. * Sets the auto-increment value to the auto increment field, if there is one.
  1365. * If the ID passed in is 0, nothing will be updated.
  1366. *
  1367. * @param integer Auto-increment value from 0.
  1368. * @param string Name of the table set the auto increment field in
  1369. * @param bool Update all tables with cross referenced auto increment fields
  1370. *
  1371. * @return boolean True on update
  1372. */
  1373. protected function _setAutoIncrementValue($insertId, $tableName, $updateAll = false)
  1374. {
  1375. if (!$insertId)
  1376. {
  1377. return false;
  1378. }
  1379. $field = $this->_getAutoIncrementField($tableName);
  1380. if (!$field)
  1381. {
  1382. return false;
  1383. }
  1384. $this->_newData[$tableName][$field] = $insertId;
  1385. if ($updateAll)
  1386. {
  1387. foreach ($this->_fields AS $table => $fieldData)
  1388. {
  1389. foreach ($fieldData AS $fieldName => $fieldType)
  1390. {
  1391. if (!isset($fieldType['default']) || !is_array($fieldType['default']))
  1392. {
  1393. continue;
  1394. }
  1395. if ($fieldType['default'][0] == $tableName && !$this->get($field, $table))
  1396. {
  1397. $this->_newData[$table][$field] = $insertId;
  1398. }
  1399. }
  1400. }
  1401. }
  1402. return true;
  1403. }
  1404. /**
  1405. * Finds the auto-increment field in the list of fields. This field is simply
  1406. * the first field tagged with the autoIncrement flag.
  1407. *
  1408. * @param string Name of the table to obtain the field for
  1409. *
  1410. * @return string|false Name of the field if found or false
  1411. */
  1412. protected function _getAutoIncrementField($tableName)
  1413. {
  1414. foreach ($this->_fields[$tableName] AS $field => $fieldData)
  1415. {
  1416. if (!empty($fieldData['autoIncrement']))
  1417. {
  1418. return $field;
  1419. }
  1420. }
  1421. return false;
  1422. }
  1423. /**
  1424. * Finds the first table in the _fields array
  1425. *
  1426. * @return string|false Name of the table or false
  1427. */
  1428. protected function _getPrimaryTable()
  1429. {
  1430. $tables = array_keys($this->_fields);
  1431. return (isset($tables[0]) ? $tables[0] : false);
  1432. }
  1433. /**
  1434. * Gets an array with a list of all table names or, if provided, only
  1435. * the specified table.
  1436. *
  1437. * @param string $tableName Optional table to limit results to.
  1438. *
  1439. * @return array
  1440. */
  1441. protected function _getTableList($tableName = '')
  1442. {
  1443. return ($tableName ? array($tableName) : array_keys($this->_fields));
  1444. }
  1445. /**
  1446. * Method designed to be overridden by child classes to add post-save behaviors.
  1447. * This is not called in import mode.
  1448. */
  1449. protected function _postSave()
  1450. {
  1451. }
  1452. /**
  1453. * Method designed to be overridden by child classes to add post-save behaviors
  1454. * that should be run after the transaction is committed. This is not called in
  1455. * import mode.
  1456. */
  1457. protected function _postSaveAfterTransaction()
  1458. {
  1459. }
  1460. /**
  1461. * Deletes the record that was selected by a call to {@link setExistingData()}.
  1462. *
  1463. * @return boolean True on success
  1464. */
  1465. public function delete()
  1466. {
  1467. $this->preDelete();
  1468. if ($this->_haveErrorsPreventSave())
  1469. {
  1470. return false;
  1471. }
  1472. $this->_beginDbTransaction();
  1473. $this->_delete();
  1474. $this->_postDelete();
  1475. $this->_commitDbTransaction();
  1476. return true;
  1477. }
  1478. /**
  1479. * Public facing handler for final verification before a delete. Any verification
  1480. * or complex defaults may be set by this.
  1481. *
  1482. * It is generally not advisable to override this function. If you just want to
  1483. * add pre-delete behaviors, override {@link _preDelete()}.
  1484. */
  1485. public function preDelete()
  1486. {
  1487. if ($this->_preDeleteCalled)
  1488. {
  1489. return;
  1490. }
  1491. $this->_preDelete();
  1492. $this->_preDeleteCalled = true;
  1493. }
  1494. /**
  1495. * Method designed to be overridden by child classes to add pre-delete behaviors.
  1496. */
  1497. protected function _preDelete()
  1498. {
  1499. }
  1500. /**
  1501. * Internal handler for a delete action. Actually does the delete.
  1502. */
  1503. protected function _delete()
  1504. {
  1505. foreach ($this->_getTableList() AS $tableName)
  1506. {
  1507. $condition = $this->getUpdateCondition($tableName);
  1508. if (!$condition)
  1509. {
  1510. throw new XenForo_Exception('Cannot delete data without a condition');
  1511. }
  1512. $this->_db->delete($tableName, $condition);
  1513. }
  1514. }
  1515. /**
  1516. * Method designed to be overridden by child classes to add pre-delete behaviors.
  1517. */
  1518. protected function _postDelete()
  1519. {
  1520. }
  1521. /**
  1522. * Inserts or updates a master (language 0) phrase. Errors will be silently ignored.
  1523. *
  1524. * @param string $title
  1525. * @param string $text
  1526. * @param string $addOnId
  1527. */
  1528. protected function _insertOrUpdateMasterPhrase($title, $text, $addOnId)
  1529. {
  1530. $this->_getPhraseModel()->insertOrUpdateMasterPhrase($title, $text, $addOnId);
  1531. }
  1532. /**
  1533. * Deletes the named master phrase if it exists.
  1534. *
  1535. * @param string $title
  1536. */
  1537. protected function _deleteMasterPhrase($title)
  1538. {
  1539. $this->_getPhraseModel()->deleteMasterPhrase($title);
  1540. }
  1541. /**
  1542. * Renames a master phrase. If you get a conflict, it will
  1543. * be silently ignored.
  1544. *
  1545. * @param string $oldName
  1546. * @param string $newName
  1547. */
  1548. protected function _renameMasterPhrase($oldName, $newName)
  1549. {
  1550. $this->_getPhraseModel()->renameMasterPhrase($oldName, $newName);
  1551. }
  1552. /**
  1553. * Starts a new database transaction.
  1554. */
  1555. protected function _beginDbTransaction()
  1556. {
  1557. XenForo_Db::beginTransaction($this->_db);
  1558. return true;
  1559. }
  1560. /**
  1561. * Commits a new database transaction.
  1562. */
  1563. protected function _commitDbTransaction()
  1564. {
  1565. XenForo_Db::commit($this->_db);
  1566. return true;
  1567. }
  1568. /**
  1569. * Rolls a database transaction back.
  1570. */
  1571. protected function _rollbackDbTransaction()
  1572. {
  1573. XenForo_Db::rollback($this->_db);
  1574. return true;
  1575. }
  1576. /**
  1577. * Gets the specified model object from the cache. If it does not exist,
  1578. * it will be instantiated.
  1579. *
  1580. * @param string $class Name of the class to load
  1581. *
  1582. * @return XenForo_Model
  1583. */
  1584. public function getModelFromCache($class)
  1585. {
  1586. if (!isset(self::$_modelCache[$class]))
  1587. {
  1588. self::$_modelCache[$class] = XenForo_Model::create($class);
  1589. }
  1590. return self::$_modelCache[$class];
  1591. }
  1592. /**
  1593. * Returns the user model
  1594. *
  1595. * @return XenForo_Model_User
  1596. */
  1597. protected function _getUserModel()
  1598. {
  1599. return $this->getModelFromCache('XenForo_Model_User');
  1600. }
  1601. /**
  1602. * Returns the phrase model
  1603. *
  1604. * @return XenForo_Model_Phrase
  1605. */
  1606. protected function _getPhraseModel()
  1607. {
  1608. return $this->getModelFromCache('XenForo_Model_Phrase');
  1609. }
  1610. /**
  1611. * Returns the news feed model
  1612. *
  1613. * @return XenForo_Model_NewsFeed
  1614. */
  1615. protected function _getNewsFeedModel()
  1616. {
  1617. return $this->getModelFromCache('XenForo_Model_NewsFeed');
  1618. }
  1619. /**
  1620. * Returns the alert model
  1621. *
  1622. * @return XenForo_Model_Alert
  1623. */
  1624. protected function _getAlertModel()
  1625. {
  1626. return $this->getModelFromCache('XenForo_Model_Alert');
  1627. }
  1628. /**
  1629. * Helper method to get the cache object.
  1630. *
  1631. * @return Zend_Cache_Core|Zend_Cache_Frontend|false
  1632. */
  1633. protected function _getCache()
  1634. {
  1635. if ($this->_cache === null)
  1636. {
  1637. $this->_cache = XenForo_Application::get('cache');
  1638. }
  1639. return $this->_cache;
  1640. }
  1641. /**
  1642. * Factory method to get the named data writer. The class must exist or be autoloadable
  1643. * or an exception will be thrown.
  1644. *
  1645. * @param string Class to load
  1646. * @param constant Error handler. See {@link ERROR_EXCEPTION} and related.
  1647. * @param array|null Dependencies to inject. See {@link __construct()}.
  1648. *
  1649. * @return XenForo_DataWriter
  1650. */
  1651. public static function create($class, $errorHandler = self::ERROR_ARRAY, array $inject = null)
  1652. {
  1653. $createClass = XenForo_Application::resolveDynamicClass($class, 'datawriter');
  1654. if (!$createClass)
  1655. {
  1656. throw new XenForo_Exception("Invalid data writer '$class' specified");
  1657. }
  1658. return new $createClass($errorHandler, $inject);
  1659. }
  1660. }