PageRenderTime 65ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/controllers/admin/AdminTranslationsController.php

https://github.com/netplayer/PrestaShop
PHP | 2982 lines | 2161 code | 337 blank | 484 comment | 421 complexity | 2e4c7f0d4f672698bd19e63fa00d61ce MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, LGPL-3.0
  1. <?php
  2. /*
  3. * 2007-2014 PrestaShop
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@prestashop.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
  18. * versions in the future. If you wish to customize PrestaShop for your
  19. * needs please refer to http://www.prestashop.com for more information.
  20. *
  21. * @author PrestaShop SA <contact@prestashop.com>
  22. * @copyright 2007-2014 PrestaShop SA
  23. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  24. * International Registered Trademark & Property of PrestaShop SA
  25. */
  26. class AdminTranslationsControllerCore extends AdminController
  27. {
  28. /** Name of theme by default */
  29. const DEFAULT_THEME_NAME = _PS_DEFAULT_THEME_NAME_;
  30. const TEXTAREA_SIZED = 70;
  31. /** @var string : Link which list all pack of language */
  32. protected $link_lang_pack = 'http://www.prestashop.com/download/lang_packs/get_each_language_pack.php';
  33. /** @var int : number of sentence which can be translated */
  34. protected $total_expression = 0;
  35. /** @var int : number of sentence which aren't translated */
  36. protected $missing_translations = 0;
  37. /** @var array : List of ISO code for all languages */
  38. protected $all_iso_lang = array();
  39. /** @var array */
  40. protected $modules_translations = array();
  41. /** @var array : List of folder which must be ignored */
  42. protected static $ignore_folder = array('.', '..', '.svn', '.git', '.htaccess', 'index.php');
  43. /** @var array : List of theme by translation type : FRONT, BACK, ERRORS... */
  44. protected $translations_informations = array();
  45. /** @var array : List of all languages */
  46. protected $languages;
  47. /** @var array : List of all themes */
  48. protected $themes;
  49. /** @var string : Directory of selected theme */
  50. protected $theme_selected;
  51. /** @var string : Name of translations type */
  52. protected $type_selected;
  53. /** @var Language object : Language for the selected language */
  54. protected $lang_selected;
  55. /** @var boolean : Is true if number of var exceed the suhosin request or post limit */
  56. protected $post_limit_exceed = false;
  57. public function __construct()
  58. {
  59. $this->bootstrap = true;
  60. $this->multishop_context = Shop::CONTEXT_ALL;
  61. $this->table = 'translations';
  62. parent::__construct();
  63. }
  64. /*
  65. * Set the type which is selected
  66. */
  67. public function setTypeSelected($type_selected)
  68. {
  69. $this->type_selected = $type_selected;
  70. }
  71. /**
  72. * AdminController::initContent() override
  73. * @see AdminController::initContent()
  74. */
  75. public function initContent()
  76. {
  77. $this->initTabModuleList();
  78. $this->initPageHeaderToolbar();
  79. if (!is_null($this->type_selected))
  80. {
  81. $method_name = 'initForm'.$this->type_selected;
  82. if (method_exists($this, $method_name))
  83. $this->content = $this->initForm($method_name);
  84. else
  85. {
  86. $this->errors[] = sprintf(Tools::displayError('"%s" does not exist.'), $this->type_selected);
  87. $this->content = $this->initMain();
  88. }
  89. }
  90. else
  91. $this->content = $this->initMain();
  92. $this->context->smarty->assign(array(
  93. 'content' => $this->content,
  94. 'show_page_header_toolbar' => $this->show_page_header_toolbar,
  95. 'page_header_toolbar_title' => $this->page_header_toolbar_title,
  96. 'page_header_toolbar_btn' => $this->page_header_toolbar_btn));
  97. }
  98. /**
  99. * This function create vars by default and call the good method for generate form
  100. *
  101. * @param $method_name
  102. * @return call the method $this->method_name()
  103. */
  104. public function initForm($method_name)
  105. {
  106. // Create a title for each translation page
  107. $title = sprintf(
  108. $this->l('%1$s (Language: %2$s, Theme: %3$s)'),
  109. $this->translations_informations[$this->type_selected]['name'],
  110. $this->lang_selected->name,
  111. $this->theme_selected ? $this->theme_selected : $this->l('none')
  112. );
  113. // Set vars for all forms
  114. $this->tpl_view_vars = array(
  115. 'lang' => $this->lang_selected->iso_code,
  116. 'title' => $title,
  117. 'type' => $this->type_selected,
  118. 'theme' => $this->theme_selected,
  119. 'post_limit_exceeded' => $this->post_limit_exceed,
  120. 'url_submit' => self::$currentIndex.'&submitTranslations'.ucfirst($this->type_selected).'=1&token='.$this->token,
  121. 'toggle_button' => $this->displayToggleButton(),
  122. 'textarea_sized' => AdminTranslationsControllerCore::TEXTAREA_SIZED
  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 = @stream_context_create(array('http' => array('method' => 'GET', 'timeout' => 8)));
  158. if ($lang_packs = Tools::file_get_contents($file_name, false, $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. 'packs_to_install' => $packs_to_install,
  175. 'packs_to_update' => $packs_to_update,
  176. 'url_submit' => self::$currentIndex.'&token='.$this->token,
  177. 'themes' => $this->themes,
  178. 'id_theme_current' => $this->context->shop->id_theme,
  179. 'url_create_language' => 'index.php?controller=AdminLanguages&addlang&token='.$token,
  180. );
  181. $this->toolbar_scroll = false;
  182. $this->base_tpl_view = 'main.tpl';
  183. $this->content .= $this->renderKpis();
  184. $this->content .= parent::renderView();
  185. return $this->content;
  186. }
  187. /**
  188. * This method merge each arrays of modules translation in the array of modules translations
  189. */
  190. protected function getModuleTranslations()
  191. {
  192. global $_MODULE;
  193. $name_var = $this->translations_informations[$this->type_selected]['var'];
  194. if (!isset($_MODULE) && !isset($GLOBALS[$name_var]))
  195. $GLOBALS[$name_var] = array();
  196. else if (isset($_MODULE))
  197. if (is_array($GLOBALS[$name_var]) && is_array($_MODULE))
  198. $GLOBALS[$name_var] = array_merge($GLOBALS[$name_var], $_MODULE);
  199. else
  200. $GLOBALS[$name_var] = $_MODULE;
  201. }
  202. /**
  203. * This method is only used by AdminTranslations::submitCopyLang().
  204. *
  205. * It try to create folder in new theme.
  206. *
  207. * When a translation file is copied for a module, its translation key is wrong.
  208. * We have to change the translation key and rewrite the file.
  209. *
  210. * @param string $dest file name
  211. * @return bool
  212. */
  213. protected function checkDirAndCreate($dest)
  214. {
  215. $bool = true;
  216. // To get only folder path
  217. $path = dirname($dest);
  218. // If folder wasn't already added
  219. // Do not use Tools::file_exists_cache because it changes over time!
  220. if (!file_exists($path))
  221. if (!mkdir($path, 0777, true))
  222. {
  223. $bool &= false;
  224. $this->errors[] = sprintf($this->l('Cannot create the folder "%s". Please check your directory writing permissions.'), $path);
  225. }
  226. return $bool;
  227. }
  228. /**
  229. * Read the Post var and write the translation file.
  230. * This method overwrites the old translation file.
  231. *
  232. * @param bool $override_file : set true if this file is a override
  233. */
  234. protected function writeTranslationFile($override_file = false)
  235. {
  236. $type = Tools::toCamelCase($this->type_selected, true);
  237. $translation_informations = $this->translations_informations[$this->type_selected];
  238. if ($override_file)
  239. $file_path = $translation_informations['override']['dir'].$translation_informations['override']['file'];
  240. else
  241. $file_path = $translation_informations['dir'].$translation_informations['file'];
  242. if (!file_exists($file_path))
  243. {
  244. if (!file_exists(dirname($file_path)) && !mkdir(dirname($file_path), 0777, true))
  245. throw new PrestaShopException(sprintf(Tools::displayError('Directory "%s" cannot be created'), dirname($file_path)));
  246. elseif (!touch($file_path))
  247. throw new PrestaShopException(sprintf(Tools::displayError('File "%s" cannot be created'), $file_path));
  248. }
  249. $thm_name = str_replace('.', '', Tools::getValue('theme'));
  250. $kpi_key = substr(strtoupper($thm_name.'_'.Tools::getValue('lang')), 0, 16);
  251. if ($fd = fopen($file_path, 'w'))
  252. {
  253. // Get value of button save and stay
  254. $save_and_stay = Tools::isSubmit('submitTranslations'.$type.'AndStay');
  255. // Get language
  256. $lang = strtolower(Tools::getValue('lang'));
  257. // Unset all POST which are not translations
  258. unset(
  259. $_POST['submitTranslations'.$type],
  260. $_POST['submitTranslations'.$type.'AndStay'],
  261. $_POST['lang'],
  262. $_POST['token'],
  263. $_POST['theme'],
  264. $_POST['type']
  265. );
  266. // Get all POST which aren't empty
  267. $to_insert = array();
  268. foreach ($_POST as $key => $value)
  269. if (!empty($value))
  270. $to_insert[$key] = $value;
  271. ConfigurationKPI::updateValue('FRONTOFFICE_TRANSLATIONS_EXPIRE', time());
  272. ConfigurationKPI::updateValue('TRANSLATE_TOTAL_'.$kpi_key, count($_POST));
  273. ConfigurationKPI::updateValue('TRANSLATE_DONE_'.$kpi_key, count($to_insert));
  274. // translations array is ordered by key (easy merge)
  275. ksort($to_insert);
  276. $tab = $translation_informations['var'];
  277. fwrite($fd, "<?php\n\nglobal \$".$tab.";\n\$".$tab." = array();\n");
  278. foreach ($to_insert as $key => $value)
  279. fwrite($fd, '$'.$tab.'[\''.pSQL($key, true).'\'] = \''.pSQL($value, true).'\';'."\n");
  280. fwrite($fd, "\n?>");
  281. fclose($fd);
  282. // Redirect
  283. if ($save_and_stay)
  284. $this->redirect(true);
  285. else
  286. $this->redirect();
  287. }
  288. else
  289. throw new PrestaShopException(sprintf(Tools::displayError('Cannot write this file: "%s"'), $file_path));
  290. }
  291. public function submitCopyLang()
  292. {
  293. if (!($from_lang = Tools::getValue('fromLang')) || !($to_lang = Tools::getValue('toLang')))
  294. $this->errors[] = $this->l('You must select two languages in order to copy data from one to another.');
  295. else if (!($from_theme = Tools::getValue('fromTheme')) || !($to_theme = Tools::getValue('toTheme')))
  296. $this->errors[] = $this->l('You must select two themes in order to copy data from one to another.');
  297. else if (!Language::copyLanguageData(Language::getIdByIso($from_lang), Language::getIdByIso($to_lang)))
  298. $this->errors[] = $this->l('An error occurred while copying data.');
  299. else if ($from_lang == $to_lang && $from_theme == $to_theme)
  300. $this->errors[] = $this->l('There is nothing to copy (same language and theme).');
  301. else
  302. {
  303. $theme_exists = array('from_theme' => false, 'to_theme' => false);
  304. foreach ($this->themes as $theme)
  305. {
  306. if ($theme->directory == $from_theme)
  307. $theme_exists['from_theme'] = true;
  308. if ($theme->directory == $to_theme)
  309. $theme_exists['to_theme'] = true;
  310. }
  311. if ($theme_exists['from_theme'] == false || $theme_exists['to_theme'] == false)
  312. $this->errors[] = $this->l('Theme(s) not found');
  313. }
  314. if (count($this->errors))
  315. return;
  316. $bool = true;
  317. $items = Language::getFilesList($from_lang, $from_theme, $to_lang, $to_theme, false, false, true);
  318. foreach ($items as $source => $dest)
  319. {
  320. if (!$this->checkDirAndCreate($dest))
  321. $this->errors[] = sprintf($this->l('Impossible to create the directory "%s".'), $dest);
  322. elseif (!copy($source, $dest))
  323. $this->errors[] = sprintf($this->l('Impossible to copy "%s" to "%s".'), $source, $dest);
  324. elseif (strpos($dest, 'modules') && basename($source) === $from_lang.'.php' && $bool !== false)
  325. if (!$this->changeModulesKeyTranslation($dest, $from_theme, $to_theme))
  326. $this->errors[] = sprintf($this->l('Impossible to translate "$dest".'), $dest);
  327. }
  328. if (!count($this->errors))
  329. $this->redirect(false, 14);
  330. $this->errors[] = $this->l('A part of the data has been copied but some of the language files could not be found.');
  331. }
  332. /**
  333. * Change the key translation to according it to theme name.
  334. *
  335. * @param string $path
  336. * @param string $theme_from
  337. * @param string $theme_to
  338. * @return boolean
  339. */
  340. public function changeModulesKeyTranslation($path, $theme_from, $theme_to)
  341. {
  342. $content = file_get_contents($path);
  343. $arr_replace = array();
  344. $bool_flag = true;
  345. if (preg_match_all('#\$_MODULE\[\'([^\']+)\'\]#Ui', $content, $matches))
  346. {
  347. foreach ($matches[1] as $key => $value)
  348. $arr_replace[$value] = str_replace($theme_from, $theme_to, $value);
  349. $content = str_replace(array_keys($arr_replace), array_values($arr_replace), $content);
  350. $bool_flag = (file_put_contents($path, $content) === false) ? false : true;
  351. }
  352. return $bool_flag;
  353. }
  354. public function exportTabs()
  355. {
  356. // Get name tabs by iso code
  357. $tabs = Tab::getTabs($this->lang_selected->id);
  358. // Get name of the default tabs
  359. $tabs_default_lang = Tab::getTabs(1);
  360. $tabs_default = array();
  361. foreach ($tabs_default_lang as $tab)
  362. $tabs_default[$tab['class_name']] = pSQL($tab['name']);
  363. // Create content
  364. $content = "<?php\n\n\$tabs = array();";
  365. if (!empty($tabs))
  366. foreach ($tabs as $tab)
  367. if ($tabs_default[$tab['class_name']] != pSQL($tab['name']))
  368. $content .= "\n\$tabs['".$tab['class_name']."'] = '".pSQL($tab['name'])."';";
  369. $content .= "\n\nreturn \$tabs;";
  370. $dir = _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.DIRECTORY_SEPARATOR;
  371. $path = $dir.'tabs.php';
  372. // Check if tabs.php exists for the selected Iso Code
  373. if (!Tools::file_exists_cache($dir))
  374. if (!mkdir($dir, 0777, true))
  375. throw new PrestaShopException('The file '.$dir.' cannot be created.');
  376. if (!file_put_contents($path, $content))
  377. throw new PrestaShopException('File "'.$path.'" doesn\'t exists and cannot be created in '.$dir);
  378. if (!is_writable($path))
  379. $this->displayWarning(sprintf(Tools::displayError('This file must be writable: %s'), $path));
  380. }
  381. public function submitExportLang()
  382. {
  383. if ($this->lang_selected->iso_code && $this->theme_selected)
  384. {
  385. $this->exportTabs();
  386. $items = array_flip(Language::getFilesList($this->lang_selected->iso_code, $this->theme_selected, false, false, false, false, true));
  387. $file_name = _PS_TRANSLATIONS_DIR_.'/export/'.$this->lang_selected->iso_code.'.gzip';
  388. require_once(_PS_TOOL_DIR_.'tar/Archive_Tar.php');
  389. $gz = new Archive_Tar($file_name, true);
  390. if ($gz->createModify($items, null, _PS_ROOT_DIR_))
  391. {
  392. ob_start();
  393. header('Pragma: public');
  394. header('Expires: 0');
  395. header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
  396. header('Cache-Control: public');
  397. header('Content-Description: File Transfer');
  398. header('Content-type: application/octet-stream');
  399. header('Content-Disposition: attachment; filename="'.$this->lang_selected->iso_code.'.gzip'.'"');
  400. header('Content-Transfer-Encoding: binary');
  401. ob_end_flush();
  402. readfile($file_name);
  403. @unlink($file_name);
  404. exit;
  405. }
  406. $this->errors[] = Tools::displayError('An error occurred while creating archive.');
  407. }
  408. $this->errors[] = Tools::displayError('Please select a language and a theme.');
  409. }
  410. public static function checkAndAddMailsFiles($iso_code, $files_list)
  411. {
  412. if (Language::getIdByIso('en'))
  413. $default_language = 'en';
  414. else
  415. $default_language = Language::getIsoById((int)Configuration::get('PS_LANG_DEFAULT'));
  416. if (!$default_language || !Validate::isLanguageIsoCode($default_language))
  417. return false;
  418. // 1 - Scan mails files
  419. $mails = array();
  420. if (Tools::file_exists_cache(_PS_MAIL_DIR_.$default_language.'/'))
  421. $mails = scandir(_PS_MAIL_DIR_.$default_language.'/');
  422. $mails_new_lang = array();
  423. // Get all email files
  424. foreach ($files_list as $file)
  425. {
  426. if (preg_match('#^mails\/([a-z0-9]+)\/#Ui', $file['filename'], $matches))
  427. {
  428. $slash_pos = strrpos($file['filename'], '/');
  429. $mails_new_lang[] = substr($file['filename'], -(strlen($file['filename']) - $slash_pos - 1));
  430. }
  431. }
  432. // Get the difference
  433. $arr_mails_needed = array_diff($mails, $mails_new_lang);
  434. // Add mails files
  435. foreach ($arr_mails_needed as $mail_to_add)
  436. if (!in_array($mail_to_add, self::$ignore_folder))
  437. @copy(_PS_MAIL_DIR_.$default_language.'/'.$mail_to_add, _PS_MAIL_DIR_.$iso_code.'/'.$mail_to_add);
  438. // 2 - Scan modules files
  439. $modules = scandir(_PS_MODULE_DIR_);
  440. $module_mail_en = array();
  441. $module_mail_iso_code = array();
  442. foreach ($modules as $module)
  443. {
  444. if (!in_array($module, self::$ignore_folder) && Tools::file_exists_cache(_PS_MODULE_DIR_.$module.'/mails/'.$default_language.'/'))
  445. {
  446. $arr_files = scandir(_PS_MODULE_DIR_.$module.'/mails/'.$default_language.'/');
  447. foreach ($arr_files as $file)
  448. {
  449. if (!in_array($file, self::$ignore_folder))
  450. {
  451. if (Tools::file_exists_cache(_PS_MODULE_DIR_.$module.'/mails/'.$default_language.'/'.$file))
  452. $module_mail_en[] = _PS_MODULE_DIR_.$module.'/mails/ISO_CODE/'.$file;
  453. if (Tools::file_exists_cache(_PS_MODULE_DIR_.$module.'/mails/'.$iso_code.'/'.$file))
  454. $module_mail_iso_code[] = _PS_MODULE_DIR_.$module.'/mails/ISO_CODE/'.$file;
  455. }
  456. }
  457. }
  458. }
  459. // Get the difference in this modules
  460. $arr_modules_mails_needed = array_diff($module_mail_en, $module_mail_iso_code);
  461. // Add mails files for this modules
  462. foreach ($arr_modules_mails_needed as $file)
  463. {
  464. $file_en = str_replace('ISO_CODE', $default_language, $file);
  465. $file_iso_code = str_replace('ISO_CODE', $iso_code, $file);
  466. $dir_iso_code = substr($file_iso_code, 0, -(strlen($file_iso_code) - strrpos($file_iso_code, '/') - 1));
  467. if (!file_exists($dir_iso_code))
  468. {
  469. mkdir($dir_iso_code);
  470. file_put_contents($dir_iso_code.'/index.php', Tools::getDefaultIndexContent());
  471. }
  472. if (Tools::file_exists_cache($file_en))
  473. copy($file_en, $file_iso_code);
  474. }
  475. }
  476. /**
  477. * Move theme translations in selected themes
  478. *
  479. * @param array $files
  480. * @param array $themes_selected
  481. */
  482. public function checkAndAddThemesFiles($files, $themes_selected)
  483. {
  484. foreach ($files as $file)
  485. {
  486. // Check if file is a file theme
  487. if (preg_match('#^themes\/([a-z0-9]+)\/lang\/#Ui', $file['filename'], $matches))
  488. {
  489. $slash_pos = strrpos($file['filename'], '/');
  490. $name_file = substr($file['filename'], -(strlen($file['filename']) - $slash_pos - 1));
  491. $name_default_theme = $matches[1];
  492. $deleted_old_theme = false;
  493. // Get the old file theme
  494. if (file_exists(_PS_THEME_DIR_.'lang/'.$name_file))
  495. $theme_file_old = _PS_THEME_DIR_.'lang/'.$name_file;
  496. else
  497. {
  498. $deleted_old_theme = true;
  499. $theme_file_old = str_replace(self::DEFAULT_THEME_NAME, $name_default_theme, _PS_THEME_DIR_.'lang/'.$name_file);
  500. }
  501. // Move the old file theme in the new folder
  502. foreach ($themes_selected as $theme_name)
  503. if (file_exists($theme_file_old))
  504. copy($theme_file_old, str_replace($name_default_theme, $theme_name, $theme_file_old));
  505. if ($deleted_old_theme)
  506. @unlink($theme_file_old);
  507. }
  508. }
  509. }
  510. /**
  511. * Add new translations tabs by code ISO
  512. *
  513. * @param array $iso_code
  514. * @param array $files
  515. */
  516. public static function addNewTabs($iso_code, $files)
  517. {
  518. $errors = array();
  519. foreach ($files as $file)
  520. {
  521. // Check if file is a file theme
  522. if (preg_match('#^translations\/'.$iso_code.'\/tabs.php#Ui', $file['filename'], $matches) && Validate::isLanguageIsoCode($iso_code))
  523. {
  524. // Include array width new translations tabs
  525. $_TABS = array();
  526. clearstatcache();
  527. if (file_exists(_PS_ROOT_DIR_.DIRECTORY_SEPARATOR.$file['filename']))
  528. include_once(_PS_ROOT_DIR_.DIRECTORY_SEPARATOR.$file['filename']);
  529. if (count($_TABS))
  530. {
  531. foreach ($_TABS as $class_name => $translations)
  532. {
  533. // Get instance of this tab by class name
  534. $tab = Tab::getInstanceFromClassName($class_name);
  535. //Check if class name exists
  536. if (isset($tab->class_name) && !empty($tab->class_name))
  537. {
  538. $id_lang = Language::getIdByIso($iso_code);
  539. $tab->name[(int)$id_lang] = $translations;
  540. // Do not crash at intall
  541. if (!isset($tab->name[Configuration::get('PS_LANG_DEFAULT')]))
  542. $tab->name[(int)Configuration::get('PS_LANG_DEFAULT')] = $translations;
  543. if (!Validate::isGenericName($tab->name[(int)$id_lang]))
  544. $errors[] = sprintf(Tools::displayError('Tab "%s" is not valid'), $tab->name[(int)$id_lang]);
  545. else
  546. $tab->update();
  547. }
  548. }
  549. }
  550. }
  551. }
  552. return $errors;
  553. }
  554. public static function checkTranslationFile($content)
  555. {
  556. $lines = array_map('trim', explode("\n", $content));
  557. $global = false;
  558. foreach ($lines as $line)
  559. {
  560. // PHP tags
  561. if (in_array($line, array('<?php', '?>', '')))
  562. continue;
  563. // Global variable declaration
  564. if (!$global && preg_match('/^global\s+\$([a-z0-9-_]+)\s*;$/i', $line, $matches))
  565. {
  566. $global = $matches[1];
  567. continue;
  568. }
  569. // Global variable initialization
  570. if ($global != false && preg_match('/^\$'.preg_quote($global, '/').'\s*=\s*array\(\s*\)\s*;$/i', $line))
  571. continue;
  572. // Global variable initialization without declaration
  573. if (!$global && preg_match('/^\$([a-z0-9-_]+)\s*=\s*array\(\s*\)\s*;$/i', $line, $matches))
  574. {
  575. $global = $matches[1];
  576. continue;
  577. }
  578. // Assignation
  579. if (preg_match('/^\$'.preg_quote($global, '/').'\[\''._PS_TRANS_PATTERN_.'\'\]\s*=\s*\''._PS_TRANS_PATTERN_.'\'\s*;$/i', $line))
  580. continue;
  581. // Sometimes the global variable is returned...
  582. if (preg_match('/^return\s+\$'.preg_quote($global, '/').'\s*;$/i', $line, $matches))
  583. continue;
  584. return false;
  585. }
  586. return true;
  587. }
  588. public function submitImportLang()
  589. {
  590. if (!isset($_FILES['file']['tmp_name']) || !$_FILES['file']['tmp_name'])
  591. $this->errors[] = Tools::displayError('No file has been selected.');
  592. else
  593. {
  594. require_once(_PS_TOOL_DIR_.'tar/Archive_Tar.php');
  595. $gz = new Archive_Tar($_FILES['file']['tmp_name'], true);
  596. $filename = $_FILES['file']['name'];
  597. $iso_code = str_replace(array('.tar.gz', '.gzip'), '', $filename);
  598. if (Validate::isLangIsoCode($iso_code))
  599. {
  600. $themes_selected = Tools::getValue('theme', array(self::DEFAULT_THEME_NAME));
  601. $files_list = AdminTranslationsController::filterTranslationFiles($gz->listContent());
  602. $files_paths = AdminTranslationsController::filesListToPaths($files_list);
  603. $uniqid = uniqid();
  604. $sandbox = _PS_CACHE_DIR_.'sandbox'.DIRECTORY_SEPARATOR.$uniqid.DIRECTORY_SEPARATOR;
  605. if ($gz->extractList($files_paths, $sandbox))
  606. {
  607. foreach ($files_list as $file2check)
  608. {
  609. //don't validate index.php, will be overwrite when extract in translation directory
  610. if (pathinfo($file2check['filename'], PATHINFO_BASENAME) == 'index.php')
  611. continue;
  612. if (preg_match('@^[0-9a-z-_/\\\\]+\.php$@i', $file2check['filename']))
  613. {
  614. if (!AdminTranslationsController::checkTranslationFile(file_get_contents($sandbox.$file2check['filename'])))
  615. $this->errors[] = sprintf(Tools::displayError('Validation failed for: %s'), $file2check['filename']);
  616. }
  617. elseif (!preg_match('@^[0-9a-z-_/\\\\]+\.(html|tpl|txt)$@i', $file2check['filename']))
  618. $this->errors[] = sprintf(Tools::displayError('Unidentified file found: %s'), $file2check['filename']);
  619. }
  620. Tools::deleteDirectory($sandbox, true);
  621. }
  622. $i = 0;
  623. $tmp_array = array();
  624. foreach ($files_paths as $files_path)
  625. {
  626. $path = dirname($files_path);
  627. if (is_dir(_PS_TRANSLATIONS_DIR_.'../'.$path) && !is_writable(_PS_TRANSLATIONS_DIR_.'../'.$path) && !in_array($path, $tmp_array))
  628. {
  629. $this->errors[] = (!$i++? Tools::displayError('The archive cannot be extracted.').' ' : '').Tools::displayError('The server does not have permissions for writing.').' '.sprintf(Tools::displayError('Please check rights for %s'), $path);
  630. $tmp_array[] = $path;
  631. }
  632. }
  633. if (count($this->errors))
  634. return false;
  635. if ($error = $gz->extractList($files_paths, _PS_TRANSLATIONS_DIR_.'../'))
  636. {
  637. if (is_object($error) && !empty($error->message))
  638. $this->errors[] = Tools::displayError('The archive cannot be extracted.'). ' '.$error->message;
  639. else
  640. {
  641. foreach ($files_list as $file2check)
  642. if (pathinfo($file2check['filename'], PATHINFO_BASENAME) == 'index.php' && file_put_contents(_PS_TRANSLATIONS_DIR_.'../'.$file2check['filename'], Tools::getDefaultIndexContent()))
  643. continue;
  644. // Clear smarty modules cache
  645. Tools::clearCache();
  646. if (Validate::isLanguageFileName($filename))
  647. {
  648. if (!Language::checkAndAddLanguage($iso_code))
  649. $conf = 20;
  650. else
  651. {
  652. // Reset cache
  653. Language::loadLanguages();
  654. AdminTranslationsController::checkAndAddMailsFiles($iso_code, $files_list);
  655. $this->checkAndAddThemesFiles($files_list, $themes_selected);
  656. $tab_errors = AdminTranslationsController::addNewTabs($iso_code, $files_list);
  657. if (count($tab_errors))
  658. {
  659. $this->errors += $tab_errors;
  660. return false;
  661. }
  662. }
  663. }
  664. $this->redirect(false, (isset($conf) ? $conf : '15'));
  665. }
  666. }
  667. $this->errors[] = Tools::displayError('The archive cannot be extracted.');
  668. }
  669. else
  670. $this->errors[] = sprintf(Tools::displayError('ISO CODE invalid "%1$s" for the following file: "%2$s"'), $iso_code, $filename);
  671. }
  672. }
  673. /**
  674. * Filter the translation files contained in a .gzip pack
  675. * and return only the ones that we want.
  676. *
  677. * Right now the function only needs to check that
  678. * the modules for which we want to add translations
  679. * are present on the shop (installed or not).
  680. *
  681. * $list is the output of Archive_Tar::listContent()
  682. */
  683. public static function filterTranslationFiles($list)
  684. {
  685. $kept = array();
  686. foreach ($list as $file)
  687. {
  688. if ('index.php' == basename($file['filename']))
  689. continue;
  690. if (preg_match('#^modules/([^/]+)/#', $file['filename'], $m))
  691. {
  692. if (is_dir(_PS_MODULE_DIR_.$m[1]))
  693. $kept[] = $file;
  694. }
  695. else
  696. $kept[] = $file;
  697. }
  698. return $kept;
  699. }
  700. /**
  701. * Turn the list returned by
  702. * AdminTranslationsController::filterTranslationFiles()
  703. * into a list of paths that can be passed to
  704. * Archive_Tar::extractList()
  705. */
  706. public static function filesListToPaths($list)
  707. {
  708. $paths = array();
  709. foreach ($list as $item)
  710. $paths[] = $item['filename'];
  711. return $paths;
  712. }
  713. public function submitAddLang()
  714. {
  715. $arr_import_lang = explode('|', Tools::getValue('params_import_language')); /* 0 = Language ISO code, 1 = PS version */
  716. if (Validate::isLangIsoCode($arr_import_lang[0]))
  717. {
  718. $array_stream_context = @stream_context_create(array('http' => array('method' => 'GET', 'timeout' => 10)));
  719. $content = Tools::file_get_contents('http://www.prestashop.com/download/lang_packs/gzip/'.$arr_import_lang[1].'/'.Tools::strtolower($arr_import_lang[0]).'.gzip', false, $array_stream_context);
  720. if ($content)
  721. {
  722. $file = _PS_TRANSLATIONS_DIR_.$arr_import_lang[0].'.gzip';
  723. if ((bool)@file_put_contents($file, $content))
  724. {
  725. require_once(_PS_TOOL_DIR_.'/tar/Archive_Tar.php');
  726. $gz = new Archive_Tar($file, true);
  727. $files_list = AdminTranslationsController::filterTranslationFiles($gz->listContent());
  728. if ($error = $gz->extractList(AdminTranslationsController::filesListToPaths($files_list), _PS_TRANSLATIONS_DIR_.'../'))
  729. {
  730. if (is_object($error) && !empty($error->message))
  731. $this->errors[] = Tools::displayError('The archive cannot be extracted.'). ' '.$error->message;
  732. else
  733. {
  734. if (!Language::checkAndAddLanguage($arr_import_lang[0]))
  735. $conf = 20;
  736. else
  737. {
  738. // Reset cache
  739. Language::loadLanguages();
  740. // Clear smarty modules cache
  741. Tools::clearCache();
  742. AdminTranslationsController::checkAndAddMailsFiles($arr_import_lang[0], $files_list);
  743. if ($tab_errors = AdminTranslationsController::addNewTabs($arr_import_lang[0], $files_list))
  744. $this->errors += $tab_errors;
  745. }
  746. if (!unlink($file))
  747. $this->errors[] = sprintf(Tools::displayError('Cannot delete the archive %s.'), $file);
  748. $this->redirect(false, (isset($conf) ? $conf : '15'));
  749. }
  750. }
  751. elseif (!unlink($file))
  752. $this->errors[] = sprintf(Tools::displayError('Cannot delete the archive %s.'), $file);
  753. }
  754. else
  755. $this->errors[] = Tools::displayError('The server does not have permissions for writing.').' '.sprintf(Tools::displayError('Please check rights for %s'), dirname($file));
  756. }
  757. else
  758. $this->errors[] = Tools::displayError('Language not found.');
  759. }
  760. else
  761. $this->errors[] = Tools::displayError('Invalid parameter.');
  762. }
  763. /**
  764. * This method check each file (tpl or php file), get its sentences to translate,
  765. * compare with posted values and write in iso code translation file.
  766. *
  767. * @param string $file_name
  768. * @param array $files
  769. * @param string $theme_name
  770. * @param string $module_name
  771. * @param string|boolean $dir
  772. * @return void
  773. */
  774. protected function findAndWriteTranslationsIntoFile($file_name, $files, $theme_name, $module_name, $dir = false)
  775. {
  776. // These static vars allow to use file to write just one time.
  777. static $cache_file = array();
  778. static $str_write = '';
  779. static $array_check_duplicate = array();
  780. // Set file_name in static var, this allow to open and wright the file just one time
  781. if (!isset($cache_file[$theme_name.'-'.$file_name]))
  782. {
  783. $str_write = '';
  784. $cache_file[$theme_name.'-'.$file_name] = true;
  785. if (!Tools::file_exists_cache(dirname($file_name)))
  786. mkdir(dirname($file_name), 0777, true);
  787. if (!Tools::file_exists_cache($file_name))
  788. file_put_contents($file_name, '');
  789. if (!is_writable($file_name))
  790. throw new PrestaShopException(sprintf(
  791. Tools::displayError('Cannot write to the theme\'s language file (%s). Please check write permissions.'),
  792. $file_name
  793. ));
  794. // this string is initialized one time for a file
  795. $str_write .= "<?php\n\nglobal \$_MODULE;\n\$_MODULE = array();\n";
  796. $array_check_duplicate = array();
  797. }
  798. foreach ($files as $file)
  799. {
  800. if (preg_match('/^(.*)\.(tpl|php)$/', $file) && Tools::file_exists_cache($dir.$file) && !in_array($file, self::$ignore_folder))
  801. {
  802. // Get content for this file
  803. $content = file_get_contents($dir.$file);
  804. // Get file type
  805. $type_file = substr($file, -4) == '.tpl' ? 'tpl' : 'php';
  806. // Parse this content
  807. $matches = $this->userParseFile($content, $this->type_selected, $type_file, $module_name);
  808. // Write each translation on its module file
  809. $template_name = substr(basename($file), 0, -4);
  810. foreach ($matches as $key)
  811. {
  812. if ($theme_name)
  813. {
  814. $post_key = md5(strtolower($module_name).'_'.strtolower($theme_name).'_'.strtolower($template_name).'_'.md5($key));
  815. $pattern = '\'<{'.strtolower($module_name).'}'.strtolower($theme_name).'>'.strtolower($template_name).'_'.md5($key).'\'';
  816. }
  817. else
  818. {
  819. $post_key = md5(strtolower($module_name).'_'.strtolower($template_name).'_'.md5($key));
  820. $pattern = '\'<{'.strtolower($module_name).'}prestashop>'.strtolower($template_name).'_'.md5($key).'\'';
  821. }
  822. if (array_key_exists($post_key, $_POST) && !empty($_POST[$post_key]) && !in_array($pattern, $array_check_duplicate))
  823. {
  824. $array_check_duplicate[] = $pattern;
  825. $str_write .= '$_MODULE['.$pattern.'] = \''.pSQL(str_replace(array("\r\n", "\r", "\n"), ' ', $_POST[$post_key])).'\';'."\n";
  826. $this->total_expression++;
  827. }
  828. }
  829. }
  830. }
  831. if (isset($cache_file[$theme_name.'-'.$file_name]) && $str_write != "<?php\n\nglobal \$_MODULE;\n\$_MODULE = array();\n")
  832. file_put_contents($file_name, $str_write);
  833. }
  834. /**
  835. * Clear the list of module file by type (file or directory)
  836. *
  837. * @param $files : list of files
  838. * @param string $type_clear (file|directory)
  839. * @param string $path
  840. * @return array : list of a good files
  841. */
  842. public function clearModuleFiles($files, $type_clear = 'file', $path = '')
  843. {
  844. // List of directory which not must be parsed
  845. $arr_exclude = array('img', 'js', 'mails','override');
  846. // List of good extention files
  847. $arr_good_ext = array('.tpl', '.php');
  848. foreach ($files as $key => $file)
  849. {
  850. if ($file{0} === '.' || in_array(substr($file, 0, strrpos($file, '.')), $this->all_iso_lang))
  851. unset($files[$key]);
  852. else if ($type_clear === 'file' && !in_array(substr($file, strrpos($file, '.')), $arr_good_ext))
  853. unset($files[$key]);
  854. else if ($type_clear === 'directory' && (!is_dir($path.$file) || in_array($file, $arr_exclude)))
  855. unset($files[$key]);
  856. }
  857. return $files;
  858. }
  859. /**
  860. * This method get translation for each files of a module,
  861. * compare with global $_MODULES array and fill AdminTranslations::modules_translations array
  862. * With key as English sentences and values as their iso code translations.
  863. *
  864. * @param array $files
  865. * @param string $theme_name
  866. * @param string $module_name
  867. * @param string|boolean $dir
  868. * @param string $iso_code
  869. * @return void
  870. */
  871. protected function findAndFillTranslations($files, $theme_name, $module_name, $dir = false)
  872. {
  873. $name_var = $this->translations_informations[$this->type_selected]['var'];
  874. // added for compatibility
  875. $GLOBALS[$name_var] = array_change_key_case($GLOBALS[$name_var]);
  876. // Thank to this var similar keys are not duplicate
  877. // in AndminTranslation::modules_translations array
  878. // see below
  879. $array_check_duplicate = array();
  880. foreach ($files as $file)
  881. {
  882. if ((preg_match('/^(.*).tpl$/', $file) || preg_match('/^(.*).php$/', $file)) && Tools::file_exists_cache($file_path = $dir.$file))
  883. {
  884. // Get content for this file
  885. $content = file_get_contents($file_path);
  886. // Module files can now be ignored by adding this string in a file
  887. if (strpos($content, 'IGNORE_THIS_FILE_FOR_TRANSLATION') !== false)
  888. continue;
  889. // Get file type
  890. $type_file = substr($file, -4) == '.tpl' ? 'tpl' : 'php';
  891. // Parse this content
  892. $matches = $this->userParseFile($content, $this->type_selected, $type_file, $module_name);
  893. // Write each translation on its module file
  894. $template_name = substr(basename($file), 0, -4);
  895. foreach ($matches as $key)
  896. {
  897. $md5_key = md5($key);
  898. $module_key = '<{'.Tools::strtolower($module_name).'}'.strtolower($theme_name).'>'.Tools::strtolower($template_name).'_'.$md5_key;
  899. $default_key = '<{'.Tools::strtolower($module_name).'}prestashop>'.Tools::strtolower($template_name).'_'.$md5_key;
  900. // to avoid duplicate entry
  901. if (!in_array($module_key, $array_check_duplicate))
  902. {
  903. $array_check_duplicate[] = $module_key;
  904. if (!isset($this->modules_translations[$theme_name][$module_name][$template_name][$key]['trad']))
  905. $this->total_expression++;
  906. if ($theme_name && array_key_exists($module_key, $GLOBALS[$name_var]))
  907. $this->modules_translations[$theme_name][$module_name][$template_name][$key]['trad'] = html_entity_decode($GLOBALS[$name_var][$module_key], ENT_COMPAT, 'UTF-8');
  908. elseif (array_key_exists($default_key, $GLOBALS[$name_var]))
  909. $this->modules_translations[$theme_name][$module_name][$template_name][$key]['trad'] = html_entity_decode($GLOBALS[$name_var][$default_key], ENT_COMPAT, 'UTF-8');
  910. else
  911. {
  912. $this->modules_translations[$theme_name][$module_name][$template_name][$key]['trad'] = '';
  913. $this->missing_translations++;
  914. }
  915. $this->modules_translations[$theme_name][$module_name][$template_name][$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  916. }
  917. }
  918. }
  919. }
  920. }
  921. /**
  922. * Get list of files which must be parsed by directory and by type of translations
  923. *
  924. * @return array : list of files by directory
  925. */
  926. public function getFileToParseByTypeTranslation()
  927. {
  928. $directories = array();
  929. switch ($this->type_selected)
  930. {
  931. case 'front':
  932. $directories['tpl'] = array(_PS_ALL_THEMES_DIR_ => scandir(_PS_ALL_THEMES_DIR_));
  933. self::$ignore_folder[] = 'modules';
  934. $directories['tpl'] = array_merge($directories['tpl'], $this->listFiles(_PS_THEME_SELECTED_DIR_));
  935. if (isset($directories['tpl'][_PS_THEME_SELECTED_DIR_.'pdf/']))
  936. unset($directories['tpl'][_PS_THEME_SELECTED_DIR_.'pdf/']);
  937. if (Tools::file_exists_cache(_PS_THEME_OVERRIDE_DIR_))
  938. $directories['tpl'] = array_merge($directories['tpl'], $this->listFiles(_PS_THEME_OVERRIDE_DIR_));
  939. break;
  940. case 'back':
  941. $directories = array(
  942. 'php' => array(
  943. _PS_ADMIN_CONTROLLER_DIR_ => scandir(_PS_ADMIN_CONTROLLER_DIR_),
  944. _PS_OVERRIDE_DIR_.'controllers/admin/' => scandir(_PS_OVERRIDE_DIR_.'controllers/admin/'),
  945. _PS_CLASS_DIR_.'helper/' => scandir(_PS_CLASS_DIR_.'helper/'),
  946. _PS_CLASS_DIR_.'controller/' => array('AdminController.php'),
  947. _PS_CLASS_DIR_ => array('PaymentModule.php')
  948. ),
  949. 'tpl' => $this->listFiles(_PS_ADMIN_DIR_.DIRECTORY_SEPARATOR.'themes/'),
  950. 'specific' => array(
  951. _PS_ADMIN_DIR_.DIRECTORY_SEPARATOR => array(
  952. 'header.inc.php',
  953. 'footer.inc.php',
  954. 'index.php',
  955. 'functions.php'
  956. )
  957. )
  958. );
  959. // For translate the template which are overridden
  960. if (file_exists(_PS_OVERRIDE_DIR_.'controllers'.DIRECTORY_SEPARATOR.'admin'.DIRECTORY_SEPARATOR.'templates'))
  961. $directories['tpl'] = array_merge($directories['tpl'], $this->listFiles(_PS_OVERRIDE_DIR_.'controllers'.DIRECTORY_SEPARATOR.'admin'.DIRECTORY_SEPARATOR.'templates'));
  962. break;
  963. case 'errors':
  964. $directories['php'] = array(
  965. _PS_ROOT_DIR_ => scandir(_PS_ROOT_DIR_),
  966. _PS_ADMIN_DIR_.DIRECTORY_SEPARATOR => scandir(_PS_ADMIN_DIR_.DIRECTORY_SEPARATOR),
  967. _PS_FRONT_CONTROLLER_DIR_ => scandir(_PS_FRONT_CONTROLLER_DIR_),
  968. _PS_ADMIN_CONTROLLER_DIR_ => scandir(_PS_ADMIN_CONTROLLER_DIR_),
  969. _PS_OVERRIDE_DIR_.'controllers/front/' => scandir(_PS_OVERRIDE_DIR_.'controllers/front/'),
  970. _PS_OVERRIDE_DIR_.'controllers/admin/' => scandir(_PS_OVERRIDE_DIR_.'controllers/admin/')
  971. );
  972. // Get all files for folders classes/ and override/classes/ recursively
  973. $directories['php'] = array_merge($directories['php'], $this->listFiles(_PS_CLASS_DIR_, array(), 'php'));
  974. $directories['php'] = array_merge($directories['php'], $this->listFiles(_PS_OVERRIDE_DIR_.'classes/', array(), 'php'));
  975. break;
  976. case 'fields':
  977. $directories['php'] = $this->listFiles(_PS_CLASS_DIR_, array(), 'php');
  978. break;
  979. case 'pdf':
  980. $tpl_theme = Tools::file_exists_cache(_PS_THEME_SELECTED_DIR_.'pdf/') ? scandir(_PS_THEME_SELECTED_DIR_.'pdf/') : array();
  981. $directories = array(
  982. 'php' => array(
  983. _PS_CLASS_DIR_.'pdf/' => scandir(_PS_CLASS_DIR_.'pdf/'),
  984. _PS_OVERRIDE_DIR_.'classes/pdf/' => scandir(_PS_OVERRIDE_DIR_.'classes/pdf/')
  985. ),
  986. 'tpl' => array(
  987. _PS_PDF_DIR_ => scandir(_PS_PDF_DIR_),
  988. _PS_THEME_SELECTED_DIR_.'pdf/' => $tpl_theme
  989. )
  990. );
  991. break;
  992. case 'mails':
  993. $directories['php'] = array(
  994. _PS_FRONT_CONTROLLER_DIR_ => scandir(_PS_FRONT_CONTROLLER_DIR_),
  995. _PS_ADMIN_CONTROLLER_DIR_ => scandir(_PS_ADMIN_CONTROLLER_DIR_),
  996. _PS_OVERRIDE_DIR_.'controllers/front/' => scandir(_PS_OVERRIDE_DIR_.'controllers/front/'),
  997. _PS_OVERRIDE_DIR_.'controllers/admin/' => scandir(_PS_OVERRIDE_DIR_.'controllers/admin/'),
  998. _PS_ADMIN_DIR_.DIRECTORY_SEPARATOR => scandir(_PS_ADMIN_DIR_.DIRECTORY_SEPARATOR),
  999. _PS_ADMIN_DIR_.DIRECTORY_SEPARATOR.'tabs/' => scandir(_PS_ADMIN_DIR_.DIRECTORY_SEPARATOR.'/tabs')
  1000. );
  1001. // Get all files for folders classes/ and override/classes/ recursively
  1002. $directories['php'] = array_merge($directories['php'], $this->listFiles(_PS_CLASS_DIR_, array(), 'php'));
  1003. $directories['php'] = array_merge($directories['php'], $this->listFiles(_PS_OVERRIDE_DIR_.'classes/', array(), 'php'));
  1004. $directories['php'] = array_merge($directories['php'], $this->getModulesHasMails());
  1005. break;
  1006. }
  1007. return $directories;
  1008. }
  1009. /**
  1010. * This method parse a file by type of translation and type file
  1011. *
  1012. * @param $content
  1013. * @param $type_translation : front, back, errors, modules...
  1014. * @param string|bool $type_file : (tpl|php)
  1015. * @param string $module_name : name of the module
  1016. * @return return $matches
  1017. */
  1018. protected function userParseFile($content, $type_translation, $type_file = false, $module_name = '')
  1019. {
  1020. switch ($type_translation)
  1021. {
  1022. case 'front':
  1023. // Parsing file in Front office
  1024. $regex = '/\{l\s*s=([\'\"])'._PS_TRANS_PATTERN_.'\1(\s*sprintf=.*)?(\s*js=1)?\s*\}/U';
  1025. break;
  1026. case 'back':
  1027. // Parsing file in Back office
  1028. if ($type_file == 'php')
  1029. $regex = '/this->l\((\')'._PS_TRANS_PATTERN_.'\'[\)|\,]/U';
  1030. else if ($type_file == 'specific')
  1031. $regex = '/Translate::getAdminTranslation\((\')'._PS_TRANS_PATTERN_.'\'(?:,.*)*\)/U';
  1032. else
  1033. $regex = '/\{l\s*s\s*=([\'\"])'._PS_TRANS_PATTERN_.'\1(\s*sprintf=.*)?(\s*js=1)?(\s*slashes=1)?.*\}/U';
  1034. break;
  1035. case 'errors':
  1036. // Parsing file for all errors syntax
  1037. $regex = '/Tools::displayError\((\')'._PS_TRANS_PATTERN_.'\'(,\s*(.+))?\)/U';
  1038. break;
  1039. case 'modules':
  1040. // Parsing modules file
  1041. if ($type_file == 'php')
  1042. $regex = '/->l\((\')'._PS_TRANS_PATTERN_.'\'(, ?\'(.+)\')?(, ?(.+))?\)/U';
  1043. else
  1044. // In tpl file look for something that should contain mod='module_name' according to the documentation
  1045. $regex = '/\{l\s*s=([\'\"])'._PS_TRANS_PATTERN_.'\1.*\s+mod=\''.$module_name.'\'.*\}/U';
  1046. break;
  1047. case 'pdf':
  1048. // Parsing PDF file
  1049. if ($type_file == 'php')
  1050. $regex = '/HTMLTemplate.*::l\((\')'._PS_TRANS_PATTERN_.'\'[\)|\,]/U';
  1051. else
  1052. $regex = '/\{l\s*s=([\'\"])'._PS_TRANS_PATTERN_.'\1(\s*sprintf=.*)?(\s*js=1)?(\s*pdf=\'true\')?\s*\}/U';
  1053. break;
  1054. }
  1055. if (!is_array($regex))
  1056. $regex = array($regex);
  1057. $strings = array();
  1058. foreach ($regex as $regex_row)
  1059. {
  1060. $matches = array();
  1061. $n = preg_match_all($regex_row, $content, $matches);
  1062. for ($i = 0; $i < $n; $i += 1)
  1063. {
  1064. $quote = $matches[1][$i];
  1065. $string = $matches[2][$i];
  1066. if ($quote === '"')
  1067. {
  1068. // Escape single quotes because the core will do it when looking for the translation of this string
  1069. $string = str_replace('\'', '\\\'', $string);
  1070. // Unescape double quotes
  1071. $string = preg_replace('/\\\\+"/', '"', $string);
  1072. }
  1073. $strings[] = $string;
  1074. }
  1075. }
  1076. return array_unique($strings);
  1077. }
  1078. /**
  1079. * Get all translations informations for all type of translations
  1080. *
  1081. * array(
  1082. * 'type' => array(
  1083. * 'name' => string : title for the translation type,
  1084. * 'var' => string : name of var for the translation file,
  1085. * 'dir' => string : dir of translation file
  1086. * 'file' => string : file name of translation file
  1087. * )
  1088. * )
  1089. */
  1090. public function getTranslationsInformations()
  1091. {
  1092. $this->translations_informations = array(
  1093. 'front' => array(
  1094. 'name' => $this->l('Front Office translations'),
  1095. 'var' => '_LANG',
  1096. 'dir' => defined('_PS_THEME_SELECTED_DIR_') ? _PS_THEME_SELECTED_DIR_.'lang/' : '',
  1097. 'file' => $this->lang_selected->iso_code.'.php'
  1098. ),
  1099. 'back' => array(
  1100. 'name' => $this->l('Back Office translations'),
  1101. 'var' => '_LANGADM',
  1102. 'dir' => _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.'/',
  1103. 'file' => 'admin.php'
  1104. ),
  1105. 'errors' => array(
  1106. 'name' => $this->l('Error message translations'),
  1107. 'var' => '_ERRORS',
  1108. 'dir' => _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.'/',
  1109. 'file' => 'errors.php'
  1110. ),
  1111. 'fields' => array(
  1112. 'name' => $this->l('Field name translations'),
  1113. 'var' => '_FIELDS',
  1114. 'dir' => _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.'/',
  1115. 'file' => 'fields.php'
  1116. ),
  1117. 'modules' => array(
  1118. 'name' => $this->l('Installed modules translations'),
  1119. 'var' => '_MODULES',
  1120. 'dir' => _PS_MODULE_DIR_,
  1121. 'file' => ''
  1122. ),
  1123. 'pdf' => array(
  1124. 'name' => $this->l('PDF translations'),
  1125. 'var' => '_LANGPDF',
  1126. 'dir' => _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.'/',
  1127. 'file' => 'pdf.php'
  1128. ),
  1129. 'mails' => array(
  1130. 'name' => $this->l('Email templates translations'),
  1131. 'var' => '_LANGMAIL',
  1132. 'dir' => _PS_MAIL_DIR_.$this->lang_selected->iso_code.'/',
  1133. 'file' => 'lang.php'
  1134. )
  1135. );
  1136. if (defined('_PS_THEME_SELECTED_DIR_'))
  1137. {
  1138. $this->translations_informations['modules']['override'] = array('dir' => _PS_THEME_SELECTED_DIR_.'modules/', 'file' => '');
  1139. $this->translations_informations['pdf']['override'] = array('dir' => _PS_THEME_SELECTED_DIR_.'pdf/lang/', 'file' => $this->lang_selected->iso_code.'.php');
  1140. $this->translations_informations['mails']['override'] = array('dir' => _PS_THEME_SELECTED_DIR_.'mails/'.$this->lang_selected->iso_code.'/', 'file' => 'lang.php');
  1141. }
  1142. }
  1143. /**
  1144. * Get all informations on : languages, theme and the translation type.
  1145. */
  1146. public function getInformations()
  1147. {
  1148. // Get all Languages
  1149. $this->languages = Language::getLanguages(false);
  1150. // Get all iso_code of languages
  1151. foreach ($this->languages as $language)
  1152. $this->all_iso_lang[] = $language['iso_code'];
  1153. // Get all themes
  1154. $this->themes = Theme::getThemes();
  1155. // Get folder name of theme
  1156. if (($theme = Tools::getValue('theme')) && !is_array($theme))
  1157. {
  1158. $theme_exists = $this->theme_exists($theme);
  1159. if (!$theme_exists)
  1160. throw new PrestaShopException(sprintf(Tools::displayError('Invalid theme "%s"'), Tools::safeOutput($theme)));
  1161. $this->theme_selected = Tools::safeOutput($theme);
  1162. }
  1163. // Set the path of selected theme
  1164. if ($this->theme_selected)
  1165. define('_PS_THEME_SELECTED_DIR_', _PS_ROOT_DIR_.'/themes/'.$this->theme_selected.'/');
  1166. else
  1167. define('_PS_THEME_SELECTED_DIR_', '');
  1168. // Get type of translation
  1169. if (($type = Tools::getValue('type')) && !is_array($type))
  1170. $this->type_selected = strtolower(Tools::safeOutput($type));
  1171. // Get selected language
  1172. if (Tools::getValue('lang') || Tools::getValue('iso_code'))
  1173. {
  1174. $iso_code = Tools::getValue('lang') ? Tools::getValue('lang') : Tools::getValue('iso_code');
  1175. if (!Validate::isLangIsoCode($iso_code) || !in_array($iso_code, $this->all_iso_lang))
  1176. throw new PrestaShopException(sprintf(Tools::displayError('Invalid iso code "%s"'), Tools::safeOutput($iso_code)));
  1177. $this->lang_selected = new Language((int)Language::getIdByIso($iso_code));
  1178. }
  1179. else
  1180. $this->lang_selected = new Language((int)Language::getIdByIso('en'));
  1181. // Get all information for translations
  1182. $this->getTranslationsInformations();
  1183. }
  1184. public function renderKpis()
  1185. {
  1186. $time = time();
  1187. $kpis = array();
  1188. /* The data generation is located in AdminStatsControllerCore */
  1189. $helper = new HelperKpi();
  1190. $helper->id = 'box-languages';
  1191. $helper->icon = 'icon-microphone';
  1192. $helper->color = 'color1';
  1193. $helper->href = $this->context->link->getAdminLink('AdminLanguages');
  1194. $helper->title = $this->l('Enabled Languages', null, null, false);
  1195. if (ConfigurationKPI::get('ENABLED_LANGUAGES') !== false)
  1196. $helper->value = ConfigurationKPI::get('ENABLED_LANGUAGES');
  1197. if (ConfigurationKPI::get('ENABLED_LANGUAGES_EXPIRE') < $time)
  1198. $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=enabled_languages';
  1199. $kpis[] = $helper->generate();
  1200. $helper = new HelperKpi();
  1201. $helper->id = 'box-country';
  1202. $helper->icon = 'icon-home';
  1203. $helper->color = 'color2';
  1204. $helper->title = $this->l('Main Country', null, null, false);
  1205. $helper->subtitle = $this->l('30 Days', null, null, false);
  1206. if (ConfigurationKPI::get('MAIN_COUNTRY', $this->context->language->id) !== false)
  1207. $helper->value = ConfigurationKPI::get('MAIN_COUNTRY', $this->context->language->id);
  1208. if (ConfigurationKPI::get('MAIN_COUNTRY_EXPIRE', $this->context->language->id) < $time)
  1209. $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=main_country';
  1210. $kpis[] = $helper->generate();
  1211. $helper = new HelperKpi();
  1212. $helper->id = 'box-translations';
  1213. $helper->icon = 'icon-list';
  1214. $helper->color = 'color3';
  1215. $helper->title = $this->l('Front Office Translations', null, null, false);
  1216. if (ConfigurationKPI::get('FRONTOFFICE_TRANSLATIONS') !== false)
  1217. $helper->value = ConfigurationKPI::get('FRONTOFFICE_TRANSLATIONS');
  1218. if (ConfigurationKPI::get('FRONTOFFICE_TRANSLATIONS_EXPIRE') < $time)
  1219. $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=frontoffice_translations';
  1220. $kpis[] = $helper->generate();
  1221. $helper = new HelperKpiRow();
  1222. $helper->kpis = $kpis;
  1223. return $helper->generate();
  1224. }
  1225. /**
  1226. * AdminController::postProcess() override
  1227. * @see AdminController::postProcess()
  1228. */
  1229. public function postProcess()
  1230. {
  1231. $this->getInformations();
  1232. /* PrestaShop demo mode */
  1233. if (_PS_MODE_DEMO_)
  1234. {
  1235. $this->errors[] = Tools::displayError('This functionality has been disabled.');
  1236. return;
  1237. }
  1238. /* PrestaShop demo mode */
  1239. try {
  1240. if (Tools::isSubmit('submitCopyLang'))
  1241. {
  1242. if ($this->tabAccess['add'] === '1')
  1243. $this->submitCopyLang();
  1244. else
  1245. $this->errors[] = Tools::displayError('You do not have permission to add this.');
  1246. }
  1247. elseif (Tools::isSubmit('submitExport'))
  1248. {
  1249. if ($this->tabAccess['add'] === '1')
  1250. $this->submitExportLang();
  1251. else
  1252. $this->errors[] = Tools::displayError('You do not have permission to add this.');
  1253. }
  1254. elseif (Tools::isSubmit('submitImport'))
  1255. {
  1256. if ($this->tabAccess['add'] === '1')
  1257. $this->submitImportLang();
  1258. else
  1259. $this->errors[] = Tools::displayError('You do not have permission to add this.');
  1260. }
  1261. elseif (Tools::isSubmit('submitAddLanguage'))
  1262. {
  1263. if ($this->tabAccess['add'] === '1')
  1264. $this->submitAddLang();
  1265. else
  1266. $this->errors[] = Tools::displayError('You do not have permission to add this.');
  1267. }
  1268. elseif (Tools::isSubmit('submitTranslationsPdf'))
  1269. {
  1270. if ($this->tabAccess['edit'] === '1')
  1271. // Only the PrestaShop team should write the translations into the _PS_TRANSLATIONS_DIR_
  1272. if (!$this->theme_selected)
  1273. $this->writeTranslationFile();
  1274. else
  1275. $this->writeTranslationFile(true);
  1276. else
  1277. $this->errors[] = Tools::displayError('You do not have permission to edit this.');
  1278. }
  1279. elseif (Tools::isSubmit('submitTranslationsBack') || Tools::isSubmit('submitTranslationsErrors') || Tools::isSubmit('submitTranslationsFields') || Tools::isSubmit('submitTranslationsFront'))
  1280. {
  1281. if ($this->tabAccess['edit'] === '1')
  1282. $this->writeTranslationFile();
  1283. else
  1284. $this->errors[] = Tools::displayError('You do not have permission to edit this.');
  1285. }
  1286. elseif (Tools::isSubmit('submitTranslationsMails') || Tools::isSubmit('submitTranslationsMailsAndStay'))
  1287. {
  1288. if ($this->tabAccess['edit'] === '1')
  1289. $this->submitTranslationsMails();
  1290. else
  1291. $this->errors[] = Tools::displayError('You do not have permission to edit this.');
  1292. }
  1293. elseif (Tools::isSubmit('submitTranslationsModules'))
  1294. {
  1295. if ($this->tabAccess['edit'] === '1')
  1296. {
  1297. // Get list of modules
  1298. if ($modules = $this->getListModules())
  1299. {
  1300. // Get files of all modules
  1301. $arr_files = $this->getAllModuleFiles($modules, null, $this->lang_selected->iso_code, true);
  1302. // Find and write all translation modules files
  1303. foreach ($arr_files as $value)
  1304. $this->findAndWriteTranslationsIntoFile($value['file_name'], $value['files'], $value['theme'], $value['module'], $value['dir']);
  1305. // Clear modules cache
  1306. Tools::clearCache();
  1307. // Redirect
  1308. if (Tools::getIsset('submitTranslationsModulesAndStay'))
  1309. $this->redirect(true);
  1310. else
  1311. $this->redirect();
  1312. }
  1313. }
  1314. else
  1315. $this->errors[] = Tools::displayError('You do not have permission to edit this.');
  1316. }
  1317. } catch (PrestaShopException $e) {
  1318. $this->errors[] = $e->getMessage();
  1319. }
  1320. }
  1321. /**
  1322. * This method redirect in the translation main page or in the translation page
  1323. *
  1324. * @param bool $save_and_stay : true if the user has clicked on the button "save and stay"
  1325. * @param bool $conf : id of confirmation message
  1326. */
  1327. protected function redirect($save_and_stay = false, $conf = false)
  1328. {
  1329. $conf = !$conf ? 4 : $conf;
  1330. $url_base = self::$currentIndex.'&token='.$this->token.'&conf='.$conf;
  1331. if ($save_and_stay)
  1332. Tools::redirectAdmin($url_base.'&lang='.$this->lang_selected->iso_code.'&type='.$this->type_selected.'&theme='.$this->theme_selected);
  1333. else
  1334. Tools::redirectAdmin($url_base);
  1335. }
  1336. protected function getMailPattern()
  1337. {
  1338. // Let the indentation like it.
  1339. return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/1999/REC-html401-19991224/strict.dtd">
  1340. <html>
  1341. <head>
  1342. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  1343. <title>#title</title>
  1344. </head>
  1345. <body>
  1346. #content
  1347. </body>
  1348. </html>';
  1349. }
  1350. /**
  1351. * This method is used to write translation for mails.
  1352. * This writes subject translation files
  1353. * (in root/mails/lang_choosen/lang.php or root/_PS_THEMES_DIR_/mails/lang_choosen/lang.php)
  1354. * and mails files.
  1355. */
  1356. protected function submitTranslationsMails()
  1357. {
  1358. $arr_mail_content = array();
  1359. $arr_mail_path = array();
  1360. if (Tools::getValue('core_mail'))
  1361. {
  1362. $arr_mail_content['core_mail'] = Tools::getValue('core_mail');
  1363. // Get path of directory for find a good path of translation file
  1364. if (!$this->theme_selected)
  1365. $arr_mail_path['core_mail'] = $this->translations_informations[$this->type_selected]['dir'];
  1366. else
  1367. $arr_mail_path['core_mail'] = $this->translations_informations[$this->type_selected]['override']['dir'];
  1368. }
  1369. if (Tools::getValue('module_mail'))
  1370. {
  1371. $arr_mail_content['module_mail'] = Tools::getValue('module_mail');
  1372. // Get path of directory for find a good path of translation file
  1373. if (!$this->theme_selected)
  1374. $arr_mail_path['module_mail'] = $this->translations_informations['modules']['dir'].'{module}/mails/'.$this->lang_selected->iso_code.'/';
  1375. else
  1376. $arr_mail_path['module_mail'] = $this->translations_informations['modules']['override']['dir'].'{module}/mails/'.$this->lang_selected->iso_code.'/';
  1377. }
  1378. // Save each mail content
  1379. foreach ($arr_mail_content as $group_name => $all_content)
  1380. {
  1381. foreach ($all_content as $type_content => $mails)
  1382. {
  1383. foreach ($mails as $mail_name => $content)
  1384. {
  1385. $module_name = false;
  1386. $module_name_pipe_pos = stripos($mail_name, '|');
  1387. if ($module_name_pipe_pos)
  1388. {
  1389. $module_name = substr($mail_name, 0, $module_name_pipe_pos);
  1390. if (!Validate::isModuleName($module_name))
  1391. throw new PrestaShopException(sprintf(Tools::displayError('Invalid module name "%s"'), Tools::safeOutput($module_name)));
  1392. $mail_name = substr($mail_name, $module_name_pipe_pos + 1);
  1393. if (!Validate::isTplName($mail_name))
  1394. throw new PrestaShopException(sprintf(Tools::displayError('Invalid mail name "%s"'), Tools::safeOutput($mail_name)));
  1395. }
  1396. if ($type_content == 'html')
  1397. {
  1398. $content = Tools::htmlentitiesUTF8($content);
  1399. $content = htmlspecialchars_decode($content);
  1400. // replace correct end of line
  1401. $content = str_replace("\r\n", PHP_EOL, $content);
  1402. $title = '';
  1403. if (Tools::getValue('title_'.$group_name.'_'.$mail_name))
  1404. $title = Tools::getValue('title_'.$group_name.'_'.$mail_name);
  1405. $string_mail = $this->getMailPattern();
  1406. $content = str_replace(array('#title', '#content'), array($title, $content), $string_mail);
  1407. // Magic Quotes shall... not.. PASS!
  1408. if (_PS_MAGIC_QUOTES_GPC_)
  1409. $content = stripslashes($content);
  1410. }
  1411. if (Validate::isCleanHTML($content))
  1412. {
  1413. $path = $arr_mail_path[$group_name];
  1414. if ($module_name)
  1415. $path = str_replace('{module}', $module_name, $path);
  1416. if (!file_exists($path) && !mkdir($path, 0777, true))
  1417. throw new PrestaShopException(sprintf(Tools::displayError('Directory "%s" cannot be created'), dirname($path)));
  1418. file_put_contents($path.$mail_name.'.'.$type_content, $content);
  1419. }
  1420. else
  1421. throw new PrestaShopException(Tools::displayError('Your HTML email templates cannot contain JavaScript code.'));
  1422. }
  1423. }
  1424. }
  1425. // Update subjects
  1426. $array_subjects = array();
  1427. if (($subjects = Tools::getValue('subject')) && is_array($subjects))
  1428. {
  1429. $array_subjects['core_and_modules'] = array('translations' => array(), 'path' => $arr_mail_path['core_mail'].'lang.php');
  1430. foreach ($subjects as $subject_translation)
  1431. $array_subjects['core_and_modules']['translations'] = array_merge($array_subjects['core_and_modules']['translations'], $subject_translation);
  1432. }
  1433. if (!empty($array_subjects))
  1434. foreach ($array_subjects as $infos)
  1435. $this->writeSubjectTranslationFile($infos['translations'], $infos['path']);
  1436. if (Tools::isSubmit('submitTranslationsMailsAndStay'))
  1437. $this->redirect(true);
  1438. else
  1439. $this->redirect();
  1440. }
  1441. /**
  1442. * Include file $dir/$file and return the var $var declared in it.
  1443. * This create the file if not exists
  1444. *
  1445. * return array : translations
  1446. */
  1447. public function fileExists()
  1448. {
  1449. $var = $this->translations_informations[$this->type_selected]['var'];
  1450. $dir = $this->translations_informations[$this->type_selected]['dir'];
  1451. $file = $this->translations_informations[$this->type_selected]['file'];
  1452. $$var = array();
  1453. if (!Tools::file_exists_cache($dir))
  1454. if (!mkdir($dir, 0700))
  1455. throw new PrestaShopException('Directory '.$dir.' cannot be created.');
  1456. if (!Tools::file_exists_cache($dir.DIRECTORY_SEPARATOR.$file))
  1457. if (!file_put_contents($dir.'/'.$file, "<?php\n\nglobal \$".$var.";\n\$".$var." = array();\n\n?>"))
  1458. throw new PrestaShopException('File "'.$file.'" doesn\'t exists and cannot be created in '.$dir);
  1459. if (!is_writable($dir.DIRECTORY_SEPARATOR.$file))
  1460. $this->displayWarning(Tools::displayError('This file must be writable:').' '.$dir.'/'.$file);
  1461. include($dir.DIRECTORY_SEPARATOR.$file);
  1462. return $$var;
  1463. }
  1464. public function displayToggleButton($closed = false)
  1465. {
  1466. $str_output = '
  1467. <script type="text/javascript">';
  1468. if (Tools::getValue('type') == 'mails')
  1469. $str_output .= '$(document).ready(function(){
  1470. toggleDiv(\''.$this->type_selected.'_div\'); toggleButtonValue(this.id, openAll, closeAll);
  1471. });';
  1472. $str_output .= '
  1473. var openAll = \''.html_entity_decode($this->l('Expand all fieldsets'), ENT_NOQUOTES, 'UTF-8').'\';
  1474. var closeAll = \''.html_entity_decode($this->l('Close all fieldsets'), ENT_NOQUOTES, 'UTF-8').'\';
  1475. </script>
  1476. <button type="button" class="btn btn-default" id="buttonall" data-status="open" onclick="toggleDiv(\''.$this->type_selected.'_div\', $(this).data(\'status\')); toggleButtonValue(this.id, openAll, closeAll);"><i class="process-icon-compress"></i> <span>'.$this->l('Close all fieldsets').'</span></button>';
  1477. return $str_output;
  1478. }
  1479. public function displayLimitPostWarning($count)
  1480. {
  1481. $return = array();
  1482. 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))
  1483. {
  1484. $return['error_type'] = 'suhosin';
  1485. $return['post.max_vars'] = ini_get('suhosin.post.max_vars');
  1486. $return['request.max_vars'] = ini_get('suhosin.request.max_vars');
  1487. $return['needed_limit'] = $count + 100;
  1488. }
  1489. elseif (ini_get('max_input_vars') && ini_get('max_input_vars') < $count)
  1490. {
  1491. $return['error_type'] = 'conf';
  1492. $return['max_input_vars'] = ini_get('max_input_vars');
  1493. $return['needed_limit'] = $count + 100;
  1494. }
  1495. return $return;
  1496. }
  1497. /**
  1498. * Find sentence which use %d, %s, %%, %1$d, %1$s...
  1499. *
  1500. * @param $key : english sentence
  1501. * @return array|bool return list of matches
  1502. */
  1503. public function checkIfKeyUseSprintf($key)
  1504. {
  1505. if (preg_match_all('#(?:%%|%(?:[0-9]+\$)?[+-]?(?:[ 0]|\'.)?-?[0-9]*(?:\.[0-9]+)?[bcdeufFosxX])#', $key, $matches))
  1506. return implode(', ', $matches[0]);
  1507. return false;
  1508. }
  1509. /**
  1510. * This method generate the form for front translations
  1511. */
  1512. public function initFormFront()
  1513. {
  1514. if (!$this->theme_exists(Tools::getValue('theme')))
  1515. {
  1516. $this->errors[] = sprintf(Tools::displayError('Invalid theme "%s"'), Tools::getValue('theme'));
  1517. return;
  1518. }
  1519. $missing_translations_front = array();
  1520. $name_var = $this->translations_informations[$this->type_selected]['var'];
  1521. $GLOBALS[$name_var] = $this->fileExists();
  1522. /* List templates to parse */
  1523. $files_by_directory = $this->getFileToParseByTypeTranslation();
  1524. $count = 0;
  1525. $tabs_array = array();
  1526. foreach ($files_by_directory['tpl'] as $dir => $files)
  1527. {
  1528. $prefix = '';
  1529. if ($dir == _PS_THEME_OVERRIDE_DIR_)
  1530. $prefix = 'override_';
  1531. foreach ($files as $file)
  1532. {
  1533. if (preg_match('/^(.*).tpl$/', $file) && (Tools::file_exists_cache($file_path = $dir.$file)))
  1534. {
  1535. $prefix_key = $prefix.substr(basename($file), 0, -4);
  1536. $new_lang = array();
  1537. // Get content for this file
  1538. $content = file_get_contents($file_path);
  1539. // Parse this content
  1540. $matches = $this->userParseFile($content, $this->type_selected);
  1541. /* Get string translation */
  1542. foreach ($matches as $key)
  1543. {
  1544. if (empty($key))
  1545. {
  1546. $this->errors[] = sprintf($this->l('Empty string found, please edit: "%s"'), $file_path);
  1547. $new_lang[$key] = '';
  1548. }
  1549. else
  1550. {
  1551. // Caution ! front has underscore between prefix key and md5, back has not
  1552. if (isset($GLOBALS[$name_var][$prefix_key.'_'.md5($key)]))
  1553. $new_lang[$key]['trad'] = stripslashes(html_entity_decode($GLOBALS[$name_var][$prefix_key.'_'.md5($key)], ENT_COMPAT, 'UTF-8'));
  1554. else
  1555. {
  1556. if (!isset($new_lang[$key]['trad']))
  1557. {
  1558. $new_lang[$key]['trad'] = '';
  1559. if (!isset($missing_translations_front[$prefix_key]))
  1560. $missing_translations_front[$prefix_key] = 1;
  1561. else
  1562. $missing_translations_front[$prefix_key]++;
  1563. }
  1564. }
  1565. $new_lang[$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  1566. }
  1567. }
  1568. if (isset($tabs_array[$prefix_key]))
  1569. $tabs_array[$prefix_key] = array_merge($tabs_array[$prefix_key], $new_lang);
  1570. else
  1571. $tabs_array[$prefix_key] = $new_lang;
  1572. $count += count($new_lang);
  1573. }
  1574. }
  1575. }
  1576. $this->tpl_view_vars = array_merge($this->tpl_view_vars, array(
  1577. 'missing_translations' => $missing_translations_front,
  1578. 'count' => $count,
  1579. 'cancel_url' => $this->context->link->getAdminLink('AdminTranslations'),
  1580. 'limit_warning' => $this->displayLimitPostWarning($count),
  1581. 'mod_security_warning' => Tools::apacheModExists('mod_security'),
  1582. 'tabsArray' => $tabs_array,
  1583. ));
  1584. $this->initToolbar();
  1585. $this->base_tpl_view = 'translation_form.tpl';
  1586. return parent::renderView();
  1587. }
  1588. /**
  1589. * This method generate the form for back translations
  1590. */
  1591. public function initFormBack()
  1592. {
  1593. $name_var = $this->translations_informations[$this->type_selected]['var'];
  1594. $GLOBALS[$name_var] = $this->fileExists();
  1595. $missing_translations_back = array();
  1596. // Get all types of file (PHP, TPL...) and a list of files to parse by folder
  1597. $files_per_directory = $this->getFileToParseByTypeTranslation();
  1598. foreach ($files_per_directory['php'] as $dir => $files)
  1599. foreach ($files as $file)
  1600. // Check if is a PHP file and if the override file exists
  1601. if (preg_match('/^(.*)\.php$/', $file) && Tools::file_exists_cache($file_path = $dir.$file) && !in_array($file, self::$ignore_folder))
  1602. {
  1603. $prefix_key = basename($file);
  1604. // -4 becomes -14 to remove the ending "Controller.php" from the filename
  1605. if (strpos($file, 'Controller.php') !== false)
  1606. $prefix_key = basename(substr($file, 0, -14));
  1607. else if (strpos($file, 'Helper') !== false)
  1608. $prefix_key = 'Helper';
  1609. if ($prefix_key == 'Admin')
  1610. $prefix_key = 'AdminController';
  1611. if ($prefix_key == 'PaymentModule.php')
  1612. $prefix_key = 'PaymentModule';
  1613. // Get content for this file
  1614. $content = file_get_contents($file_path);
  1615. // Parse this content
  1616. $matches = $this->userParseFile($content, $this->type_selected, 'php');
  1617. foreach ($matches as $key)
  1618. {
  1619. // Caution ! front has underscore between prefix key and md5, back has not
  1620. if (isset($GLOBALS[$name_var][$prefix_key.md5($key)]))
  1621. $tabs_array[$prefix_key][$key]['trad'] = stripslashes(html_entity_decode($GLOBALS[$name_var][$prefix_key.md5($key)], ENT_COMPAT, 'UTF-8'));
  1622. else
  1623. {
  1624. if (!isset($tabs_array[$prefix_key][$key]['trad']))
  1625. {
  1626. $tabs_array[$prefix_key][$key]['trad'] = '';
  1627. if (!isset($missing_translations_back[$prefix_key]))
  1628. $missing_translations_back[$prefix_key] = 1;
  1629. else
  1630. $missing_translations_back[$prefix_key]++;
  1631. }
  1632. }
  1633. $tabs_array[$prefix_key][$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  1634. }
  1635. }
  1636. foreach ($files_per_directory['specific'] as $dir => $files)
  1637. foreach ($files as $file)
  1638. if (Tools::file_exists_cache($file_path = $dir.$file) && !in_array($file, self::$ignore_folder))
  1639. {
  1640. $prefix_key = 'index';
  1641. // Get content for this file
  1642. $content = file_get_contents($file_path);
  1643. // Parse this content
  1644. $matches = $this->userParseFile($content, $this->type_selected, 'specific');
  1645. foreach ($matches as $key)
  1646. {
  1647. // Caution ! front has underscore between prefix key and md5, back has not
  1648. if (isset($GLOBALS[$name_var][$prefix_key.md5($key)]))
  1649. $tabs_array[$prefix_key][$key]['trad'] = stripslashes(html_entity_decode($GLOBALS[$name_var][$prefix_key.md5($key)], ENT_COMPAT, 'UTF-8'));
  1650. else
  1651. {
  1652. if (!isset($tabs_array[$prefix_key][$key]['trad']))
  1653. {
  1654. $tabs_array[$prefix_key][$key]['trad'] = '';
  1655. if (!isset($missing_translations_back[$prefix_key]))
  1656. $missing_translations_back[$prefix_key] = 1;
  1657. else
  1658. $missing_translations_back[$prefix_key]++;
  1659. }
  1660. }
  1661. $tabs_array[$prefix_key][$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  1662. }
  1663. }
  1664. foreach ($files_per_directory['tpl'] as $dir => $files)
  1665. foreach ($files as $file)
  1666. if (preg_match('/^(.*).tpl$/', $file) && Tools::file_exists_cache($file_path = $dir.$file))
  1667. {
  1668. // get controller name instead of file name
  1669. $prefix_key = Tools::toCamelCase(str_replace(_PS_ADMIN_DIR_.DIRECTORY_SEPARATOR.'themes', '', $file_path), true);
  1670. $pos = strrpos($prefix_key, DIRECTORY_SEPARATOR);
  1671. $tmp = substr($prefix_key, 0, $pos);
  1672. if (preg_match('#controllers#', $tmp))
  1673. {
  1674. $parent_class = explode(DIRECTORY_SEPARATOR, str_replace('/', DIRECTORY_SEPARATOR, $tmp));
  1675. $override = array_search('override', $parent_class);
  1676. if ($override !== false)
  1677. // case override/controllers/admin/templates/controller_name
  1678. $prefix_key = 'Admin'.ucfirst($parent_class[$override + 4]);
  1679. else
  1680. {
  1681. // case admin_name/themes/theme_name/template/controllers/controller_name
  1682. $key = array_search('controllers', $parent_class);
  1683. $prefix_key = 'Admin'.ucfirst($parent_class[$key + 1]);
  1684. }
  1685. }
  1686. else
  1687. $prefix_key = 'Admin'.ucfirst(substr($tmp, strrpos($tmp, DIRECTORY_SEPARATOR) + 1, $pos));
  1688. // Adding list, form, option in Helper Translations
  1689. $list_prefix_key = array('AdminHelpers', 'AdminList', 'AdminView', 'AdminOptions', 'AdminForm',
  1690. 'AdminCalendar', 'AdminTree', 'AdminUploader', 'AdminDataviz', 'AdminKpi', 'AdminModule_list', 'AdminModulesList');
  1691. if (in_array($prefix_key, $list_prefix_key))
  1692. $prefix_key = 'Helper';
  1693. // Adding the folder backup/download/ in AdminBackup Translations
  1694. if ($prefix_key == 'AdminDownload')
  1695. $prefix_key = 'AdminBackup';
  1696. // use the prefix "AdminController" (like old php files 'header', 'footer.inc', 'index', 'login', 'password', 'functions'
  1697. if ($prefix_key == 'Admin' || $prefix_key == 'AdminTemplate')
  1698. $prefix_key = 'AdminController';
  1699. $new_lang = array();
  1700. // Get content for this file
  1701. $content = file_get_contents($file_path);
  1702. // Parse this content
  1703. $matches = $this->userParseFile($content, $this->type_selected, 'tpl');
  1704. /* Get string translation for each tpl file */
  1705. foreach ($matches as $english_string)
  1706. {
  1707. if (empty($english_string))
  1708. {
  1709. $this->errors[] = sprintf($this->l('There\'s an error in template, an empty string has been found. Please edit: "%s"'), $file_path);
  1710. $new_lang[$english_string] = '';
  1711. }
  1712. else
  1713. {
  1714. $trans_key = $prefix_key.md5($english_string);
  1715. if (isset($GLOBALS[$name_var][$trans_key]))
  1716. $new_lang[$english_string]['trad'] = html_entity_decode($GLOBALS[$name_var][$trans_key], ENT_COMPAT, 'UTF-8');
  1717. else
  1718. {
  1719. if (!isset($new_lang[$english_string]['trad']))
  1720. {
  1721. $new_lang[$english_string]['trad'] = '';
  1722. if (!isset($missing_translations_back[$prefix_key]))
  1723. $missing_translations_back[$prefix_key] = 1;
  1724. else
  1725. $missing_translations_back[$prefix_key]++;
  1726. }
  1727. }
  1728. $new_lang[$english_string]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  1729. }
  1730. }
  1731. if (isset($tabs_array[$prefix_key]))
  1732. $tabs_array[$prefix_key] = array_merge($tabs_array[$prefix_key], $new_lang);
  1733. else
  1734. $tabs_array[$prefix_key] = $new_lang;
  1735. }
  1736. // count will contain the number of expressions of the page
  1737. $count = 0;
  1738. foreach ($tabs_array as $array)
  1739. $count += count($array);
  1740. $this->tpl_view_vars = array_merge($this->tpl_view_vars, array(
  1741. 'count' => $count,
  1742. 'cancel_url' => $this->context->link->getAdminLink('AdminTranslations'),
  1743. 'limit_warning' => $this->displayLimitPostWarning($count),
  1744. 'mod_security_warning' => Tools::apacheModExists('mod_security'),
  1745. 'tabsArray' => $tabs_array,
  1746. 'missing_translations' => $missing_translations_back
  1747. ));
  1748. $this->initToolbar();
  1749. $this->base_tpl_view = 'translation_form.tpl';
  1750. return parent::renderView();
  1751. }
  1752. /**
  1753. * Check if directory and file exist and return an list of modules
  1754. *
  1755. * @return array : list of modules
  1756. */
  1757. public function getListModules()
  1758. {
  1759. if (!Tools::file_exists_cache($this->translations_informations['modules']['dir']))
  1760. throw new PrestaShopException(Tools::displayError('Fatal error: The module directory does not exist.').'('.$this->translations_informations['modules']['dir'].')');
  1761. if (!is_writable($this->translations_informations['modules']['dir']))
  1762. throw new PrestaShopException(Tools::displayError('The module directory must be writable.'));
  1763. $modules = array();
  1764. // Get all module which are installed for to have a minimum of POST
  1765. $modules = Module::getModulesInstalled();
  1766. foreach ($modules as &$module)
  1767. $module = $module['name'];
  1768. return $modules;
  1769. }
  1770. /**
  1771. * This method generate the form for errors translations
  1772. */
  1773. public function initFormErrors()
  1774. {
  1775. $name_var = $this->translations_informations[$this->type_selected]['var'];
  1776. $GLOBALS[$name_var] = $this->fileExists();
  1777. $count_empty = array();
  1778. /* List files to parse */
  1779. $string_to_translate = array();
  1780. $file_by_directory = $this->getFileToParseByTypeTranslation();
  1781. if ($modules = $this->getListModules())
  1782. {
  1783. foreach ($modules as $module)
  1784. if (is_dir(_PS_MODULE_DIR_.$module) && !in_array($module, self::$ignore_folder))
  1785. $file_by_directory['php'] = array_merge($file_by_directory['php'], $this->listFiles(_PS_MODULE_DIR_.$module.'/', array(), 'php'));
  1786. }
  1787. foreach ($file_by_directory['php'] as $dir => $files)
  1788. foreach ($files as $file)
  1789. if (preg_match('/\.php$/', $file) && Tools::file_exists_cache($file_path = $dir.$file) && !in_array($file, self::$ignore_folder))
  1790. {
  1791. if (!filesize($file_path))
  1792. continue;
  1793. // Get content for this file
  1794. $content = file_get_contents($file_path);
  1795. // Parse this content
  1796. $matches = $this->userParseFile($content, $this->type_selected);
  1797. foreach ($matches as $key)
  1798. {
  1799. if (array_key_exists(md5($key), $GLOBALS[$name_var]))
  1800. $string_to_translate[$key]['trad'] = html_entity_decode($GLOBALS[$name_var][md5($key)], ENT_COMPAT, 'UTF-8');
  1801. else
  1802. {
  1803. $string_to_translate[$key]['trad'] = '';
  1804. if (!isset($count_empty[$key]))
  1805. $count_empty[$key] = 1;
  1806. }
  1807. $string_to_translate[$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  1808. }
  1809. }
  1810. $this->tpl_view_vars = array_merge($this->tpl_view_vars, array(
  1811. 'count' => count($string_to_translate),
  1812. 'cancel_url' => $this->context->link->getAdminLink('AdminTranslations'),
  1813. 'limit_warning' => $this->displayLimitPostWarning(count($string_to_translate)),
  1814. 'mod_security_warning' => Tools::apacheModExists('mod_security'),
  1815. 'errorsArray' => $string_to_translate,
  1816. 'missing_translations' => $count_empty
  1817. ));
  1818. $this->initToolbar();
  1819. $this->base_tpl_view = 'translation_errors.tpl';
  1820. return parent::renderView();
  1821. }
  1822. /**
  1823. * This method generate the form for fields translations
  1824. */
  1825. public function initFormFields()
  1826. {
  1827. $name_var = $this->translations_informations[$this->type_selected]['var'];
  1828. $GLOBALS[$name_var] = $this->fileExists();
  1829. $missing_translations_fields = array();
  1830. $class_array = array();
  1831. $tabs_array = array();
  1832. $count = 0;
  1833. $files_by_directory = $this->getFileToParseByTypeTranslation();
  1834. foreach ($files_by_directory['php'] as $dir => $files)
  1835. foreach ($files as $file)
  1836. {
  1837. $exclude_files = array('index.php', 'PrestaShopAutoload.php', 'StockManagerInterface.php',
  1838. 'TaxManagerInterface.php', 'WebserviceOutputInterface.php', 'WebserviceSpecificManagementInterface.php');
  1839. if (!preg_match('/\.php$/', $file) || in_array($file, $exclude_files))
  1840. continue;
  1841. $class_name = substr($file, 0, -4);
  1842. if (!class_exists($class_name, false) && !class_exists($class_name.'Core', false))
  1843. PrestaShopAutoload::getInstance()->load($class_name);
  1844. if (!is_subclass_of($class_name.'Core', 'ObjectModel'))
  1845. continue;
  1846. $class_array[$class_name] = call_user_func(array($class_name, 'getValidationRules'), $class_name);
  1847. }
  1848. foreach ($class_array as $prefix_key => $rules)
  1849. {
  1850. if (isset($rules['validate']))
  1851. foreach ($rules['validate'] as $key => $value)
  1852. {
  1853. if (isset($GLOBALS[$name_var][$prefix_key.'_'.md5($key)]))
  1854. {
  1855. $tabs_array[$prefix_key][$key]['trad'] = html_entity_decode($GLOBALS[$name_var][$prefix_key.'_'.md5($key)], ENT_COMPAT, 'UTF-8');
  1856. $count++;
  1857. }
  1858. else
  1859. {
  1860. if (!isset($tabs_array[$prefix_key][$key]['trad']))
  1861. {
  1862. $tabs_array[$prefix_key][$key]['trad'] = '';
  1863. if (!isset($missing_translations_fields[$prefix_key]))
  1864. $missing_translations_fields[$prefix_key] = 1;
  1865. else
  1866. $missing_translations_fields[$prefix_key]++;
  1867. $count++;
  1868. }
  1869. }
  1870. }
  1871. if (isset($rules['validateLang']))
  1872. foreach ($rules['validateLang'] as $key => $value)
  1873. {
  1874. if (isset($GLOBALS[$name_var][$prefix_key.'_'.md5($key)]))
  1875. {
  1876. $tabs_array[$prefix_key][$key]['trad'] = '';
  1877. if (array_key_exists($prefix_key.'_'.md5(addslashes($key)), $GLOBALS[$name_var]))
  1878. $tabs_array[$prefix_key][$key]['trad'] = html_entity_decode($GLOBALS[$name_var][$prefix_key.'_'.md5(addslashes($key))], ENT_COMPAT, 'UTF-8');
  1879. $count++;
  1880. }
  1881. else
  1882. {
  1883. if (!isset($tabs_array[$prefix_key][$key]['trad']))
  1884. {
  1885. $tabs_array[$prefix_key][$key]['trad'] = '';
  1886. if (!isset($missing_translations_fields[$prefix_key]))
  1887. $missing_translations_fields[$prefix_key] = 1;
  1888. else
  1889. $missing_translations_fields[$prefix_key]++;
  1890. $count++;
  1891. }
  1892. }
  1893. }
  1894. }
  1895. $this->tpl_view_vars = array_merge($this->tpl_view_vars, array(
  1896. 'count' => $count,
  1897. 'limit_warning' => $this->displayLimitPostWarning($count),
  1898. 'mod_security_warning' => Tools::apacheModExists('mod_security'),
  1899. 'tabsArray' => $tabs_array,
  1900. 'cancel_url' => $this->context->link->getAdminLink('AdminTranslations'),
  1901. 'missing_translations' => $missing_translations_fields
  1902. ));
  1903. $this->initToolbar();
  1904. $this->base_tpl_view = 'translation_form.tpl';
  1905. return parent::renderView();
  1906. }
  1907. /**
  1908. * Get each informations for each mails found in the folder $dir.
  1909. *
  1910. * @since 1.4.0.14
  1911. * @param string $dir
  1912. * @param string $group_name
  1913. * @return array : list of mails
  1914. */
  1915. public function getMailFiles($dir, $group_name = 'mail')
  1916. {
  1917. $arr_return = array();
  1918. if (Language::getIdByIso('en'))
  1919. $default_language = 'en';
  1920. else
  1921. $default_language = Language::getIsoById((int)Configuration::get('PS_LANG_DEFAULT'));
  1922. if (!$default_language || !Validate::isLanguageIsoCode($default_language))
  1923. return false;
  1924. // Very usefull to name input and textarea fields
  1925. $arr_return['group_name'] = $group_name;
  1926. $arr_return['empty_values'] = 0;
  1927. $arr_return['total_filled'] = 0;
  1928. $arr_return['directory'] = $dir;
  1929. // Get path for english mail directory
  1930. $dir_en = str_replace('/'.$this->lang_selected->iso_code.'/', '/'.$default_language.'/', $dir);
  1931. if (Tools::file_exists_cache($dir_en))
  1932. {
  1933. // Get all english files to compare with the language to translate
  1934. foreach (scandir($dir_en) as $email_file)
  1935. {
  1936. if (strripos($email_file, '.html') > 0 || strripos($email_file, '.txt') > 0)
  1937. {
  1938. $email_name = substr($email_file, 0, strripos($email_file, '.'));
  1939. $type = substr($email_file, strripos($email_file, '.') + 1);
  1940. if (!isset($arr_return['files'][$email_name]))
  1941. $arr_return['files'][$email_name] = array();
  1942. // $email_file is from scandir ($dir), so we already know that file exists
  1943. $arr_return['files'][$email_name][$type]['en'] = $this->getMailContent($dir_en, $email_file);
  1944. // check if the file exists in the language to translate
  1945. if (Tools::file_exists_cache($dir.'/'.$email_file))
  1946. {
  1947. $arr_return['files'][$email_name][$type][$this->lang_selected->iso_code] = $this->getMailContent($dir, $email_file);
  1948. $this->total_expression++;
  1949. }
  1950. else
  1951. $arr_return['files'][$email_name][$type][$this->lang_selected->iso_code] = '';
  1952. if ($arr_return['files'][$email_name][$type][$this->lang_selected->iso_code] == '')
  1953. $arr_return['empty_values']++;
  1954. else
  1955. $arr_return['total_filled']++;
  1956. }
  1957. }
  1958. }
  1959. else
  1960. $this->warnings[] = sprintf(Tools::displayError('A mail directory exists for the "%1$s" language, but not for the default language in %2$s'),
  1961. $this->lang_selected->iso_code, str_replace(_PS_ROOT_DIR_, '', $dir));
  1962. return $arr_return;
  1963. }
  1964. /**
  1965. * Get content of the mail file.
  1966. *
  1967. * @since 1.4.0.14
  1968. * @param string $dir
  1969. * @param string $file
  1970. * @return array : content of file
  1971. */
  1972. protected function getMailContent($dir, $file)
  1973. {
  1974. $content = file_get_contents($dir.'/'.$file);
  1975. if (Tools::strlen($content) === 0)
  1976. $content = '';
  1977. return $content;
  1978. }
  1979. /**
  1980. * Display mails in html format.
  1981. * This was create for factorize the html displaying
  1982. *
  1983. * @since 1.4.0.14
  1984. * @param array $mails
  1985. * @param array $all_subject_mail
  1986. * @param Language $obj_lang
  1987. * @param string $id_html use for set html id attribute for the block
  1988. * @param string $title Set the title for the block
  1989. * @param string|boolean $name_for_module is not false define add a name for disntiguish mails module
  1990. */
  1991. protected function displayMailContent($mails, $all_subject_mail, $obj_lang, $id_html, $title, $name_for_module = false)
  1992. {
  1993. $str_return = '';
  1994. $group_name = 'mail';
  1995. if (array_key_exists('group_name', $mails))
  1996. $group_name = $mails['group_name'];
  1997. if($mails['empty_values'] == 0) {
  1998. $translation_missing_badge_type = 'badge-success';
  1999. } else {
  2000. $translation_missing_badge_type = 'badge-danger';
  2001. }
  2002. $str_return .= '<div class="mails_field">
  2003. <h4>
  2004. <span class="badge">'.((int)$mails['empty_values'] + (int)$mails['total_filled']).' <i class="icon-envelope-o"></i></span>
  2005. <a href="javascript:void(0);" onclick="$(\'#'.$id_html.'\').slideToggle();">'.$title.'</a>
  2006. <span class="pull-right badge '.$translation_missing_badge_type.'">'.$mails['empty_values'].' '.$this->l('missing translation(s)').'</span>
  2007. </h4>
  2008. <div name="mails_div" id="'.$id_html.'" class="panel-group">';
  2009. if (!empty($mails['files']))
  2010. {
  2011. $topic_already_displayed = array();
  2012. foreach ($mails['files'] as $mail_name => $mail_files)
  2013. {
  2014. $str_return .= '<div class="panel translations-email-panel">';
  2015. $str_return .= '<a href="#email-'.$mail_name.'" class="panel-title" data-toggle="collapse" data-parent="#'.$id_html.'" >'.$mail_name.' <i class="icon-caret-down"></i> </a>';
  2016. $str_return .= '<div id="email-'.$mail_name.'" class="email-collapse panel-collapse collapse">';
  2017. if (array_key_exists('html', $mail_files) || array_key_exists('txt', $mail_files))
  2018. {
  2019. if (array_key_exists($mail_name, $all_subject_mail))
  2020. {
  2021. foreach ($all_subject_mail[$mail_name] as $subject_mail)
  2022. {
  2023. $subject_key = 'subject['.Tools::htmlentitiesUTF8($group_name).']['.Tools::htmlentitiesUTF8($subject_mail).']';
  2024. if (in_array($subject_key, $topic_already_displayed))
  2025. continue;
  2026. $topic_already_displayed[] = $subject_key;
  2027. $value_subject_mail = isset($mails['subject'][$subject_mail]) ? $mails['subject'][$subject_mail] : '';
  2028. $str_return .= '
  2029. <div class="label-subject row">
  2030. <label class="control-label col-lg-3">'.sprintf($this->l('Email subject'));
  2031. if (isset($value_subject_mail['use_sprintf']) && $value_subject_mail['use_sprintf'])
  2032. $str_return .= '<span class="useSpecialSyntax" title="'.$this->l('This expression uses a special syntax:').' '.$value_subject_mail['use_sprintf'].'">
  2033. <i class="icon-exclamation-triangle"></i>
  2034. </span>';
  2035. $str_return .= '</label><div class="col-lg-9">';
  2036. if (isset($value_subject_mail['trad']) && $value_subject_mail['trad'])
  2037. $str_return .= '<input class="form-control" type="text" name="subject['.Tools::htmlentitiesUTF8($group_name).']['.Tools::htmlentitiesUTF8($subject_mail).']" value="'.$value_subject_mail['trad'].'" />';
  2038. else
  2039. $str_return .= '<input class="form-control" type="text" name="subject['.Tools::htmlentitiesUTF8($group_name).']['.Tools::htmlentitiesUTF8($subject_mail).']" value="" />';
  2040. $str_return .= '<p class="help-block">'.$subject_mail.'</p>';
  2041. $str_return .= '</div></div>';
  2042. }
  2043. }
  2044. else
  2045. {
  2046. $str_return .= '
  2047. <hr><div class="alert alert-info">'
  2048. .sprintf($this->l('No Subject was found for %s in the database.'), $mail_name)
  2049. .'</div>';
  2050. }
  2051. // tab menu
  2052. $str_return .= '<hr><ul class="nav nav-pills">
  2053. <li class="active"><a href="#'.$mail_name.'-html" data-toggle="tab">'.$this->l('View HTML version').'</a></li>
  2054. <li><a href="#'.$mail_name.'-editor" data-toggle="tab">'.$this->l('Edit HTML version').'</a></li>
  2055. <li><a href="#'.$mail_name.'-text" data-toggle="tab">'.$this->l('View/Edit TXT version').'</a></li>
  2056. </ul>';
  2057. // tab-content
  2058. $str_return .= '<div class="tab-content">';
  2059. if (array_key_exists('html', $mail_files))
  2060. {
  2061. $str_return .= '<div class="tab-pane active" id="'.$mail_name.'-html">';
  2062. $base_uri = str_replace(_PS_ROOT_DIR_, __PS_BASE_URI__, $mails['directory']);
  2063. $base_uri = str_replace('//', '/', $base_uri);
  2064. $url_mail = $base_uri.$mail_name.'.html';
  2065. $str_return .= $this->displayMailBlockHtml($mail_files['html'], $obj_lang->iso_code, $url_mail, $mail_name, $group_name, $name_for_module);
  2066. $str_return .= '</div>';
  2067. }
  2068. if (array_key_exists('txt', $mail_files))
  2069. {
  2070. $str_return .= '<div class="tab-pane" id="'.$mail_name.'-text">';
  2071. $str_return .= $this->displayMailBlockTxt($mail_files['txt'], $obj_lang->iso_code, $mail_name, $group_name, $name_for_module);
  2072. $str_return .= '</div>';
  2073. }
  2074. $str_return .= '<div class="tab-pane" id="'.$mail_name.'-editor">';
  2075. if (isset($mail_files['html']))
  2076. $str_return .= $this->displayMailEditor($mail_files['html'], $obj_lang->iso_code, $url_mail, $mail_name, $group_name, $name_for_module);
  2077. $str_return .= '</div>';
  2078. $str_return .= '</div>';
  2079. $str_return .= '</div><!--end .panel-collapse -->';
  2080. $str_return .= '</div><!--end .panel -->';
  2081. }
  2082. }
  2083. }
  2084. else
  2085. $str_return .= '<p class="error">
  2086. '.$this->l('There was a problem getting the mail files.').'<br>
  2087. '.sprintf($this->l('English language files must exist in %s folder'), '<em>'.preg_replace('@/[a-z]{2}(/?)$@', '/en$1', $mails['directory']).'</em>').'
  2088. </p>';
  2089. $str_return .= '</div><!-- #'.$id_html.' --></div><!-- end .mails_field -->';
  2090. return $str_return;
  2091. }
  2092. /**
  2093. * Just build the html structure for display txt mails
  2094. *
  2095. * @since 1.4.0.14
  2096. * @param array $content with english and language needed contents
  2097. * @param string $lang iso code of the needed language
  2098. * @param string $mail_name name of the file to translate (same for txt and html files)
  2099. * @param string $group_name group name allow to distinguish each block of mail.
  2100. * @param string|boolean $name_for_module is not false define add a name for disntiguish mails module
  2101. */
  2102. protected function displayMailBlockTxt($content, $lang, $mail_name, $group_name, $name_for_module = false)
  2103. {
  2104. return '<div class="block-mail" >
  2105. <div class="mail-form">
  2106. <div><textarea class="rte noEditor" name="'.$group_name.'[txt]['.($name_for_module ? $name_for_module.'|' : '' ).$mail_name.']">'.Tools::htmlentitiesUTF8(stripslashes(strip_tags($content[$lang]))).'</textarea></div>
  2107. </div>
  2108. </div>';
  2109. }
  2110. /**
  2111. * Just build the html structure for display html mails.
  2112. *
  2113. * @since 1.4.0.14
  2114. * @param array $content with english and language needed contents
  2115. * @param string $lang iso code of the needed language
  2116. * @param string $url for the html page and displaying an outline
  2117. * @param string $mail_name name of the file to translate (same for txt and html files)
  2118. * @param string $group_name group name allow to distinguish each block of mail.
  2119. * @param string|boolean $name_for_module is not false define add a name for disntiguish mails module
  2120. */
  2121. protected function displayMailBlockHtml($content, $lang, $url, $mail_name, $group_name, $name_for_module = false)
  2122. {
  2123. $title = array();
  2124. $this->cleanMailContent($content, $lang, $title);
  2125. $name_for_module = $name_for_module ? $name_for_module.'|' : '';
  2126. return '<div class="block-mail" >
  2127. <div class="mail-form">
  2128. <div class="form-group">
  2129. <label class="control-label col-lg-3">'.$this->l('HTML "title" tag').'</label>
  2130. <div class="col-lg-9">
  2131. <input class="form-control" type="text" name="title_'.$group_name.'_'.$mail_name.'" value="'.(isset($title[$lang]) ? $title[$lang] : '').'" />
  2132. <p class="help-block">'.(isset($title['en']) ? $title['en'] : '').'</p>
  2133. </div>
  2134. </div>
  2135. <div class="thumbnail email-html-frame" data-email-src="'.$url.'?'.(rand(0, 1000000000)).'"></div>
  2136. </div>
  2137. </div>';
  2138. }
  2139. protected function displayMailEditor($content, $lang, $url, $mail_name, $group_name, $name_for_module = false)
  2140. {
  2141. $title = array();
  2142. $this->cleanMailContent($content, $lang, $title);
  2143. $name_for_module = $name_for_module ? $name_for_module.'|' : '';
  2144. return '<textarea class="rte-mail rte-mail-'.$mail_name.' form-control" data-rte="'.$mail_name.'" name="'.$group_name.'[html]['.$name_for_module.$mail_name.']">'.$content[$lang].'</textarea>';
  2145. }
  2146. protected function cleanMailContent(&$content, $lang, &$title)
  2147. {
  2148. // Because TinyMCE don't work correctly with <DOCTYPE>, <html> and <body> tags
  2149. if (stripos($content[$lang], '<body'))
  2150. {
  2151. $array_lang = $lang != 'en' ? array('en', $lang) : array($lang);
  2152. foreach ($array_lang as $language)
  2153. {
  2154. $title[$language] = substr($content[$language], 0, stripos($content[$language], '<body'));
  2155. preg_match('#<title>([^<]+)</title>#Ui', $title[$language], $matches);
  2156. $title[$language] = empty($matches[1])?'':$matches[1];
  2157. // The 2 lines below allow to exlude <body> tag from the content.
  2158. // This allow to exclude body tag even if attributs are setted.
  2159. $content[$language] = substr($content[$language], stripos($content[$language], '<body') + 5);
  2160. $content[$language] = substr($content[$language], stripos($content[$language], '>') + 1);
  2161. $content[$language] = substr($content[$language], 0, stripos($content[$language], '</body>'));
  2162. }
  2163. }
  2164. $content[$lang] = (isset($content[$lang]) ? Tools::htmlentitiesUTF8(stripslashes($content[$lang])) : '');
  2165. }
  2166. /**
  2167. * Check in each module if contains mails folder.
  2168. *
  2169. * @return array of module which has mails
  2170. */
  2171. public function getModulesHasMails($with_module_name = false)
  2172. {
  2173. $arr_modules = array();
  2174. foreach (scandir($this->translations_informations['modules']['dir']) as $module_dir)
  2175. {
  2176. if (!in_array($module_dir, self::$ignore_folder))
  2177. {
  2178. $dir = false;
  2179. if ($this->theme_selected && Tools::file_exists_cache($this->translations_informations['modules']['override']['dir'].$module_dir.'/mails/'))
  2180. $dir = $this->translations_informations['modules']['override']['dir'].$module_dir.'/';
  2181. elseif (Tools::file_exists_cache($this->translations_informations['modules']['dir'].$module_dir.'/mails/'))
  2182. $dir = $this->translations_informations['modules']['dir'].$module_dir.'/';
  2183. if ($dir !== false)
  2184. {
  2185. if ($with_module_name)
  2186. $arr_modules[$module_dir] = $dir;
  2187. else
  2188. $arr_modules[$dir] = scandir($dir);
  2189. }
  2190. }
  2191. }
  2192. return $arr_modules;
  2193. }
  2194. protected function getTinyMCEForMails($iso_lang)
  2195. {
  2196. // TinyMCE
  2197. $iso_tiny_mce = (Tools::file_exists_cache(_PS_ROOT_DIR_.'/js/tiny_mce/langs/'.$iso_lang.'.js') ? $iso_lang : 'en');
  2198. $ad = __PS_BASE_URI__.basename(_PS_ADMIN_DIR_);
  2199. //return false;
  2200. return '
  2201. <script type="text/javascript">
  2202. var iso = \''.$iso_tiny_mce.'\' ;
  2203. var pathCSS = \''._THEME_CSS_DIR_.'\' ;
  2204. var ad = \''.$ad.'\' ;
  2205. </script>
  2206. <script type="text/javascript" src="'.__PS_BASE_URI__.'js/tiny_mce/tiny_mce.js"></script>
  2207. <script type="text/javascript" src="'.__PS_BASE_URI__.'js/tinymce.inc.js"></script>';
  2208. }
  2209. /**
  2210. * This method generate the form for mails translations
  2211. */
  2212. public function initFormMails($no_display = false)
  2213. {
  2214. $module_mails = array();
  2215. // get all mail subjects, this method parse each files in Prestashop !!
  2216. $subject_mail = array();
  2217. $modules_has_mails = $this->getModulesHasMails(true);
  2218. $files_by_directiories = $this->getFileToParseByTypeTranslation();
  2219. if (!$this->theme_selected || !@filemtime($this->translations_informations[$this->type_selected]['override']['dir']))
  2220. $this->copyMailFilesForAllLanguages();
  2221. foreach ($files_by_directiories['php'] as $dir => $files)
  2222. foreach ($files as $file)
  2223. // If file exist and is not in ignore_folder, in the next step we check if a folder or mail
  2224. if (Tools::file_exists_cache($dir.$file) && !in_array($file, self::$ignore_folder))
  2225. $subject_mail = $this->getSubjectMail($dir, $file, $subject_mail);
  2226. // Get path of directory for find a good path of translation file
  2227. if ($this->theme_selected && @filemtime($this->translations_informations[$this->type_selected]['override']['dir']))
  2228. $i18n_dir = $this->translations_informations[$this->type_selected]['override']['dir'];
  2229. else
  2230. $i18n_dir = $this->translations_informations[$this->type_selected]['dir'];
  2231. $core_mails = $this->getMailFiles($i18n_dir, 'core_mail');
  2232. $core_mails['subject'] = $this->getSubjectMailContent($i18n_dir);
  2233. foreach ($modules_has_mails as $module_name => $module_path)
  2234. {
  2235. $module_mails[$module_name] = $this->getMailFiles($module_path.'mails/'.$this->lang_selected->iso_code.'/', 'module_mail');
  2236. $module_mails[$module_name]['subject'] = $core_mails['subject'];
  2237. $module_mails[$module_name]['display'] = $this->displayMailContent($module_mails[$module_name], $subject_mail, $this->lang_selected, Tools::strtolower($module_name), $module_name, $module_name);
  2238. }
  2239. if ($no_display)
  2240. {
  2241. $empty = 0;
  2242. $total = 0;
  2243. $total += (int)$core_mails['total_filled'];
  2244. $empty += (int)$core_mails['empty_values'];
  2245. foreach ($module_mails as $mod_infos)
  2246. {
  2247. $total += (int)$mod_infos['total_filled'];
  2248. $empty += (int)$mod_infos['empty_values'];
  2249. }
  2250. return array('total' => $total, 'empty' => $empty);
  2251. }
  2252. $this->tpl_view_vars = array_merge($this->tpl_view_vars, array(
  2253. 'limit_warning' => $this->displayLimitPostWarning($this->total_expression),
  2254. 'mod_security_warning' => Tools::apacheModExists('mod_security'),
  2255. 'tinyMCE' => $this->getTinyMCEForMails($this->lang_selected->iso_code),
  2256. 'mail_content' => $this->displayMailContent($core_mails, $subject_mail, $this->lang_selected, 'core', $this->l('Core emails')),
  2257. 'cancel_url' => $this->context->link->getAdminLink('AdminTranslations'),
  2258. 'module_mails' => $module_mails,
  2259. 'theme_name' => $this->theme_selected
  2260. ));
  2261. $this->initToolbar();
  2262. $this->base_tpl_view = 'translation_mails.tpl';
  2263. return parent::renderView();
  2264. }
  2265. public function copyMailFilesForAllLanguages()
  2266. {
  2267. $current_theme = Tools::safeOutput($this->context->theme->name);
  2268. $languages = Language::getLanguages();
  2269. foreach ($languages as $key => $lang) {
  2270. $dir_to_copy_iso = array();
  2271. $files_to_copy_iso = array();
  2272. $current_iso_code = $lang['iso_code'];
  2273. $dir_to_copy_iso[] = _PS_MAIL_DIR_.$current_iso_code.'/';
  2274. $modules_has_mails = $this->getModulesHasMails(true);
  2275. foreach ($modules_has_mails as $module_name => $module_path)
  2276. {
  2277. if ($pos = strpos($module_path, '/modules'))
  2278. $dir_to_copy_iso[] = _PS_ROOT_DIR_.substr($module_path, $pos).'mails/'.$current_iso_code.'/';
  2279. }
  2280. foreach ($dir_to_copy_iso as $dir)
  2281. foreach (scandir($dir) as $file)
  2282. if (!in_array($file, self::$ignore_folder))
  2283. $files_to_copy_iso[] = array(
  2284. "from" => $dir.$file,
  2285. "to" => str_replace(_PS_ROOT_DIR_, _PS_ROOT_DIR_.'/themes/'.$current_theme, $dir).$file
  2286. );
  2287. foreach ($files_to_copy_iso as $file)
  2288. {
  2289. if (!file_exists($file['to']))
  2290. {
  2291. $content = file_get_contents($file['from']);
  2292. $stack = array();
  2293. $folder = dirname($file['to']);
  2294. while (!is_dir($folder))
  2295. {
  2296. array_push($stack, $folder);
  2297. $folder = dirname($folder);
  2298. }
  2299. while ($folder = array_pop($stack))
  2300. mkdir($folder);
  2301. $success = file_put_contents($file['to'], $content);
  2302. if ($success === false)
  2303. Tools::dieOrLog(sprintf("%s cannot be copied to %s", $file['from'], $file['to']), false);
  2304. }
  2305. }
  2306. }
  2307. return true;
  2308. }
  2309. /**
  2310. * Get list of subjects of mails
  2311. *
  2312. * @param $dir
  2313. * @param $file
  2314. * @param $subject_mail
  2315. * @return array : list of subjects of mails
  2316. */
  2317. protected function getSubjectMail($dir, $file, $subject_mail)
  2318. {
  2319. // If is file and is not in ignore_folder
  2320. if (is_file($dir.'/'.$file) && !in_array($file, self::$ignore_folder) && preg_match('/\.php$/', $file))
  2321. {
  2322. $content = file_get_contents($dir.'/'.$file);
  2323. $content = str_replace("\n", ' ', $content);
  2324. // Subject must match with a template, therefor we first grep the Mail::Send() function then the Mail::l() inside.
  2325. if (preg_match_all('/Mail::Send([^;]*);/si', $content, $tab))
  2326. {
  2327. for ($i = 0; isset($tab[1][$i]); $i++)
  2328. {
  2329. $tab2 = explode(',', $tab[1][$i]);
  2330. if (is_array($tab2) && isset($tab2[1]))
  2331. {
  2332. $template = trim(str_replace('\'', '', $tab2[1]));
  2333. foreach ($tab2 as $tab3)
  2334. if (preg_match('/Mail::l\(\''._PS_TRANS_PATTERN_.'\'\)/Us', $tab3.')', $matches))
  2335. {
  2336. if (!isset($subject_mail[$template]))
  2337. $subject_mail[$template] = array();
  2338. if (!in_array($matches[1], $subject_mail[$template]))
  2339. $subject_mail[$template][] = $matches[1];
  2340. }
  2341. }
  2342. }
  2343. }
  2344. }
  2345. // Or if is colder, we scan colder for check if find in folder and subfolder
  2346. else if (!in_array($file, self::$ignore_folder) && is_dir($dir.'/'.$file))
  2347. foreach( scandir($dir.'/'.$file ) as $temp )
  2348. $subject_mail = $this->getSubjectMail($dir.'/'.$file, $temp, $subject_mail);
  2349. return $subject_mail;
  2350. }
  2351. /**
  2352. * @param $directory : name of directory
  2353. * @return array
  2354. */
  2355. protected function getSubjectMailContent($directory)
  2356. {
  2357. $subject_mail_content = array();
  2358. if (Tools::file_exists_cache($directory.'/lang.php'))
  2359. {
  2360. // we need to include this even if already included (no include once)
  2361. include($directory.'/lang.php');
  2362. foreach ($GLOBALS[$this->translations_informations[$this->type_selected]['var']] as $key => $subject)
  2363. {
  2364. $this->total_expression++;
  2365. $subject = str_replace('\n', ' ', $subject);
  2366. $subject = str_replace("\\'", "\'", $subject);
  2367. $subject_mail_content[$key]['trad'] = htmlentities($subject, ENT_QUOTES, 'UTF-8');
  2368. $subject_mail_content[$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  2369. }
  2370. }
  2371. else
  2372. $this->errors[] = sprintf($this->l('Email subject translation file not found in "%s".'), $directory);
  2373. return $subject_mail_content;
  2374. }
  2375. protected function writeSubjectTranslationFile($sub, $path)
  2376. {
  2377. if (!Tools::file_exists_cache(dirname($path)))
  2378. if (!mkdir(dirname(path), 0700))
  2379. throw new PrestaShopException('Directory '.dirname(path).' cannot be created.');
  2380. if ($fd = @fopen($path, 'w'))
  2381. {
  2382. $tab = 'LANGMAIL';
  2383. fwrite($fd, "<?php\n\nglobal \$_".$tab.";\n\$_".$tab." = array();\n");
  2384. foreach ($sub as $key => $value)
  2385. {
  2386. // Magic Quotes shall... not.. PASS!
  2387. if (_PS_MAGIC_QUOTES_GPC_)
  2388. $value = stripslashes($value);
  2389. fwrite($fd, '$_'.$tab.'[\''.pSQL($key).'\'] = \''.pSQL($value).'\';'."\n");
  2390. }
  2391. fwrite($fd, "\n?>");
  2392. fclose($fd);
  2393. }
  2394. else
  2395. throw new PrestaShopException(sprintf(Tools::displayError('Cannot write language file for email subjects. Path is: %s'), $path));
  2396. }
  2397. /**
  2398. * This get files to translate in module directory.
  2399. * Recursive method allow to get each files for a module no matter his depth.
  2400. *
  2401. * @param string $path directory path to scan
  2402. * @param array $array_files by reference - array which saved files to parse.
  2403. * @param string $module_name module name
  2404. * @param string $lang_file full path of translation file
  2405. * @param boolean $is_default
  2406. */
  2407. protected function recursiveGetModuleFiles($path, &$array_files, $module_name, $lang_file, $is_default = false)
  2408. {
  2409. $files_module = array();
  2410. if (Tools::file_exists_cache($path))
  2411. $files_module = scandir($path);
  2412. $files_for_module = $this->clearModuleFiles($files_module, 'file');
  2413. if (!empty($files_for_module))
  2414. $array_files[] = array(
  2415. 'file_name' => $lang_file,
  2416. 'dir' => $path,
  2417. 'files' => $files_for_module,
  2418. 'module' => $module_name,
  2419. 'is_default' => $is_default,
  2420. 'theme' => $this->theme_selected,
  2421. );
  2422. $dir_module = $this->clearModuleFiles($files_module, 'directory', $path);
  2423. if (!empty($dir_module))
  2424. foreach ($dir_module as $folder)
  2425. $this->recursiveGetModuleFiles($path.$folder.'/', $array_files, $module_name, $lang_file, $is_default);
  2426. }
  2427. /**
  2428. * This method get translation in each translations file.
  2429. * The file depend on $lang param.
  2430. *
  2431. * @param array $modules list of modules
  2432. * @param string $root_dir path where it get each modules
  2433. * @param string $lang iso code of choosen language to translate
  2434. * @param boolean $is_default set it if modules are located in root/prestashop/modules folder
  2435. * This allow to distinguish overrided prestashop theme and original module
  2436. */
  2437. protected function getAllModuleFiles($modules, $root_dir = null, $lang, $is_default = false)
  2438. {
  2439. $array_files = array();
  2440. $initial_root_dir = $root_dir;
  2441. foreach ($modules as $module)
  2442. {
  2443. $root_dir = $initial_root_dir;
  2444. if ($module{0} == '.')
  2445. continue;
  2446. // First we load the default translation file
  2447. if ($root_dir == null)
  2448. {
  2449. $i18n_dir = $this->translations_informations[$this->type_selected]['dir'];
  2450. if (is_dir($i18n_dir.$module))
  2451. $root_dir = $i18n_dir;
  2452. $lang_file = $root_dir.$module.'/translations/'.$lang.'.php';
  2453. if (!Tools::file_exists_cache($root_dir.$module.'/translations/'.$lang.'.php') && Tools::file_exists_cache($root_dir.$module.'/'.$lang.'.php'))
  2454. $lang_file = $root_dir.$module.'/'.$lang.'.php';
  2455. @include($lang_file);
  2456. $this->getModuleTranslations();
  2457. // If a theme is selected, then the destination translation file must be in the theme
  2458. if ($this->theme_selected)
  2459. $lang_file = $this->translations_informations[$this->type_selected]['override']['dir'].$module.'/translations/'.$lang.'.php';
  2460. $this->recursiveGetModuleFiles($root_dir.$module.'/', $array_files, $module, $lang_file, $is_default);
  2461. }
  2462. $root_dir = $initial_root_dir;
  2463. // Then we load the overriden translation file
  2464. if ($this->theme_selected && isset($this->translations_informations[$this->type_selected]['override']))
  2465. {
  2466. $i18n_dir = $this->translations_informations[$this->type_selected]['override']['dir'];
  2467. if (is_dir($i18n_dir.$module))
  2468. $root_dir = $i18n_dir;
  2469. if (Tools::file_exists_cache($root_dir.$module.'/translations/'.$lang.'.php'))
  2470. $lang_file = $root_dir.$module.'/translations/'.$lang.'.php';
  2471. elseif (Tools::file_exists_cache($root_dir.$module.'/'.$lang.'.php'))
  2472. $lang_file = $root_dir.$module.'/'.$lang.'.php';
  2473. @include($lang_file);
  2474. $this->getModuleTranslations();
  2475. $this->recursiveGetModuleFiles($root_dir.$module.'/', $array_files, $module, $lang_file, $is_default);
  2476. }
  2477. }
  2478. return $array_files;
  2479. }
  2480. /**
  2481. * This method generate the form for modules translations
  2482. */
  2483. public function initFormModules()
  2484. {
  2485. // Get list of modules
  2486. $modules = $this->getListModules();
  2487. if (!empty($modules))
  2488. {
  2489. // Get all modules files and include all translation files
  2490. $arr_files = $this->getAllModuleFiles($modules, null, $this->lang_selected->iso_code, true);
  2491. foreach ($arr_files as $value)
  2492. $this->findAndFillTranslations($value['files'], $value['theme'], $value['module'], $value['dir']);
  2493. $this->tpl_view_vars = array_merge($this->tpl_view_vars, array(
  2494. 'default_theme_name' => self::DEFAULT_THEME_NAME,
  2495. 'count' => $this->total_expression,
  2496. 'limit_warning' => $this->displayLimitPostWarning($this->total_expression),
  2497. 'mod_security_warning' => Tools::apacheModExists('mod_security'),
  2498. 'textarea_sized' => AdminTranslationsControllerCore::TEXTAREA_SIZED,
  2499. 'cancel_url' => $this->context->link->getAdminLink('AdminTranslations'),
  2500. 'modules_translations' => isset($this->modules_translations) ? $this->modules_translations : array(),
  2501. 'missing_translations' => $this->missing_translations
  2502. ));
  2503. $this->initToolbar();
  2504. $this->base_tpl_view = 'translation_modules.tpl';
  2505. return parent::renderView();
  2506. }
  2507. }
  2508. /** Parse PDF class
  2509. *
  2510. * @param string $file_path file to parse
  2511. * @param string $file_type type of file
  2512. * @param array $langArray contains expression in the chosen language
  2513. * @param string $tab name to use with the md5 key
  2514. * @param array $tabs_array
  2515. * @return array containing all datas needed for building the translation form
  2516. * @since 1.4.5.0
  2517. */
  2518. protected function parsePdfClass($file_path, $file_type, $lang_array, $tab, $tabs_array, &$count_missing)
  2519. {
  2520. // Get content for this file
  2521. $content = file_get_contents($file_path);
  2522. // Parse this content
  2523. $matches = $this->userParseFile($content, $this->type_selected, $file_type);
  2524. foreach ($matches as $key)
  2525. {
  2526. if (stripslashes(array_key_exists($tab.md5(addslashes($key)), $lang_array)))
  2527. $tabs_array[$tab][$key]['trad'] = html_entity_decode($lang_array[$tab.md5(addslashes($key))], ENT_COMPAT, 'UTF-8');
  2528. else
  2529. {
  2530. $tabs_array[$tab][$key]['trad'] = '';
  2531. if (!isset($count_missing[$tab]))
  2532. $count_missing[$tab] = 1;
  2533. else
  2534. $count_missing[$tab]++;
  2535. }
  2536. $tabs_array[$tab][$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  2537. }
  2538. return $tabs_array;
  2539. }
  2540. /**
  2541. * This method generate the form for PDF translations
  2542. */
  2543. public function initFormPDF()
  2544. {
  2545. $name_var = $this->translations_informations[$this->type_selected]['var'];
  2546. $GLOBALS[$name_var] = array();
  2547. $missing_translations_pdf = array();
  2548. $i18n_dir = $this->translations_informations[$this->type_selected]['dir'];
  2549. $default_i18n_file = $i18n_dir.$this->translations_informations[$this->type_selected]['file'];
  2550. if (!$this->theme_selected)
  2551. $i18n_file = $default_i18n_file;
  2552. else
  2553. {
  2554. $i18n_dir = $this->translations_informations[$this->type_selected]['override']['dir'];
  2555. $i18n_file = $i18n_dir.$this->translations_informations[$this->type_selected]['override']['file'];
  2556. }
  2557. $this->checkDirAndCreate($i18n_file);
  2558. if ((!file_exists($i18n_file) && !is_writable($i18n_dir)) && !is_writable($i18n_file))
  2559. $this->errors[] = sprintf(Tools::displayError('Cannot write into the "%s"'), $i18n_file);
  2560. @include($i18n_file);
  2561. // if the override's translation file is empty load the default file
  2562. if (!isset($GLOBALS[$name_var]) || count($GLOBALS[$name_var]) == 0)
  2563. @include($default_i18n_file);
  2564. $prefix_key = 'PDF';
  2565. $tabs_array = array($prefix_key => array());
  2566. $files_by_directory = $this->getFileToParseByTypeTranslation();
  2567. foreach ($files_by_directory as $type => $directories)
  2568. foreach ($directories as $dir => $files)
  2569. foreach ($files as $file)
  2570. if (!in_array($file, self::$ignore_folder) && Tools::file_exists_cache($file_path = $dir.$file))
  2571. {
  2572. if ($type == 'tpl')
  2573. {
  2574. if (Tools::file_exists_cache($file_path) && is_file($file_path))
  2575. {
  2576. // Get content for this file
  2577. $content = file_get_contents($file_path);
  2578. // Parse this content
  2579. $matches = $this->userParseFile($content, $this->type_selected, 'tpl');
  2580. foreach ($matches as $key)
  2581. {
  2582. if (isset($GLOBALS[$name_var][$prefix_key.md5($key)]))
  2583. $tabs_array[$prefix_key][$key]['trad'] = (html_entity_decode($GLOBALS[$name_var][$prefix_key.md5($key)], ENT_COMPAT, 'UTF-8'));
  2584. else
  2585. {
  2586. if (!isset($tabs_array[$prefix_key][$key]['trad']))
  2587. {
  2588. $tabs_array[$prefix_key][$key]['trad'] = '';
  2589. if (!isset($missing_translations_pdf[$prefix_key]))
  2590. $missing_translations_pdf[$prefix_key] = 1;
  2591. else
  2592. $missing_translations_pdf[$prefix_key]++;
  2593. }
  2594. }
  2595. $tabs_array[$prefix_key][$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key);
  2596. }
  2597. }
  2598. }
  2599. else
  2600. if (Tools::file_exists_cache($file_path))
  2601. $tabs_array = $this->parsePdfClass($file_path, 'php', $GLOBALS[$name_var], $prefix_key, $tabs_array, $missing_translations_pdf);
  2602. }
  2603. $this->tpl_view_vars = array_merge($this->tpl_view_vars, array(
  2604. 'count' => count($tabs_array['PDF']),
  2605. 'limit_warning' => $this->displayLimitPostWarning(count($tabs_array['PDF'])),
  2606. 'mod_security_warning' => Tools::apacheModExists('mod_security'),
  2607. 'tabsArray' => $tabs_array,
  2608. 'cancel_url' => $this->context->link->getAdminLink('AdminTranslations'),
  2609. 'missing_translations' => $missing_translations_pdf
  2610. ));
  2611. $this->initToolbar();
  2612. $this->base_tpl_view = 'translation_form.tpl';
  2613. return parent::renderView();
  2614. }
  2615. /**
  2616. * recursively list files in directory $dir
  2617. */
  2618. public function listFiles($dir, $list = array(), $file_ext = 'tpl')
  2619. {
  2620. $dir = rtrim($dir, '/').DIRECTORY_SEPARATOR;
  2621. $to_parse = scandir($dir);
  2622. // copied (and kind of) adapted from AdminImages.php
  2623. foreach ($to_parse as $file)
  2624. {
  2625. if (!in_array($file, self::$ignore_folder))
  2626. {
  2627. if (preg_match('#'.preg_quote($file_ext, '#').'$#i', $file))
  2628. $list[$dir][] = $file;
  2629. else if (is_dir($dir.$file))
  2630. $list = $this->listFiles($dir.$file, $list, $file_ext);
  2631. }
  2632. }
  2633. return $list;
  2634. }
  2635. protected function theme_exists($theme)
  2636. {
  2637. if (!is_array($this->themes))
  2638. $this->themes = Theme::getThemes();
  2639. $theme_exists = false;
  2640. foreach ($this->themes as $existing_theme)
  2641. if ($existing_theme->directory == $theme)
  2642. return true;
  2643. return false;
  2644. }
  2645. }