PageRenderTime 59ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/ObjectModel.php

https://bitbucket.org/marcenuc/prestashop
PHP | 1531 lines | 979 code | 199 blank | 353 comment | 284 complexity | 4d35057dcbfb012d21a2e8e3fe4c17db MD5 | raw file
Possible License(s): LGPL-2.1, LGPL-3.0
  1. <?php
  2. /*
  3. * 2007-2012 PrestaShop
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@prestashop.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
  18. * versions in the future. If you wish to customize PrestaShop for your
  19. * needs please refer to http://www.prestashop.com for more information.
  20. *
  21. * @author PrestaShop SA <contact@prestashop.com>
  22. * @copyright 2007-2012 PrestaShop SA
  23. * @version Release: $Revision: 7499 $
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. * International Registered Trademark & Property of PrestaShop SA
  26. */
  27. abstract class ObjectModelCore
  28. {
  29. /**
  30. * List of field types
  31. */
  32. const TYPE_INT = 1;
  33. const TYPE_BOOL = 2;
  34. const TYPE_STRING = 3;
  35. const TYPE_FLOAT = 4;
  36. const TYPE_DATE = 5;
  37. const TYPE_HTML = 6;
  38. const TYPE_NOTHING = 7;
  39. /**
  40. * List of data to format
  41. */
  42. const FORMAT_COMMON = 1;
  43. const FORMAT_LANG = 2;
  44. const FORMAT_SHOP = 3;
  45. /**
  46. * List of association types
  47. */
  48. const HAS_ONE = 1;
  49. const HAS_MANY = 2;
  50. /** @var integer Object id */
  51. public $id;
  52. /** @var integer lang id */
  53. protected $id_lang = null;
  54. protected $id_shop = null;
  55. public $id_shop_list = null;
  56. protected $get_shop_from_context = true;
  57. protected static $fieldsRequiredDatabase = null;
  58. /**
  59. * @deprecated 1.5.0 This property shouldn't be overloaded anymore in class, use static $definition['table'] property instead
  60. */
  61. protected $table;
  62. /**
  63. * @deprecated 1.5.0 This property shouldn't be overloaded anymore in class, use static $definition['primary'] property instead
  64. */
  65. protected $identifier;
  66. /**
  67. * @deprecated 1.5.0 This property shouldn't be overloaded anymore in class, use static $definition['fields'] property instead
  68. */
  69. protected $fieldsRequired = array();
  70. /**
  71. * @deprecated 1.5.0 This property shouldn't be overloaded anymore in class, use static $definition['fields'] property instead
  72. */
  73. protected $fieldsSize = array();
  74. /**
  75. * @deprecated 1.5.0 This property shouldn't be overloaded anymore in class, use static $definition['fields'] property instead
  76. */
  77. protected $fieldsValidate = array();
  78. /**
  79. * @deprecated 1.5.0 This property shouldn't be overloaded anymore in class, use static $definition['fields'] property instead
  80. */
  81. protected $fieldsRequiredLang = array();
  82. /**
  83. * @deprecated 1.5.0 This property shouldn't be overloaded anymore in class, use static $definition['fields'] property instead
  84. */
  85. protected $fieldsSizeLang = array();
  86. /**
  87. * @deprecated 1.5.0 This property shouldn't be overloaded anymore in class, use static $definition['fields'] property instead
  88. */
  89. protected $fieldsValidateLang = array();
  90. /**
  91. * @deprecated 1.5.0
  92. */
  93. protected $tables = array();
  94. /** @var array tables */
  95. protected $webserviceParameters = array();
  96. /** @var string path to image directory. Used for image deletion. */
  97. protected $image_dir = null;
  98. /** @var string file type of image files. Used for image deletion. */
  99. protected $image_format = 'jpg';
  100. /**
  101. * @var array Contain object definition
  102. * @since 1.5.0
  103. */
  104. public static $definition = array();
  105. /**
  106. * @var array Contain current object definition
  107. */
  108. protected $def;
  109. /**
  110. * @var array List of specific fields to update (all fields if null)
  111. */
  112. protected $update_fields = null;
  113. /**
  114. * @var Db An instance of the db in order to avoid calling Db::getInstance() thousands of time
  115. */
  116. protected static $db = false;
  117. /**
  118. * Returns object validation rules (fields validity)
  119. *
  120. * @param string $class Child class name for static use (optional)
  121. * @return array Validation rules (fields validity)
  122. */
  123. public static function getValidationRules($class = __CLASS__)
  124. {
  125. $object = new $class();
  126. return array(
  127. 'required' => $object->fieldsRequired,
  128. 'size' => $object->fieldsSize,
  129. 'validate' => $object->fieldsValidate,
  130. 'requiredLang' => $object->fieldsRequiredLang,
  131. 'sizeLang' => $object->fieldsSizeLang,
  132. 'validateLang' => $object->fieldsValidateLang,
  133. );
  134. }
  135. /**
  136. * Build object
  137. *
  138. * @param int $id Existing object id in order to load object (optional)
  139. * @param int $id_lang Required if object is multilingual (optional)
  140. * @param int $id_shop ID shop for objects with multishop on langs
  141. */
  142. public function __construct($id = null, $id_lang = null, $id_shop = null)
  143. {
  144. if (!ObjectModel::$db)
  145. ObjectModel::$db = Db::getInstance();
  146. $this->def = self::getDefinition($this);
  147. $this->setDefinitionRetrocompatibility();
  148. if ($id_lang !== null)
  149. $this->id_lang = (Language::getLanguage($id_lang) !== false) ? $id_lang : Configuration::get('PS_LANG_DEFAULT');
  150. if ($id_shop && $this->isMultishop())
  151. {
  152. $this->id_shop = (int)$id_shop;
  153. $this->get_shop_from_context = false;
  154. }
  155. if ($this->isMultishop() && !$this->id_shop)
  156. $this->id_shop = Context::getContext()->shop->id;
  157. if (!Validate::isTableOrIdentifier($this->def['primary']) || !Validate::isTableOrIdentifier($this->def['table']))
  158. throw new PrestaShopException('Identifier or table format not valid for class '.get_class($this));
  159. if ($id)
  160. {
  161. // Load object from database if object id is present
  162. $cache_id = 'objectmodel_'.$this->def['table'].'_'.(int)$id.'_'.(int)$id_shop.'_'.(int)$id_lang;
  163. if (!Cache::isStored($cache_id))
  164. {
  165. $sql = new DbQuery();
  166. $sql->from($this->def['table'], 'a');
  167. $sql->where('a.'.$this->def['primary'].' = '.(int)$id);
  168. // Get lang informations
  169. if ($id_lang)
  170. {
  171. $sql->leftJoin($this->def['table'].'_lang', 'b', 'a.'.$this->def['primary'].' = b.'.$this->def['primary'].' AND b.id_lang = '.(int)$id_lang);
  172. if ($this->id_shop && !empty($this->def['multilang_shop']))
  173. $sql->where('b.id_shop = '.$this->id_shop);
  174. }
  175. // Get shop informations
  176. if (Shop::isTableAssociated($this->def['table']))
  177. $sql->leftJoin($this->def['table'].'_shop', 'c', 'a.'.$this->def['primary'].' = c.'.$this->def['primary'].' AND c.id_shop = '.(int)$this->id_shop);
  178. if ($row = ObjectModel::$db->getRow($sql))
  179. Cache::store($cache_id, $row);
  180. }
  181. $result = Cache::retrieve($cache_id);
  182. if ($result)
  183. {
  184. $this->id = (int)$id;
  185. foreach ($result as $key => $value)
  186. if (array_key_exists($key, $this))
  187. $this->{$key} = $value;
  188. if (!$id_lang && isset($this->def['multilang']) && $this->def['multilang'])
  189. {
  190. $sql = 'SELECT * FROM `'.pSQL(_DB_PREFIX_.$this->def['table']).'_lang`
  191. WHERE `'.$this->def['primary'].'` = '.(int)$id
  192. .(($this->id_shop && $this->isLangMultishop()) ? ' AND `id_shop` = '.$this->id_shop : '');
  193. $result = ObjectModel::$db->executeS($sql);
  194. if ($result)
  195. foreach ($result as $row)
  196. foreach ($row as $key => $value)
  197. {
  198. if (array_key_exists($key, $this) && $key != $this->def['primary'])
  199. {
  200. if (!is_array($this->{$key}))
  201. $this->{$key} = array();
  202. $this->{$key}[$row['id_lang']] = $value;
  203. }
  204. }
  205. }
  206. }
  207. }
  208. if (!is_array(self::$fieldsRequiredDatabase))
  209. {
  210. $fields = $this->getfieldsRequiredDatabase(true);
  211. if ($fields)
  212. foreach ($fields as $row)
  213. self::$fieldsRequiredDatabase[$row['object_name']][(int)$row['id_required_field']] = pSQL($row['field_name']);
  214. else
  215. self::$fieldsRequiredDatabase = array();
  216. }
  217. }
  218. /**
  219. * Prepare fields for ObjectModel class (add, update)
  220. * All fields are verified (pSQL, intval...)
  221. *
  222. * @return array All object fields
  223. */
  224. public function getFields()
  225. {
  226. $this->validateFields();
  227. $fields = $this->formatFields(self::FORMAT_COMMON);
  228. // For retro compatibility
  229. if (Shop::isTableAssociated($this->def['table']))
  230. $fields = array_merge($fields, $this->getFieldsShop());
  231. // Ensure that we get something to insert
  232. if (!$fields && isset($this->id) && Validate::isUnsignedId($this->id))
  233. $fields[$this->def['primary']] = $this->id;
  234. return $fields;
  235. }
  236. /**
  237. * Prepare fields for multishop
  238. * Fields are not validated here, we considere they are already validated in getFields() method, this
  239. * not the best solution but this is the only one possible for retro compatibility.
  240. *
  241. * @since 1.5.0
  242. * @return array All object fields
  243. */
  244. public function getFieldsShop()
  245. {
  246. $fields = $this->formatFields(self::FORMAT_SHOP);
  247. if (!$fields && isset($this->id) && Validate::isUnsignedId($this->id))
  248. $fields[$this->def['primary']] = $this->id;
  249. return $fields;
  250. }
  251. /**
  252. * Prepare multilang fields
  253. *
  254. * @since 1.5.0
  255. * @return array
  256. */
  257. public function getFieldsLang()
  258. {
  259. // Retrocompatibility
  260. if (method_exists($this, 'getTranslationsFieldsChild'))
  261. return $this->getTranslationsFieldsChild();
  262. $this->validateFieldsLang();
  263. $is_lang_multishop = $this->isLangMultishop();
  264. $fields = array();
  265. if ($this->id_lang === null)
  266. foreach (Language::getLanguages(false) as $language)
  267. {
  268. $fields[$language['id_lang']] = $this->formatFields(self::FORMAT_LANG, $language['id_lang']);
  269. $fields[$language['id_lang']]['id_lang'] = $language['id_lang'];
  270. if ($this->id_shop && $is_lang_multishop)
  271. $fields[$language['id_lang']]['id_shop'] = (int)$this->id_shop;
  272. }
  273. else
  274. {
  275. $fields = array($this->id_lang => $this->formatFields(self::FORMAT_LANG, $this->id_lang));
  276. $fields[$this->id_lang]['id_lang'] = $this->id_lang;
  277. if ($this->id_shop && $is_lang_multishop)
  278. $fields[$this->id_lang]['id_shop'] = (int)$this->id_shop;
  279. }
  280. return $fields;
  281. }
  282. /**
  283. * @since 1.5.0
  284. * @param int $type FORMAT_COMMON or FORMAT_LANG or FORMAT_SHOP
  285. * @param int $id_lang If this parameter is given, only take lang fields
  286. * @return array
  287. */
  288. protected function formatFields($type, $id_lang = null)
  289. {
  290. $fields = array();
  291. // Set primary key in fields
  292. if (isset($this->id))
  293. $fields[$this->def['primary']] = $this->id;
  294. foreach ($this->def['fields'] as $field => $data)
  295. {
  296. // Only get fields we need for the type
  297. // E.g. if only lang fields are filtered, ignore fields without lang => true
  298. if (($type == self::FORMAT_LANG && empty($data['lang']))
  299. || ($type == self::FORMAT_SHOP && empty($data['shop']))
  300. || ($type == self::FORMAT_COMMON && (!empty($data['shop']) || !empty($data['lang']))))
  301. continue;
  302. if (is_array($this->update_fields))
  303. if ((!empty($data['lang']) || !empty($data['shop'])) && (empty($this->update_fields[$field]) || ($type == self::FORMAT_LANG && empty($this->update_fields[$field][$id_lang]))))
  304. continue;
  305. // Get field value, if value is multilang and field is empty, use value from default lang
  306. $value = $this->$field;
  307. if ($type == self::FORMAT_LANG && $id_lang && is_array($value))
  308. {
  309. if (!empty($value[$id_lang]))
  310. $value = $value[$id_lang];
  311. else if (!empty($data['required']))
  312. $value = $value[Configuration::get('PS_LANG_DEFAULT')];
  313. else
  314. $value = '';
  315. }
  316. // Format field value
  317. $fields[$field] = ObjectModel::formatValue($value, $data['type']);
  318. }
  319. return $fields;
  320. }
  321. /**
  322. * Format a data
  323. *
  324. * @param mixed $value
  325. * @param int $type
  326. */
  327. public static function formatValue($value, $type, $with_quotes = false)
  328. {
  329. switch ($type)
  330. {
  331. case self::TYPE_INT :
  332. return (int)$value;
  333. case self::TYPE_BOOL :
  334. return (int)$value;
  335. case self::TYPE_FLOAT :
  336. return (float)$value;
  337. case self::TYPE_DATE :
  338. if (!$value)
  339. return '0000-00-00';
  340. if ($with_quotes)
  341. return '\''.pSQL($value).'\'';
  342. return pSQL($value);
  343. case self::TYPE_HTML :
  344. if ($with_quotes)
  345. return '\''.pSQL($value, true).'\'';
  346. return pSQL($value, true);
  347. case self::TYPE_NOTHING :
  348. return $value;
  349. case self::TYPE_STRING :
  350. default :
  351. if ($with_quotes)
  352. return '\''.pSQL($value).'\'';
  353. return pSQL($value);
  354. }
  355. }
  356. /**
  357. * Save current object to database (add or update)
  358. *
  359. * @param bool $null_values
  360. * @param bool $autodate
  361. * @return boolean Insertion result
  362. */
  363. public function save($null_values = false, $autodate = true)
  364. {
  365. return (int)$this->id > 0 ? $this->update($null_values) : $this->add($autodate, $null_values);
  366. }
  367. /**
  368. * Add current object to database
  369. *
  370. * @param bool $null_values
  371. * @param bool $autodate
  372. * @return boolean Insertion result
  373. */
  374. public function add($autodate = true, $null_values = false)
  375. {
  376. if (!ObjectModel::$db)
  377. ObjectModel::$db = Db::getInstance();
  378. // @hook actionObject*AddBefore
  379. Hook::exec('actionObjectAddBefore', array('object' => $this));
  380. Hook::exec('actionObject'.get_class($this).'AddBefore', array('object' => $this));
  381. // Automatically fill dates
  382. if ($autodate && property_exists($this, 'date_add'))
  383. $this->date_add = date('Y-m-d H:i:s');
  384. if ($autodate && property_exists($this, 'date_upd'))
  385. $this->date_upd = date('Y-m-d H:i:s');
  386. if (Shop::isTableAssociated($this->def['table']))
  387. {
  388. $id_shop_list = Shop::getContextListShopID();
  389. if (count($this->id_shop_list) > 0)
  390. $id_shop_list = $this->id_shop_list;
  391. }
  392. // Database insertion
  393. if (isset($this->id) && !Tools::getValue('forceIDs'))
  394. unset($this->id);
  395. if (Shop::checkIdShopDefault($this->def['table']))
  396. $this->id_shop_default = min($id_shop_list);
  397. if (!$result = ObjectModel::$db->insert($this->def['table'], $this->getFields(), $null_values))
  398. return false;
  399. // Get object id in database
  400. $this->id = ObjectModel::$db->Insert_ID();
  401. // Database insertion for multishop fields related to the object
  402. if (Shop::isTableAssociated($this->def['table']))
  403. {
  404. $fields = $this->getFieldsShop();
  405. $fields[$this->def['primary']] = (int)$this->id;
  406. foreach ($id_shop_list as $id_shop)
  407. {
  408. $fields['id_shop'] = (int)$id_shop;
  409. $result &= ObjectModel::$db->insert($this->def['table'].'_shop', $fields, $null_values);
  410. }
  411. }
  412. if (!$result)
  413. return false;
  414. // Database insertion for multilingual fields related to the object
  415. if (!empty($this->def['multilang']))
  416. {
  417. $fields = $this->getFieldsLang();
  418. if ($fields && is_array($fields))
  419. {
  420. $shops = Shop::getCompleteListOfShopsID();
  421. $asso = Shop::getAssoTable($this->def['table'].'_lang');
  422. foreach ($fields as $field)
  423. {
  424. foreach (array_keys($field) as $key)
  425. if (!Validate::isTableOrIdentifier($key))
  426. throw new PrestaShopException('key '.$key.' is not table or identifier, ');
  427. $field[$this->def['primary']] = (int)$this->id;
  428. if ($asso !== false && $asso['type'] == 'fk_shop')
  429. {
  430. foreach ($shops as $id_shop)
  431. {
  432. $field['id_shop'] = (int)$id_shop;
  433. $result &= ObjectModel::$db->insert($this->def['table'].'_lang', $field);
  434. }
  435. }
  436. else
  437. $result &= ObjectModel::$db->insert($this->def['table'].'_lang', $field);
  438. }
  439. }
  440. }
  441. // @hook actionObject*AddAfter
  442. Hook::exec('actionObjectAddAfter', array('object' => $this));
  443. Hook::exec('actionObject'.get_class($this).'AddAfter', array('object' => $this));
  444. return $result;
  445. }
  446. /**
  447. * Update current object to database
  448. *
  449. * @param bool $null_values
  450. * @return boolean Update result
  451. */
  452. public function update($null_values = false)
  453. {
  454. if (!ObjectModel::$db)
  455. ObjectModel::$db = Db::getInstance();
  456. // @hook actionObject*UpdateBefore
  457. Hook::exec('actionObjectUpdateBefore', array('object' => $this));
  458. Hook::exec('actionObject'.get_class($this).'UpdateBefore', array('object' => $this));
  459. $this->clearCache();
  460. // Automatically fill dates
  461. if (array_key_exists('date_upd', $this))
  462. $this->date_upd = date('Y-m-d H:i:s');
  463. $id_shop_list = Shop::getContextListShopID();
  464. if (count($this->id_shop_list) > 0)
  465. $id_shop_list = $this->id_shop_list;
  466. if (Shop::checkIdShopDefault($this->def['table']) && !$this->id_shop_default)
  467. $this->id_shop_default = min($id_shop_list);
  468. // Database update
  469. if (!$result = ObjectModel::$db->update($this->def['table'], $this->getFields(), '`'.pSQL($this->def['primary']).'` = '.(int)$this->id, 0, $null_values))
  470. return false;
  471. // Database insertion for multishop fields related to the object
  472. if (Shop::isTableAssociated($this->def['table']))
  473. {
  474. $fields = $this->getFieldsShop();
  475. $fields[$this->def['primary']] = (int)$this->id;
  476. if (is_array($this->update_fields))
  477. {
  478. $update_fields = $this->update_fields;
  479. $this->update_fields = null;
  480. $all_fields = $this->getFieldsShop();
  481. $all_fields[$this->def['primary']] = (int)$this->id;
  482. $this->update_fields = $update_fields;
  483. }
  484. else
  485. $all_fields = $fields;
  486. foreach ($id_shop_list as $id_shop)
  487. {
  488. $fields['id_shop'] = (int)$id_shop;
  489. $all_fields['id_shop'] = (int)$id_shop;
  490. $where = $this->def['primary'].' = '.(int)$this->id.' AND id_shop = '.(int)$id_shop;
  491. // A little explanation of what we do here : we want to create multishop entry when update is called, but
  492. // only if we are in a shop context (if we are in all context, we just want to update entries that alread exists)
  493. $shop_exists = ObjectModel::$db->getValue('SELECT '.$this->def['primary'].' FROM '._DB_PREFIX_.$this->def['table'].'_shop WHERE '.$where);
  494. if ($shop_exists)
  495. $result &= ObjectModel::$db->update($this->def['table'].'_shop', $fields, $where, 0, $null_values);
  496. else if (Shop::getContext() == Shop::CONTEXT_SHOP)
  497. $result &= ObjectModel::$db->insert($this->def['table'].'_shop', $all_fields, $null_values);
  498. }
  499. }
  500. // Database update for multilingual fields related to the object
  501. if (isset($this->def['multilang']) && $this->def['multilang'])
  502. {
  503. $fields = $this->getFieldsLang();
  504. if (is_array($fields))
  505. {
  506. foreach ($fields as $field)
  507. {
  508. foreach (array_keys($field) as $key)
  509. if (!Validate::isTableOrIdentifier($key))
  510. throw new PrestaShopException('key '.$key.' is not a valid table or identifier');
  511. // If this table is linked to multishop system, update / insert for all shops from context
  512. if ($this->isLangMultishop())
  513. {
  514. $id_shop_list = Shop::getContextListShopID();
  515. if (count($this->id_shop_list) > 0)
  516. $id_shop_list = $this->id_shop_list;
  517. foreach ($id_shop_list as $id_shop)
  518. {
  519. $field['id_shop'] = (int)$id_shop;
  520. $where = pSQL($this->def['primary']).' = '.(int)$this->id
  521. .' AND id_lang = '.(int)$field['id_lang']
  522. .' AND id_shop = '.(int)$id_shop;
  523. if (ObjectModel::$db->getValue('SELECT COUNT(*) FROM '.pSQL(_DB_PREFIX_.$this->def['table']).'_lang WHERE '.$where))
  524. $result &= ObjectModel::$db->update($this->def['table'].'_lang', $field, $where);
  525. else
  526. $result &= ObjectModel::$db->insert($this->def['table'].'_lang', $field);
  527. }
  528. }
  529. // If this table is not linked to multishop system ...
  530. else
  531. {
  532. $where = pSQL($this->def['primary']).' = '.(int)$this->id
  533. .' AND id_lang = '.(int)$field['id_lang'];
  534. if (Db::getInstance()->getValue('SELECT COUNT(*) FROM '.pSQL(_DB_PREFIX_.$this->def['table']).'_lang WHERE '.$where))
  535. $result &= ObjectModel::$db->update($this->def['table'].'_lang', $field, $where);
  536. else
  537. $result &= ObjectModel::$db->insert($this->def['table'].'_lang', $field, $null_values);
  538. }
  539. }
  540. }
  541. }
  542. // @hook actionObject*UpdateAfter
  543. Hook::exec('actionObjectUpdateAfter', array('object' => $this));
  544. Hook::exec('actionObject'.get_class($this).'UpdateAfter', array('object' => $this));
  545. return $result;
  546. }
  547. /**
  548. * Delete current object from database
  549. *
  550. * @return boolean Deletion result
  551. */
  552. public function delete()
  553. {
  554. if (!ObjectModel::$db)
  555. ObjectModel::$db = Db::getInstance();
  556. // @hook actionObject*DeleteBefore
  557. Hook::exec('actionObjectDeleteBefore', array('object' => $this));
  558. Hook::exec('actionObject'.get_class($this).'DeleteBefore', array('object' => $this));
  559. $this->clearCache();
  560. $result = true;
  561. // Remove association to multishop table
  562. if (Shop::isTableAssociated($this->def['table']))
  563. {
  564. $id_shop_list = Shop::getContextListShopID();
  565. if (count($this->id_shop_list))
  566. $id_shop_list = $this->id_shop_list;
  567. $result &= ObjectModel::$db->delete($this->def['table'].'_shop', '`'.$this->def['primary'].'`='.(int)$this->id.' AND id_shop IN ('.implode(', ', $id_shop_list).')');
  568. }
  569. // Database deletion
  570. $has_multishop_entries = $this->hasMultishopEntries();
  571. if ($result && !$has_multishop_entries)
  572. $result &= ObjectModel::$db->delete($this->def['table'], '`'.pSQL($this->def['primary']).'` = '.(int)$this->id);
  573. if (!$result)
  574. return false;
  575. // Database deletion for multilingual fields related to the object
  576. if (!empty($this->def['multilang']) && !$has_multishop_entries)
  577. $result &= ObjectModel::$db->delete($this->def['table'].'_lang', '`'.pSQL($this->def['primary']).'` = '.(int)$this->id);
  578. // @hook actionObject*DeleteAfter
  579. Hook::exec('actionObjectDeleteAfter', array('object' => $this));
  580. Hook::exec('actionObject'.get_class($this).'DeleteAfter', array('object' => $this));
  581. return $result;
  582. }
  583. /**
  584. * Delete several objects from database
  585. *
  586. * @param array $selection
  587. * @return bool Deletion result
  588. */
  589. public function deleteSelection($selection)
  590. {
  591. $result = true;
  592. foreach ($selection as $id)
  593. {
  594. $this->id = (int)$id;
  595. $result = $result && $this->delete();
  596. }
  597. return $result;
  598. }
  599. /**
  600. * Toggle object status in database
  601. *
  602. * @return boolean Update result
  603. */
  604. public function toggleStatus()
  605. {
  606. // Object must have a variable called 'active'
  607. if (!array_key_exists('active', $this))
  608. throw new PrestaShopException('property "active" is missing in object '.get_class($this));
  609. // Update active status on object
  610. $this->active = !(int)$this->active;
  611. // Change status to active/inactive
  612. return $this->update(false);
  613. }
  614. /**
  615. * @deprecated 1.5.0 (use getFieldsLang())
  616. */
  617. protected function getTranslationsFields($fields_array)
  618. {
  619. $fields = array();
  620. if ($this->id_lang == null)
  621. foreach (Language::getLanguages(false) as $language)
  622. $this->makeTranslationFields($fields, $fields_array, $language['id_lang']);
  623. else
  624. $this->makeTranslationFields($fields, $fields_array, $this->id_lang);
  625. return $fields;
  626. }
  627. /**
  628. * @deprecated 1.5.0
  629. */
  630. protected function makeTranslationFields(&$fields, &$fields_array, $id_language)
  631. {
  632. $fields[$id_language]['id_lang'] = $id_language;
  633. $fields[$id_language][$this->def['primary']] = (int)$this->id;
  634. if ($this->id_shop && $this->isLangMultishop())
  635. $fields[$id_language]['id_shop'] = (int)$this->id_shop;
  636. foreach ($fields_array as $k => $field)
  637. {
  638. $html = false;
  639. $field_name = $field;
  640. if (is_array($field))
  641. {
  642. $field_name = $k;
  643. $html = (isset($field['html'])) ? $field['html'] : false;
  644. }
  645. /* Check fields validity */
  646. if (!Validate::isTableOrIdentifier($field_name))
  647. throw new PrestaShopException('identifier is not table or identifier : '.$field_name);
  648. // Copy the field, or the default language field if it's both required and empty
  649. if ((!$this->id_lang && isset($this->{$field_name}[$id_language]) && !empty($this->{$field_name}[$id_language]))
  650. || ($this->id_lang && isset($this->$field_name) && !empty($this->$field_name)))
  651. $fields[$id_language][$field_name] = $this->id_lang ? pSQL($this->$field_name, $html) : pSQL($this->{$field_name}[$id_language], $html);
  652. else if (in_array($field_name, $this->fieldsRequiredLang))
  653. $fields[$id_language][$field_name] = pSQL($this->id_lang ? $this->$field_name : $this->{$field_name}[Configuration::get('PS_LANG_DEFAULT')], $html);
  654. else
  655. $fields[$id_language][$field_name] = '';
  656. }
  657. }
  658. /**
  659. * Check for fields validity before database interaction
  660. *
  661. * @param bool $die
  662. * @param bool $error_return
  663. * @return bool|string
  664. */
  665. public function validateFields($die = true, $error_return = false)
  666. {
  667. foreach ($this->def['fields'] as $field => $data)
  668. {
  669. if (!empty($data['lang']))
  670. continue;
  671. if (is_array($this->update_fields) && empty($this->update_fields[$field]))
  672. continue;
  673. $message = $this->validateField($field, $this->$field);
  674. if ($message !== true)
  675. {
  676. if ($die)
  677. throw new PrestaShopException($message);
  678. return $error_return ? $message : false;
  679. }
  680. }
  681. return true;
  682. }
  683. /**
  684. * Check for multilingual fields validity before database interaction
  685. *
  686. * @param bool $die
  687. * @param bool $error_return
  688. * @return bool|string
  689. */
  690. public function validateFieldsLang($die = true, $error_return = false)
  691. {
  692. foreach ($this->def['fields'] as $field => $data)
  693. {
  694. if (empty($data['lang']))
  695. continue;
  696. $values = $this->$field;
  697. if (!is_array($values))
  698. $values = array($this->id_lang => $values);
  699. foreach ($values as $id_lang => $value)
  700. {
  701. if (is_array($this->update_fields) && empty($this->update_fields[$field][$id_lang]))
  702. continue;
  703. $message = $this->validateField($field, $value, $id_lang);
  704. if ($message !== true)
  705. {
  706. if ($die)
  707. throw new PrestaShopException($message);
  708. return $error_return ? $message : false;
  709. }
  710. }
  711. }
  712. return true;
  713. }
  714. /**
  715. * Validate a single field
  716. *
  717. * @since 1.5.0
  718. * @param string $field Field name
  719. * @param mixed $value Field value
  720. * @param int $id_lang
  721. * @return bool|string
  722. */
  723. public function validateField($field, $value, $id_lang = null)
  724. {
  725. $data = $this->def['fields'][$field];
  726. // Check if field is required
  727. $required_fields = (isset(self::$fieldsRequiredDatabase[get_class($this)])) ? self::$fieldsRequiredDatabase[get_class($this)] : array();
  728. if (!$id_lang || $id_lang == Configuration::get('PS_LANG_DEFAULT'))
  729. if (!empty($data['required']) || in_array($field, $required_fields))
  730. if (Tools::isEmpty($value))
  731. return 'Property '.get_class($this).'->'.$field.' is empty';
  732. // Default value
  733. if (!$value && !empty($data['default']))
  734. {
  735. $value = $data['default'];
  736. $this->$field = $value;
  737. }
  738. // Check field values
  739. if (!empty($data['values']) && is_array($data['values']) && !in_array($value, $data['values']))
  740. return 'Property '.get_class($this).'->'.$field.' has bad value (allowed values are: '.implode(', ', $data['values']).')';
  741. // Check field size
  742. if (!empty($data['size']))
  743. {
  744. $size = $data['size'];
  745. if (!is_array($data['size']))
  746. $size = array('min' => 0, 'max' => $data['size']);
  747. $length = Tools::strlen($value);
  748. if ($length < $size['min'] || $length > $size['max'])
  749. return 'Property '.get_class($this).'->'.$field.' length ('.$length.') must be between '.$size['min'].' and '.$size['max'];
  750. }
  751. // Check field validator
  752. if (!empty($data['validate']))
  753. {
  754. if (!method_exists('Validate', $data['validate']))
  755. throw new PrestaShopException('Validation function not found. '.$data['validate']);
  756. if (!empty($value) && !call_user_func(array('Validate', $data['validate']), $value))
  757. return 'Property '.get_class($this).'->'.$field.' is not valid';
  758. }
  759. return true;
  760. }
  761. public static function displayFieldName($field, $class = __CLASS__, $htmlentities = true, Context $context = null)
  762. {
  763. global $_FIELDS;
  764. if (file_exists(_PS_TRANSLATIONS_DIR_.Context::getContext()->language->iso_code.'/fields.php'))
  765. include(_PS_TRANSLATIONS_DIR_.Context::getContext()->language->iso_code.'/fields.php');
  766. $key = $class.'_'.md5($field);
  767. return ((is_array($_FIELDS) && array_key_exists($key, $_FIELDS)) ? ($htmlentities ? htmlentities($_FIELDS[$key], ENT_QUOTES, 'utf-8') : $_FIELDS[$key]) : $field);
  768. }
  769. /**
  770. * TODO: refactor rename all calls to this to validateController
  771. * @deprecated since 1.5 use validateController instead
  772. */
  773. public function validateControler($htmlentities = true)
  774. {
  775. Tools::displayAsDeprecated();
  776. return $this->validateController($htmlentities);
  777. }
  778. public function validateController($htmlentities = true)
  779. {
  780. $errors = array();
  781. $required_fields_database = (isset(self::$fieldsRequiredDatabase[get_class($this)])) ? self::$fieldsRequiredDatabase[get_class($this)] : array();
  782. foreach ($this->def['fields'] as $field => $data)
  783. {
  784. // Check if field is required by user
  785. if (in_array($field, $required_fields_database))
  786. $data['required'] = true;
  787. // Checking for required fields
  788. if (isset($data['required']) && $data['required'] && ($value = Tools::getValue($field, $this->{$field})) == false && (string)$value != '0')
  789. if (!$this->id || $field != 'passwd')
  790. $errors[] = '<b>'.self::displayFieldName($field, get_class($this), $htmlentities).'</b> '.Tools::displayError('is required.');
  791. // Checking for maximum fields sizes
  792. if (isset($data['size']) && ($value = Tools::getValue($field, $this->{$field})) && Tools::strlen($value) > $data['size'])
  793. $errors[] = sprintf(
  794. Tools::displayError('%1$s is too long. Maximum length: %2$d'),
  795. self::displayFieldName($field, get_class($this), $htmlentities),
  796. $data['size']
  797. );
  798. // Checking for fields validity
  799. // Hack for postcode required for country which does not have postcodes
  800. if (($value = Tools::getValue($field, $this->{$field})) || ($field == 'postcode' && $value == '0'))
  801. {
  802. if (isset($data['validate']) && !Validate::$data['validate']($value) && (!empty($value) || $data['required']))
  803. $errors[] = '<b>'.self::displayFieldName($field, get_class($this), $htmlentities).'</b> '.Tools::displayError('is invalid.');
  804. else
  805. {
  806. if (isset($data['copy_post']) && !$data['copy_post'])
  807. continue;
  808. if ($field == 'passwd')
  809. {
  810. if ($value = Tools::getValue($field))
  811. $this->{$field} = Tools::encrypt($value);
  812. }
  813. else
  814. $this->{$field} = $value;
  815. }
  816. }
  817. }
  818. return $errors;
  819. }
  820. public function getWebserviceParameters($ws_params_attribute_name = null)
  821. {
  822. $default_resource_parameters = array(
  823. 'objectSqlId' => $this->def['primary'],
  824. 'retrieveData' => array(
  825. 'className' => get_class($this),
  826. 'retrieveMethod' => 'getWebserviceObjectList',
  827. 'params' => array(),
  828. 'table' => $this->def['table'],
  829. ),
  830. 'fields' => array(
  831. 'id' => array('sqlId' => $this->def['primary'], 'i18n' => false),
  832. ),
  833. );
  834. if ($ws_params_attribute_name === null)
  835. $ws_params_attribute_name = 'webserviceParameters';
  836. if (!isset($this->{$ws_params_attribute_name}['objectNodeName']))
  837. $default_resource_parameters['objectNodeName'] = $this->def['table'];
  838. if (!isset($this->{$ws_params_attribute_name}['objectsNodeName']))
  839. $default_resource_parameters['objectsNodeName'] = $this->def['table'].'s';
  840. if (isset($this->{$ws_params_attribute_name}['associations']))
  841. foreach ($this->{$ws_params_attribute_name}['associations'] as $assoc_name => &$association)
  842. {
  843. if (!array_key_exists('setter', $association) || (isset($association['setter']) && !$association['setter']))
  844. $association['setter'] = Tools::toCamelCase('set_ws_'.$assoc_name);
  845. if (!array_key_exists('getter', $association))
  846. $association['getter'] = Tools::toCamelCase('get_ws_'.$assoc_name);
  847. }
  848. if (isset($this->{$ws_params_attribute_name}['retrieveData']) && isset($this->{$ws_params_attribute_name}['retrieveData']['retrieveMethod']))
  849. unset($default_resource_parameters['retrieveData']['retrieveMethod']);
  850. $resource_parameters = array_merge_recursive($default_resource_parameters, $this->{$ws_params_attribute_name});
  851. $required_fields = (isset(self::$fieldsRequiredDatabase[get_class($this)]) ? self::$fieldsRequiredDatabase[get_class($this)] : array());
  852. foreach ($this->def['fields'] as $field_name => $details)
  853. {
  854. if (!isset($resource_parameters['fields'][$field_name]))
  855. $resource_parameters['fields'][$field_name] = array();
  856. $current_field = array();
  857. $current_field['sqlId'] = $field_name;
  858. if (isset($details['size']))
  859. $current_field['maxSize'] = $details['size'];
  860. if (isset($details['lang']))
  861. $current_field['i18n'] = $details['lang'];
  862. else
  863. $current_field['i18n'] = false;
  864. if ((isset($details['required']) && $details['required'] === true) || in_array($field_name, $required_fields))
  865. $current_field['required'] = true;
  866. else
  867. $current_field['required'] = false;
  868. if (isset($details['validate']))
  869. {
  870. $current_field['validateMethod'] = (
  871. array_key_exists('validateMethod', $resource_parameters['fields'][$field_name]) ?
  872. array_merge($resource_parameters['fields'][$field_name]['validateMethod'], array($details['validate'])) :
  873. array($details['validate'])
  874. );
  875. }
  876. $resource_parameters['fields'][$field_name] = array_merge($resource_parameters['fields'][$field_name], $current_field);
  877. }
  878. if (isset($this->date_add))
  879. $resource_parameters['fields']['date_add']['setter'] = false;
  880. if (isset($this->date_upd))
  881. $resource_parameters['fields']['date_upd']['setter'] = false;
  882. foreach ($resource_parameters['fields'] as $key => $resource_parameters_field)
  883. if (!isset($resource_parameters_field['sqlId']))
  884. $resource_parameters['fields'][$key]['sqlId'] = $key;
  885. return $resource_parameters;
  886. }
  887. public function getWebserviceObjectList($sql_join, $sql_filter, $sql_sort, $sql_limit)
  888. {
  889. $assoc = Shop::getAssoTable($this->def['table']);
  890. $class_name = WebserviceRequest::$ws_current_classname;
  891. $vars = get_class_vars($class_name);
  892. if ($assoc !== false)
  893. {
  894. if ($assoc['type'] !== 'fk_shop')
  895. {
  896. $multi_shop_join = ' LEFT JOIN `'._DB_PREFIX_.bqSQL($this->def['table']).'_'.bqSQL($assoc['type']).'`
  897. AS `multi_shop_'.bqSQL($this->def['table']).'`
  898. ON (main.`'.bqSQL($this->def['primary']).'` = `multi_shop_'.bqSQL($this->def['table']).'`.`'.bqSQL($this->def['primary']).'`)';
  899. $sql_filter = 'AND `multi_shop_'.bqSQL($this->def['table']).'`.id_shop = '.Context::getContext()->shop->id.' '.$sql_filter;
  900. $sql_join = $multi_shop_join.' '.$sql_join;
  901. }
  902. else
  903. {
  904. $vars = get_class_vars($class_name);
  905. foreach ($vars['shopIDs'] as $id_shop)
  906. $or[] = ' main.id_shop = '.(int)$id_shop.' ';
  907. $prepend = '';
  908. if (count($or))
  909. $prepend = 'AND ('.implode('OR', $or).')';
  910. $sql_filter = $prepend.' '.$sql_filter;
  911. }
  912. }
  913. $query = '
  914. SELECT DISTINCT main.`'.bqSQL($this->def['primary']).'` FROM `'._DB_PREFIX_.bqSQL($this->def['table']).'` AS main
  915. '.$sql_join.'
  916. WHERE 1 '.$sql_filter.'
  917. '.($sql_sort != '' ? $sql_sort : '').'
  918. '.($sql_limit != '' ? $sql_limit : '');
  919. return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
  920. }
  921. public function validateFieldsRequiredDatabase($htmlentities = true)
  922. {
  923. $errors = array();
  924. $required_fields = (isset(self::$fieldsRequiredDatabase[get_class($this)])) ? self::$fieldsRequiredDatabase[get_class($this)] : array();
  925. foreach ($this->def['fields'] as $field => $data)
  926. {
  927. if (!in_array($field, $required_fields))
  928. continue;
  929. if (!method_exists('Validate', $data['validate']))
  930. throw new PrestaShopException('Validation function not found. '.$data['validate']);
  931. $value = Tools::getValue($field);
  932. if (empty($value))
  933. $errors[] = sprintf(Tools::displayError('The field %s is required.'), self::displayFieldName($field, get_class($this), $htmlentities));
  934. }
  935. return $errors;
  936. }
  937. public function getFieldsRequiredDatabase($all = false)
  938. {
  939. return Db::getInstance()->executeS('
  940. SELECT id_required_field, object_name, field_name
  941. FROM '._DB_PREFIX_.'required_field
  942. '.(!$all ? 'WHERE object_name = \''.pSQL(get_class($this)).'\'' : ''));
  943. }
  944. public function addFieldsRequiredDatabase($fields)
  945. {
  946. if (!is_array($fields))
  947. return false;
  948. if (!Db::getInstance()->execute('DELETE FROM '._DB_PREFIX_.'required_field WHERE object_name = \''.get_class($this).'\''))
  949. return false;
  950. foreach ($fields as $field)
  951. if (!Db::getInstance()->insert('required_field', array('object_name' => get_class($this), 'field_name' => pSQL($field))))
  952. return false;
  953. return true;
  954. }
  955. public function clearCache($all = false)
  956. {
  957. if ($all)
  958. Cache::clean('objectmodel_'.$this->def['table'].'_*');
  959. else if ($this->id)
  960. Cache::clean('objectmodel_'.$this->def['table'].'_'.(int)$this->id.'_*');
  961. }
  962. /**
  963. * Check if current object is associated to a shop
  964. *
  965. * @since 1.5.0
  966. * @param int $id_shop
  967. * @return bool
  968. */
  969. public function isAssociatedToShop($id_shop = null)
  970. {
  971. if ($id_shop === null)
  972. $id_shop = Context::getContext()->shop->id;
  973. $sql = 'SELECT id_shop
  974. FROM `'.pSQL(_DB_PREFIX_.$this->def['table']).'_shop`
  975. WHERE `'.$this->def['primary'].'` = '.(int)$this->id.'
  976. AND id_shop = '.(int)$id_shop;
  977. return (bool)Db::getInstance()->getValue($sql);
  978. }
  979. /**
  980. * This function associate an item to its context
  981. *
  982. * @param int|array $id_shops
  983. * @return boolean
  984. */
  985. public function associateTo($id_shops)
  986. {
  987. if (!$this->id)
  988. return;
  989. if (!is_array($id_shops))
  990. $id_shops = array($id_shops);
  991. $data = array();
  992. foreach ($id_shops as $id_shop)
  993. {
  994. if (!$this->isAssociatedToShop($id_shop))
  995. $data[] = array(
  996. $this->def['primary'] => (int)$this->id,
  997. 'id_shop' => (int)$id_shop,
  998. );
  999. }
  1000. if ($data)
  1001. return Db::getInstance()->insert($this->def['table'].'_shop', $data);
  1002. return true;
  1003. }
  1004. /**
  1005. * Get the list of associated id_shop
  1006. *
  1007. * @since 1.5.0
  1008. * @return array
  1009. */
  1010. public function getAssociatedShops()
  1011. {
  1012. if (!Shop::isTableAssociated($this->def['table']))
  1013. return array();
  1014. $list = array();
  1015. $sql = 'SELECT id_shop FROM `'._DB_PREFIX_.$this->def['table'].'_shop` WHERE `'.$this->def['primary'].'` = '.(int)$this->id;
  1016. foreach (Db::getInstance()->executeS($sql) as $row)
  1017. $list[] = $row['id_shop'];
  1018. return $list;
  1019. }
  1020. /**
  1021. * @since 1.5.0
  1022. */
  1023. public function duplicateShops($id)
  1024. {
  1025. if (!Shop::isTableAssociated($this->def['table']))
  1026. return false;
  1027. $sql = 'SELECT id_shop
  1028. FROM '._DB_PREFIX_.$this->def['table'].'_shop
  1029. WHERE '.$this->def['primary'].' = '.(int)$id;
  1030. if ($results = Db::getInstance()->executeS($sql))
  1031. {
  1032. $ids = array();
  1033. foreach ($results as $row)
  1034. $ids[] = $row['id_shop'];
  1035. return $this->associateTo($ids);
  1036. }
  1037. return false;
  1038. }
  1039. /**
  1040. * Check if there is more than one entries in associated shop table for current entity
  1041. *
  1042. * @since 1.5.0
  1043. * @return bool
  1044. */
  1045. public function hasMultishopEntries()
  1046. {
  1047. if (!Shop::isTableAssociated($this->def['table']) || !Shop::isFeatureActive())
  1048. return false;
  1049. return (bool)Db::getInstance()->getValue('SELECT COUNT(*) FROM `'._DB_PREFIX_.$this->def['table'].'_shop` WHERE `'.$this->def['primary'].'` = '.(int)$this->id);
  1050. }
  1051. public function isMultishop()
  1052. {
  1053. return Shop::isTableAssociated($this->def['table']) || !empty($this->def['multilang_shop']);
  1054. }
  1055. public function isLangMultishop()
  1056. {
  1057. return !empty($this->def['multilang']) && !empty($this->def['multilang_shop']);
  1058. }
  1059. /**
  1060. * Update a table and splits the common datas and the shop datas
  1061. *
  1062. * @since 1.5.0
  1063. * @param string $classname
  1064. * @param array $data
  1065. * @param string $where
  1066. * @param string $specific_where Only executed for common table
  1067. * @return bool
  1068. */
  1069. public static function updateMultishopTable($classname, $data, $where, $specific_where = '')
  1070. {
  1071. $def = ObjectModel::getDefinition($classname);
  1072. $update_data = array();
  1073. foreach ($data as $field => $value)
  1074. {
  1075. if (!isset($def['fields'][$field]))
  1076. continue;
  1077. if (!empty($def['fields'][$field]['shop']))
  1078. {
  1079. $update_data[] = "a.$field = '$value'";
  1080. $update_data[] = "{$def['table']}_shop.$field = '$value'";
  1081. }
  1082. else
  1083. $update_data[] = "a.$field = '$value'";
  1084. }
  1085. $sql = 'UPDATE '._DB_PREFIX_.$def['table'].' a
  1086. '.Shop::addSqlAssociation($def['table'], 'a').'
  1087. SET '.implode(', ', $update_data).'
  1088. WHERE '.$where;
  1089. return Db::getInstance()->execute($sql);
  1090. }
  1091. /**
  1092. * Delete images associated with the object
  1093. *
  1094. * @return bool success
  1095. */
  1096. public function deleteImage($force_delete = false)
  1097. {
  1098. if (!$this->id)
  1099. return false;
  1100. if ($force_delete || !$this->hasMultishopEntries())
  1101. {
  1102. /* Deleting object images and thumbnails (cache) */
  1103. if ($this->image_dir)
  1104. {
  1105. if (file_exists($this->image_dir.$this->id.'.'.$this->image_format)
  1106. && !unlink($this->image_dir.$this->id.'.'.$this->image_format))
  1107. return false;
  1108. }
  1109. if (file_exists(_PS_TMP_IMG_DIR_.$this->def['table'].'_'.$this->id.'.'.$this->image_format)
  1110. && !unlink(_PS_TMP_IMG_DIR_.$this->def['table'].'_'.$this->id.'.'.$this->image_format))
  1111. return false;
  1112. if (file_exists(_PS_TMP_IMG_DIR_.$this->def['table'].'_mini_'.$this->id.'.'.$this->image_format)
  1113. && !unlink(_PS_TMP_IMG_DIR_.$this->def['table'].'_mini_'.$this->id.'.'.$this->image_format))
  1114. return false;
  1115. $types = ImageType::getImagesTypes();
  1116. foreach ($types as $image_type)
  1117. if (file_exists($this->image_dir.$this->id.'-'.stripslashes($image_type['name']).'.'.$this->image_format)
  1118. && !unlink($this->image_dir.$this->id.'-'.stripslashes($image_type['name']).'.'.$this->image_format))
  1119. return false;
  1120. }
  1121. return true;
  1122. }
  1123. /**
  1124. * Specify if an ObjectModel is already in database
  1125. *
  1126. * @param int $id_entity
  1127. * @param string $table
  1128. * @return boolean
  1129. */
  1130. public static function existsInDatabase($id_entity, $table)
  1131. {
  1132. $row = Db::getInstance()->getRow('
  1133. SELECT `id_'.$table.'` as id
  1134. FROM `'._DB_PREFIX_.$table.'` e
  1135. WHERE e.`id_'.$table.'` = '.(int)$id_entity
  1136. );
  1137. return isset($row['id']);
  1138. }
  1139. /**
  1140. * This method is allow to know if a entity is currently used
  1141. * @since 1.5.0.1
  1142. * @param string $table name of table linked to entity
  1143. * @param bool $has_active_column true if the table has an active column
  1144. * @return bool
  1145. */
  1146. public static function isCurrentlyUsed($table = null, $has_active_column = false)
  1147. {
  1148. if ($table === null)
  1149. $table = self::$definition['table'];
  1150. $query = new DbQuery();
  1151. $query->select('`id_'.pSQL($table).'`');
  1152. $query->from($table);
  1153. if ($has_active_column)
  1154. $query->where('`active` = 1');
  1155. return (bool)Db::getInstance()->getValue($query);
  1156. }
  1157. /**
  1158. * Fill an object with given data. Data must be an array with this syntax: array(objProperty => value, objProperty2 => value, etc.)
  1159. *
  1160. * @since 1.5.0
  1161. * @param array $data
  1162. * @param int $id_lang
  1163. */
  1164. public function hydrate(array $data, $id_lang = null)
  1165. {
  1166. $this->id_lang = $id_lang;
  1167. if (isset($data[$this->def['primary']]))
  1168. $this->id = $data[$this->def['primary']];
  1169. foreach ($data as $key => $value)
  1170. if (array_key_exists($key, $this))
  1171. $this->$key = $value;
  1172. }
  1173. /**
  1174. * Fill (hydrate) a list of objects in order to get a collection of these objects
  1175. *
  1176. * @since 1.5.0
  1177. * @param string $class Class of objects to hydrate
  1178. * @param array $datas List of data (multi-dimensional array)
  1179. * @param int $id_lang
  1180. * @return array
  1181. */
  1182. public static function hydrateCollection($class, array $datas, $id_lang = null)
  1183. {
  1184. if (!class_exists($class))
  1185. throw new PrestaShopException("Class '$class' not found");
  1186. $collection = array();
  1187. $rows = array();
  1188. if ($datas)
  1189. {
  1190. $definition = ObjectModel::getDefinition($class);
  1191. if (!array_key_exists($definition['primary'], $datas[0]))
  1192. throw new PrestaShopException("Identifier '{$definition['primary']}' not found for class '$class'");
  1193. foreach ($datas as $row)
  1194. {
  1195. // Get object common properties
  1196. $id = $row[$definition['primary']];
  1197. if (!isset($rows[$id]))
  1198. $rows[$id] = $row;
  1199. // Get object lang properties
  1200. if (isset($row['id_lang']) && !$id_lang)
  1201. foreach ($definition['fields'] as $field => $data)
  1202. if (!empty($data['lang']))
  1203. {
  1204. if (!is_array($rows[$id][$field]))
  1205. $rows[$id][$field] = array();
  1206. $rows[$id][$field][$row['id_lang']] = $row[$field];
  1207. }
  1208. }
  1209. }
  1210. // Hydrate objects
  1211. foreach ($rows as $row)
  1212. {
  1213. $obj = new $class;
  1214. $obj->hydrate($row, $id_lang);
  1215. $collection[] = $obj;
  1216. }
  1217. return $collection;
  1218. }
  1219. /**
  1220. * Get object definition
  1221. *
  1222. * @param string $class Name of object
  1223. * @param string $field Name of field if we want the definition of one field only
  1224. * @return array
  1225. */
  1226. public static function getDefinition($class, $field = null)
  1227. {
  1228. $reflection = new ReflectionClass($class);
  1229. $definition = $reflection->getStaticPropertyValue('definition');
  1230. if (is_object($class))
  1231. $definition['classname'] = get_class($class);
  1232. else
  1233. $definition['classname'] = $class;
  1234. if (!empty($definition['multilang']))
  1235. $definition['associations'][Collection::LANG_ALIAS] = array(
  1236. 'type' => self::HAS_MANY,
  1237. 'field' => $definition['primary'],
  1238. 'foreign_field' => $definition['primary'],
  1239. );
  1240. if ($field)
  1241. return isset($definition['fields'][$field]) ? $definition['fields'][$field] : null;
  1242. return $definition;
  1243. }
  1244. /**
  1245. * Retrocompatibility for classes without $definition static
  1246. * Remove this in 1.6 !
  1247. *
  1248. * @since 1.5.0
  1249. */
  1250. protected function setDefinitionRetrocompatibility()
  1251. {
  1252. // Retrocompatibility with $table property ($definition['table'])
  1253. if (isset($this->def['table']))
  1254. $this->table = $this->def['table'];
  1255. else
  1256. $this->def['table'] = $this->table;
  1257. // Retrocompatibility with $identifier property ($definition['primary'])
  1258. if (isset($this->def['primary']))
  1259. $this->identifier = $this->def['primary'];
  1260. else
  1261. $this->def['primary'] = $this->identifier;
  1262. // Check multilang retrocompatibility
  1263. if (method_exists($this, 'getTranslationsFieldsChild'))
  1264. $this->def['multilang'] = true;
  1265. // Retrocompatibility with $fieldsValidate, $fieldsRequired and $fieldsSize properties ($definition['fields'])
  1266. if (isset($this->def['fields']))
  1267. {
  1268. foreach ($this->def['fields'] as $field => $data)
  1269. {
  1270. $suffix = (isset($data['lang']) && $data['lang']) ? 'Lang' : '';
  1271. if (isset($data['validate']))
  1272. $this->{'fieldsValidate'.$suffix}[$field] = $data['validate'];
  1273. if (isset($data['required']) && $data['required'])
  1274. $this->{'fieldsRequired'.$suffix}[] = $field;
  1275. if (isset($data['size']))
  1276. $this->{'fieldsSize'.$suffix}[$field] = $data['size'];
  1277. }
  1278. }
  1279. else
  1280. {
  1281. $this->def['fields'] = array();
  1282. $suffixs = array('', 'Lang');
  1283. foreach($suffixs as $suffix)
  1284. {
  1285. foreach ($this->{'fieldsValidate'.$suffix} as $field => $validate)
  1286. $this->def['fields'][$field]['validate'] = $validate;
  1287. foreach ($this->{'fieldsRequired'.$suffix} as $field)
  1288. $this->def['fields'][$field]['required'] = true;
  1289. foreach ($this->{'fieldsSize'.$suffix} as $field => $size)
  1290. $this->def['fields'][$field]['size'] = $size;
  1291. }
  1292. }
  1293. }
  1294. /**
  1295. * Return the field value for the specified language if the field is multilang, else the field value.
  1296. *
  1297. * @param $field_name
  1298. * @param null $id_lang
  1299. * @return mixed
  1300. * @throws PrestaShopException
  1301. * @since 1.5
  1302. */
  1303. public function getFieldByLang($field_name, $id_lang = null)
  1304. {
  1305. $definition = ObjectModel::getDefinition($this);
  1306. // Is field in definition?
  1307. if ($definition && isset($definition['fields'][$field_name]))
  1308. {
  1309. $field = $definition['fields'][$field_name];
  1310. // Is field multilang?
  1311. if (isset($field['lang']) && $field['lang'])
  1312. {
  1313. if (is_array($this->{$field_name}))
  1314. return $this->{$field_name}[$id_lang ? $id_lang : Context::getContext()->language->id];
  1315. }
  1316. return $this->{$field_name};
  1317. }
  1318. else
  1319. throw new PrestaShopException('Could not load field from definition.');
  1320. }
  1321. /**
  1322. * Set a list of specific fields to update
  1323. * array(field1 => true, field2 => false, langfield1 => array(1 => true, 2 => false))
  1324. *
  1325. * @since 1.5.0
  1326. * @param array $fields
  1327. */
  1328. public function setFieldsToUpdate(array $fields)
  1329. {
  1330. $this->update_fields = $fields;
  1331. }
  1332. }