PageRenderTime 4ms CodeModel.GetById 2ms app.highlight 92ms RepoModel.GetById 14ms app.codeStats 1ms

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