PageRenderTime 30ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/install.ps/classes/xmlLoader.php

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