PageRenderTime 88ms CodeModel.GetById 39ms RepoModel.GetById 1ms app.codeStats 0ms

/install-dev/classes/xmlLoader.php

https://bitbucket.org/marcenuc/prestashop
PHP | 1277 lines | 947 code | 170 blank | 160 comment | 179 complexity | c692d6854961711855f2ee12589f56c7 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$
  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. class InstallXmlLoader
  28. {
  29. /**
  30. * @var InstallLanguages
  31. */
  32. protected $language;
  33. /**
  34. * @var array List of languages stored as array(id_lang => iso)
  35. */
  36. protected $languages = array();
  37. /**
  38. * @var array Store in cache all loaded XML files
  39. */
  40. protected $cache_xml_entity = array();
  41. /**
  42. * @var array List of errors
  43. */
  44. protected $errors = array();
  45. protected $data_path;
  46. protected $lang_path;
  47. protected $img_path;
  48. public $path_type;
  49. protected $ids = array();
  50. protected $primaries = array();
  51. protected $delayed_inserts = array();
  52. public function __construct()
  53. {
  54. $this->language = InstallLanguages::getInstance();
  55. $this->setDefaultPath();
  56. require_once _PS_ROOT_DIR_.'/images.inc.php';
  57. }
  58. /**
  59. * Set list of installed languages
  60. *
  61. * @param array $languages array(id_lang => iso)
  62. */
  63. public function setLanguages(array $languages)
  64. {
  65. $this->languages = $languages;
  66. }
  67. public function setDefaultPath()
  68. {
  69. $this->path_type = 'common';
  70. $this->data_path = _PS_INSTALL_DATA_PATH_.'xml/';
  71. $this->lang_path = _PS_INSTALL_LANGS_PATH_;
  72. $this->img_path = _PS_INSTALL_DATA_PATH_.'img/';
  73. }
  74. public function setFixturesPath()
  75. {
  76. $this->path_type = 'fixture';
  77. $this->data_path = _PS_INSTALL_FIXTURES_PATH_.'apple/data/';
  78. $this->lang_path = _PS_INSTALL_FIXTURES_PATH_.'apple/langs/';
  79. $this->img_path = _PS_INSTALL_FIXTURES_PATH_.'apple/img/';
  80. }
  81. /**
  82. * Get list of errors
  83. *
  84. * @return array
  85. */
  86. public function getErrors()
  87. {
  88. return $this->errors;
  89. }
  90. /**
  91. * Add an error
  92. *
  93. * @param string $error
  94. */
  95. public function setError($error)
  96. {
  97. $this->errors[] = $error;
  98. }
  99. /**
  100. * Store an ID related to an entity and its identifier (E.g. we want to save that product with ID "ipod_nano" has the ID 1)
  101. *
  102. * @param string $entity
  103. * @param string $identifier
  104. * @param int $id
  105. */
  106. public function storeId($entity, $identifier, $id)
  107. {
  108. $this->ids[$entity.':'.$identifier] = $id;
  109. }
  110. /**
  111. * Retrieve an ID related to an entity and its identifier
  112. *
  113. * @param string $entity
  114. * @param string $identifier
  115. */
  116. public function retrieveId($entity, $identifier)
  117. {
  118. return isset($this->ids[$entity.':'.$identifier]) ? $this->ids[$entity.':'.$identifier] : 0;
  119. }
  120. public function getIds()
  121. {
  122. return $this->ids;
  123. }
  124. public function setIds($ids)
  125. {
  126. $this->ids = $ids;
  127. }
  128. public function getSortedEntities()
  129. {
  130. // Browse all XML files from data/xml directory
  131. $entities = array();
  132. $dependencies = array();
  133. $fd = opendir($this->data_path);
  134. while ($file = readdir($fd))
  135. if (preg_match('#^(.+)\.xml$#', $file, $m))
  136. {
  137. $entity = $m[1];
  138. $xml = $this->loadEntity($entity);
  139. // Store entities dependencies (with field type="relation")
  140. if ($xml->fields)
  141. {
  142. foreach ($xml->fields->field as $field)
  143. {
  144. if ($field['relation'] && $field['relation'] != $entity)
  145. {
  146. if (!isset($dependencies[(string)$field['relation']]))
  147. $dependencies[(string)$field['relation']] = array();
  148. $dependencies[(string)$field['relation']][] = $entity;
  149. }
  150. }
  151. }
  152. $entities[] = $entity;
  153. }
  154. closedir($fd);
  155. // Sort entities to populate database in good order (E.g. zones before countries)
  156. do
  157. {
  158. $current = (isset($sort_entities)) ? $sort_entities : array();
  159. $sort_entities = array();
  160. foreach ($entities as $key => $entity)
  161. {
  162. if (isset($dependencies[$entity]))
  163. {
  164. $min = count($entities) - 1;
  165. foreach ($dependencies[$entity] as $item)
  166. if (($key = array_search($item, $sort_entities)) !== false)
  167. $min = min($min, $key);
  168. if ($min == 0)
  169. array_unshift($sort_entities, $entity);
  170. else
  171. array_splice($sort_entities, $min, 0, array($entity));
  172. }
  173. else
  174. $sort_entities[] = $entity;
  175. }
  176. $entities = $sort_entities;
  177. }
  178. while ($current != $sort_entities);
  179. return $sort_entities;
  180. }
  181. /**
  182. * Read all XML files from data folder and populate tables
  183. */
  184. public function populateFromXmlFiles()
  185. {
  186. $entities = $this->getSortedEntities();
  187. // Populate entities
  188. foreach ($entities as $entity)
  189. $this->populateEntity($entity);
  190. }
  191. /**
  192. * Populate an entity
  193. *
  194. * @param string $entity
  195. */
  196. public function populateEntity($entity)
  197. {
  198. if (method_exists($this, 'populateEntity'.Tools::toCamelCase($entity)))
  199. {
  200. $this->{'populateEntity'.Tools::toCamelCase($entity)}();
  201. return;
  202. }
  203. $xml = $this->loadEntity($entity);
  204. // Read list of fields
  205. if (!$xml->fields)
  206. throw new PrestashopInstallerException('List of fields not found for entity '.$entity);
  207. if ($this->isMultilang($entity))
  208. {
  209. $multilang_columns = $this->getColumns($entity, true);
  210. $xml_langs = array();
  211. $default_lang = null;
  212. foreach ($this->languages as $id_lang => $iso)
  213. {
  214. if ($iso == 'en')
  215. $default_lang = $id_lang;
  216. try
  217. {
  218. $xml_langs[$id_lang] = $this->loadEntity($entity, $iso);
  219. }
  220. catch (PrestashopInstallerException $e)
  221. {
  222. $xml_langs[$id_lang] = null;
  223. }
  224. }
  225. }
  226. // Load all row for current entity and prepare data to be populated
  227. foreach ($xml->entities->$entity as $node)
  228. {
  229. $data = array();
  230. $identifier = (string)$node['id'];
  231. // Read attributes
  232. foreach ($node->attributes() as $k => $v)
  233. if ($k != 'id')
  234. $data[$k] = (string)$v;
  235. // Read cdatas
  236. foreach ($node->children() as $child)
  237. $data[$child->getName()] = (string)$child;
  238. // Load multilang data
  239. $data_lang = array();
  240. if ($this->isMultilang($entity))
  241. {
  242. $xpath_query = $entity.'[@id="'.$identifier.'"]';
  243. foreach ($xml_langs as $id_lang => $xml_lang)
  244. {
  245. if (!$xml_lang)
  246. continue;
  247. if (($node_lang = $xml_lang->xpath($xpath_query)) || ($node_lang = $xml_langs[$default_lang]->xpath($xpath_query)))
  248. {
  249. $node_lang = $node_lang[0];
  250. foreach ($multilang_columns as $column => $is_text)
  251. {
  252. $value = '';
  253. if ($node_lang[$column])
  254. $value = (string)$node_lang[$column];
  255. if ($node_lang->$column)
  256. $value = (string)$node_lang->$column;
  257. $data_lang[$column][$id_lang] = $value;
  258. }
  259. }
  260. }
  261. }
  262. $data = $this->rewriteRelationedData($entity, $data);
  263. if (method_exists($this, 'createEntity'.Tools::toCamelCase($entity)))
  264. {
  265. // Create entity with custom method in current class
  266. $method = 'createEntity'.Tools::toCamelCase($entity);
  267. $this->$method($identifier, $data, $data_lang);
  268. }
  269. else
  270. $this->createEntity($entity, $identifier, (string)$xml->fields['class'], $data, $data_lang);
  271. if ($xml->fields['image'])
  272. {
  273. if (method_exists($this, 'copyImages'.Tools::toCamelCase($entity)))
  274. $this->{'copyImages'.Tools::toCamelCase($entity)}($identifier, $data);
  275. else
  276. $this->copyImages($entity, $identifier, (string)$xml->fields['image'], $data);
  277. }
  278. }
  279. $this->flushDelayedInserts();
  280. unset($this->cache_xml_entity[$this->path_type][$entity]);
  281. }
  282. /**
  283. * Special case for "tag" entity
  284. */
  285. public function populateEntityTag()
  286. {
  287. foreach ($this->languages as $id_lang => $iso)
  288. {
  289. if (!file_exists($this->lang_path.$iso.'/data/tag.xml'))
  290. continue;
  291. $xml = $this->loadEntity('tag', $iso);
  292. $tags = array();
  293. foreach ($xml->tag as $tag_node)
  294. {
  295. $products = trim((string)$tag_node['products']);
  296. if (!$products)
  297. continue;
  298. foreach (explode(',', $products) as $product)
  299. {
  300. $product = trim($product);
  301. $product_id = $this->retrieveId('product', $product);
  302. if (!isset($tags[$product_id]))
  303. $tags[$product_id] = array();
  304. $tags[$product_id][] = trim((string)$tag_node['name']);
  305. }
  306. }
  307. foreach ($tags as $id_product => $tag_list)
  308. Tag::addTags($id_lang, $id_product, $tag_list);
  309. }
  310. }
  311. /**
  312. * Load an entity XML file
  313. *
  314. * @param string $entity
  315. * @return SimpleXMLElement
  316. */
  317. protected function loadEntity($entity, $iso = null)
  318. {
  319. if (!isset($this->cache_xml_entity[$this->path_type][$entity][$iso]))
  320. {
  321. $path = $this->data_path.$entity.'.xml';
  322. if ($iso)
  323. $path = $this->lang_path.$iso.'/data/'.$entity.'.xml';
  324. if (!file_exists($path))
  325. throw new PrestashopInstallerException('XML data file '.$entity.'.xml not found');
  326. $this->cache_xml_entity[$this->path_type][$entity][$iso] = @simplexml_load_file($path, 'InstallSimplexmlElement');
  327. if (!$this->cache_xml_entity[$this->path_type][$entity][$iso])
  328. throw new PrestashopInstallerException('XML data file '.$entity.'.xml invalid');
  329. }
  330. return $this->cache_xml_entity[$this->path_type][$entity][$iso];
  331. }
  332. /**
  333. * Check fields related to an other entity, and replace their values by the ID created by the other entity
  334. *
  335. * @param string $entity
  336. * @param array $data
  337. */
  338. protected function rewriteRelationedData($entity, array $data)
  339. {
  340. $xml = $this->loadEntity($entity);
  341. foreach ($xml->fields->field as $field)
  342. if ($field['relation'])
  343. {
  344. $id = $this->retrieveId((string)$field['relation'], $data[(string)$field['name']]);
  345. if (!$id && $data[(string)$field['name']] && is_numeric($data[(string)$field['name']]))
  346. $id = $data[(string)$field['name']];
  347. $data[(string)$field['name']] = $id;
  348. }
  349. return $data;
  350. }
  351. public function flushDelayedInserts()
  352. {
  353. foreach ($this->delayed_inserts as $entity => $queries)
  354. {
  355. $type = Db::INSERT_IGNORE;
  356. if ($entity == 'access')
  357. $type = Db::REPLACE;
  358. if (!Db::getInstance()->insert($entity, $queries, false, true, $type))
  359. $this->setError($this->language->l('An SQL error occured for entity <i>%1$s</i>: <i>%2$s</i>', $entity, Db::getInstance()->getMsgError()));
  360. unset($this->delayed_inserts[$entity]);
  361. }
  362. }
  363. /**
  364. * Create a simple entity with all its data and lang data
  365. * If a methode createEntity$entity exists, use it. Else if $classname is given, use it. Else do a simple insert in database.
  366. *
  367. * @param string $entity
  368. * @param string $identifier
  369. * @param string $classname
  370. * @param array $data
  371. * @param array $data_lang
  372. */
  373. public function createEntity($entity, $identifier, $classname, array $data, array $data_lang = array())
  374. {
  375. $xml = $this->loadEntity($entity);
  376. if ($classname)
  377. {
  378. // Create entity with ObjectModel class
  379. $object = new $classname();
  380. $object->hydrate($data);
  381. if ($data_lang)
  382. $object->hydrate($data_lang);
  383. $object->add(true, (isset($xml->fields['null'])) ? true : false);
  384. $entity_id = $object->id;
  385. unset($object);
  386. }
  387. else
  388. {
  389. // Generate primary key manually
  390. $primary = '';
  391. $entity_id = 0;
  392. if (!$xml->fields['primary'])
  393. $primary = 'id_'.$entity;
  394. else if (strpos((string)$xml->fields['primary'], ',') === false)
  395. $primary = (string)$xml->fields['primary'];
  396. unset($xml);
  397. if ($primary)
  398. {
  399. $entity_id = $this->generatePrimary($entity, $primary);
  400. $data[$primary] = $entity_id;
  401. }
  402. // Store INSERT queries in order to optimize install with grouped inserts
  403. $this->delayed_inserts[$entity][] = array_map('pSQL', $data);
  404. if ($data_lang)
  405. {
  406. $real_data_lang = array();
  407. foreach ($data_lang as $field => $list)
  408. foreach ($list as $id_lang => $value)
  409. $real_data_lang[$id_lang][$field] = $value;
  410. foreach ($real_data_lang as $id_lang => $insert_data_lang)
  411. {
  412. $insert_data_lang['id_'.$entity] = $entity_id;
  413. $insert_data_lang['id_lang'] = $id_lang;
  414. $this->delayed_inserts[$entity.'_lang'][] = array_map('pSQL', $insert_data_lang);
  415. }
  416. // Store INSERT queries for _shop associations
  417. $entity_asso = Shop::getAssoTable($entity);
  418. if ($entity_asso !== false && $entity_asso['type'] == 'shop')
  419. $this->delayed_inserts[$entity.'_shop'][] = array(
  420. 'id_shop' => 1,
  421. 'id_'.$entity => $entity_id,
  422. );
  423. }
  424. }
  425. $this->storeId($entity, $identifier, $entity_id);
  426. }
  427. public function createEntityConfiguration($identifier, array $data, array $data_lang)
  428. {
  429. if (Db::getInstance()->getValue('SELECT id_configuration FROM '._DB_PREFIX_.'configuration WHERE name = \''.pSQL($data['name']).'\''))
  430. return;
  431. $entity = 'configuration';
  432. $entity_id = $this->generatePrimary($entity, 'id_configuration');
  433. $data['id_configuration'] = $entity_id;
  434. // Store INSERT queries in order to optimize install with grouped inserts
  435. $this->delayed_inserts[$entity][] = array_map('pSQL', $data);
  436. if ($data_lang)
  437. {
  438. $real_data_lang = array();
  439. foreach ($data_lang as $field => $list)
  440. foreach ($list as $id_lang => $value)
  441. $real_data_lang[$id_lang][$field] = $value;
  442. foreach ($real_data_lang as $id_lang => $insert_data_lang)
  443. {
  444. $insert_data_lang['id_'.$entity] = $entity_id;
  445. $insert_data_lang['id_lang'] = $id_lang;
  446. $this->delayed_inserts[$entity.'_lang'][] = array_map('pSQL', $insert_data_lang);
  447. }
  448. }
  449. $this->storeId($entity, $identifier, $entity_id);
  450. }
  451. public function createEntityStockAvailable($identifier, array $data, array $data_lang)
  452. {
  453. $stock_available = new StockAvailable();
  454. $stock_available->updateQuantity($data['id_product'], $data['id_product_attribute'], $data['quantity'], $data['id_shop']);
  455. }
  456. public function createEntityTab($identifier, array $data, array $data_lang)
  457. {
  458. static $position = array();
  459. $entity = 'tab';
  460. $xml = $this->loadEntity($entity);
  461. if (!isset($position[$data['id_parent']]))
  462. $position[$data['id_parent']] = 0;
  463. $data['position'] = $position[$data['id_parent']]++;
  464. // Generate primary key manually
  465. $primary = '';
  466. $entity_id = 0;
  467. if (!$xml->fields['primary'])
  468. $primary = 'id_'.$entity;
  469. else if (strpos((string)$xml->fields['primary'], ',') === false)
  470. $primary = (string)$xml->fields['primary'];
  471. if ($primary)
  472. {
  473. $entity_id = $this->generatePrimary($entity, $primary);
  474. $data[$primary] = $entity_id;
  475. }
  476. // Store INSERT queries in order to optimize install with grouped inserts
  477. $this->delayed_inserts[$entity][] = array_map('pSQL', $data);
  478. if ($data_lang)
  479. {
  480. $real_data_lang = array();
  481. foreach ($data_lang as $field => $list)
  482. foreach ($list as $id_lang => $value)
  483. $real_data_lang[$id_lang][$field] = $value;
  484. foreach ($real_data_lang as $id_lang => $insert_data_lang)
  485. {
  486. $insert_data_lang['id_'.$entity] = $entity_id;
  487. $insert_data_lang['id_lang'] = $id_lang;
  488. $this->delayed_inserts[$entity.'_lang'][] = array_map('pSQL', $insert_data_lang);
  489. }
  490. }
  491. $this->storeId($entity, $identifier, $entity_id);
  492. }
  493. public function generatePrimary($entity, $primary)
  494. {
  495. if (!isset($this->primaries[$entity]))
  496. $this->primaries[$entity] = (int)Db::getInstance()->getValue('SELECT '.$primary.' FROM '._DB_PREFIX_.$entity.' ORDER BY '.$primary.' DESC');
  497. return ++$this->primaries[$entity];
  498. }
  499. public function copyImages($entity, $identifier, $path, array $data, $extension = 'jpg')
  500. {
  501. // Get list of image types
  502. $reference = array(
  503. 'product' => 'products',
  504. 'category' => 'categories',
  505. 'manufacturer' => 'manufacturers',
  506. 'supplier' => 'suppliers',
  507. 'scene' => 'scenes',
  508. 'store' => 'stores',
  509. );
  510. $types = array();
  511. if (isset($reference[$entity]))
  512. $types = ImageType::getImagesTypes($reference[$entity]);
  513. // For each path copy images
  514. $path = array_map('trim', explode(',', $path));
  515. foreach ($path as $p)
  516. {
  517. $from_path = $this->img_path.$p.'/';
  518. $dst_path = _PS_IMG_DIR_.$p.'/';
  519. $entity_id = $this->retrieveId($entity, $identifier);
  520. if (!copy($from_path.$identifier.'.'.$extension, $dst_path.$entity_id.'.'.$extension))
  521. {
  522. $this->setError($this->language->l('Cannot create image "%1$s" for entity "%2$s"', $identifier, $entity));
  523. return;
  524. }
  525. foreach ($types as $type)
  526. {
  527. $origin_file = $from_path.$identifier.'-'.$type['name'].'.'.$extension;
  528. $target_file = $dst_path.$entity_id.'-'.$type['name'].'.'.$extension;
  529. // Test if dest folder is writable
  530. if (!is_writable(dirname($target_file)))
  531. $this->setError($this->language->l('Cannot create image "%1$s" (bad permissions on folder "%2$s")', $identifier.'-'.$type['name'], dirname($target_file)));
  532. // If a file named folder/entity-type.extension exists just copy it, this is an optimisation in order to prevent to much resize
  533. elseif (file_exists($origin_file))
  534. {
  535. if (!@copy($origin_file, $target_file))
  536. $this->setError($this->language->l('Cannot create image "%s"', $identifier.'-'.$type['name']));
  537. @chmod($target_file, 0644);
  538. }
  539. // Resize the image if no cache was prepared in fixtures
  540. elseif (!ImageManager::resize($from_path.$identifier.'.'.$extension, $target_file, $type['width'], $type['height']))
  541. $this->setError($this->language->l('Cannot create image "%1$s" for entity "%2$s"', $identifier.'-'.$type['name'], $entity));
  542. }
  543. }
  544. Image::moveToNewFileSystem();
  545. }
  546. public function copyImagesScene($identifier, array $data)
  547. {
  548. $this->copyImages('scene', $identifier, 'scenes', $data);
  549. $from_path = $this->img_path.'scenes/thumbs/';
  550. $dst_path = _PS_IMG_DIR_.'scenes/thumbs/';
  551. $entity_id = $this->retrieveId('scene', $identifier);
  552. if (!@copy($from_path.$identifier.'-m_scene_default.jpg', $dst_path.$entity_id.'-m_scene_default.jpg'))
  553. {
  554. $this->setError($this->language->l('Cannot create image "%1$s" for entity "%2$s"', $identifier, 'scene'));
  555. return;
  556. }
  557. }
  558. public function copyImagesOrderState($identifier, array $data)
  559. {
  560. $this->copyImages('order_state', $identifier, 'os', $data, 'gif');
  561. }
  562. public function copyImagesTab($identifier, array $data)
  563. {
  564. $from_path = $this->img_path.'t/';
  565. $dst_path = _PS_IMG_DIR_.'t/';
  566. if (file_exists($from_path.$data['class_name'].'.gif') && !file_exists($dst_path.$data['class_name'].'.gif'))//test if file exist in install dir and if do not exist in dest folder.
  567. if (!@copy($from_path.$data['class_name'].'.gif', $dst_path.$data['class_name'].'.gif'))
  568. {
  569. $this->setError($this->language->l('Cannot create image "%1$s" for entity "%2$s"', $identifier, 'tab'));
  570. return;
  571. }
  572. }
  573. public function copyImagesImage($identifier)
  574. {
  575. $path = $this->img_path.'p/';
  576. $image = new Image($this->retrieveId('image', $identifier));
  577. $dst_path = $image->getPathForCreation();
  578. if (!@copy($path.$identifier.'.jpg', $dst_path.'.'.$image->image_format))
  579. {
  580. $this->setError($this->language->l('Cannot create image "%1$s" for entity "%2$s"', $identifier, 'product'));
  581. return;
  582. }
  583. @chmod($dst_path.'.'.$image->image_format, 0644);
  584. $types = ImageType::getImagesTypes('products');
  585. foreach ($types as $type)
  586. {
  587. $origin_file = $path.$identifier.'-'.$type['name'].'.jpg';
  588. $target_file = $dst_path.'-'.$type['name'].'.'.$image->image_format;
  589. // Test if dest folder is writable
  590. if (!is_writable(dirname($target_file)))
  591. $this->setError($this->language->l('Cannot create image "%1$s" (bad permissions on folder "%2$s")', $identifier.'-'.$type['name'], dirname($target_file)));
  592. // If a file named folder/entity-type.jpg exists just copy it, this is an optimisation in order to prevent to much resize
  593. else if (file_exists($origin_file))
  594. {
  595. if (!@copy($origin_file, $target_file))
  596. $this->setError($this->language->l('Cannot create image "%1$s" for entity "%2$s"', $identifier.'-'.$type['name'], 'product'));
  597. @chmod($target_file, 0644);
  598. }
  599. // Resize the image if no cache was prepared in fixtures
  600. else if (!ImageManager::resize($path.$identifier.'.jpg', $target_file, $type['width'], $type['height']))
  601. $this->setError($this->language->l('Cannot create image "%1$s" for entity "%2$s"', $identifier.'-'.$type['name'], 'product'));
  602. }
  603. }
  604. public function getTables()
  605. {
  606. static $tables = null;
  607. if (is_null($tables))
  608. {
  609. $sql = 'SHOW TABLES';
  610. $tables = array();
  611. foreach (Db::getInstance()->executeS($sql) as $row)
  612. {
  613. $table = current($row);
  614. if (preg_match('#^'._DB_PREFIX_.'(.+?)(_lang)?$#i', $table, $m))
  615. if (preg_match('#^'._DB_PREFIX_.'(.+?)_shop$#i', $table, $m2) && !isset($tables[$m2[1]]))
  616. $tables[$m[1]] = (isset($m[2]) && $m[2]) ? true : false;
  617. else
  618. $tables[$m[1]] = (isset($m[2]) && $m[2]) ? true : false;
  619. }
  620. }
  621. return $tables;
  622. }
  623. public function hasElements($table)
  624. {
  625. return (bool)Db::getInstance()->getValue('SELECT COUNT(*) FROM '._DB_PREFIX_.$table);
  626. }
  627. public function getColumns($table, $multilang = false, array $exclude = array())
  628. {
  629. static $columns = array();
  630. if ($multilang)
  631. return ($this->isMultilang($table)) ? $this->getColumns($table.'_lang', false, array('id_'.$table)) : array();
  632. if (!isset($columns[$table]))
  633. {
  634. $columns[$table] = array();
  635. $sql = 'SHOW COLUMNS FROM `'._DB_PREFIX_.bqSQL($table).'`';
  636. foreach (Db::getInstance()->executeS($sql) as $row)
  637. $columns[$table][$row['Field']] = $this->checkIfTypeIsText($row['Type']);
  638. }
  639. $exclude = array_merge(array('id_'.$table, 'date_add', 'date_upd', 'deleted', 'id_lang'), $exclude);
  640. $list = array();
  641. foreach ($columns[$table] as $k => $v)
  642. if (!in_array($k, $exclude))
  643. $list[$k] = $v;
  644. return $list;
  645. }
  646. public function getClasses($path = null)
  647. {
  648. static $cache = null;
  649. if (!is_null($cache))
  650. return $cache;
  651. $dir = $path;
  652. if (is_null($dir))
  653. $dir = _PS_CLASS_DIR_;
  654. $classes = array();
  655. foreach (scandir($dir) as $file)
  656. if ($file[0] != '.' && $file != 'index.php')
  657. {
  658. if (is_dir($dir.$file))
  659. $classes = array_merge($classes, $this->getClasses($dir.$file.'/'));
  660. else if (preg_match('#^(.+)\.php$#', $file, $m))
  661. $classes[] = $m[1];
  662. }
  663. sort($classes);
  664. if (is_null($path))
  665. $cache = $classes;
  666. return $classes;
  667. }
  668. public function checkIfTypeIsText($type)
  669. {
  670. if (preg_match('#^(longtext|text|tinytext)#i', $type))
  671. return true;
  672. if (preg_match('#^varchar\(([0-9]+)\)$#i', $type, $m))
  673. return intval($m[1]) >= 64 ? true : false;
  674. return false;
  675. }
  676. public function isMultilang($entity)
  677. {
  678. $tables = $this->getTables();
  679. return isset($tables[$entity]) && $tables[$entity];
  680. }
  681. public function entityExists($entity)
  682. {
  683. return file_exists($this->data_path.$entity.'.xml');
  684. }
  685. public function getEntitiesList()
  686. {
  687. $entities = array();
  688. foreach (scandir($this->data_path) as $file)
  689. if ($file[0] != '.' && preg_match('#^(.+)\.xml$#', $file, $m))
  690. $entities[] = $m[1];
  691. return $entities;
  692. }
  693. public function getEntityInfo($entity)
  694. {
  695. $info = array(
  696. 'config' => array(
  697. 'id' => '',
  698. 'primary' => '',
  699. 'class' => '',
  700. 'sql' => '',
  701. 'ordersql' => '',
  702. 'image' => '',
  703. 'null' => '',
  704. ),
  705. 'fields' => array(),
  706. );
  707. if (!$this->entityExists($entity))
  708. return $info;
  709. $xml = @simplexml_load_file($this->data_path.$entity.'.xml', 'InstallSimplexmlElement');
  710. if (!$xml)
  711. return $info;
  712. if ($xml->fields['id'])
  713. $info['config']['id'] = (string)$xml->fields['id'];
  714. if ($xml->fields['primary'])
  715. $info['config']['primary'] = (string)$xml->fields['primary'];
  716. if ($xml->fields['class'])
  717. $info['config']['class'] = (string)$xml->fields['class'];
  718. if ($xml->fields['sql'])
  719. $info['config']['sql'] = (string)$xml->fields['sql'];
  720. if ($xml->fields['ordersql'])
  721. $info['config']['ordersql'] = (string)$xml->fields['ordersql'];
  722. if ($xml->fields['null'])
  723. $info['config']['null'] = (string)$xml->fields['null'];
  724. if ($xml->fields['image'])
  725. $info['config']['image'] = (string)$xml->fields['image'];
  726. foreach ($xml->fields->field as $field)
  727. {
  728. $column = (string)$field['name'];
  729. $info['fields'][$column] = array();
  730. if (isset($field['relation']))
  731. $info['fields'][$column]['relation'] = (string)$field['relation'];
  732. }
  733. return $info;
  734. }
  735. public function getDependencies()
  736. {
  737. $entities = array();
  738. foreach ($this->getEntitiesList() as $entity)
  739. $entities[$entity] = $this->getEntityInfo($entity);
  740. $dependencies = array();
  741. foreach ($entities as $entity => $info)
  742. {
  743. foreach ($info['fields'] as $field => $info_field)
  744. {
  745. if (isset($info_field['relation']) && $info_field['relation'] != $entity)
  746. {
  747. if (!isset($dependencies[$info_field['relation']]))
  748. $dependencies[$info_field['relation']] = array();
  749. $dependencies[$info_field['relation']][] = $entity;
  750. }
  751. }
  752. }
  753. return $dependencies;
  754. }
  755. public function generateEntitySchema($entity, array $fields, array $config)
  756. {
  757. if ($this->entityExists($entity))
  758. $xml = $this->loadEntity($entity);
  759. else
  760. $xml = new InstallSimplexmlElement('<entity_'.$entity.' />');
  761. unset($xml->fields);
  762. // Fill <fields> attributes (config)
  763. $xml_fields = $xml->addChild('fields');
  764. foreach ($config as $k => $v)
  765. if ($v)
  766. $xml_fields[$k] = $v;
  767. // Create list of fields
  768. foreach ($fields as $column => $info)
  769. {
  770. $field = $xml_fields->addChild('field');
  771. $field['name'] = $column;
  772. if (isset($info['relation']))
  773. $field['relation'] = $info['relation'];
  774. }
  775. // Recreate entities nodes, in order to have the <entities> node after the <fields> node
  776. $store_entities = clone $xml->entities;
  777. unset($xml->entities);
  778. $xml->addChild('entities', $store_entities);
  779. $xml->asXML($this->data_path.$entity.'.xml');
  780. }
  781. /**
  782. * ONLY FOR DEVELOPMENT PURPOSE
  783. */
  784. public function generateAllEntityFiles()
  785. {
  786. $entities = array();
  787. foreach ($this->getEntitiesList() as $entity)
  788. $entities[$entity] = $this->getEntityInfo($entity);
  789. $this->generateEntityFiles($entities);
  790. }
  791. /**
  792. * ONLY FOR DEVELOPMENT PURPOSE
  793. */
  794. public function generateEntityFiles($entities)
  795. {
  796. $dependencies = $this->getDependencies();
  797. // Sort entities to populate database in good order (E.g. zones before countries)
  798. do
  799. {
  800. $current = (isset($sort_entities)) ? $sort_entities : array();
  801. $sort_entities = array();
  802. foreach ($entities as $entity)
  803. {
  804. if (isset($dependencies[$entity]))
  805. {
  806. $min = count($entities) - 1;
  807. foreach ($dependencies[$entity] as $item)
  808. if (($key = array_search($item, $sort_entities)) !== false)
  809. $min = min($min, $key);
  810. if ($min == 0)
  811. array_unshift($sort_entities, $entity);
  812. else
  813. array_splice($sort_entities, $min, 0, array($entity));
  814. }
  815. else
  816. $sort_entities[] = $entity;
  817. }
  818. $entities = $sort_entities;
  819. }
  820. while ($current != $sort_entities);
  821. foreach ($sort_entities as $entity)
  822. $this->generateEntityContent($entity);
  823. }
  824. public function generateEntityContent($entity)
  825. {
  826. $xml = $this->loadEntity($entity);
  827. if (method_exists($this, 'getEntityContents'.Tools::toCamelCase($entity)))
  828. $content = $this->{'getEntityContents'.Tools::toCamelCase($entity)}($entity);
  829. else
  830. $content = $this->getEntityContents($entity);
  831. unset($xml->entities);
  832. $entities = $xml->addChild('entities');
  833. $this->createXmlEntityNodes($entity, $content['nodes'], $entities);
  834. $xml->asXML($this->data_path.$entity.'.xml');
  835. // Generate multilang XML files
  836. if ($content['nodes_lang'])
  837. foreach ($content['nodes_lang'] as $id_lang => $nodes)
  838. {
  839. if (!isset($this->languages[$id_lang]))
  840. continue;
  841. $iso = $this->languages[$id_lang];
  842. if (!is_dir($this->lang_path.$iso.'/data'))
  843. mkdir($this->lang_path.$iso.'/data');
  844. $xml_node = new InstallSimplexmlElement('<entity_'.$entity.' />');
  845. $this->createXmlEntityNodes($entity, $nodes, $xml_node);
  846. $xml_node->asXML($this->lang_path.$iso.'/data/'.$entity.'.xml');
  847. }
  848. if ($xml->fields['image'])
  849. {
  850. if (method_exists($this, 'backupImage'.Tools::toCamelCase($entity)))
  851. $this->{'backupImage'.Tools::toCamelCase($entity)}((string)$xml->fields['image']);
  852. else
  853. $this->backupImage($entity, (string)$xml->fields['image']);
  854. }
  855. }
  856. /**
  857. * ONLY FOR DEVELOPMENT PURPOSE
  858. */
  859. public function getEntityContents($entity)
  860. {
  861. $xml = $this->loadEntity($entity);
  862. $primary = (isset($xml->fields['primary']) && $xml->fields['primary']) ? (string)$xml->fields['primary'] : 'id_'.$entity;
  863. $is_multilang = $this->isMultilang($entity);
  864. // Check if current table is an association table (if multiple primary keys)
  865. $is_association = false;
  866. if (strpos($primary, ',') !== false)
  867. {
  868. $is_association = true;
  869. $primary = array_map('trim', explode(',', $primary));
  870. }
  871. // Build query
  872. $sql = new DbQuery();
  873. $sql->select('a.*');
  874. $sql->from($entity, 'a');
  875. if ($is_multilang)
  876. {
  877. $sql->select('b.*');
  878. $sql->leftJoin($entity.'_lang', 'b', 'a.'.$primary.' = b.'.$primary);
  879. }
  880. if (isset($xml->fields['sql']) && $xml->fields['sql'])
  881. $sql->where((string)$xml->fields['sql']);
  882. if (!$is_association)
  883. {
  884. $sql->select('a.'.$primary);
  885. if (!isset($xml->fields['ordersql']) || !$xml->fields['ordersql'])
  886. $sql->orderBy('a.'.$primary);
  887. }
  888. if ($is_multilang && (!isset($xml->fields['ordersql']) || !$xml->fields['ordersql']))
  889. $sql->orderBy('b.id_lang');
  890. if (isset($xml->fields['ordersql']) && $xml->fields['ordersql'])
  891. $sql->orderBy((string)$xml->fields['ordersql']);
  892. // Get multilang columns
  893. $alias_multilang = array();
  894. if ($is_multilang)
  895. {
  896. $columns = $this->getColumns($entity);
  897. $multilang_columns = $this->getColumns($entity, true);
  898. // If some columns from _lang table have same name than original table, rename them (E.g. value in configuration)
  899. foreach ($multilang_columns as $c => $is_text)
  900. if (isset($columns[$c]))
  901. {
  902. $alias = $c.'_alias';
  903. $alias_multilang[$c] = $alias;
  904. $sql->select('a.'.$c.' as '.$c.', b.'.$c.' as '.$alias);
  905. }
  906. }
  907. // Get all results
  908. $nodes = $nodes_lang = array();
  909. $results = Db::getInstance()->executeS($sql);
  910. if (Db::getInstance()->getNumberError())
  911. $this->setError($this->language->l('SQL error on query <i>%s</i>', $sql));
  912. else
  913. {
  914. foreach ($results as $row)
  915. {
  916. // Store common columns
  917. if ($is_association)
  918. {
  919. $id = $entity;
  920. foreach ($primary as $key)
  921. $id .= '_'.$row[$key];
  922. }
  923. else
  924. $id = $this->generateId($entity, $row[$primary], $row, (isset($xml->fields['id']) && $xml->fields['id']) ? (string)$xml->fields['id'] : null);
  925. if (!isset($nodes[$id]))
  926. {
  927. $node = array();
  928. foreach ($xml->fields->field as $field)
  929. {
  930. $column = (string)$field['name'];
  931. if (isset($field['relation']))
  932. {
  933. $sql = 'SELECT `id_'.bqSQL($field['relation']).'`
  934. FROM `'.bqSQL(_DB_PREFIX_.$field['relation']).'`
  935. WHERE `id_'.bqSQL($field['relation']).'` = '.(int)$row[$column];
  936. $node[$column] = $this->generateId((string)$field['relation'], Db::getInstance()->getValue($sql));
  937. // A little trick to allow storage of some hard values, like '-1' for tab.id_parent
  938. if (!$node[$column] && $row[$column])
  939. $node[$column] = $row[$column];
  940. }
  941. else
  942. $node[$column] = $row[$column];
  943. }
  944. $nodes[$id] = $node;
  945. }
  946. // Store multilang columns
  947. if ($is_multilang && $row['id_lang'])
  948. {
  949. $node = array();
  950. foreach ($multilang_columns as $column => $is_text)
  951. $node[$column] = $row[isset($alias_multilang[$column]) ? $alias_multilang[$column] : $column];
  952. $nodes_lang[$row['id_lang']][$id] = $node;
  953. }
  954. }
  955. }
  956. return array(
  957. 'nodes' => $nodes,
  958. 'nodes_lang' => $nodes_lang,
  959. );
  960. }
  961. public function getEntityContentsTag()
  962. {
  963. $nodes_lang = array();
  964. $sql = 'SELECT t.id_tag, t.id_lang, t.name, pt.id_product
  965. FROM '._DB_PREFIX_.'tag t
  966. LEFT JOIN '._DB_PREFIX_.'product_tag pt ON t.id_tag = pt.id_tag
  967. ORDER BY id_lang';
  968. foreach (Db::getInstance()->executeS($sql) as $row)
  969. {
  970. $identifier = $this->generateId('tag', $row['id_tag']);
  971. if (!isset($nodes_lang[$row['id_lang']]))
  972. $nodes_lang[$row['id_lang']] = array();
  973. if (!isset($nodes_lang[$row['id_lang']][$identifier]))
  974. $nodes_lang[$row['id_lang']][$identifier] = array(
  975. 'name' => $row['name'],
  976. 'products' => '',
  977. );
  978. $nodes_lang[$row['id_lang']][$identifier]['products'] .= (($nodes_lang[$row['id_lang']][$identifier]['products']) ? ',' : '').$this->generateId('product', $row['id_product']);
  979. }
  980. return array(
  981. 'nodes' => array(),
  982. 'nodes_lang' => $nodes_lang,
  983. );
  984. }
  985. /**
  986. * ONLY FOR DEVELOPMENT PURPOSE
  987. */
  988. public function generateId($entity, $primary, array $row = array(), $id_format = null)
  989. {
  990. static $ids = array();
  991. if (isset($ids[$entity][$primary]))
  992. return $ids[$entity][$primary];
  993. if (!isset($ids[$entity]))
  994. $ids[$entity] = array();
  995. if (!$primary)
  996. return '';
  997. if (!$id_format || !$row || !$row[$id_format])
  998. $ids[$entity][$primary] = $entity.'_'.$primary;
  999. else
  1000. {
  1001. $value = $row[$id_format];
  1002. $value = preg_replace('#[^a-z0-9_-]#i', '_', $value);
  1003. $value = preg_replace('#_+#', '_', $value);
  1004. $value = preg_replace('#^_+#', '', $value);
  1005. $value = preg_replace('#_+$#', '', $value);
  1006. $store_identifier = $value;
  1007. $i = 1;
  1008. while (in_array($store_identifier, $ids[$entity]))
  1009. $store_identifier = $value.'_'.$i++;
  1010. $ids[$entity][$primary] = $store_identifier;
  1011. }
  1012. return $ids[$entity][$primary];
  1013. }
  1014. /**
  1015. * ONLY FOR DEVELOPMENT PURPOSE
  1016. */
  1017. public function createXmlEntityNodes($entity, array $nodes, SimpleXMLElement $entities)
  1018. {
  1019. $types = array_merge($this->getColumns($entity), $this->getColumns($entity, true));
  1020. foreach ($nodes as $id => $node)
  1021. {
  1022. $entity_node = $entities->addChild($entity);
  1023. $entity_node['id'] = $id;
  1024. foreach ($node as $k => $v)
  1025. {
  1026. if (isset($types[$k]) && $types[$k])
  1027. $entity_node->addChild($k, $v);
  1028. else
  1029. $entity_node[$k] = $v;
  1030. }
  1031. }
  1032. }
  1033. /**
  1034. * ONLY FOR DEVELOPMENT PURPOSE
  1035. */
  1036. public function backupImage($entity, $path)
  1037. {
  1038. $reference = array(
  1039. 'product' => 'products',
  1040. 'category' => 'categories',
  1041. 'manufacturer' => 'manufacturers',
  1042. 'supplier' => 'suppliers',
  1043. 'scene' => 'scenes',
  1044. 'store' => 'stores',
  1045. );
  1046. $types = array();
  1047. if (isset($reference[$entity]))
  1048. {
  1049. $types = array();
  1050. foreach (ImageType::getImagesTypes($reference[$entity]) as $type)
  1051. $types[] = $type['name'];
  1052. }
  1053. $path_list = array_map('trim', explode(',', $path));
  1054. foreach ($path_list as $p)
  1055. {
  1056. $backup_path = $this->img_path.$p.'/';
  1057. $from_path = _PS_IMG_DIR_.$p.'/';
  1058. if (!is_dir($backup_path) && !mkdir($backup_path))
  1059. $this->setError(sprintf('Cannot create directory <i>%s</i>', $backup_path));
  1060. foreach (scandir($from_path) as $file)
  1061. if ($file[0] != '.' && preg_match('#^(([0-9]+)(-('.implode('|', $types).'))?)\.(gif|jpg|jpeg|png)$#i', $file, $m))
  1062. {
  1063. $file_id = $m[2];
  1064. $file_type = $m[3];
  1065. $file_extension = $m[5];
  1066. copy($from_path.$file, $backup_path.$this->generateId($entity, $file_id).$file_type.'.'.$file_extension);
  1067. }
  1068. }
  1069. }
  1070. /**
  1071. * ONLY FOR DEVELOPMENT PURPOSE
  1072. */
  1073. public function backupImageImage()
  1074. {
  1075. $types = array();
  1076. foreach (ImageType::getImagesTypes('products') as $type)
  1077. $types[] = $type['name'];
  1078. $backup_path = $this->img_path.'p/';
  1079. $from_path = _PS_PROD_IMG_DIR_;
  1080. if (!is_dir($backup_path) && !mkdir($backup_path))
  1081. $this->setError(sprintf('Cannot create directory <i>%s</i>', $backup_path));
  1082. foreach (Image::getAllImages() as $image)
  1083. {
  1084. $image = new Image($image['id_image']);
  1085. $image_path = $image->getExistingImgPath();
  1086. if (file_exists($from_path.$image_path.'.'.$image->image_format))
  1087. copy($from_path.$image_path.'.'.$image->image_format, $backup_path.$this->generateId('image', $image->id).'.'.$image->image_format);
  1088. foreach ($types as $type)
  1089. if (file_exists($from_path.$image_path.'-'.$type.'.'.$image->image_format))
  1090. copy($from_path.$image_path.'-'.$type.'.'.$image->image_format, $backup_path.$this->generateId('image', $image->id).'-'.$type.'.'.$image->image_format);
  1091. }
  1092. }
  1093. /**
  1094. * ONLY FOR DEVELOPMENT PURPOSE
  1095. */
  1096. public function backupImageTab()
  1097. {
  1098. $backup_path = $this->img_path.'t/';
  1099. $from_path = _PS_IMG_DIR_.'t/';
  1100. if (!is_dir($backup_path) && !mkdir($backup_path))
  1101. $this->setError(sprintf('Cannot create directory <i>%s</i>', $backup_path));
  1102. $xml = $this->loadEntity('tab');
  1103. foreach ($xml->entities->tab as $tab)
  1104. if (file_exists($from_path.$tab->class_name.'.gif'))
  1105. copy($from_path.$tab->class_name.'.gif', $backup_path.$tab->class_name.'.gif');
  1106. }
  1107. }