PageRenderTime 73ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 1ms

/classes/ObjectModel.php

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