PageRenderTime 37ms CodeModel.GetById 37ms RepoModel.GetById 1ms app.codeStats 1ms

/controllers/admin/AdminTranslationsController.php

https://bitbucket.org/marcenuc/prestashop
PHP | 2593 lines | 1835 code | 304 blank | 454 comment | 358 complexity | 6435c843691b585aebad1b0dd11a4794 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: 7310 $
  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. define ('TEXTAREA_SIZED', 70);
  28. class AdminTranslationsControllerCore extends AdminController
  29. {
  30. /** Name of theme by default */
  31. const DEFAULT_THEME_NAME = 'default';
  32. /** @var string : Link which list all pack of language */
  33. protected $link_lang_pack = 'http://www.prestashop.com/download/lang_packs/get_each_language_pack.php';
  34. /** @var int : number of sentence which can be translated */
  35. protected $total_expression = 0;
  36. /** @var int : number of sentence which aren't translated */
  37. protected $missing_translations = 0;
  38. /** @var array : List of ISO code for all languages */
  39. protected $all_iso_lang = array();
  40. /** @var array */
  41. protected $modules_translations = array();
  42. /** @var array : List of folder which must be ignored */
  43. protected static $ignore_folder = array('.', '..', '.svn', '.htaccess', 'index.php');
  44. /** @var array : List of theme by translation type : FRONT, BACK, ERRORS... */
  45. protected $translations_informations = array();
  46. /** @var array : List of theme by translation type : FRONT, BACK, ERRORS... */
  47. protected $translations_type_for_theme = array('front', 'modules', 'pdf', 'mails');
  48. /** @var array : List of all languages */
  49. protected $languages;
  50. /** @var array : List of all themes */
  51. protected $themes;
  52. /** @var string : Directory of selected theme */
  53. protected $theme_selected;
  54. /** @var string : Name of translations type */
  55. protected $type_selected;
  56. /** @var object : Language for the selected language */
  57. protected $lang_selected;
  58. /** @var boolean : Is true if number of var exceed the suhosin request or post limit */
  59. protected $post_limit_exceed = false;
  60. public function __construct()
  61. {
  62. $this->multishop_context = Shop::CONTEXT_ALL;
  63. parent::__construct();
  64. $this->table = 'translations';
  65. // Include all file for create or read an archive
  66. include_once(_PS_ADMIN_DIR_.'/../tools/tar/Archive_Tar.php');
  67. include_once(_PS_ADMIN_DIR_.'/../tools/pear/PEAR.php');
  68. }
  69. /*
  70. * Set the type which is selected
  71. */
  72. public function setTypeSelected($type_selected)
  73. {
  74. $this->type_selected = $type_selected;
  75. }
  76. /**
  77. * AdminController::initContent() override
  78. * @see AdminController::initContent()
  79. */
  80. public function initContent()
  81. {
  82. if (!is_null($this->type_selected))
  83. {
  84. $method_name = 'initForm'.$this->type_selected;
  85. if (method_exists($this, $method_name))
  86. $this->content = $this->initForm($method_name);
  87. else
  88. {
  89. $this->errors[] = sprintf(Tools::displayError('"%s" does not exist. Maybe you typed the URL manually.'), $this->type_selected);
  90. $this->content = $this->initMain();
  91. }
  92. }
  93. else
  94. $this->content = $this->initMain();
  95. $this->context->smarty->assign(array('content' => $this->content));
  96. }
  97. /**
  98. * This function create vars by default and call the good method for generate form
  99. *
  100. * @param $method_name
  101. * @return call the method $this->method_name()
  102. */
  103. public function initForm($method_name)
  104. {
  105. // Create a title for each translation page
  106. $title = sprintf(
  107. $this->l('%1$s (Language: %2$s, Theme: %3$s)'),
  108. $this->translations_informations[$this->type_selected]['name'],
  109. $this->lang_selected->name,
  110. $this->theme_selected
  111. );
  112. // Set vars for all forms
  113. $this->tpl_view_vars = array(
  114. 'lang' => $this->lang_selected->iso_code,
  115. 'title' => $title,
  116. 'type' => $this->type_selected,
  117. 'theme' => $this->theme_selected,
  118. 'post_limit_exceeded' => $this->post_limit_exceed,
  119. 'url_submit' => self::$currentIndex.'&submitTranslations'.ucfirst($this->type_selected).'=1&token='.$this->token,
  120. 'toggle_button' => $this->displayToggleButton(),
  121. 'textarea_sized' => TEXTAREA_SIZED,
  122. 'auto_translate' => ''
  123. );
  124. // Call method initForm for a type
  125. return $this->{$method_name}();
  126. }
  127. /**
  128. * AdminController::initToolbar() override
  129. * @see AdminController::initToolbar()
  130. */
  131. public function initToolbar()
  132. {
  133. $this->toolbar_btn['save-and-stay'] = array(
  134. 'short' => 'SaveAndStay',
  135. 'href' => '#',
  136. 'desc' => $this->l('Save and stay'),
  137. );
  138. $this->toolbar_btn['save'] = array(
  139. 'href' => '#',
  140. 'desc' => $this->l('Update translations')
  141. );
  142. $this->toolbar_btn['cancel'] = array(
  143. 'href' => self::$currentIndex.'&token='.$this->token,
  144. 'desc' => $this->l('Cancel')
  145. );
  146. }
  147. /**
  148. * Generate the Main page
  149. */
  150. public function initMain()
  151. {
  152. // Block add/update a language
  153. $packs_to_install = array();
  154. $packs_to_update = array();
  155. $token = Tools::getAdminToken('AdminLanguages'.(int)Tab::getIdFromClassName('AdminLanguages').(int)$this->context->employee->id);
  156. $file_name = $this->link_lang_pack.'?version='._PS_VERSION_;
  157. $array_stream_context = array('http' => array('method' => 'GET', 'timeout' => 5));
  158. if ($lang_packs = Tools::file_get_contents($file_name, false, @stream_context_create($array_stream_context)))
  159. // Notice : for php < 5.2 compatibility, Tools::jsonDecode. The second parameter to true will set us
  160. if ($lang_packs != '' && $lang_packs = Tools::jsonDecode($lang_packs, true))
  161. foreach ($lang_packs as $key => $lang_pack)
  162. {
  163. if (!Language::isInstalled($lang_pack['iso_code']))
  164. $packs_to_install[$key] = $lang_pack;
  165. else
  166. $packs_to_update[$key] = $lang_pack;
  167. }
  168. $this->tpl_view_vars = array(
  169. 'theme_default' => self::DEFAULT_THEME_NAME,
  170. 'theme_lang_dir' =>_THEME_LANG_DIR_,
  171. 'token' => $this->token,
  172. 'languages' => $this->languages,
  173. 'translations_type' => $this->translations_informations,
  174. 'translations_type_for_theme' => $this->translations_type_for_theme,
  175. 'packs_to_install' => $packs_to_install,
  176. 'packs_to_update' => $packs_to_update,
  177. 'url_submit' => self::$currentIndex.'&token='.$this->token,
  178. 'themes' => $this->themes,
  179. 'id_theme_current' => $this->context->shop->id_theme,
  180. 'url_create_language' => 'index.php?controller=AdminLanguages&addlang&token='.$token,
  181. );
  182. $this->toolbar_scroll = false;
  183. $this->base_tpl_view = 'main.tpl';
  184. return parent::renderView();
  185. }
  186. /**
  187. * This method merge each arrays of modules translation in the array of modules translations
  188. */
  189. protected function getModuleTranslations()
  190. {
  191. global $_MODULE;
  192. $name_var = $this->translations_informations[$this->type_selected]['var'];
  193. if (!isset($_MODULE) && !isset($GLOBALS[$name_var]))
  194. $GLOBALS[$name_var] = array();
  195. else if (isset($_MODULE))
  196. if (is_array($GLOBALS[$name_var]) && is_array($_MODULE))
  197. $GLOBALS[$name_var] = array_merge($GLOBALS[$name_var], $_MODULE);
  198. else
  199. $GLOBALS[$name_var] = $_MODULE;
  200. }
  201. /**
  202. * This method is only used by AdminTranslations::submitCopyLang().
  203. *
  204. * It try to create folder in new theme.
  205. *
  206. * When a translation file is copied for a module, its translation key is wrong.
  207. * We have to change the translation key and rewrite the file.
  208. *
  209. * @param string $dest file name
  210. * @return bool
  211. */
  212. protected function checkDirAndCreate($dest)
  213. {
  214. $bool = true;
  215. // To get only folder path
  216. $path = dirname($dest);
  217. // If folder wasn't already added
  218. if (!Tools::file_exists_cache($path))
  219. if (!mkdir($path, 0777, true))
  220. {
  221. $bool &= false;
  222. $this->errors[] = sprintf($this->l('Cannot create the folder "%s". Check directory writing permisions.'), $path);
  223. }
  224. return $bool;
  225. }
  226. /**
  227. * Read the Post var and write the translation file.
  228. * This method overwrites the old translation file.
  229. *
  230. * @param bool $override_file : set true if this file is a override
  231. */
  232. protected function writeTranslationFile($override_file = false)
  233. {
  234. $type = Tools::toCamelCase($this->type_selected, true);
  235. $translation_informations = $this->translations_informations[$this->type_selected];
  236. if ($override_file)
  237. $file_path = $translation_informations['override']['dir'].$translation_informations['override']['file'];
  238. else
  239. $file_path = $translation_informations['dir'].$translation_informations['file'];
  240. if (!file_exists($file_path))
  241. throw new PrestaShopException(sprintf(Tools::displayError('This file doesn\'t exists: "%s". Please create this file.'), $file_path));
  242. if ($fd = fopen($file_path, 'w'))
  243. {
  244. // Get value of button save and stay
  245. $save_and_stay = Tools::getValue('submitTranslations'.$type.'AndStay');
  246. // Get language
  247. $lang = strtolower(Tools::getValue('lang'));
  248. // Unset all POST which are not translations
  249. unset(
  250. $_POST['submitTranslations'.$type],
  251. $_POST['submitTranslations'.$type.'AndStay'],
  252. $_POST['lang'],
  253. $_POST['token'],
  254. $_POST['theme'],
  255. $_POST['type']
  256. );
  257. // Get all POST which aren't empty
  258. $to_insert = array();
  259. foreach ($_POST as $key => $value)
  260. if (!empty($value))
  261. $to_insert[$key] = $value;
  262. // translations array is ordered by key (easy merge)
  263. ksort($to_insert);
  264. $tab = $translation_informations['var'];
  265. fwrite($fd, "<?php\n\nglobal \$".$tab.";\n\$".$tab." = array();\n");
  266. foreach ($to_insert as $key => $value)
  267. fwrite($fd, '$'.$tab.'[\''.pSQL($key, true).'\'] = \''.pSQL($value, true).'\';'."\n");
  268. fwrite($fd, "\n?>");
  269. fclose($fd);
  270. // Redirect
  271. if ($save_and_stay)
  272. $this->redirect(true);
  273. else
  274. $this->redirect();
  275. }
  276. else
  277. throw new PrestaShopException(sprintf(Tools::displayError('Cannot write this file: "%s"'), $file_path));
  278. }
  279. public function submitCopyLang()
  280. {
  281. if (!($from_lang = strval(Tools::getValue('fromLang'))) || !($to_lang = strval(Tools::getValue('toLang'))))
  282. $this->errors[] = $this->l('You must select 2 languages in order to copy data from one to another');
  283. else if (!($from_theme = strval(Tools::getValue('fromTheme'))) || !($to_theme = strval(Tools::getValue('toTheme'))))
  284. $this->errors[] = $this->l('You must select 2 themes in order to copy data from one to another');
  285. else if (!Language::copyLanguageData(Language::getIdByIso($from_lang), Language::getIdByIso($to_lang)))
  286. $this->errors[] = $this->l('An error occurred while copying data');
  287. else if ($from_lang == $to_lang && $from_theme == $to_theme)
  288. $this->errors[] = $this->l('Nothing to copy! (same language and theme)');
  289. if (count($this->errors))
  290. return;
  291. $bool = true;
  292. $items = Language::getFilesList($from_lang, $from_theme, $to_lang, $to_theme, false, false, true);
  293. foreach ($items as $source => $dest)
  294. {
  295. $bool &= $this->checkDirAndCreate($dest);
  296. $bool &= @copy($source, $dest);
  297. if (strpos($dest, 'modules') && basename($source) === $from_lang.'.php' && $bool !== false)
  298. $bool &= $this->changeModulesKeyTranslation($dest, $from_theme, $to_theme);
  299. }
  300. if ($bool)
  301. $this->redirect(false, 14);
  302. $this->errors[] = $this->l('A part of the data has been copied but some language files could not be found or copied');
  303. }
  304. /**
  305. * Change the key translation to according it to theme name.
  306. *
  307. * @param string $path
  308. * @param string $theme_from
  309. * @param string $theme_to
  310. * @return boolean
  311. */
  312. public function changeModulesKeyTranslation($path, $theme_from, $theme_to)
  313. {
  314. $content = file_get_contents($path);
  315. $arr_replace = array();
  316. $bool_flag = true;
  317. if (preg_match_all('#\$_MODULE\[\'([^\']+)\'\]#Ui', $content, $matches))
  318. {
  319. foreach ($matches[1] as $key => $value)
  320. $arr_replace[$value] = str_replace($theme_from, $theme_to, $value);
  321. $content = str_replace(array_keys($arr_replace), array_values($arr_replace), $content);
  322. $bool_flag = (file_put_contents($path, $content) === false) ? false : true;
  323. }
  324. return $bool_flag;
  325. }
  326. public function exportTabs()
  327. {
  328. // Get name tabs by iso code
  329. $tabs = Tab::getTabs($this->lang_selected->id);
  330. // Get name of the default tabs
  331. $tabs_default_lang = Tab::getTabs(1);
  332. $tabs_default = array();
  333. foreach ($tabs_default_lang as $tab)
  334. $tabs_default[$tab['class_name']] = pSQL($tab['name']);
  335. // Create content
  336. $content = "<?php\n\n\$tabs = array();";
  337. if (!empty($tabs))
  338. foreach ($tabs as $tab)
  339. if ($tabs_default[$tab['class_name']] != pSQL($tab['name']))
  340. $content .= "\n\$tabs['".$tab['class_name']."'] = '".pSQL($tab['name'])."';";
  341. $content .= "\n\nreturn \$tabs;";
  342. $dir = _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.DIRECTORY_SEPARATOR;
  343. $path = $dir.'tabs.php';
  344. // Check if tabs.php exists for the selected Iso Code
  345. if (!Tools::file_exists_cache($dir))
  346. if (!mkdir($dir, 0777, true))
  347. throw new PrestaShopException('The file '.$dir.' cannot be created.');
  348. if (!file_put_contents($path, $content))
  349. throw new PrestaShopException('File "'.$path.'" doesn\'t exists and cannot be created in '.$dir);
  350. if (!is_writable($path))
  351. $this->displayWarning(sprintf(Tools::displayError('This file must be writable: %s'), $path));
  352. }
  353. public function submitExportLang()
  354. {
  355. if ($this->lang_selected->iso_code && $this->theme_selected)
  356. {
  357. $this->exportTabs();
  358. $items = array_flip(Language::getFilesList($this->lang_selected->iso_code, $this->theme_selected, false, false, false, false, true));
  359. $gz = new Archive_Tar(_PS_TRANSLATIONS_DIR_.'/export/'.$this->lang_selected->iso_code.'.gzip', true);
  360. $file_name = Tools::getCurrentUrlProtocolPrefix().Tools::getShopDomain().__PS_BASE_URI__.'translations/export/'.$this->lang_selected->iso_code.'.gzip';
  361. if ($gz->createModify($items, null, _PS_ROOT_DIR_));
  362. {
  363. ob_start();
  364. header('Pragma: public');
  365. header('Expires: 0');
  366. header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
  367. header('Cache-Control: public');
  368. header('Content-Description: File Transfer');
  369. header('Content-type: application/octet-stream');
  370. header('Content-Disposition: attachment; filename="'.$this->lang_selected->iso_code.'.gzip'.'"');
  371. header('Content-Transfer-Encoding: binary');
  372. ob_end_flush();
  373. @readfile($file_name);
  374. exit;
  375. }
  376. $this->errors[] = Tools::displayError('An error occurred while creating archive.');
  377. }
  378. $this->errors[] = Tools::displayError('Please choose a language and a theme.');
  379. }
  380. public static function checkAndAddMailsFiles($iso_code, $files_list)
  381. {
  382. // 1 - Scan mails files
  383. $mails = scandir(_PS_MAIL_DIR_.'en/');
  384. $mails_new_lang = array();
  385. // Get all email files
  386. foreach ($files_list as $file)
  387. {
  388. if (preg_match('#^mails\/([a-z0-9]+)\/#Ui', $file['filename'], $matches))
  389. {
  390. $slash_pos = strrpos($file['filename'], '/');
  391. $mails_new_lang[] = substr($file['filename'], -(strlen($file['filename']) - $slash_pos - 1));
  392. }
  393. }
  394. // Get the difference
  395. $arr_mails_needed = array_diff($mails, $mails_new_lang);
  396. // Add mails files
  397. foreach ($arr_mails_needed as $mail_to_add)
  398. if (!in_array($mail_to_add, self::$ignore_folder))
  399. @copy(_PS_MAIL_DIR_.'en/'.$mail_to_add, _PS_MAIL_DIR_.$iso_code.'/'.$mail_to_add);
  400. // 2 - Scan modules files
  401. $modules = scandir(_PS_MODULE_DIR_);
  402. $module_mail_en = array();
  403. $module_mail_iso_code = array();
  404. foreach ($modules as $module)
  405. {
  406. if (!in_array($module, self::$ignore_folder) && Tools::file_exists_cache(_PS_MODULE_DIR_.$module.'/mails/en/'))
  407. {
  408. $arr_files = scandir(_PS_MODULE_DIR_.$module.'/mails/en/');
  409. foreach ($arr_files as $file)
  410. {
  411. if (!in_array($file, self::$ignore_folder))
  412. {
  413. if (Tools::file_exists_cache(_PS_MODULE_DIR_.$module.'/mails/en/'.$file))
  414. $module_mail_en[] = _PS_MODULE_DIR_.$module.'/mails/ISO_CODE/'.$file;
  415. if (Tools::file_exists_cache(_PS_MODULE_DIR_.$module.'/mails/'.$iso_code.'/'.$file))
  416. $module_mail_iso_code[] = _PS_MODULE_DIR_.$module.'/mails/ISO_CODE/'.$file;
  417. }
  418. }
  419. }
  420. }
  421. // Get the difference in this modules
  422. $arr_modules_mails_needed = array_diff($module_mail_en, $module_mail_iso_code);
  423. // Add mails files for this modules
  424. foreach ($arr_modules_mails_needed as $file)
  425. {
  426. $file_en = str_replace('ISO_CODE', 'en', $file);
  427. $file_iso_code = str_replace('ISO_CODE', $iso_code, $file);
  428. $dir_iso_code = substr($file_iso_code, 0, -(strlen($file_iso_code) - strrpos($file_iso_code, '/') - 1));
  429. if (!file_exists($dir_iso_code))
  430. mkdir($dir_iso_code);
  431. if (Tools::file_exists_cache($file_en))
  432. copy($file_en, $file_iso_code);
  433. }
  434. }
  435. /**
  436. * Move theme translations in selected themes
  437. *
  438. * @param array $files
  439. * @param array $themes_selected
  440. */
  441. public function checkAndAddThemesFiles($files, $themes_selected)
  442. {
  443. foreach ($files as $file)
  444. {
  445. // Check if file is a file theme
  446. if (preg_match('#^themes\/([a-z0-9]+)\/lang\/#Ui', $file['filename'], $matches))
  447. {
  448. $slash_pos = strrpos($file['filename'], '/');
  449. $name_file = substr($file['filename'], -(strlen($file['filename']) - $slash_pos - 1));
  450. $name_default_theme = $matches[1];
  451. $deleted_old_theme = false;
  452. // Get the old file theme
  453. if (file_exists(_PS_THEME_DIR_.'lang/'.$name_file))
  454. $theme_file_old = _PS_THEME_DIR_.'lang/'.$name_file;
  455. else
  456. {
  457. $deleted_old_theme = true;
  458. $theme_file_old = str_replace(self::DEFAULT_THEME_NAME, $name_default_theme, _PS_THEME_DIR_.'lang/'.$name_file);
  459. }
  460. // Move the old file theme in the new folder
  461. foreach ($themes_selected as $theme_name)
  462. if (file_exists($theme_file_old))
  463. copy($theme_file_old, str_replace($name_default_theme, $theme_name, $theme_file_old));
  464. if ($deleted_old_theme)
  465. @unlink($theme_file_old);
  466. }
  467. }
  468. }
  469. /**
  470. * Add new translations tabs by code ISO
  471. *
  472. * @param array $iso_code
  473. * @param array $files
  474. */
  475. public static function addNewTabs($iso_code, $files)
  476. {
  477. foreach ($files as $file)
  478. {
  479. // Check if file is a file theme
  480. if (preg_match('#^translations\/'.$iso_code.'\/tabs.php#Ui', $file['filename'], $matches) && Validate::isLanguageIsoCode($iso_code))
  481. {
  482. // Include array width new translations tabs
  483. $tabs = include _PS_ROOT_DIR_.DIRECTORY_SEPARATOR.$file['filename'];
  484. foreach ($tabs as $class_name => $translations)
  485. {
  486. // Get instance of this tab by class name
  487. $tab = Tab::getInstanceFromClassName($class_name);
  488. //Check if class name exists
  489. if (isset($tab->class_name) && !empty($tab->class_name))
  490. {
  491. $id_lang = Language::getIdByIso($iso_code);
  492. $tab->name[(int)$id_lang] = pSQL($translations);
  493. // Update this tab
  494. $tab->update();
  495. }
  496. }
  497. }
  498. }
  499. }
  500. public function submitImportLang()
  501. {
  502. if (!isset($_FILES['file']['tmp_name']) || !$_FILES['file']['tmp_name'])
  503. $this->errors[] = Tools::displayError('No file selected');
  504. else
  505. {
  506. $gz = new Archive_Tar($_FILES['file']['tmp_name'], true);
  507. $filename = $_FILES['file']['name'];
  508. $iso_code = str_replace(array('.tar.gz', '.gzip'), '', $filename);
  509. if (Validate::isLangIsoCode($iso_code))
  510. {
  511. $themes_selected = Tools::getValue('theme', array(self::DEFAULT_THEME_NAME));
  512. $files_list = $gz->listContent();
  513. if ($gz->extract(_PS_TRANSLATIONS_DIR_.'../', false))
  514. {
  515. AdminTranslationsController::checkAndAddMailsFiles($iso_code, $files_list);
  516. $this->checkAndAddThemesFiles($files_list, $themes_selected);
  517. AdminTranslationsController::addNewTabs($iso_code, $files_list);
  518. if (Validate::isLanguageFileName($filename))
  519. {
  520. if (!Language::checkAndAddLanguage($iso_code))
  521. $conf = 20;
  522. }
  523. $this->redirect(false, (isset($conf) ? $conf : '15'));
  524. }
  525. $this->errors[] = Tools::displayError('Archive cannot be extracted.');
  526. }
  527. else
  528. $this->errors[] = sprintf(Tools::displayError('ISO CODE invalid "%1$s" for the following file: "%2$s"'), $iso_code, $filename);
  529. }
  530. }
  531. public function submitAddLang()
  532. {
  533. $arr_import_lang = explode('|', Tools::getValue('params_import_language')); /* 0 = Language ISO code, 1 = PS version */
  534. if (Validate::isLangIsoCode($arr_import_lang[0]))
  535. {
  536. if ($content = Tools::file_get_contents(
  537. 'http://www.prestashop.com/download/lang_packs/gzip/'.$arr_import_lang[1].'/'.$arr_import_lang[0].'.gzip', false,
  538. @stream_context_create(array('http' => array('method' => 'GET', 'timeout' => 5)))))
  539. {
  540. $file = _PS_TRANSLATIONS_DIR_.$arr_import_lang[0].'.gzip';
  541. if (file_put_contents($file, $content))
  542. {
  543. $gz = new Archive_Tar($file, true);
  544. $files_list = $gz->listContent();
  545. if ($gz->extract(_PS_TRANSLATIONS_DIR_.'../', false))
  546. {
  547. AdminTranslationsController::checkAndAddMailsFiles($arr_import_lang[0], $files_list);
  548. AdminTranslationsController::addNewTabs($arr_import_lang[0], $files_list);
  549. if (!Language::checkAndAddLanguage($arr_import_lang[0]))
  550. $conf = 20;
  551. if (!unlink($file))
  552. $this->errors[] = Tools::displayError('Cannot delete archive');
  553. $this->redirect(false, (isset($conf) ? $conf : '15'));
  554. }
  555. $this->errors[] = Tools::displayError('Archive cannot be extracted.');
  556. if (!unlink($file))
  557. $this->errors[] = Tools::displayError('Cannot delete archive');
  558. }
  559. else
  560. $this->errors[] = Tools::displayError('Server does not have permissions for writing.');
  561. }
  562. else
  563. $this->errors[] = Tools::displayError('Language not found');
  564. }
  565. else
  566. $this->errors[] = Tools::displayError('Invalid parameter');
  567. }
  568. /**
  569. * This method check each file (tpl or php file), get its sentences to translate,
  570. * compare with posted values and write in iso code translation file.
  571. *
  572. * @param string $file_name
  573. * @param array $files
  574. * @param string $theme_name
  575. * @param string $module_name
  576. * @param string|boolean $dir
  577. * @return void
  578. */
  579. protected function findAndWriteTranslationsIntoFile($file_name, $files, $theme_name, $module_name, $dir = false)
  580. {
  581. // These static vars allow to use file to write just one time.
  582. static $cache_file = array();
  583. static $str_write = '';
  584. static $array_check_duplicate = array();
  585. // Default translations and Prestashop overriding themes are distinguish
  586. $is_default = $theme_name === self::DEFAULT_THEME_NAME ? true : false;
  587. // Set file_name in static var, this allow to open and wright the file just one time
  588. if (!isset($cache_file[$theme_name.'-'.$file_name]))
  589. {
  590. $str_write = '';
  591. $cache_file[$theme_name.'-'.$file_name] = true;
  592. if (!Tools::file_exists_cache($file_name))
  593. file_put_contents($file_name, '');
  594. if (!is_writable($file_name))
  595. throw new PrestaShopException(sprintf(
  596. Tools::displayError('Cannot write to the theme\'s language file (%s). Please check write permissions.'),
  597. $file_name
  598. ));
  599. // this string is initialized one time for a file
  600. $str_write .= "<?php\n\nglobal \$_MODULE;\n\$_MODULE = array();\n";
  601. $array_check_duplicate = array();
  602. }
  603. foreach ($files as $file)
  604. {
  605. if (preg_match('/^(.*).(tpl|php)$/', $file) && Tools::file_exists_cache($file_path = $dir.$file) && !in_array($file, self::$ignore_folder))
  606. {
  607. // Get content for this file
  608. $content = file_get_contents($file_path);
  609. // Get file type
  610. $type_file = substr($file, -4) == '.tpl' ? 'tpl' : 'php';
  611. // Parse this content
  612. $matches = $this->userParseFile($content, $this->type_selected, $type_file);
  613. // Write each translation on its module file
  614. $template_name = substr(basename($file), 0, -4);
  615. foreach ($matches as $key)
  616. {
  617. if ($is_default)
  618. {
  619. $post_key = md5(strtolower($module_name).'_'.self::DEFAULT_THEME_NAME.'_'.strtolower($template_name).'_'.md5($key));
  620. $pattern = '\'<{'.strtolower($module_name).'}prestashop>'.strtolower($template_name).'_'.md5($key).'\'';
  621. }
  622. else
  623. {
  624. $post_key = md5(strtolower($module_name).'_'.strtolower($theme_name).'_'.strtolower($template_name).'_'.md5($key));
  625. $pattern = '\'<{'.strtolower($module_name).'}'.strtolower($theme_name).'>'.strtolower($template_name).'_'.md5($key).'\'';
  626. }
  627. if (array_key_exists($post_key, $_POST) && !empty($_POST[$post_key]) && !in_array($pattern, $array_check_duplicate))
  628. {
  629. $array_check_duplicate[] = $pattern;
  630. $str_write .= '$_MODULE['.$pattern.'] = \''.pSQL(str_replace(array("\r\n", "\r", "\n"), ' ', $_POST[$post_key])).'\';'."\n";
  631. $this->total_expression++;
  632. }
  633. }
  634. }
  635. }
  636. if (isset($cache_file[$theme_name.'-'.$file_name]) && $str_write != "<?php\n\nglobal \$_MODULE;\n\$_MODULE = array();\n")
  637. file_put_contents($file_name, $str_write);
  638. }
  639. /**
  640. * Clear the list of module file by type (file or directory)
  641. *
  642. * @param $files : list of files
  643. * @param string $type_clear (file|directory)
  644. * @param string $path
  645. * @return array : list of a good files
  646. */
  647. public function clearModuleFiles($files, $type_clear = 'file', $path = '')
  648. {
  649. // List of directory which not must be parsed
  650. $arr_exclude = array('img', 'js', 'mails');
  651. // List of good extention files
  652. $arr_good_ext = array('.tpl', '.php');
  653. foreach ($files as $key => $file)
  654. {
  655. if ($file{0} === '.' || in_array(substr($file, 0, strrpos($file, '.')), $this->all_iso_lang))
  656. unset($files[$key]);
  657. else if ($type_clear === 'file' && !in_array(substr($file, strrpos($file, '.')), $arr_good_ext))
  658. unset($files[$key]);
  659. else if ($type_clear === 'directory' && (!is_dir($path.$file) || in_array($file, $arr_exclude)))
  660. unset($files[$key]);
  661. }
  662. return $files;
  663. }
  664. /**
  665. * This method get translation for each files of a module,
  666. * compare with global $_MODULES array and fill AdminTranslations::modules_translations array
  667. * With key as English sentences and values as their iso code translations.
  668. *
  669. * @param array $files
  670. * @param string $theme_name
  671. * @param string $module_name
  672. * @param string|boolean $dir
  673. * @param string $iso_code
  674. * @return void
  675. */
  676. protected function findAndFillTranslations($files, $theme_name, $module_name, $dir = false)
  677. {
  678. $name_var = $this->translations_informations[$this->type_selected]['var'];
  679. // added for compatibility
  680. $GLOBALS[$name_var] = array_change_key_case($GLOBALS[$name_var]);
  681. // Default translations and Prestashop overriding themes are distinguish
  682. $is_default = $theme_name === self::DEFAULT_THEME_NAME ? true : false;
  683. // Thank to this var similar keys are not duplicate
  684. // in AndminTranslation::modules_translations array
  685. // see below
  686. $array_check_duplicate = array();
  687. foreach ($files as $file)
  688. {
  689. if ((preg_match('/^(.*).tpl$/', $file) || preg_match('/^(.*).php$/', $file)) && Tools::file_exists_cache($file_path = $dir.$file))
  690. {
  691. // Get content for this file
  692. $content = file_get_contents($file_path);
  693. // Module files can now be ignored by adding this string in a file
  694. if (strpos($content, 'IGNORE_THIS_FILE_FOR_TRANSLATION') !== false)
  695. continue;
  696. // Get file type
  697. $type_file = substr($file, -4) == '.tpl' ? 'tpl' : 'php';
  698. // Parse this content
  699. $matches = $this->userParseFile($content, $this->type_selected, $type_file);
  700. // Write each translation on its module file
  701. $template_name = substr(basename($file), 0, -4);
  702. foreach ($matches as $key)
  703. {
  704. $module_key = '<{'.Tools::strtolower($module_name).'}'.
  705. strtolower($is_default ? 'prestashop' : $theme_name).'>'.Tools::strtolower($template_name).'_'.md5($key);
  706. // to avoid duplicate entry
  707. if (!in_array($module_key, $array_check_duplicate))
  708. {
  709. $array_check_duplicate[] = $module_key;
  710. if (!isset($this->modules_translations[$theme_name][$module_name][$template_name][$key]['trad']))
  711. $this->total_expression++;
  712. if (array_key_exists($module_key, $GLOBALS[$name_var]))
  713. $this->modules_translations[$theme_name][$module_name][$template_name][$key]['trad'] = html_entity_decode($GLOBALS[$name_var][$module_key], ENT_COMPAT, 'UTF-8');
  714. else
  715. {
  716. $this->modules_translations[$theme_name][$module_name][$template_name][$key]['trad'] = '';
  717. $this->missing_translations++;
  718. }
  719. $this->modules_translations[$theme_name][$module_name][$template_name][$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  720. }
  721. }
  722. }
  723. }
  724. }
  725. /**
  726. * Get list of files which must be parsed by directory and by type of translations
  727. *
  728. * @return array : list of files by directory
  729. */
  730. public function getFileToParseByTypeTranslation()
  731. {
  732. $directories = array();
  733. switch ($this->type_selected)
  734. {
  735. case 'front':
  736. $directories['tpl'] = array(_PS_ALL_THEMES_DIR_.'/' => scandir(_PS_ALL_THEMES_DIR_));
  737. self::$ignore_folder[] = 'modules';
  738. $directories['tpl'] = array_merge($directories['tpl'], $this->listFiles(_PS_THEME_SELECTED_DIR_));
  739. if (Tools::file_exists_cache(_PS_THEME_OVERRIDE_DIR_))
  740. $directories['tpl'] = array_merge($directories['tpl'], $this->listFiles(_PS_THEME_OVERRIDE_DIR_));
  741. break;
  742. case 'back':
  743. $directories = array(
  744. 'php' => array(
  745. _PS_ADMIN_CONTROLLER_DIR_.'/' => scandir(_PS_ADMIN_CONTROLLER_DIR_),
  746. _PS_OVERRIDE_DIR_.'controllers/admin/' => scandir(_PS_OVERRIDE_DIR_.'controllers/admin/'),
  747. _PS_CLASS_DIR_.'helper/' => scandir(_PS_CLASS_DIR_.'helper/'),
  748. _PS_CLASS_DIR_.'controller/' => array('AdminController.php'),
  749. _PS_CLASS_DIR_ => array('PaymentModule.php')
  750. ),
  751. 'tpl' => $this->listFiles(_PS_ADMIN_DIR_.'/themes/'),
  752. 'specific' => array(
  753. _PS_ADMIN_DIR_.'/' => array(
  754. 'header.inc.php',
  755. 'footer.inc.php',
  756. 'index.php',
  757. 'login.php',
  758. 'password.php',
  759. 'functions.php'
  760. )
  761. )
  762. );
  763. // For translate the template which are overridden
  764. if (file_exists(_PS_OVERRIDE_DIR_.'controllers'.DIRECTORY_SEPARATOR.'admin'.DIRECTORY_SEPARATOR.'templates'))
  765. $directories['tpl'] = array_merge($directories['tpl'], $this->listFiles(_PS_OVERRIDE_DIR_.'controllers'.DIRECTORY_SEPARATOR.'admin'.DIRECTORY_SEPARATOR.'templates'));
  766. break;
  767. case 'errors':
  768. $directories['php'] = array(
  769. _PS_ROOT_DIR_.'/' => scandir(_PS_ROOT_DIR_),
  770. _PS_ADMIN_DIR_.'/' => scandir(_PS_ADMIN_DIR_),
  771. _PS_FRONT_CONTROLLER_DIR_ => scandir(_PS_FRONT_CONTROLLER_DIR_),
  772. _PS_ADMIN_CONTROLLER_DIR_ => scandir(_PS_ADMIN_CONTROLLER_DIR_),
  773. _PS_OVERRIDE_DIR_.'controllers/front/' => scandir(_PS_OVERRIDE_DIR_.'controllers/front/'),
  774. _PS_OVERRIDE_DIR_.'controllers/admin/' => scandir(_PS_OVERRIDE_DIR_.'controllers/admin/')
  775. );
  776. // Get all files for folders classes/ and override/classes/ recursively
  777. $directories['php'] = array_merge($directories['php'], $this->listFiles(_PS_CLASS_DIR_, array(), 'php'));
  778. $directories['php'] = array_merge($directories['php'], $this->listFiles(_PS_OVERRIDE_DIR_.'classes/', array(), 'php'));
  779. break;
  780. case 'fields':
  781. $directories['php'] = $this->listFiles(_PS_CLASS_DIR_, array(), 'php');
  782. break;
  783. case 'pdf':
  784. $tpl_theme = Tools::file_exists_cache(_PS_THEME_SELECTED_DIR_.'pdf/') ? scandir(_PS_THEME_SELECTED_DIR_.'pdf/') : array();
  785. $directories = array(
  786. 'php' => array(
  787. _PS_CLASS_DIR_.'pdf/' => scandir(_PS_CLASS_DIR_.'pdf/'),
  788. _PS_OVERRIDE_DIR_.'classes/pdf/' => scandir(_PS_OVERRIDE_DIR_.'classes/pdf/')
  789. ),
  790. 'tpl' => array(
  791. _PS_PDF_DIR_ => scandir(_PS_PDF_DIR_),
  792. _PS_THEME_SELECTED_DIR_.'pdf/' => $tpl_theme
  793. )
  794. );
  795. break;
  796. case 'mails':
  797. $directories['php'] = array(
  798. _PS_FRONT_CONTROLLER_DIR_ => scandir(_PS_FRONT_CONTROLLER_DIR_),
  799. _PS_ADMIN_CONTROLLER_DIR_ => scandir(_PS_ADMIN_CONTROLLER_DIR_),
  800. _PS_OVERRIDE_DIR_.'controllers/front/' => scandir(_PS_OVERRIDE_DIR_.'controllers/front/'),
  801. _PS_OVERRIDE_DIR_.'controllers/admin/' => scandir(_PS_OVERRIDE_DIR_.'controllers/admin/'),
  802. _PS_ADMIN_DIR_.'/' => scandir(_PS_ADMIN_DIR_),
  803. _PS_ADMIN_DIR_.'/tabs/' => scandir(_PS_ADMIN_DIR_.'/tabs')
  804. );
  805. // Get all files for folders classes/ and override/classes/ recursively
  806. $directories['php'] = array_merge($directories['php'], $this->listFiles(_PS_CLASS_DIR_, array(), 'php'));
  807. $directories['php'] = array_merge($directories['php'], $this->listFiles(_PS_OVERRIDE_DIR_.'classes/', array(), 'php'));
  808. $directories['php'] = array_merge($directories['php'], $this->getModulesHasMails());
  809. break;
  810. }
  811. return $directories;
  812. }
  813. /**
  814. * This method parse a file by type of translation and type file
  815. *
  816. * @param $content
  817. * @param $type_translation : front, back, errors, modules...
  818. * @param string|bool $type_file : (tpl|php)
  819. * @return return $matches
  820. */
  821. protected function userParseFile($content, $type_translation, $type_file = false)
  822. {
  823. switch ($type_translation)
  824. {
  825. case 'front':
  826. // Parsing file in Front office
  827. $regex = '/\{l\s*s=\''._PS_TRANS_PATTERN_.'\'(\s*sprintf=.*)?(\s*js=1)?\s*\}/U';
  828. break;
  829. case 'back':
  830. // Parsing file in Back office
  831. if ($type_file == 'php')
  832. $regex = '/this->l\(\''._PS_TRANS_PATTERN_.'\'[\)|\,]/U';
  833. else if ($type_file == 'specific')
  834. $regex = '/translate\(\''._PS_TRANS_PATTERN_.'\'\)/U';
  835. else
  836. $regex = '/\{l\s*s\s*=\''._PS_TRANS_PATTERN_.'\'(\s*sprintf=.*)?(\s*js=1)?(\s*slashes=1)?\s*\}/U';
  837. break;
  838. case 'errors':
  839. // Parsing file for all errors syntax
  840. $regex = '/Tools::displayError\(\''._PS_TRANS_PATTERN_.'\'(,\s*(true|false))?\)/U';
  841. break;
  842. case 'modules':
  843. // Parsing modules file
  844. if ($type_file == 'php')
  845. $regex = '/->l\(\''._PS_TRANS_PATTERN_.'\'(, ?\'(.+)\')?(, ?(.+))?\)/U';
  846. else
  847. $regex = '/\{l\s*s=\''._PS_TRANS_PATTERN_.'\'(\s*sprintf=.*)?(\s*mod=\'.+\')?(\s*js=1)?\s*\}/U';
  848. break;
  849. case 'pdf':
  850. // Parsing PDF file
  851. if ($type_file == 'php')
  852. $regex = '/HTMLTemplate.*::l\(\''._PS_TRANS_PATTERN_.'\'[\)|\,]/U';
  853. else
  854. $regex = '/\{l\s*s=\''._PS_TRANS_PATTERN_.'\'(\s*sprintf=.*)?(\s*js=1)?(\s*pdf=\'true\')?\s*\}/U';
  855. break;
  856. }
  857. preg_match_all($regex, $content, $matches);
  858. return $matches[1];
  859. }
  860. /**
  861. * Get all translations informations for all type of translations
  862. *
  863. * array(
  864. * 'type' => array(
  865. * 'name' => string : title for the translation type,
  866. * 'var' => string : name of var for the translation file,
  867. * 'dir' => string : dir of translation file
  868. * 'file' => string : file name of translation file
  869. * )
  870. * )
  871. */
  872. public function getTranslationsInformations()
  873. {
  874. $this->translations_informations = array(
  875. 'front' => array(
  876. 'name' => $this->l('Front Office translations'),
  877. 'var' => '_LANG',
  878. 'dir' => _PS_THEME_SELECTED_DIR_.'lang/',
  879. 'file' => $this->lang_selected->iso_code.'.php'
  880. ),
  881. 'back' => array(
  882. 'name' => $this->l('Back Office translations'),
  883. 'var' => '_LANGADM',
  884. 'dir' => _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.'/',
  885. 'file' => 'admin.php'
  886. ),
  887. 'errors' => array(
  888. 'name' => $this->l('Error message translations'),
  889. 'var' => '_ERRORS',
  890. 'dir' => _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.'/',
  891. 'file' => 'errors.php'
  892. ),
  893. 'fields' => array(
  894. 'name' => $this->l('Field name translations'),
  895. 'var' => '_FIELDS',
  896. 'dir' => _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.'/',
  897. 'file' => 'fields.php'
  898. ),
  899. 'modules' => array(
  900. 'name' => $this->l('Installed module translations'),
  901. 'var' => '_MODULES',
  902. 'dir' => _PS_MODULE_DIR_,
  903. 'file' => '',
  904. 'override' => array(
  905. 'dir' => _PS_THEME_SELECTED_DIR_.'modules/',
  906. 'file' => ''
  907. )
  908. ),
  909. 'pdf' => array(
  910. 'name' => $this->l('PDF translations'),
  911. 'var' => '_LANGPDF',
  912. 'dir' => _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.'/',
  913. 'file' => 'pdf.php',
  914. 'override' => array(
  915. 'dir' => _PS_THEME_SELECTED_DIR_.'pdf/lang/',
  916. 'file' => $this->lang_selected->iso_code.'.php'
  917. )
  918. ),
  919. 'mails' => array(
  920. 'name' => $this->l('E-mail template translations'),
  921. 'var' => '_LANGMAIL',
  922. 'dir' => _PS_MAIL_DIR_.$this->lang_selected->iso_code.'/',
  923. 'file' => 'lang.php',
  924. 'override' => array(
  925. 'dir' => _PS_THEME_SELECTED_DIR_.'mails/'.$this->lang_selected->iso_code.'/',
  926. 'file' => 'lang.php'
  927. )
  928. )
  929. );
  930. }
  931. /**
  932. * Get all informations on : languages, theme and the translation type.
  933. */
  934. public function getInformations()
  935. {
  936. // Get all Languages
  937. $this->languages = Language::getLanguages(false);
  938. // Get all iso_code of languages
  939. foreach ($this->languages as $language)
  940. $this->all_iso_lang[] = $language['iso_code'];
  941. // Get all themes
  942. $this->themes = Theme::getThemes();
  943. // Get folder name of theme
  944. if (($theme = Tools::getValue('theme')) && !is_array($theme))
  945. $this->theme_selected = Tools::safeOutput($theme);
  946. else
  947. $this->theme_selected = self::DEFAULT_THEME_NAME;
  948. // Set the path of selected theme
  949. define('_PS_THEME_SELECTED_DIR_', _PS_ROOT_DIR_.'/themes/'.$this->theme_selected.'/');
  950. // Get type of translation
  951. if (($type = Tools::getValue('type')) && !is_array($type))
  952. $this->type_selected = strtolower(Tools::safeOutput($type));
  953. // Get selected language
  954. if (Tools::getValue('lang') || Tools::getValue('iso_code'))
  955. {
  956. $iso_code = Tools::getValue('lang') ? Tools::getValue('lang') : Tools::getValue('iso_code');
  957. if (!Validate::isLangIsoCode($iso_code) || !in_array($iso_code, $this->all_iso_lang))
  958. throw new PrestaShopException(sprintf(Tools::displayError('Invalid iso code "%s"'), $iso_code));
  959. $this->lang_selected = new Language((int)Language::getIdByIso($iso_code));
  960. }
  961. else
  962. $this->lang_selected = new Language((int)Language::getIdByIso('en'));
  963. // Get all information for translations
  964. $this->getTranslationsInformations();
  965. }
  966. /**
  967. * AdminController::postProcess() override
  968. * @see AdminController::postProcess()
  969. */
  970. public function postProcess()
  971. {
  972. $this->getInformations();
  973. /* PrestaShop demo mode */
  974. if (_PS_MODE_DEMO_)
  975. {
  976. $this->errors[] = Tools::displayError('This functionality has been disabled.');
  977. return;
  978. }
  979. /* PrestaShop demo mode */
  980. if (Tools::isSubmit('submitCopyLang'))
  981. {
  982. if ($this->tabAccess['add'] === '1')
  983. $this->submitCopyLang();
  984. else
  985. $this->errors[] = Tools::displayError('You do not have permission to add here.');
  986. }
  987. else if (Tools::isSubmit('submitExport'))
  988. {
  989. if ($this->tabAccess['add'] === '1')
  990. $this->submitExportLang();
  991. else
  992. $this->errors[] = Tools::displayError('You do not have permission to add here.');
  993. }
  994. else if (Tools::isSubmit('submitImport'))
  995. {
  996. if ($this->tabAccess['add'] === '1')
  997. $this->submitImportLang();
  998. else
  999. $this->errors[] = Tools::displayError('You do not have permission to add here.');
  1000. }
  1001. else if (Tools::isSubmit('submitAddLanguage'))
  1002. {
  1003. if ($this->tabAccess['add'] === '1')
  1004. $this->submitAddLang();
  1005. else
  1006. $this->errors[] = Tools::displayError('You do not have permission to add here.');
  1007. }
  1008. else if (Tools::isSubmit('submitTranslationsFront'))
  1009. {
  1010. if ($this->tabAccess['edit'] === '1')
  1011. $this->writeTranslationFile();
  1012. else
  1013. $this->errors[] = Tools::displayError('You do not have permission to edit here.');
  1014. }
  1015. else if (Tools::isSubmit('submitTranslationsPdf'))
  1016. {
  1017. if ($this->tabAccess['edit'] === '1')
  1018. // Only the PrestaShop team should write the translations into the _PS_TRANSLATIONS_DIR_
  1019. if (($this->theme_selected == self::DEFAULT_THEME_NAME) && _PS_MODE_DEV_)
  1020. $this->writeTranslationFile();
  1021. else
  1022. $this->writeTranslationFile(true);
  1023. else
  1024. $this->errors[] = Tools::displayError('You do not have permission to edit here.');
  1025. }
  1026. else if (Tools::isSubmit('submitTranslationsBack'))
  1027. {
  1028. if ($this->tabAccess['edit'] === '1')
  1029. $this->writeTranslationFile();
  1030. else
  1031. $this->errors[] = Tools::displayError('You do not have permission to edit here.');
  1032. }
  1033. else if (Tools::isSubmit('submitTranslationsErrors'))
  1034. {
  1035. if ($this->tabAccess['edit'] === '1')
  1036. $this->writeTranslationFile();
  1037. else
  1038. $this->errors[] = Tools::displayError('You do not have permission to edit here.');
  1039. }
  1040. else if (Tools::isSubmit('submitTranslationsFields'))
  1041. {
  1042. if ($this->tabAccess['edit'] === '1')
  1043. $this->writeTranslationFile();
  1044. else
  1045. $this->errors[] = Tools::displayError('You do not have permission to edit here.');
  1046. }
  1047. else if (Tools::isSubmit('submitTranslationsMails') || Tools::isSubmit('submitTranslationsMailsAndStay'))
  1048. {
  1049. if ($this->tabAccess['edit'] === '1')
  1050. $this->submitTranslationsMails();
  1051. else
  1052. $this->errors[] = Tools::displayError('You do not have permission to edit here.');
  1053. }
  1054. else if (Tools::isSubmit('submitTranslationsModules'))
  1055. {
  1056. if ($this->tabAccess['edit'] === '1')
  1057. {
  1058. // Get a good path for module directory
  1059. if ($this->theme_selected == self::DEFAULT_THEME_NAME)
  1060. $i18n_dir = $this->translations_informations[$this->type_selected]['dir'];
  1061. else
  1062. $i18n_dir = $this->translations_informations[$this->type_selected]['override']['dir'];
  1063. // Get list of modules
  1064. if ($modules = $this->getListModules())
  1065. {
  1066. // Get files of all modules
  1067. $arr_files = $this->getAllModuleFiles($modules, $i18n_dir, $this->lang_selected->iso_code, true);
  1068. // Find and write all translation modules files
  1069. foreach ($arr_files as $value)
  1070. $this->findAndWriteTranslationsIntoFile($value['file_name'], $value['files'], $value['theme'], $value['module'], $value['dir']);
  1071. // Redirect
  1072. if (Tools::getValue('submitTranslationsModulesAndStay'))
  1073. $this->redirect(true);
  1074. else
  1075. $this->redirect();
  1076. }
  1077. }
  1078. else
  1079. $this->errors[] = Tools::displayError('You do not have permission to edit here.');
  1080. }
  1081. }
  1082. /**
  1083. * This method redirect in the translation main page or in the translation page
  1084. *
  1085. * @param bool $save_and_stay : true if the user has clicked on the button "save and stay"
  1086. * @param bool $conf : id of confirmation message
  1087. */
  1088. protected function redirect($save_and_stay = false, $conf = false)
  1089. {
  1090. $conf = !$conf ? 4 : $conf;
  1091. $url_base = self::$currentIndex.'&token='.$this->token.'&conf='.$conf;
  1092. if ($save_and_stay)
  1093. Tools::redirectAdmin($url_base.'&lang='.$this->lang_selected->iso_code.'&type='.$this->type_selected.'&theme='.$this->theme_selected);
  1094. else
  1095. Tools::redirectAdmin($url_base);
  1096. }
  1097. protected function getMailPattern()
  1098. {
  1099. // Let the indentation like it.
  1100. return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/1999/REC-html401-19991224/strict.dtd">
  1101. <html>
  1102. <head>
  1103. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  1104. <title>#title</title>
  1105. </head>
  1106. <body>
  1107. #content
  1108. </body>
  1109. </html>';
  1110. }
  1111. /**
  1112. * This method is used to wright translation for mails.
  1113. * This wrights subject translation files
  1114. * (in root/mails/lang_choosen/lang.php or root/_PS_THEMES_DIR_/mails/lang_choosen/lang.php)
  1115. * and mails files.
  1116. */
  1117. protected function submitTranslationsMails()
  1118. {
  1119. $arr_mail_content = array();
  1120. $arr_mail_path = array();
  1121. if (Tools::getValue('core_mail'))
  1122. {
  1123. $arr_mail_content['core_mail'] = Tools::getValue('core_mail');
  1124. // Get path of directory for find a good path of translation file
  1125. if ($this->theme_selected != self::DEFAULT_THEME_NAME)
  1126. $arr_mail_path['core_mail'] = $this->translations_informations[$this->type_selected]['override']['dir'];
  1127. else
  1128. $arr_mail_path['core_mail'] = $this->translations_informations[$this->type_selected]['dir'];
  1129. }
  1130. if (Tools::getValue('module_mail'))
  1131. {
  1132. $arr_mail_content['module_mail'] = Tools::getValue('module_mail');
  1133. // Get path of directory for find a good path of translation file
  1134. if ($this->theme_selected != self::DEFAULT_THEME_NAME)
  1135. $arr_mail_path['module_mail'] = $this->translations_informations['modules']['override']['dir'].'{module}/mails/'.$this->lang_selected->iso_code.'/';
  1136. else
  1137. $arr_mail_path['module_mail'] = $this->translations_informations['modules']['dir'].'{module}/mails/'.$this->lang_selected->iso_code.'/';
  1138. }
  1139. // Save each mail content
  1140. foreach ($arr_mail_content as $group_name => $all_content)
  1141. {
  1142. foreach ($all_content as $type_content => $mails)
  1143. {
  1144. foreach ($mails as $mail_name => $content)
  1145. {
  1146. $module_name = false;
  1147. $module_name_pipe_pos = stripos($mail_name, '|');
  1148. if ($module_name_pipe_pos)
  1149. {
  1150. $module_name = substr($mail_name, 0, $module_name_pipe_pos);
  1151. if (!Validate::isModuleName($module_name))
  1152. throw new PrestaShopException(sprinf(Tools::displayError('Invalid module name "%s"'), $module_name));
  1153. $mail_name = substr($mail_name, $module_name_pipe_pos + 1);
  1154. if (!Validate::isTplName($mail_name))
  1155. throw new PrestaShopException(sprintf(Tools::displayError('Invalid mail name "%s"'), $mail_name));
  1156. }
  1157. if ($type_content == 'html')
  1158. {
  1159. $content = Tools::htmlentitiesUTF8($content);
  1160. $content = htmlspecialchars_decode($content);
  1161. // replace correct end of line
  1162. $content = str_replace("\r\n", PHP_EOL, $content);
  1163. $title = '';
  1164. if (Tools::getValue('title_'.$group_name.'_'.$mail_name))
  1165. $title = Tools::getValue('title_'.$group_name.'_'.$mail_name);
  1166. $string_mail = $this->getMailPattern();
  1167. $content = str_replace(array('#title', '#content'), array($title, $content), $string_mail);
  1168. // Magic Quotes shall... not.. PASS!
  1169. if (_PS_MAGIC_QUOTES_GPC_)
  1170. $content = stripslashes($content);
  1171. }
  1172. if (Validate::isCleanHTML($content))
  1173. {
  1174. $path = $arr_mail_path[$group_name];
  1175. if ($module_name)
  1176. $path = str_replace('{module}', $module_name, $path);
  1177. file_put_contents($path.$mail_name.'.'.$type_content, $content);
  1178. }
  1179. else
  1180. throw new PrestaShopException(Tools::displayError('HTML e-mail templates cannot contain JavaScript code.'));
  1181. }
  1182. }
  1183. }
  1184. // Update subjects
  1185. $array_subjects = array();
  1186. if (($subjects = Tools::getValue('subject')) && is_array($subjects))
  1187. {
  1188. $array_subjects['core_and_modules'] = array('translations'=>array(), 'path'=>$arr_mail_path['core_mail'].'lang.php');
  1189. foreach ($subjects as $subject_translation)
  1190. $array_subjects['core_and_modules']['translations'] = array_merge($array_subjects['core_and_modules']['translations'], $subject_translation);
  1191. }
  1192. if (!empty($array_subjects))
  1193. foreach ($array_subjects as $infos)
  1194. $this->writeSubjectTranslationFile($infos['translations'], $infos['path']);
  1195. if (Tools::isSubmit('submitTranslationsMailsAndStay'))
  1196. $this->redirect(true);
  1197. else
  1198. $this->redirect();
  1199. }
  1200. /**
  1201. * Include file $dir/$file and return the var $var declared in it.
  1202. * This create the file if not exists
  1203. *
  1204. * return array : translations
  1205. */
  1206. public function fileExists()
  1207. {
  1208. $var = $this->translations_informations[$this->type_selected]['var'];
  1209. $dir = $this->translations_informations[$this->type_selected]['dir'];
  1210. $file = $this->translations_informations[$this->type_selected]['file'];
  1211. $$var = array();
  1212. if (!Tools::file_exists_cache($dir))
  1213. if (!mkdir($dir, 0700))
  1214. throw new PrestaShopException('Directory '.$dir.' cannot be created.');
  1215. if (!Tools::file_exists_cache($dir.DIRECTORY_SEPARATOR.$file))
  1216. if (!file_put_contents($dir.'/'.$file, "<?php\n\nglobal \$".$var.";\n\$".$var." = array();\n\n?>"))
  1217. throw new PrestaShopException('File "'.$file.'" doesn\'t exists and cannot be created in '.$dir);
  1218. if (!is_writable($dir.DIRECTORY_SEPARATOR.$file))
  1219. $this->displayWarning(Tools::displayError('This file must be writable:').' '.$dir.'/'.$file);
  1220. include($dir.DIRECTORY_SEPARATOR.$file);
  1221. return $$var;
  1222. }
  1223. public function displayToggleButton($closed = false)
  1224. {
  1225. $str_output = '
  1226. <script type="text/javascript">';
  1227. if (Tools::getValue('type') == 'mails')
  1228. $str_output .= '$(document).ready(function(){
  1229. openCloseAllDiv(\''.$this->type_selected.'_div\', this.value == openAll); toggleElemValue(this.id, openAll, closeAll);
  1230. });';
  1231. $str_output .= '
  1232. var openAll = \''.html_entity_decode($this->l('Expand all fieldsets'), ENT_NOQUOTES, 'UTF-8').'\';
  1233. var closeAll = \''.html_entity_decode($this->l('Close all fieldsets'), ENT_NOQUOTES, 'UTF-8').'\';
  1234. </script>
  1235. <input type="button" class="button" id="buttonall" onclick="openCloseAllDiv(\''.$this->type_selected.'_div\', this.value == openAll); toggleElemValue(this.id, openAll, closeAll);" />
  1236. <script type="text/javascript">toggleElemValue(\'buttonall\', '.($closed ? 'openAll' : 'closeAll').', '.($closed ? 'closeAll' : 'openAll').');</script>';
  1237. return $str_output;
  1238. }
  1239. protected function displaySubmitButtons($name)
  1240. {
  1241. return '
  1242. <input type="submit" name="submitTranslations'.ucfirst($name).'" value="'.$this->l('Update translations').'" class="button" />
  1243. <input type="submit" name="submitTranslations'.ucfirst($name).'AndStay" value="'.$this->l('Update and stay').'" class="button" />';
  1244. }
  1245. /**
  1246. * Init js variables for translation with google
  1247. *
  1248. * @return array of variables to assign to the smarty template
  1249. */
  1250. public function initAutoTranslate()
  1251. {
  1252. $this->addJS('http://www.google.com/jsapi');
  1253. $this->addJS(_PS_JS_DIR_.'gg-translate.js');
  1254. $this->addJS(_PS_JS_DIR_.'admin-translations.js');
  1255. $language_code = Tools::htmlentitiesUTF8(Language::getLanguageCodeByIso(Tools::getValue('lang')));
  1256. return array('language_code' => $language_code,
  1257. 'not_available' => addslashes(html_entity_decode($this->l('this language is not available in Google Translate\'s API'), ENT_QUOTES, 'utf-8')),
  1258. 'tooltip_title' => addslashes(html_entity_decode($this->l('Google Translate suggests:'), ENT_QUOTES, 'utf-8'))
  1259. );
  1260. }
  1261. public function displayLimitPostWarning($count)
  1262. {
  1263. $return = array();
  1264. if ((ini_get('suhosin.post.max_vars') && ini_get('suhosin.post.max_vars') < $count) || (ini_get('suhosin.request.max_vars') && ini_get('suhosin.request.max_vars') < $count))
  1265. {
  1266. $return['error_type'] = 'suhosin';
  1267. $return['post.max_vars'] = ini_get('suhosin.post.max_vars');
  1268. $return['request.max_vars'] = ini_get('suhosin.request.max_vars');
  1269. $return['needed_limit'] = $count + 100;
  1270. }
  1271. elseif (ini_get('max_input_vars') && ini_get('max_input_vars') < $count)
  1272. {
  1273. $return['error_type'] = 'conf';
  1274. $return['max_input_vars'] = ini_get('max_input_vars');
  1275. $return['needed_limit'] = $count + 100;
  1276. }
  1277. return $return;
  1278. }
  1279. /**
  1280. * Find sentence which use %d, %s, %%, %1$d, %1$s...
  1281. *
  1282. * @param $key : english sentence
  1283. * @return array|bool return list of matches
  1284. */
  1285. public function checkIfKeyUseSprintf($key)
  1286. {
  1287. if (preg_match_all('#(?:%%|%(?:[0-9]+\$)?[+-]?(?:[ 0]|\'.)?-?[0-9]*(?:\.[0-9]+)?[bcdeufFosxX])#', $key, $matches))
  1288. return implode(', ', $matches[0]);
  1289. return false;
  1290. }
  1291. /**
  1292. * This method generate the form for front translations
  1293. */
  1294. public function initFormFront()
  1295. {
  1296. $missing_translations_front = array();
  1297. $name_var = $this->translations_informations[$this->type_selected]['var'];
  1298. $GLOBALS[$name_var] = $this->fileExists();
  1299. /* List templates to parse */
  1300. $files_by_directory = $this->getFileToParseByTypeTranslation();
  1301. $count = 0;
  1302. $tabs_array = array();
  1303. foreach ($files_by_directory['tpl'] as $dir => $files)
  1304. {
  1305. $prefix = '';
  1306. if ($dir == _PS_THEME_OVERRIDE_DIR_)
  1307. $prefix = 'override_';
  1308. foreach ($files as $file)
  1309. {
  1310. if (preg_match('/^(.*).tpl$/', $file) && (Tools::file_exists_cache($file_path = $dir.$file)))
  1311. {
  1312. $prefix_key = $prefix.substr(basename($file), 0, -4);
  1313. $new_lang = array();
  1314. // Get content for this file
  1315. $content = file_get_contents($file_path);
  1316. // Parse this content
  1317. $matches = $this->userParseFile($content, $this->type_selected);
  1318. /* Get string translation */
  1319. foreach ($matches as $key)
  1320. {
  1321. if (empty($key))
  1322. {
  1323. $this->errors[] = sprintf($this->l('Empty string found, please edit: "%s"'), $file_path);
  1324. $new_lang[$key] = '';
  1325. }
  1326. else
  1327. {
  1328. // Caution ! front has underscore between prefix key and md5, back has not
  1329. if (isset($GLOBALS[$name_var][$prefix_key.'_'.md5($key)]))
  1330. $new_lang[$key]['trad'] = stripslashes(html_entity_decode($GLOBALS[$name_var][$prefix_key.'_'.md5($key)], ENT_COMPAT, 'UTF-8'));
  1331. else
  1332. {
  1333. if (!isset($new_lang[$key]['trad']))
  1334. {
  1335. $new_lang[$key]['trad'] = '';
  1336. if (!isset($missing_translations_front[$prefix_key]))
  1337. $missing_translations_front[$prefix_key] = 1;
  1338. else
  1339. $missing_translations_front[$prefix_key]++;
  1340. }
  1341. }
  1342. $new_lang[$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  1343. }
  1344. }
  1345. if (isset($tabs_array[$prefix_key]))
  1346. $tabs_array[$prefix_key] = array_merge($tabs_array[$prefix_key], $new_lang);
  1347. else
  1348. $tabs_array[$prefix_key] = $new_lang;
  1349. $count += count($new_lang);
  1350. }
  1351. }
  1352. }
  1353. $this->tpl_view_vars = array_merge($this->tpl_view_vars, array(
  1354. 'missing_translations' => $missing_translations_front,
  1355. 'count' => $count,
  1356. 'limit_warning' => $this->displayLimitPostWarning($count),
  1357. 'tabsArray' => $tabs_array,
  1358. ));
  1359. // Add js variables needed for autotranslate
  1360. //$this->tpl_view_vars = array_merge($this->tpl_view_vars, $this->initAutoTranslate());
  1361. $this->initToolbar();
  1362. $this->base_tpl_view = 'translation_form.tpl';
  1363. return parent::renderView();
  1364. }
  1365. /**
  1366. * This method generate the form for back translations
  1367. */
  1368. public function initFormBack()
  1369. {
  1370. $name_var = $this->translations_informations[$this->type_selected]['var'];
  1371. $GLOBALS[$name_var] = $this->fileExists();
  1372. $missing_translations_back = array();
  1373. // Get all types of file (PHP, TPL...) and a list of files to parse by folder
  1374. $files_per_directory = $this->getFileToParseByTypeTranslation();
  1375. foreach ($files_per_directory['php'] as $dir => $files)
  1376. foreach ($files as $file)
  1377. // Check if is a PHP file and if the override file exists
  1378. if (preg_match('/^(.*)\.php$/', $file) && Tools::file_exists_cache($file_path = $dir.$file) && !in_array($file, self::$ignore_folder))
  1379. {
  1380. $prefix_key = basename($file);
  1381. // -4 becomes -14 to remove the ending "Controller.php" from the filename
  1382. if (strpos($file, 'Controller.php') !== false)
  1383. $prefix_key = basename(substr($file, 0, -14));
  1384. else if (strpos($file, 'Helper') !== false)
  1385. $prefix_key = 'Helper';
  1386. if ($prefix_key == 'Admin')
  1387. $prefix_key = 'AdminController';
  1388. if ($prefix_key == 'PaymentModule.php')
  1389. $prefix_key = 'PaymentModule';
  1390. // Get content for this file
  1391. $content = file_get_contents($file_path);
  1392. // Parse this content
  1393. $matches = $this->userParseFile($content, $this->type_selected, 'php');
  1394. foreach ($matches as $key)
  1395. {
  1396. // Caution ! front has underscore between prefix key and md5, back has not
  1397. if (isset($GLOBALS[$name_var][$prefix_key.md5($key)]))
  1398. $tabs_array[$prefix_key][$key]['trad'] = stripslashes(html_entity_decode($GLOBALS[$name_var][$prefix_key.md5($key)], ENT_COMPAT, 'UTF-8'));
  1399. else
  1400. {
  1401. if (!isset($tabs_array[$prefix_key][$key]['trad']))
  1402. {
  1403. $tabs_array[$prefix_key][$key]['trad'] = '';
  1404. if (!isset($missing_translations_back[$prefix_key]))
  1405. $missing_translations_back[$prefix_key] = 1;
  1406. else
  1407. $missing_translations_back[$prefix_key]++;
  1408. }
  1409. }
  1410. $tabs_array[$prefix_key][$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  1411. }
  1412. }
  1413. foreach ($files_per_directory['specific'] as $dir => $files)
  1414. foreach ($files as $file)
  1415. if (Tools::file_exists_cache($file_path = $dir.$file) && !in_array($file, self::$ignore_folder))
  1416. {
  1417. $prefix_key = 'index';
  1418. // Get content for this file
  1419. $content = file_get_contents($file_path);
  1420. // Parse this content
  1421. $matches = $this->userParseFile($content, $this->type_selected, 'specific');
  1422. foreach ($matches as $key)
  1423. {
  1424. // Caution ! front has underscore between prefix key and md5, back has not
  1425. if (isset($GLOBALS[$name_var][$prefix_key.md5($key)]))
  1426. $tabs_array[$prefix_key][$key]['trad'] = stripslashes(html_entity_decode($GLOBALS[$name_var][$prefix_key.md5($key)], ENT_COMPAT, 'UTF-8'));
  1427. else
  1428. {
  1429. if (!isset($tabs_array[$prefix_key][$key]['trad']))
  1430. {
  1431. $tabs_array[$prefix_key][$key]['trad'] = '';
  1432. if (!isset($missing_translations_back[$prefix_key]))
  1433. $missing_translations_back[$prefix_key] = 1;
  1434. else
  1435. $missing_translations_back[$prefix_key]++;
  1436. }
  1437. }
  1438. $tabs_array[$prefix_key][$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  1439. }
  1440. }
  1441. foreach ($files_per_directory['tpl'] as $dir => $files)
  1442. foreach ($files as $file)
  1443. if (preg_match('/^(.*).tpl$/', $file) && Tools::file_exists_cache($file_path = $dir.$file))
  1444. {
  1445. // get controller name instead of file name
  1446. $prefix_key = Tools::toCamelCase(str_replace(_PS_ADMIN_DIR_.'/themes', '', $file_path), true);
  1447. $pos = strrpos($prefix_key, DIRECTORY_SEPARATOR);
  1448. $tmp = substr($prefix_key, 0, $pos);
  1449. if (preg_match('#controllers#', $tmp))
  1450. {
  1451. $parent_class = explode(DIRECTORY_SEPARATOR, $tmp);
  1452. $key = array_search('controllers', $parent_class);
  1453. $prefix_key = 'Admin'.ucfirst($parent_class[$key + 1]);
  1454. }
  1455. else
  1456. $prefix_key = 'Admin'.ucfirst(substr($tmp, strrpos($tmp, DIRECTORY_SEPARATOR) + 1, $pos));
  1457. // Adding list, form, option in Helper Translations
  1458. $list_prefix_key = array('AdminHelpers', 'AdminList', 'AdminView', 'AdminOptions', 'AdminForm', 'AdminHelpAccess');
  1459. if (in_array($prefix_key, $list_prefix_key))
  1460. $prefix_key = 'Helper';
  1461. // Adding the folder backup/download/ in AdminBackup Translations
  1462. if ($prefix_key == 'AdminDownload')
  1463. $prefix_key = 'AdminBackup';
  1464. // use the prefix "AdminController" (like old php files 'header', 'footer.inc', 'index', 'login', 'password', 'functions'
  1465. if ($prefix_key == 'Admin' || $prefix_key == 'AdminTemplate')
  1466. $prefix_key = 'AdminController';
  1467. $new_lang = array();
  1468. // Get content for this file
  1469. $content = file_get_contents($file_path);
  1470. // Parse this content
  1471. $matches = $this->userParseFile($content, $this->type_selected, 'tpl');
  1472. /* Get string translation for each tpl file */
  1473. foreach ($matches as $english_string)
  1474. {
  1475. if (empty($english_string))
  1476. {
  1477. $this->errors[] = sprintf($this->l('Error in template - Empty string found, please edit: "%s"'), $file_path);
  1478. $new_lang[$english_string] = '';
  1479. }
  1480. else
  1481. {
  1482. $trans_key = $prefix_key.md5($english_string);
  1483. if (isset($GLOBALS[$name_var][$trans_key]))
  1484. $new_lang[$english_string]['trad'] = html_entity_decode($GLOBALS[$name_var][$trans_key], ENT_COMPAT, 'UTF-8');
  1485. else
  1486. {
  1487. if (!isset($new_lang[$english_string]['trad']))
  1488. {
  1489. $new_lang[$english_string]['trad'] = '';
  1490. if (!isset($missing_translations_back[$prefix_key]))
  1491. $missing_translations_back[$prefix_key] = 1;
  1492. else
  1493. $missing_translations_back[$prefix_key]++;
  1494. }
  1495. }
  1496. $new_lang[$english_string]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  1497. }
  1498. }
  1499. if (isset($tabs_array[$prefix_key]))
  1500. $tabs_array[$prefix_key] = array_merge($tabs_array[$prefix_key], $new_lang);
  1501. else
  1502. $tabs_array[$prefix_key] = $new_lang;
  1503. }
  1504. // count will contain the number of expressions of the page
  1505. $count = 0;
  1506. foreach ($tabs_array as $array)
  1507. $count += count($array);
  1508. $this->tpl_view_vars = array_merge($this->tpl_view_vars, array(
  1509. 'count' => $count,
  1510. 'limit_warning' => $this->displayLimitPostWarning($count),
  1511. 'tabsArray' => $tabs_array,
  1512. 'missing_translations' => $missing_translations_back
  1513. ));
  1514. // Add js variables needed for autotranslate
  1515. //$this->tpl_view_vars = array_merge($this->tpl_view_vars, $this->initAutoTranslate());
  1516. $this->initToolbar();
  1517. $this->base_tpl_view = 'translation_form.tpl';
  1518. return parent::renderView();
  1519. }
  1520. /**
  1521. * Check if directory and file exist and return an list of modules
  1522. *
  1523. * @return array : list of modules
  1524. */
  1525. public function getListModules()
  1526. {
  1527. if (!Tools::file_exists_cache($this->translations_informations['modules']['dir']))
  1528. throw new PrestaShopException(Tools::displayError('Fatal error: Module directory does not exist').'('.$this->translations_informations['modules']['dir'].')');
  1529. if (!is_writable($this->translations_informations['modules']['dir']))
  1530. throw new PrestaShopException(Tools::displayError('The module directory must be writable'));
  1531. $modules = array();
  1532. if (!_PS_MODE_DEV_ && $this->theme_selected == self::DEFAULT_THEME_NAME)
  1533. {
  1534. // Get all module which are installed for to have a minimum of POST
  1535. $modules = Module::getModulesInstalled();
  1536. foreach ($modules as &$module)
  1537. $module = $module['name'];
  1538. }
  1539. else if ($this->theme_selected == self::DEFAULT_THEME_NAME)
  1540. if (Tools::file_exists_cache($this->translations_informations['modules']['dir']))
  1541. $modules = scandir($this->translations_informations['modules']['dir']);
  1542. else
  1543. $this->displayWarning(Tools::displayError('There are no modules in your copy of PrestaShop. Use the Modules page to activate them or go to our Website to download additional Modules.'));
  1544. else
  1545. if (Tools::file_exists_cache($this->translations_informations['modules']['override']['dir']))
  1546. $modules = scandir($this->translations_informations['modules']['override']['dir']);
  1547. else
  1548. $this->displayWarning(Tools::displayError('There are no modules in your copy of PrestaShop. Use the Modules page to activate them or go to our Website to download additional Modules.'));
  1549. return $modules;
  1550. }
  1551. /**
  1552. * This method generate the form for errors translations
  1553. */
  1554. public function initFormErrors()
  1555. {
  1556. $name_var = $this->translations_informations[$this->type_selected]['var'];
  1557. $GLOBALS[$name_var] = $this->fileExists();
  1558. $count_empty = array();
  1559. /* List files to parse */
  1560. $string_to_translate = array();
  1561. $file_by_directory = $this->getFileToParseByTypeTranslation();
  1562. if ($modules = $this->getListModules())
  1563. {
  1564. foreach ($modules as $module)
  1565. if (is_dir(_PS_MODULE_DIR_.$module) && !in_array($module, self::$ignore_folder))
  1566. $file_by_directory['php'] = array_merge($file_by_directory['php'], $this->listFiles(_PS_MODULE_DIR_.$module.'/', array(), 'php'));
  1567. }
  1568. foreach ($file_by_directory['php'] as $dir => $files)
  1569. foreach ($files as $file)
  1570. if (preg_match('/\.php$/', $file) && Tools::file_exists_cache($file_path = $dir.$file) && !in_array($file, self::$ignore_folder))
  1571. {
  1572. if (!filesize($file_path))
  1573. continue;
  1574. // Get content for this file
  1575. $content = file_get_contents($file_path);
  1576. // Parse this content
  1577. $matches = $this->userParseFile($content, $this->type_selected);
  1578. foreach ($matches as $key)
  1579. {
  1580. if (array_key_exists(md5($key), $GLOBALS[$name_var]))
  1581. $string_to_translate[$key]['trad'] = html_entity_decode($GLOBALS[$name_var][md5($key)], ENT_COMPAT, 'UTF-8');
  1582. else
  1583. {
  1584. $string_to_translate[$key]['trad'] = '';
  1585. if (!isset($count_empty[$key]))
  1586. $count_empty[$key] = 1;
  1587. }
  1588. $string_to_translate[$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  1589. }
  1590. }
  1591. $this->tpl_view_vars = array_merge($this->tpl_view_vars, array(
  1592. 'count' => count($string_to_translate),
  1593. 'limit_warning' => $this->displayLimitPostWarning(count($string_to_translate)),
  1594. 'errorsArray' => $string_to_translate,
  1595. 'missing_translations' => $count_empty
  1596. ));
  1597. $this->initToolbar();
  1598. $this->base_tpl_view = 'translation_errors.tpl';
  1599. return parent::renderView();
  1600. }
  1601. /**
  1602. * This method generate the form for fields translations
  1603. */
  1604. public function initFormFields()
  1605. {
  1606. $name_var = $this->translations_informations[$this->type_selected]['var'];
  1607. $GLOBALS[$name_var] = $this->fileExists();
  1608. $missing_translations_fields = array();
  1609. $class_array = array();
  1610. $tabs_array = array();
  1611. $count = 0;
  1612. $files_by_directory = $this->getFileToParseByTypeTranslation();
  1613. foreach ($files_by_directory['php'] as $dir => $files)
  1614. foreach ($files as $file)
  1615. {
  1616. $exclude_files = array('index.php', 'Autoload.php', 'StockManagerInterface.php',
  1617. 'TaxManagerInterface.php', 'WebserviceOutputInterface.php', 'WebserviceSpecificManagementInterface.php');
  1618. if (!preg_match('/\.php$/', $file) || in_array($file, $exclude_files))
  1619. continue;
  1620. $class_name = substr($file, 0, -4);
  1621. if (!class_exists($class_name, false))
  1622. Autoload::getInstance()->load($class_name);
  1623. if (!is_subclass_of($class_name.'Core', 'ObjectModel'))
  1624. continue;
  1625. $class_array[$class_name] = call_user_func(array($class_name, 'getValidationRules'), $class_name);
  1626. }
  1627. foreach ($class_array as $prefix_key => $rules)
  1628. {
  1629. if (isset($rules['validate']))
  1630. foreach ($rules['validate'] as $key => $value)
  1631. {
  1632. if (isset($GLOBALS[$name_var][$prefix_key.'_'.md5($key)]))
  1633. {
  1634. $tabs_array[$prefix_key][$key]['trad'] = html_entity_decode($GLOBALS[$name_var][$prefix_key.'_'.md5($key)], ENT_COMPAT, 'UTF-8');
  1635. $count++;
  1636. }
  1637. else
  1638. {
  1639. if (!isset($tabs_array[$prefix_key][$key]['trad']))
  1640. {
  1641. $tabs_array[$prefix_key][$key]['trad'] = '';
  1642. if (!isset($missing_translations_fields[$prefix_key]))
  1643. $missing_translations_fields[$prefix_key] = 1;
  1644. else
  1645. $missing_translations_fields[$prefix_key]++;
  1646. $count++;
  1647. }
  1648. }
  1649. }
  1650. if (isset($rules['validateLang']))
  1651. foreach ($rules['validateLang'] as $key => $value)
  1652. {
  1653. if (isset($GLOBALS[$name_var][$prefix_key.'_'.md5($key)]))
  1654. {
  1655. $tabs_array[$prefix_key][$key]['trad'] = '';
  1656. if (array_key_exists($prefix_key.'_'.md5(addslashes($key)), $GLOBALS[$name_var]))
  1657. $tabs_array[$prefix_key][$key]['trad'] = html_entity_decode($GLOBALS[$name_var][$prefix_key.'_'.md5(addslashes($key))], ENT_COMPAT, 'UTF-8');
  1658. $count++;
  1659. }
  1660. else
  1661. {
  1662. if (!isset($tabs_array[$prefix_key][$key]['trad']))
  1663. {
  1664. $tabs_array[$prefix_key][$key]['trad'] = '';
  1665. if (!isset($missing_translations_fields[$prefix_key]))
  1666. $missing_translations_fields[$prefix_key] = 1;
  1667. else
  1668. $missing_translations_fields[$prefix_key]++;
  1669. $count++;
  1670. }
  1671. }
  1672. }
  1673. }
  1674. $this->tpl_view_vars = array_merge($this->tpl_view_vars, array(
  1675. 'count' => $count,
  1676. 'limit_warning' => $this->displayLimitPostWarning($count),
  1677. 'tabsArray' => $tabs_array,
  1678. 'missing_translations' => $missing_translations_fields
  1679. ));
  1680. $this->initToolbar();
  1681. $this->base_tpl_view = 'translation_form.tpl';
  1682. return parent::renderView();
  1683. }
  1684. /**
  1685. * Get each informations for each mails founded in the folder $dir.
  1686. *
  1687. * @since 1.4.0.14
  1688. * @param string $dir
  1689. * @param string $group_name
  1690. * @return array : list of mails
  1691. */
  1692. public function getMailFiles($dir, $group_name = 'mail')
  1693. {
  1694. $arr_return = array();
  1695. // Very usefull to name input and textarea fields
  1696. $arr_return['group_name'] = $group_name;
  1697. $arr_return['empty_values'] = 0;
  1698. $arr_return['total_filled'] = 0;
  1699. $arr_return['directory'] = $dir;
  1700. // Get path for english mail directory
  1701. $dir_en = str_replace('/'.$this->lang_selected->iso_code.'/', '/en/', $dir);
  1702. if (Tools::file_exists_cache($dir_en))
  1703. {
  1704. // Get all english files to compare with the language to translate
  1705. foreach (scandir($dir_en) as $email_file)
  1706. {
  1707. if (strripos($email_file, '.html') > 0 || strripos($email_file, '.txt') > 0)
  1708. {
  1709. $email_name = substr($email_file, 0, strripos($email_file, '.'));
  1710. $type = substr($email_file, strripos($email_file, '.') + 1);
  1711. if (!isset($arr_return['files'][$email_name]))
  1712. $arr_return['files'][$email_name] = array();
  1713. // $email_file is from scandir ($dir), so we already know that file exists
  1714. $arr_return['files'][$email_name][$type]['en'] = $this->getMailContent($dir_en, $email_file);
  1715. // check if the file exists in the language to translate
  1716. if (Tools::file_exists_cache($dir.'/'.$email_file))
  1717. {
  1718. $arr_return['files'][$email_name][$type][$this->lang_selected->iso_code] = $this->getMailContent($dir, $email_file);
  1719. $this->total_expression++;
  1720. }
  1721. else
  1722. $arr_return['files'][$email_name][$type][$this->lang_selected->iso_code] = '';
  1723. if ($arr_return['files'][$email_name][$type][$this->lang_selected->iso_code] == '')
  1724. $arr_return['empty_values']++;
  1725. else
  1726. $arr_return['total_filled']++;
  1727. }
  1728. }
  1729. }
  1730. else
  1731. $this->warnings[] = sprintf(Tools::displayError('mail directory exists for %1$s but not for english in %2$s'),
  1732. $this->lang_selected->iso_code, str_replace(_PS_ROOT_DIR_, '', $dir));
  1733. return $arr_return;
  1734. }
  1735. /**
  1736. * Get content of the mail file.
  1737. *
  1738. * @since 1.4.0.14
  1739. * @param string $dir
  1740. * @param string $file
  1741. * @return array : content of file
  1742. */
  1743. protected function getMailContent($dir, $file)
  1744. {
  1745. $content = file_get_contents($dir.'/'.$file);
  1746. if (Tools::strlen($content) === 0)
  1747. $content = '';
  1748. return $content;
  1749. }
  1750. /**
  1751. * Display mails in html format.
  1752. * This was create for factorize the html displaying
  1753. *
  1754. * @since 1.4.0.14
  1755. * @param array $mails
  1756. * @param array $all_subject_mail
  1757. * @param Language $obj_lang
  1758. * @param string $id_html use for set html id attribute for the block
  1759. * @param string $title Set the title for the block
  1760. * @param string|boolean $name_for_module is not false define add a name for disntiguish mails module
  1761. */
  1762. protected function displayMailContent($mails, $all_subject_mail, $obj_lang, $id_html, $title, $name_for_module = false)
  1763. {
  1764. $str_return = '';
  1765. $group_name = 'mail';
  1766. if (array_key_exists('group_name', $mails))
  1767. $group_name = $mails['group_name'];
  1768. $str_return .= '
  1769. <div class="mails_field" >
  1770. <h3 style="cursor : pointer" onclick="$(\'#'.$id_html.'\').slideToggle();">'.$title.' - <font color="red">'.$mails['empty_values'].'</font> '
  1771. .sprintf($this->l('missing translation(s) on %1$s template(s) for %2$s'),
  1772. '<font color="blue">'.((int)$mails['empty_values'] + (int)$mails['total_filled']).'</font>',
  1773. $obj_lang->name)
  1774. .':</h3>
  1775. <div name="mails_div" id="'.$id_html.'">';
  1776. if (!empty($mails['files']))
  1777. {
  1778. foreach ($mails['files'] as $mail_name => $mail_files)
  1779. {
  1780. if (array_key_exists('html', $mail_files) || array_key_exists('txt', $mail_files))
  1781. {
  1782. if (array_key_exists($mail_name, $all_subject_mail))
  1783. {
  1784. $subject_mail = $all_subject_mail[$mail_name];
  1785. $value_subject_mail = isset($mails['subject'][$subject_mail]) ? $mails['subject'][$subject_mail] : '';
  1786. $str_return .= '
  1787. <div class="label-subject" style="text-align:center;">
  1788. <label style="text-align:right">'.sprintf($this->l('Subject for %s:'), '<em>'.$mail_name.'</em>').'</label>
  1789. <div class="mail-form" style="text-align:left">
  1790. <b>'.$subject_mail.'</b><br />';
  1791. if (isset($value_subject_mail['trad']) && $value_subject_mail['trad'])
  1792. $str_return .= '<input type="text" name="subject['.$group_name.']['.$subject_mail.']" value="'.$value_subject_mail['trad'].'" />';
  1793. else
  1794. $str_return .= '<input type="text" name="subject['.$group_name.']['.$subject_mail.']" value="" />';
  1795. if (isset($value_subject_mail['use_sprintf']) && $value_subject_mail['use_sprintf'])
  1796. {
  1797. $str_return .= '<a class="useSpecialSyntax" title="'.$this->l('This expression uses a special syntax:').' '.$value_subject_mail['use_sprintf'].'" style="cursor:pointer">
  1798. <img src="'._PS_IMG_.'admin/error.png" alt="'.$value_subject_mail['use_sprintf'].'" />
  1799. </a>';
  1800. }
  1801. $str_return .= '</div>
  1802. </div>';
  1803. }
  1804. else
  1805. {
  1806. $str_return .= '
  1807. <div class="label-subject">
  1808. <b>'.sprintf($this->l('No Subject was found for %s, or subject is generated in database.'), '<em>'.$mail_name.'</em>').'</b>
  1809. </div>';
  1810. }
  1811. if (array_key_exists('html', $mail_files))
  1812. {
  1813. $base_uri = str_replace(_PS_ROOT_DIR_, __PS_BASE_URI__, $mails['directory']);
  1814. $base_uri = str_replace('//', '/', $base_uri);
  1815. $url_mail = $base_uri.$mail_name.'.html';
  1816. $str_return .= $this->displayMailBlockHtml($mail_files['html'], $obj_lang->iso_code, $url_mail, $mail_name, $group_name, $name_for_module);
  1817. }
  1818. if (array_key_exists('txt', $mail_files))
  1819. $str_return .= $this->displayMailBlockTxt($mail_files['txt'], $obj_lang->iso_code, $mail_name, $group_name, $name_for_module);
  1820. }
  1821. }
  1822. }
  1823. else
  1824. {
  1825. $str_return .= '
  1826. <p class="error">'.$this->l('There is a problem getting the Mail files.').'<br />'
  1827. .sprintf($this->l('Please ensure that English files exist in %s folder'), '<em>'.$mails['directory'].'en</em>')
  1828. .'</p>';
  1829. }
  1830. $str_return .= '
  1831. </div><!-- #'.$id_html.' -->
  1832. <div class="clear"></div>
  1833. </div>';
  1834. return $str_return;
  1835. }
  1836. /**
  1837. * Just build the html structure for display txt mails
  1838. *
  1839. * @since 1.4.0.14
  1840. * @param array $content with english and language needed contents
  1841. * @param string $lang iso code of the needed language
  1842. * @param string $mail_name name of the file to translate (same for txt and html files)
  1843. * @param string $group_name group name allow to distinguish each block of mail.
  1844. * @param string|boolean $name_for_module is not false define add a name for disntiguish mails module
  1845. */
  1846. protected function displayMailBlockTxt($content, $lang, $mail_name, $group_name, $name_for_module = false)
  1847. {
  1848. return '
  1849. <div class="block-mail" >
  1850. <label>'.$mail_name.'.txt</label>
  1851. <div class="mail-form">
  1852. <div><textarea class="rte mailrte noEditor" cols="80" rows="30" name="'.$group_name.'[txt]['.($name_for_module ? $name_for_module.'|' : '' ).$mail_name.']" style="width:560px;margin=0;">'.Tools::htmlentitiesUTF8(stripslashes(strip_tags($content[$lang]))).'</textarea></div>
  1853. </div><!-- .mail-form -->
  1854. </div><!-- .block-mail -->';
  1855. }
  1856. /**
  1857. * Just build the html structure for display html mails.
  1858. *
  1859. * @since 1.4.0.14
  1860. * @param array $content with english and language needed contents
  1861. * @param string $lang iso code of the needed language
  1862. * @param string $url for the html page and displaying an outline
  1863. * @param string $mail_name name of the file to translate (same for txt and html files)
  1864. * @param string $group_name group name allow to distinguish each block of mail.
  1865. * @param string|boolean $name_for_module is not false define add a name for disntiguish mails module
  1866. */
  1867. protected function displayMailBlockHtml($content, $lang, $url, $mail_name, $group_name, $name_for_module = false)
  1868. {
  1869. $title = array();
  1870. // Because TinyMCE don't work correctly with <DOCTYPE>, <html> and <body> tags
  1871. if (stripos($content[$lang], '<body'))
  1872. {
  1873. $array_lang = $lang != 'en' ? array('en', $lang) : array($lang);
  1874. foreach ($array_lang as $language)
  1875. {
  1876. $title[$language] = substr($content[$language], 0, stripos($content[$language], '<body'));
  1877. preg_match('#<title>([^<]+)</title>#Ui', $title[$language], $matches);
  1878. $title[$language] = empty($matches[1])?'':$matches[1];
  1879. // The 2 lines below allow to exlude <body> tag from the content.
  1880. // This allow to exclude body tag even if attributs are setted.
  1881. $content[$language] = substr($content[$language], stripos($content[$language], '<body') + 5);
  1882. $content[$language] = substr($content[$language], stripos($content[$language], '>') + 1);
  1883. $content[$language] = substr($content[$language], 0, stripos($content[$language], '</body>'));
  1884. }
  1885. }
  1886. $str_return = '';
  1887. $name_for_module = $name_for_module ? $name_for_module.'|' : '';
  1888. $content[$lang] = (isset($content[$lang]) ? Tools::htmlentitiesUTF8(stripslashes($content[$lang])) : '');
  1889. $str_return .= '
  1890. <div class="block-mail" >
  1891. <label>'.$mail_name.'.html</label>
  1892. <div class="mail-form">
  1893. <div>';
  1894. $str_return .= '
  1895. <div class="label-subject">
  1896. <b>'.$this->l('"title" tag:').'</b>&nbsp;'.(isset($title['en']) ? $title['en'] : '').'<br />
  1897. <input type="text" name="title_'.$group_name.'_'.$mail_name.'" value="'.(isset($title[$lang]) ? $title[$lang] : '').'" />
  1898. </div><!-- .label-subject -->';
  1899. $str_return .= '
  1900. <iframe style="background:white;border:1px solid #DFD5C3;" border="0" src ="'.$url.'?'.(rand(0, 1000000000)).'" width="565" height="497"></iframe>
  1901. <a style="display:block;margin-top:5px;width:130px;" href="#" onclick="$(this).parent().hide(); displayTiny($(this).parent().next()); return false;" class="button">'.$this->l('Edit this e-mail template').'</a>
  1902. </div>
  1903. <textarea style="display:none;" class="rte mailrte" cols="80" rows="30" name="'.$group_name.'[html]['.$name_for_module.$mail_name.']">'.$content[$lang].'</textarea>
  1904. </div><!-- .mail-form -->
  1905. </div><!-- .block-mail -->';
  1906. return $str_return;
  1907. }
  1908. /**
  1909. * Check in each module if contains mails folder.
  1910. *
  1911. * @return array of module which has mails
  1912. */
  1913. public function getModulesHasMails($with_module_name = false)
  1914. {
  1915. if ($this->theme_selected != self::DEFAULT_THEME_NAME)
  1916. $i18n_dir = $this->translations_informations['modules']['override']['dir'];
  1917. else
  1918. $i18n_dir = $this->translations_informations['modules']['dir'];
  1919. $arr_modules = array();
  1920. foreach (scandir($i18n_dir) as $module_dir)
  1921. {
  1922. $dir = $i18n_dir.$module_dir.'/';
  1923. if (!in_array($module_dir, self::$ignore_folder) && Tools::file_exists_cache($dir.'mails/'))
  1924. if ($with_module_name)
  1925. $arr_modules[$module_dir] = $dir;
  1926. else
  1927. $arr_modules[$dir] = scandir($dir);
  1928. }
  1929. return $arr_modules;
  1930. }
  1931. protected function getTinyMCEForMails($iso_lang)
  1932. {
  1933. // TinyMCE
  1934. $iso_tiny_mce = (Tools::file_exists_cache(_PS_ROOT_DIR_.'/js/tiny_mce/langs/'.$iso_lang.'.js') ? $iso_lang : 'en');
  1935. $ad = dirname($_SERVER['PHP_SELF']);
  1936. return '
  1937. <script type="text/javascript">
  1938. var iso = \''.$iso_tiny_mce.'\' ;
  1939. var pathCSS = \''._THEME_CSS_DIR_.'\' ;
  1940. var ad = \''.$ad.'\' ;
  1941. </script>
  1942. <script type="text/javascript" src="'.__PS_BASE_URI__.'js/tiny_mce/tiny_mce.js"></script>
  1943. <script type="text/javascript" src="'.__PS_BASE_URI__.'js/tinymce.inc.js"></script>
  1944. <script type="text/javascript">
  1945. $(document).ready(function () {
  1946. tinySetup();
  1947. });
  1948. function displayTiny(obj) {
  1949. tinyMCE.get(obj.attr(\'name\')).show();
  1950. }
  1951. </script>';
  1952. }
  1953. /**
  1954. * This method generate the form for mails translations
  1955. */
  1956. public function initFormMails($no_display = false)
  1957. {
  1958. $module_mails = array();
  1959. // get all mail subjects, this method parse each files in Prestashop !!
  1960. $subject_mail = array();
  1961. $modules_has_mails = $this->getModulesHasMails(true);
  1962. $files_by_directiories = $this->getFileToParseByTypeTranslation();
  1963. foreach ($files_by_directiories['php'] as $dir => $files)
  1964. foreach ($files as $file)
  1965. if (Tools::file_exists_cache($dir.$file) && is_file($dir.$file) && !in_array($file, self::$ignore_folder) && preg_match('/\.php$/', $file))
  1966. $subject_mail = $this->getSubjectMail($dir, $file, $subject_mail);
  1967. // Get path of directory for find a good path of translation file
  1968. if ($this->theme_selected != self::DEFAULT_THEME_NAME && @filemtime($this->translations_informations[$this->type_selected]['override']['dir']))
  1969. $i18n_dir = $this->translations_informations[$this->type_selected]['override']['dir'];
  1970. else
  1971. $i18n_dir = $this->translations_informations[$this->type_selected]['dir'];
  1972. $core_mails = $this->getMailFiles($i18n_dir, 'core_mail');
  1973. $core_mails['subject'] = $this->getSubjectMailContent($i18n_dir);
  1974. foreach ($modules_has_mails as $module_name => $module_path)
  1975. {
  1976. $module_mails[$module_name] = $this->getMailFiles($module_path.'mails/'.$this->lang_selected->iso_code.'/', 'module_mail');
  1977. $module_mails[$module_name]['subject'] = $core_mails['subject'];
  1978. $module_mails[$module_name]['display'] = $this->displayMailContent($module_mails[$module_name], $subject_mail, $this->lang_selected, Tools::strtolower($module_name), sprintf($this->l('E-mails for %s module'), '<em>'.$module_name.'</em>'), $module_name);
  1979. }
  1980. if ($no_display)
  1981. {
  1982. $empty = 0;
  1983. $total = 0;
  1984. $total += (int)$core_mails['total_filled'];
  1985. $empty += (int)$core_mails['empty_values'];
  1986. foreach ($module_mails as $mod_infos)
  1987. {
  1988. $total += (int)$mod_infos['total_filled'];
  1989. $empty += (int)$mod_infos['empty_values'];
  1990. }
  1991. return array('total' => $total, 'empty' => $empty);
  1992. }
  1993. $this->tpl_view_vars = array_merge($this->tpl_view_vars, array(
  1994. 'limit_warning' => $this->displayLimitPostWarning($this->total_expression),
  1995. 'tinyMCE' => $this->getTinyMCEForMails($this->lang_selected->iso_code),
  1996. 'mail_content' => $this->displayMailContent($core_mails, $subject_mail, $this->lang_selected, 'core', $this->l('Core e-mails')),
  1997. 'module_mails' => $module_mails,
  1998. 'theme_name' => $this->theme_selected
  1999. ));
  2000. $this->initToolbar();
  2001. $this->base_tpl_view = 'translation_mails.tpl';
  2002. return parent::renderView();
  2003. }
  2004. /**
  2005. * Get list of subjects of mails
  2006. *
  2007. * @param $dir
  2008. * @param $file
  2009. * @param $subject_mail
  2010. * @return array : list of subjects of mails
  2011. */
  2012. protected function getSubjectMail($dir, $file, $subject_mail)
  2013. {
  2014. $content = file_get_contents($dir.'/'.$file);
  2015. $content = str_replace("\n", ' ', $content);
  2016. if (preg_match_all('/Mail::Send([^;]*);/si', $content, $tab))
  2017. for ($i = 0; isset($tab[1][$i]); $i++)
  2018. {
  2019. $tab2 = explode(',', $tab[1][$i]);
  2020. if (is_array($tab2))
  2021. if ($tab2 && isset($tab2[1]))
  2022. {
  2023. $tab2[1] = trim(str_replace('\'', '', $tab2[1]));
  2024. if (preg_match('/Mail::l\(\''._PS_TRANS_PATTERN_.'\'/s', $tab2[2], $matches))
  2025. $subject_mail[$tab2[1]] = $matches[1];
  2026. }
  2027. }
  2028. if (!in_array($file, self::$ignore_folder) && is_dir($dir.'/'.$file))
  2029. $subject_mail = $this->getSubjectMail($dir, $file, $subject_mail);
  2030. return $subject_mail;
  2031. }
  2032. /**
  2033. * @param $directory : name of directory
  2034. * @return array
  2035. */
  2036. protected function getSubjectMailContent($directory)
  2037. {
  2038. $subject_mail_content = array();
  2039. if (Tools::file_exists_cache($directory.'/lang.php'))
  2040. {
  2041. // we need to include this even if already included (no include once)
  2042. include($directory.'/lang.php');
  2043. foreach ($GLOBALS[$this->translations_informations[$this->type_selected]['var']] as $key => $subject)
  2044. {
  2045. $this->total_expression++;
  2046. $subject = str_replace('\n', ' ', $subject);
  2047. $subject = str_replace("\\'", "\'", $subject);
  2048. $subject_mail_content[$key]['trad'] = htmlentities($subject, ENT_QUOTES, 'UTF-8');
  2049. $subject_mail_content[$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  2050. }
  2051. }
  2052. else
  2053. $this->errors[] = sprintf($this->l('Subject mail translation file not found in "%s"'), $directory);
  2054. return $subject_mail_content;
  2055. }
  2056. protected function writeSubjectTranslationFile($sub, $path)
  2057. {
  2058. if ($fd = @fopen($path, 'w'))
  2059. {
  2060. $tab = 'LANGMAIL';
  2061. fwrite($fd, "<?php\n\nglobal \$_".$tab.";\n\$_".$tab." = array();\n");
  2062. foreach ($sub as $key => $value)
  2063. {
  2064. // Magic Quotes shall... not.. PASS!
  2065. if (_PS_MAGIC_QUOTES_GPC_)
  2066. $value = stripslashes($value);
  2067. fwrite($fd, '$_'.$tab.'[\''.pSQL($key).'\'] = \''.pSQL($value).'\';'."\n");
  2068. }
  2069. fwrite($fd, "\n?>");
  2070. fclose($fd);
  2071. }
  2072. else
  2073. throw new PrestaShopException(sprintf(Tools::displayError('Cannot write language file for e-mail subjects, path is: %s'), $path));
  2074. }
  2075. /**
  2076. * This get files to translate in module directory.
  2077. * Recursive method allow to get each files for a module no matter his depth.
  2078. *
  2079. * @param string $path directory path to scan
  2080. * @param array $array_files by reference - array which saved files to parse.
  2081. * @param string $module_name module name
  2082. * @param string $lang_file full path of translation file
  2083. * @param boolean $is_default
  2084. */
  2085. protected function recursiveGetModuleFiles($path, &$array_files, $module_name, $lang_file, $is_default = false)
  2086. {
  2087. $files_module = array();
  2088. if (Tools::file_exists_cache($path))
  2089. $files_module = scandir($path);
  2090. $files_for_module = $this->clearModuleFiles($files_module, 'file');
  2091. if (!empty($files_for_module))
  2092. $array_files[] = array(
  2093. 'file_name' => $lang_file,
  2094. 'dir' => $path,
  2095. 'files' => $files_for_module,
  2096. 'module' => $module_name,
  2097. 'is_default' => $is_default,
  2098. 'theme' => $this->theme_selected,
  2099. );
  2100. $dir_module = $this->clearModuleFiles($files_module, 'directory', $path);
  2101. if (!empty($dir_module))
  2102. foreach ($dir_module as $folder)
  2103. $this->recursiveGetModuleFiles($path.$folder.'/', $array_files, $module_name, $lang_file, $is_default);
  2104. }
  2105. /**
  2106. * This method get translation in each translations file.
  2107. * The file depend on $lang param.
  2108. *
  2109. * @param array $modules list of modules
  2110. * @param string $root_dir path where it get each modules
  2111. * @param string $lang iso code of choosen language to translate
  2112. * @param boolean $is_default set it if modules are located in root/prestashop/modules folder
  2113. * This allow to distinguish overrided prestashop theme and original module
  2114. */
  2115. protected function getAllModuleFiles($modules, $root_dir, $lang, $is_default = false)
  2116. {
  2117. $array_files = array();
  2118. foreach ($modules as $module)
  2119. {
  2120. if ($module{0} != '.' && is_dir($root_dir.$module))
  2121. {
  2122. if (Tools::file_exists_cache($root_dir.$module.'/translations/'.$lang.'.php'))
  2123. $lang_file = $root_dir.$module.'/translations/'.$lang.'.php';
  2124. else
  2125. $lang_file = $root_dir.$module.'/'.$lang.'.php';
  2126. @include($lang_file);
  2127. $this->getModuleTranslations();
  2128. $this->recursiveGetModuleFiles($root_dir.$module.'/', $array_files, $module, $lang_file, $is_default);
  2129. }
  2130. }
  2131. return $array_files;
  2132. }
  2133. /**
  2134. * This method generate the form for modules translations
  2135. */
  2136. public function initFormModules()
  2137. {
  2138. // Get path of directory for find a good path of translation file
  2139. if ($this->theme_selected != self::DEFAULT_THEME_NAME)
  2140. $i18n_dir = $this->translations_informations[$this->type_selected]['override']['dir'];
  2141. else
  2142. $i18n_dir = $this->translations_informations[$this->type_selected]['dir'];
  2143. // Get list of modules
  2144. $modules = $this->getListModules();
  2145. if (!empty($modules))
  2146. {
  2147. // Get all modules files and include all translation files
  2148. $arr_files = $this->getAllModuleFiles($modules, $i18n_dir, $this->lang_selected->iso_code, true);
  2149. foreach ($arr_files as $value)
  2150. $this->findAndFillTranslations($value['files'], $value['theme'], $value['module'], $value['dir']);
  2151. $this->tpl_view_vars = array_merge($this->tpl_view_vars, array(
  2152. 'default_theme_name' => self::DEFAULT_THEME_NAME,
  2153. 'count' => $this->total_expression,
  2154. 'limit_warning' => $this->displayLimitPostWarning($this->total_expression),
  2155. 'textarea_sized' => TEXTAREA_SIZED,
  2156. 'modules_translations' => isset($this->modules_translations) ? $this->modules_translations : array(),
  2157. 'missing_translations' => $this->missing_translations
  2158. ));
  2159. $this->initToolbar();
  2160. $this->base_tpl_view = 'translation_modules.tpl';
  2161. return parent::renderView();
  2162. }
  2163. }
  2164. /** Parse PDF class
  2165. *
  2166. * @param string $file_path file to parse
  2167. * @param string $file_type type of file
  2168. * @param array $langArray contains expression in the chosen language
  2169. * @param string $tab name to use with the md5 key
  2170. * @param array $tabs_array
  2171. * @return array containing all datas needed for building the translation form
  2172. * @since 1.4.5.0
  2173. */
  2174. protected function parsePdfClass($file_path, $file_type, $lang_array, $tab, $tabs_array, &$count_missing)
  2175. {
  2176. // Get content for this file
  2177. $content = file_get_contents($file_path);
  2178. // Parse this content
  2179. $matches = $this->userParseFile($content, $this->type_selected, $file_type);
  2180. foreach ($matches as $key)
  2181. {
  2182. if (stripslashes(array_key_exists($tab.md5(addslashes($key)), $lang_array)))
  2183. $tabs_array[$tab][$key]['trad'] = html_entity_decode($lang_array[$tab.md5(addslashes($key))], ENT_COMPAT, 'UTF-8');
  2184. else
  2185. {
  2186. $tabs_array[$tab][$key]['trad'] = '';
  2187. if (!isset($count_missing[$tab]))
  2188. $count_missing[$tab] = 1;
  2189. else
  2190. $count_missing[$tab]++;
  2191. }
  2192. $tabs_array[$tab][$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  2193. }
  2194. return $tabs_array;
  2195. }
  2196. /**
  2197. * This method generate the form for PDF translations
  2198. */
  2199. public function initFormPDF()
  2200. {
  2201. $name_var = $this->translations_informations[$this->type_selected]['var'];
  2202. $GLOBALS[$name_var] = array();
  2203. $missing_translations_pdf = array();
  2204. $i18n_dir = $this->translations_informations[$this->type_selected]['dir'];
  2205. $default_i18n_file = $i18n_dir.$this->translations_informations[$this->type_selected]['file'];
  2206. if (($this->theme_selected == self::DEFAULT_THEME_NAME) && _PS_MODE_DEV_)
  2207. $i18n_file = $default_i18n_file;
  2208. else
  2209. {
  2210. $i18n_dir = $this->translations_informations[$this->type_selected]['override']['dir'];
  2211. $i18n_file = $i18n_dir.$this->translations_informations[$this->type_selected]['override']['file'];
  2212. }
  2213. $this->checkDirAndCreate($i18n_file);
  2214. if ((!file_exists($i18n_file) && !is_writable($i18n_dir)) && !is_writable($i18n_file))
  2215. $this->errors[] = sprintf(Tools::displayError('Cannot write into the "%s"'), $i18n_file);
  2216. @include($i18n_file);
  2217. // if the override's translation file is empty load the default file
  2218. if (!isset($GLOBALS[$name_var]) || count($GLOBALS[$name_var]) == 0)
  2219. @include($default_i18n_file);
  2220. $prefix_key = 'PDF';
  2221. $tabs_array = array($prefix_key => array());
  2222. $files_by_directory = $this->getFileToParseByTypeTranslation();
  2223. foreach ($files_by_directory as $type => $directories)
  2224. foreach ($directories as $dir => $files)
  2225. foreach ($files as $file)
  2226. if (!in_array($file, self::$ignore_folder) && Tools::file_exists_cache($file_path = $dir.$file))
  2227. {
  2228. if ($type == 'tpl')
  2229. {
  2230. if (Tools::file_exists_cache($file_path) && is_file($file_path))
  2231. {
  2232. // Get content for this file
  2233. $content = file_get_contents($file_path);
  2234. // Parse this content
  2235. $matches = $this->userParseFile($content, $this->type_selected, 'tpl');
  2236. foreach ($matches as $key)
  2237. {
  2238. if (isset($GLOBALS[$name_var][$prefix_key.md5($key)]))
  2239. $tabs_array[$prefix_key][$key]['trad'] = (html_entity_decode($GLOBALS[$name_var][$prefix_key.md5($key)], ENT_COMPAT, 'UTF-8'));
  2240. else
  2241. {
  2242. if (!isset($tabs_array[$prefix_key][$key]['trad']))
  2243. {
  2244. $tabs_array[$prefix_key][$key]['trad'] = '';
  2245. if (!isset($missing_translations_pdf[$prefix_key]))
  2246. $missing_translations_pdf[$prefix_key] = 1;
  2247. else
  2248. $missing_translations_pdf[$prefix_key]++;
  2249. }
  2250. }
  2251. $tabs_array[$prefix_key][$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  2252. }
  2253. }
  2254. }
  2255. else
  2256. if (Tools::file_exists_cache($file_path))
  2257. $tabs_array = $this->parsePdfClass($file_path, 'php', $GLOBALS[$name_var], $prefix_key, $tabs_array, $missing_translations_pdf);
  2258. }
  2259. $this->tpl_view_vars = array_merge($this->tpl_view_vars, array(
  2260. 'count' => count($tabs_array['PDF']),
  2261. 'limit_warning' => $this->displayLimitPostWarning(count($tabs_array['PDF'])),
  2262. 'tabsArray' => $tabs_array,
  2263. 'missing_translations' => $missing_translations_pdf
  2264. ));
  2265. $this->initToolbar();
  2266. $this->base_tpl_view = 'translation_form.tpl';
  2267. return parent::renderView();
  2268. }
  2269. /**
  2270. * recursively list files in directory $dir
  2271. */
  2272. public function listFiles($dir, $list = array(), $file_ext = 'tpl')
  2273. {
  2274. $dir = rtrim($dir, '/').DIRECTORY_SEPARATOR;
  2275. $to_parse = scandir($dir);
  2276. // copied (and kind of) adapted from AdminImages.php
  2277. foreach ($to_parse as $file)
  2278. {
  2279. if (!in_array($file, self::$ignore_folder))
  2280. {
  2281. if (preg_match('#'.preg_quote($file_ext, '#').'$#i', $file))
  2282. $list[$dir][] = $file;
  2283. else if (is_dir($dir.$file))
  2284. $list = $this->listFiles($dir.$file, $list, $file_ext);
  2285. }
  2286. }
  2287. return $list;
  2288. }
  2289. }