* @author PrestaShop SA * @copyright 2022-2024 PhenixSuite * @copyright 2007-2017 PrestaShop SA * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) * PrestaShop is an internationally registered trademark & property of PrestaShop SA */ class AdminTranslationsControllerCore extends AdminController { /** Name of theme by default */ const DEFAULT_THEME_NAME = _PS_DEFAULT_THEME_NAME_; const TEXTAREA_SIZED = 60; /** @var string : Link which list all pack of language */ protected $link_lang_pack = _PS_VERSION_DOMAIN_.'/public/scripts/dl.php?l=getlist'; /** @var int : number of sentence which can be translated */ protected $total_expression = 0; /** @var int : number of sentence which aren't translated */ protected $missing_translations = 0; /** @var array : List of ISO code for all languages */ protected $all_iso_lang = array(); /** @var array */ protected $modules_translations = array(); /** @var array : List of folder which must be ignored */ protected static $ignore_folder = array( '.', '..', '.svn', '.git', '.htaccess', 'index.php' ); /** @var array : List of theme by translation type : FRONT, BACK, ERRORS... */ protected $translations_informations = array(); /** @var array : List of all languages */ protected $languages; /** @var array : List of all themes */ protected $themes; /** @var string : Directory of selected theme */ protected $theme_selected; /** @var string : Name of translations type */ protected $type_selected; /** @var Language object : Language for the selected language */ protected $lang_selected; protected $global_modules = array(); /** @var bool : Is true if number of var exceed the suhosin request or post limit */ protected $post_limit_exceed = false; public function __construct() { $this->bootstrap = true; $this->multishop_context = Shop::CONTEXT_ALL; $this->table = 'translations'; parent::__construct(); } public function setMedia() { parent::setMedia(); $this->addJs(_PS_JS_DIR_.'admin/translations.js'); } /** * AdminController::initContent() override * @see AdminController::initContent() */ public function initContent() { $this->initTabModuleList(); $this->initPageHeaderToolbar(); $this->saveTranslations(); if(!is_null($this->type_selected)) { $method_name = 'initForm'.$this->type_selected; if(method_exists($this, $method_name)) { $this->content = $this->initForm($method_name); } else { $this->errors[] = sprintf( Tools::displayError('"%s" does not exist.'), $this->type_selected ); $this->content = $this->initMain(); } } else { $this->content = $this->initMain(); } $this->context->smarty->assign(array( 'content' => $this->content, 'show_page_header_toolbar' => $this->show_page_header_toolbar, 'page_header_toolbar_title' => $this->page_header_toolbar_title, 'page_header_toolbar_btn' => $this->page_header_toolbar_btn)); } /** * Save translations */ public function saveTranslations() { $this->getInformations(); if(_PS_MODE_DEMO_) { $this->errors[] = Tools::displayError('This functionality has been disabled.'); return; } try { if(Tools::isSubmit('submitTranslationsPdf')) { if($this->tabAccess['edit'] == 1) { // Only PhenixSuite should write the translations // into the _PS_TRANSLATIONS_DIR_ if(!$this->theme_selected) { $this->writeTranslationFile(); } else { $this->writeTranslationFile(true); } } else { $this->errors[] = Tools::displayError('You do not have permission to edit this.'); } } elseif(Tools::isSubmit('submitTranslationsBack') || Tools::isSubmit('submitTranslationsErrors') || Tools::isSubmit('submitTranslationsConfirmations') || Tools::isSubmit('submitTranslationsFields') || Tools::isSubmit('submitTranslationsFront') ) { if($this->tabAccess['edit'] == 1) { $this->writeTranslationFile(); } else { $this->errors[] = Tools::displayError('You do not have permission to edit this.'); } } elseif(Tools::isSubmit('submitTranslationsMails') || Tools::isSubmit('submitTranslationsMailsAndStay') ) { if($this->tabAccess['edit'] == 1) { $this->submitTranslationsMails(); } else { $this->errors[] = Tools::displayError('You do not have permission to edit this.'); } } elseif(Tools::isSubmit('submitTranslationsModules')) { if($this->tabAccess['edit'] == 1) { $modules = Tools::getValue('modules'); if($modules) { // Get all translatables files of modules $arr_files = $this->getAllModuleFiles( $modules, null, $this->lang_selected->iso_code, true ); foreach($arr_files as $value) { $this->findAndFillTranslations( $value['files'], $value['theme'], $value['module'], $value['dir'] ); } foreach($this->modules_translations as $theme_name => &$module_names) { foreach($module_names as $module_name => &$template_names) { foreach($template_names as $template_name => &$items) { foreach($items as $key => $values) { if($theme_name) { $post_key = md5((string)strtolower($module_name) .'_'.strtolower($theme_name) .'_'.strtolower($template_name) .'_'.md5((string)$key)); } else { $post_key = md5((string)strtolower($module_name) .'_'.strtolower($template_name) .'_'.md5((string)$key)); } if(!Tools::getValue($post_key)){ $_POST[$post_key] = str_replace('\\', '', $values['trad']); } } unset($items); } unset($template_names); } unset($module_names); } // Find and write all translation modules files foreach($arr_files as $value) { $this->findAndWriteTranslationsIntoFile( $value['file_name'], $value['files'], $value['theme'], $value['module'], $value['dir'] ); } // Clear modules cache Tools::clearCache(); // Redirect if(Tools::getIsset('submitTranslationsModulesAndStay')) { $this->redirect(true); } else { $this->redirect(); } } } else { $this->errors[] = Tools::displayError('You do not have permission to edit this.'); } } } catch(PrestaShopException $e) { $this->errors[] = $e->getMessage(); } } /** * This function create vars by default and call the good method for generate form * * @param $method_name * @return mixed Call the method $this->method_name() */ public function initForm($method_name) { // Create a title for each translation page $title = sprintf( $this->l('%1$s (Language: %2$s, Theme: %3$s)'), $this->translations_informations[$this->type_selected]['name'], $this->lang_selected->name, $this->theme_selected ? $this->theme_selected : $this->l('none') ); // Set vars for all forms $this->tpl_view_vars = array( 'lang' => $this->lang_selected->iso_code, 'title' => $title, 'type' => $this->type_selected, 'theme' => $this->theme_selected, 'post_limit_exceeded' => $this->post_limit_exceed, 'url_submit' => self::$currentIndex.'&submitTranslations' .ucfirst($this->type_selected).'=1&token='.$this->token, 'toggle_button' => $this->displayToggleButton(), 'textarea_sized' => AdminTranslationsControllerCore::TEXTAREA_SIZED ); // Call method initForm for each type return $this->{$method_name}(); } /** * AdminController::initToolbar() override * @see AdminController::initToolbar() */ public function initToolbar() { $this->toolbar_btn['save-and-stay'] = array( 'short' => 'SaveAndStay', 'href' => '#', 'desc' => $this->l('Save and stay'), ); $this->toolbar_btn['save'] = array( 'href' => '#', 'desc' => $this->l('Update translations') ); $this->toolbar_btn['cancel'] = array( 'href' => self::$currentIndex.'&token='.$this->token, 'desc' => $this->l('Cancel') ); } /** * Generate the Main page */ public function initMain() { // Block add/update a language $packs_to_install = array(); $packs_to_update = array(); $token = Tools::getAdminToken('AdminLanguages' .(int)Tab::getIdFromClassName('AdminLanguages').(int)$this->context->employee->id); $array_stream_context = stream_context_create( array( 'http' => array( 'header' => array("Referer: ".(!empty($_SERVER['HTTPS']) ? 'https://' : 'http://') .(!empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'])), 'method' => 'GET', 'timeout' => 8 ) ) ); if($lang_packs = Tools::file_get_contents($this->link_lang_pack, false, $array_stream_context)) { if($lang_packs != '' && $lang_packs = Tools::jsonDecode($lang_packs, true)) { foreach($lang_packs as $key => $lang_pack) { if(!Language::isInstalled($lang_pack['iso_code'])) { $packs_to_install[$key] = $lang_pack; } else { $packs_to_update[$key] = $lang_pack; } } } } $this->tpl_view_vars = array( 'theme_default' => self::DEFAULT_THEME_NAME, 'theme_lang_dir' =>_THEME_LANG_DIR_, 'token' => $this->token, 'languages' => $this->languages, 'current_language' => strtolower($this->context->language->iso_code), 'translations_type' => $this->translations_informations, 'packs_to_install' => $packs_to_install, 'packs_to_update' => $packs_to_update, 'url_submit' => self::$currentIndex.'&token='.$this->token, 'themes' => $this->themes, 'id_theme_current' => $this->context->shop->id_theme, 'url_create_language' => 'index.php?controller=AdminLanguages&addlang&token='.$token, ); $this->toolbar_scroll = false; $this->base_tpl_view = 'main.tpl'; $this->content .= parent::renderView(); return $this->content; } /** * This method is only used by AdminTranslations::submitCopyLang(). * * It try to create folder in new theme. * * When a translation file is copied for a module, its translation key is wrong. * We have to change the translation key and rewrite the file. * * @param string $dest file name * @return bool */ protected function checkDirAndCreate($dest) { $bool = true; // To get only folder path $path = dirname($dest); // If folder wasn't already added // Do not use Tools::file_exists_cache because it changes over time! if(!file_exists($path)) { if(!mkdir($path, 0755, true)) { $bool &= false; $this->errors[] = sprintf( $this->l('Cannot create the folder "%s". Please check your directory writing permissions.'), $path ); } } return $bool; } /** * Read the Post var and write the translation file. * This method overwrites the old translation file. * * @param bool $override_file Set true if this file is a override * * @throws PrestaShopException */ protected function writeTranslationFile($override_file = false) { $type = Tools::toCamelCase($this->type_selected, true); if(isset($this->translations_informations[$this->type_selected])) { $translation_informations = $this->translations_informations[$this->type_selected]; } else { return; } if($override_file) { $file_path = $translation_informations['override']['dir'].$translation_informations['override']['file']; } else { $file_path = $translation_informations['dir'].$translation_informations['file']; } if($file_path && !file_exists($file_path)) { if(!file_exists(dirname($file_path)) && !mkdir(dirname($file_path), 0777, true)) { throw new PrestaShopException(sprintf( Tools::displayError('Directory "%s" cannot be created'), dirname($file_path) )); } elseif(!touch($file_path)) { throw new PrestaShopException(sprintf( Tools::displayError('File "%s" cannot be created'), $file_path )); } } $tab = $translation_informations['var']; $$tab = $this->fileExists(); $thm_name = str_replace('.', '', Tools::getValue('theme')); // Get value of button save and stay $save_and_stay = Tools::isSubmit('submitTranslations'.$type.'AndStay'); // Get language $lang = strtolower(Tools::getValue('lang')); // Unset all POST which are not translations unset( $_POST['submitTranslations'.$type], $_POST['submitTranslations'.$type.'AndStay'], $_POST['lang'], $_POST['token'], $_POST['theme'], $_POST['type'] ); // Get all POST which aren't empty $to_insert = array(); foreach($_POST as $key => $value) { if(!empty($value)) { $to_insert[$key] = $value; } } // Translations are ordered by key ksort($to_insert); if(count($$tab)) { foreach($$tab as $key => $item) { if(!isset($to_insert[$key])) $to_insert[$key] = str_replace('\\', '', $item); } ksort($to_insert); $$tab = $to_insert; } if($fd = fopen($file_path, 'w')) { fwrite($fd, " $value) { fwrite($fd, '$'.$tab.'[\''.$key.'\'] = \''.pSQL($value, true).'\';'."\n"); } fclose($fd); // Redirect if($save_and_stay) { $this->redirect(true); } else { $this->redirect(); } } else { throw new PrestaShopException(sprintf( Tools::displayError('Cannot write this file: "%s"'), $file_path )); } } public function submitCopyLang() { if(!($from_lang = Tools::getValue('fromLang')) || !($to_lang = Tools::getValue('toLang')) ) { $this->errors[] = $this->l('You must select two languages in order to copy data from one to another.'); } elseif(!($from_theme = Tools::getValue('fromTheme')) || !($to_theme = Tools::getValue('toTheme')) ) { $this->errors[] = $this->l('You must select two themes in order to copy data from one to another.'); } elseif(!Language::copyLanguageData(Language::getIdByIso($from_lang), Language::getIdByIso($to_lang))) { $this->errors[] = $this->l('An error occurred while copying data.'); } elseif($from_lang == $to_lang && $from_theme == $to_theme) { $this->errors[] = $this->l('There is nothing to copy (same language and theme).'); } else { $theme_exists = array( 'from_theme' => false, 'to_theme' => false ); foreach($this->themes as $theme) { if($theme->directory == $from_theme) { $theme_exists['from_theme'] = true; } if($theme->directory == $to_theme) { $theme_exists['to_theme'] = true; } } if($theme_exists['from_theme'] == false || $theme_exists['to_theme'] == false ) { $this->errors[] = $this->l('Theme(s) not found'); } } if(count($this->errors)) { return; } $bool = true; $items = Language::getFilesList( $from_lang, $from_theme, $to_lang, $to_theme, false, false, true ); foreach($items as $source => $dest) { if(!$this->checkDirAndCreate($dest)) { $this->errors[] = sprintf( $this->l('Impossible to create the directory "%s".'), $dest ); } elseif(!copy($source, $dest)) { $this->errors[] = sprintf( $this->l('Impossible to copy "%s" to "%s".'), $source, $dest ); } elseif(strpos($dest, 'modules') && basename($source) === $from_lang.'.php' && $bool !== false ) { if(!$this->changeModulesKeyTranslation($dest, $from_theme, $to_theme)) { $this->errors[] = sprintf( $this->l('Impossible to translate "$dest".'), $dest ); } } } if(!count($this->errors)) { $this->redirect(false, 14); } $this->errors[] = $this->l('A part of the data has been copied but some of the language files could not be found.'); } /** * Change the key translation to according it to theme name. * * @param string $path * @param string $theme_from * @param string $theme_to * @return bool */ public function changeModulesKeyTranslation($path, $theme_from, $theme_to) { $content = file_get_contents($path); $arr_replace = array(); $bool_flag = true; if(preg_match_all('#\$_MODULE\[\'([^\']+)\'\]#Ui', $content, $matches)) { foreach($matches[1] as $key => $value) { $arr_replace[$value] = str_replace($theme_from, $theme_to, $value); } $content = str_replace(array_keys($arr_replace), array_values($arr_replace), $content); $bool_flag = (file_put_contents($path, $content) === false) ? false : true; } return $bool_flag; } public function exportTabs() { // Get name tabs by iso code $tabs = Tab::getTabs($this->lang_selected->id); // Get name of the default tabs $tabs_default_lang = Tab::getTabs(1); $tabs_default = array(); foreach($tabs_default_lang as $tab) { $tabs_default[$tab['class_name']] = pSQL($tab['name']); } // Create content $content = " tabs are by default in Spanish * 2) create a new language, say, Klingon => tabs are populated using the default, Spanish, tabs * 3) export the Klingon language pack * * => Since you have not yet translated the tabs into Klingon, * without the condition below, you would get tabs exported, but in Spanish. * This would lead to a Klingon pack actually containing Spanish. * * This has caused many issues in the past, so, as a precaution, tabs from * the default language are not exported. * */ if($tabs_default[$tab['class_name']] != pSQL($tab['name'])) { $content .= "\n\$_TABS['".$tab['class_name']."'] = '".pSQL($tab['name'])."';"; } } } $content .= "\n\nreturn \$_TABS;"; $dir = _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.DIRECTORY_SEPARATOR; $path = $dir.'tabs.php'; // Check if tabs.php exists for the selected Iso Code if(!Tools::file_exists_cache($dir)) { Tools::mkdirAndIndex($dir, 0777, true); } if(!file_put_contents($path, $content)) { throw new PrestaShopException('File "'.$path.'" does not exist and cannot be created in '.$dir); } if(!is_writable($path)) { $this->displayWarning(sprintf(Tools::displayError('This file must be writable: %s'), $path)); } } public function submitExportLang() { if($this->lang_selected->iso_code && $this->theme_selected) { $this->exportTabs(); $items = array_flip(Language::getFilesList( $this->lang_selected->iso_code, $this->theme_selected, false, false, false, false, true )); $file_name = _PS_TRANSLATIONS_DIR_.'/export/'.$this->lang_selected->iso_code.'.gzip'; require_once(_PS_TOOL_DIR_.'tar/Archive_Tar.php'); $gz = new Archive_Tar($file_name, true); if($gz->createModify($items, null, _PS_ROOT_DIR_)) { ob_start(); header('Pragma: public'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Cache-Control: public'); header('Content-Description: File Transfer'); header('Content-type: application/octet-stream'); header('Content-Disposition: attachment; filename="'.$this->lang_selected->iso_code.'.gzip'.'"'); header('Content-Transfer-Encoding: binary'); ob_end_flush(); readfile($file_name); @unlink($file_name); exit; } $this->errors[] = Tools::displayError('An error occurred while creating archive.'); } $this->errors[] = Tools::displayError('Please select a language and a theme.'); } public static function checkAndAddMailsFiles($iso_code, $files_list) { if(Language::getIdByIso('en')) { $default_language = 'en'; } else { $default_language = Language::getIsoById((int)Configuration::get('PS_LANG_DEFAULT')); } if(!$default_language || !Validate::isLanguageIsoCode($default_language)) { return false; } // 1 - Scan mails files $mails = array(); if(Tools::file_exists_cache(_PS_MAIL_DIR_.$default_language.'/')) { $mails = scandir(_PS_MAIL_DIR_.$default_language.'/'); } $mails_new_lang = array(); // Get all email files foreach($files_list as $file) { if(preg_match('#^(\.\/)?mails\/([a-z0-9]+)\/#Ui', $file['filename'], $matches)) { $slash_pos = strrpos($file['filename'], '/'); $mails_new_lang[] = substr($file['filename'], -(strlen($file['filename']) - $slash_pos - 1)); } } // Get the difference $arr_mails_needed = array_diff($mails, $mails_new_lang); // Add mails files foreach($arr_mails_needed as $mail_to_add) { if(!in_array($mail_to_add, self::$ignore_folder)) { @copy( _PS_MAIL_DIR_.$default_language.'/'.$mail_to_add, _PS_MAIL_DIR_.$iso_code.'/'.$mail_to_add ); } } // 2 - Scan modules files $modules = scandir(_PS_MODULE_DIR_); $module_mail_en = array(); $module_mail_iso_code = array(); foreach($modules as $module) { if(!is_dir(_PS_MODULE_DIR_.$module)) { continue; } if(!in_array($module, self::$ignore_folder) && Tools::file_exists_cache(_PS_MODULE_DIR_.$module.'/mails/'.$default_language.'/') ) { $arr_files = scandir(_PS_MODULE_DIR_.$module.'/mails/'.$default_language.'/'); foreach($arr_files as $file) { if(!in_array($file, self::$ignore_folder)) { if(Tools::file_exists_cache(_PS_MODULE_DIR_.$module.'/mails/'.$default_language.'/'.$file)) { $module_mail_en[] = _PS_MODULE_DIR_.$module.'/mails/ISO_CODE/'.$file; } if(Tools::file_exists_cache(_PS_MODULE_DIR_.$module.'/mails/'.$iso_code.'/'.$file)) { $module_mail_iso_code[] = _PS_MODULE_DIR_.$module.'/mails/ISO_CODE/'.$file; } } } } } // Get the difference in this modules $arr_modules_mails_needed = array_diff($module_mail_en, $module_mail_iso_code); // Add mails files for this modules foreach($arr_modules_mails_needed as $file) { $file_en = str_replace('ISO_CODE', $default_language, $file); $file_iso_code = str_replace('ISO_CODE', $iso_code, $file); $dir_iso_code = substr($file_iso_code, 0, -(strlen($file_iso_code) - strrpos($file_iso_code, '/') - 1)); if(!file_exists($dir_iso_code)) { Tools::mkdirAndIndex($dir_iso_code); } if(Tools::file_exists_cache($file_en)) { copy($file_en, $file_iso_code); } } } /** * Move theme translations in selected themes * * @param array $files * @param array $themes_selected */ public function checkAndAddThemesFiles($files, $themes_selected) { foreach($files as $file) { // Check if file is a file theme if(preg_match('#^themes\/([a-z0-9]+)\/lang\/#Ui', $file['filename'], $matches)) { $slash_pos = strrpos($file['filename'], '/'); $name_file = substr($file['filename'], -(strlen($file['filename']) - $slash_pos - 1)); $name_default_theme = $matches[1]; $deleted_old_theme = false; // Get the old file theme if(file_exists(_PS_THEME_DIR_.'lang/'.$name_file)) { $theme_file_old = _PS_THEME_DIR_.'lang/'.$name_file; } else { $deleted_old_theme = true; $theme_file_old = str_replace( self::DEFAULT_THEME_NAME, $name_default_theme, _PS_THEME_DIR_.'lang/'.$name_file ); } // Move the old file theme in the new folder foreach($themes_selected as $theme_name) { if(file_exists($theme_file_old)) { copy($theme_file_old, str_replace( $name_default_theme, $theme_name, $theme_file_old )); } } if($deleted_old_theme) { @unlink($theme_file_old); } } } } /** * Add new translations tabs by code ISO * * @param array $iso_code * @param array $files * * @return array */ public static function addNewTabs($iso_code, $files) { $errors = array(); foreach($files as $file) { // Check if file is a file theme if(preg_match('#translations\/'.$iso_code.'\/tabs.php#Ui', $file['filename'], $matches) && Validate::isLanguageIsoCode($iso_code) ) { // Include array width new translations tabs $_TABS = array(); clearstatcache(); if(file_exists(_PS_ROOT_DIR_.DIRECTORY_SEPARATOR.$file['filename'])) { include_once(_PS_ROOT_DIR_.DIRECTORY_SEPARATOR.$file['filename']); } if(is_array($_TABS) && count($_TABS)) { foreach($_TABS as $class_name => $translations) { // Get instance of this tab by class name $tab = Tab::getInstanceFromClassName($class_name); //Check if class name exists if(isset($tab->class_name) && !empty($tab->class_name)) { $id_lang = Language::getIdByIso($iso_code, true); $tab->name[(int)$id_lang] = $translations; // Do not crash at intall if(!isset($tab->name[Configuration::get('PS_LANG_DEFAULT')])) { $tab->name[(int)Configuration::get('PS_LANG_DEFAULT')] = $translations; } if(!Validate::isGenericName($tab->name[(int)$id_lang])) { $errors[] = sprintf( Tools::displayError('Tab "%s" is not valid'), $tab->name[(int)$id_lang] ); } else { $tab->update(); } } } } } } return $errors; } public static function checkTranslationFile($content) { $lines = array_map('trim', explode("\n", $content)); $global = false; foreach($lines as $line) { // PHP tags if(in_array($line, array('', ''))) { continue; } // Global variable declaration if(!$global && preg_match('/^global\s+\$([a-z0-9-_]+)\s*;$/i', $line, $matches) ) { $global = $matches[1]; continue; } // Global variable initialization if($global != false && preg_match('/^\$'.preg_quote($global, '/').'\s*=\s*array\(\s*\)\s*;$/i', $line) ) { continue; } // Global variable initialization without declaration if(!$global && preg_match('/^\$([a-z0-9-_]+)\s*=\s*array\(\s*\)\s*;$/i', $line, $matches) ) { $global = $matches[1]; continue; } // Assignation if(preg_match('/^\$'.preg_quote($global, '/').'\[\''._PS_TRANS_PATTERN_.'\'\]\s*=\s*\''._PS_TRANS_PATTERN_.'\'\s*;$/i', $line)) { continue; } // Sometimes the global variable is returned... if(preg_match('/^return\s+\$'.preg_quote($global, '/').'\s*;$/i', $line, $matches)) { continue; } return false; } return true; } public function submitImportLang() { if(!isset($_FILES['file']['tmp_name']) || !$_FILES['file']['tmp_name']) { $this->errors[] = Tools::displayError('No file has been selected.'); } else { $files_list = array(); require_once(_PS_TOOL_DIR_.'tar/Archive_Tar.php'); $gz = new Archive_Tar($_FILES['file']['tmp_name'], true); $filename = $_FILES['file']['name']; $iso_code = str_replace(array('.tar.gz', '.gzip'), '', $filename); if(Validate::isLangIsoCode($iso_code)) { $themes_selected = Tools::getValue('theme', array(self::DEFAULT_THEME_NAME)); if(is_array($gz->listContent())) { $files_list = AdminTranslationsController::filterTranslationFiles($gz->listContent()); } $files_paths = AdminTranslationsController::filesListToPaths($files_list); $uniqid = uniqid(); $sandbox = _PS_CACHE_DIR_.'sandbox'.DIRECTORY_SEPARATOR.$uniqid.DIRECTORY_SEPARATOR; if($gz->extractList($files_paths, $sandbox)) { foreach($files_list as $file2check) { // Don't validate index.php, will be overwrite when extract in translation directory if(pathinfo($file2check['filename'], PATHINFO_BASENAME) == 'index.php') { continue; } if(preg_match('@^[0-9a-z-_/\\\\]+\.php$@i', $file2check['filename'])) { if(!@filemtime($sandbox.$file2check['filename']) || !AdminTranslationsController::checkTranslationFile(file_get_contents($sandbox.$file2check['filename'])) ) { $this->errors[] = sprintf( Tools::displayError('Validation failed for: %s'), $file2check['filename'] ); } } elseif(!preg_match('@mails[0-9a-z-_/\\\\]+\.(html|tpl|txt)$@i', $file2check['filename'])) { $this->errors[] = sprintf( Tools::displayError('Unidentified file found: %s'), $file2check['filename'] ); } } Tools::deleteDirectory($sandbox, true); } $i = 0; $tmp_array = array(); foreach($files_paths as $files_path) { $path = dirname($files_path); if(is_dir(_PS_TRANSLATIONS_DIR_.'../'.$path) && !is_writable(_PS_TRANSLATIONS_DIR_.'../'.$path) && !in_array($path, $tmp_array) ) { $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); $tmp_array[] = $path; } } if(count($this->errors)) { return false; } if($error = $gz->extractList($files_paths, _PS_TRANSLATIONS_DIR_.'../')) { if(is_object($error) && !empty($error->message)) { $this->errors[] = Tools::displayError('The archive cannot be extracted.'). ' '.$error->message; } else { foreach($files_list as $file2check) { $filename_path = _PS_TRANSLATIONS_DIR_.'../'.$file2check['filename']; if(pathinfo($file2check['filename'], PATHINFO_BASENAME) == 'index.php' && file_put_contents($filename_path, Tools::getDefaultIndexContent($filename_path)) ) { continue; } } // Clear smarty modules cache Tools::clearCache(); if(Validate::isLanguageFileName($filename)) { if(!Language::checkAndAddLanguage($iso_code)) { $conf = 20; } else { // Reset cache Language::loadLanguages(); AdminTranslationsController::checkAndAddMailsFiles($iso_code, $files_list); $this->checkAndAddThemesFiles($files_list, $themes_selected); $tab_errors = AdminTranslationsController::addNewTabs($iso_code, $files_list); if(count($tab_errors)) { $this->errors += $tab_errors; return false; } } } $this->redirect(false, (isset($conf) ? $conf : '15')); } } $this->errors[] = Tools::displayError('The archive cannot be extracted.'); } else { $this->errors[] = sprintf( Tools::displayError('ISO CODE invalid "%1$s" for the following file: "%2$s"'), $iso_code, $filename ); } } } /** * Filter the translation files contained in a .gzip pack * and return only the ones that we want. * * Right now the function only needs to check that * the modules for which we want to add translations * are present on the shop (installed or not). * * @param array $list Is the output of Archive_Tar::listContent() * * @return array */ public static function filterTranslationFiles($list) { if(!is_array($list)) { $list = array(); } $kept = array(); foreach($list as $file) { if('index.php' == basename($file['filename'])) { continue; } if(preg_match('#^modules/([^/]+)/#', $file['filename'], $m)) { if(is_dir(_PS_MODULE_DIR_.$m[1])) { $kept[] = $file; } } else { $kept[] = $file; } } return $kept; } /** * Turn the list returned by * AdminTranslationsController::filterTranslationFiles() * into a list of paths that can be passed to * Archive_Tar::extractList() * * @param array $list * * @return array */ public static function filesListToPaths($list) { $paths = array(); foreach($list as $item) { $paths[] = $item['filename']; } return $paths; } public function submitAddLang() { $files_list = array(); $array_stream_context = stream_context_create( array( 'http' => array( 'header' => array("Referer: ".(!empty($_SERVER['HTTPS']) ? 'https://' : 'http://') .(!empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'])), 'method' => 'GET', 'timeout' => 8 ) ) ); // 0 = Language ISO code, 1 = PS version $arr_import_lang = explode('|', Tools::getValue('params_import_language')); if(Validate::isLangIsoCode($arr_import_lang[0])) { $content = Tools::file_get_contents(_PS_VERSION_DOMAIN_.'/public/scripts/dl.php?l=' .Tools::strtolower($arr_import_lang[0]).'.gzip', false, $array_stream_context); if($content) { $file = _PS_TRANSLATIONS_DIR_.$arr_import_lang[0].'.gzip'; if((bool)file_put_contents($file, $content)) { require_once(_PS_TOOL_DIR_.'/tar/Archive_Tar.php'); $gz = new Archive_Tar($file, true); if(_PS_MODE_DEV_) { $gz->setErrorHandling(PEAR_ERROR_TRIGGER, E_USER_WARNING); } if(is_array($gz->listContent())) { $files_list = AdminTranslationsController::filterTranslationFiles($gz->listContent()); } if($error = $gz->extractList(AdminTranslationsController::filesListToPaths($files_list), _PS_TRANSLATIONS_DIR_.'../')) { if(is_object($error) && !empty($error->message)) { $this->errors[] = Tools::displayError('The archive cannot be extracted.'). ' '.$error->message; } else { if(!Language::checkAndAddLanguage($arr_import_lang[0])) { $conf = 20; } else { // Reset cache Language::loadLanguages(); // Clear smarty modules cache Tools::clearCache(); AdminTranslationsController::checkAndAddMailsFiles($arr_import_lang[0], $files_list); if($tab_errors = AdminTranslationsController::addNewTabs($arr_import_lang[0], $files_list)) { $this->errors += $tab_errors; } } if(!unlink($file)) { $this->errors[] = sprintf(Tools::displayError('Cannot delete the archive %s.'), $file); } $this->redirect(false, (isset($conf) ? $conf : '15')); } } else { $this->errors[] = sprintf( Tools::displayError('Cannot decompress the translation file for the following language: %s'), $arr_import_lang[0] ); $checks= array(); foreach($files_list as $f) { if(isset($f['filename'])) { if(is_file(_PS_ROOT_DIR_.DIRECTORY_SEPARATOR.$f['filename']) && !is_writable(_PS_ROOT_DIR_.DIRECTORY_SEPARATOR.$f['filename']) ) { $checks[] = dirname($f['filename']); } elseif(is_dir(_PS_ROOT_DIR_.DIRECTORY_SEPARATOR.$f['filename']) && !is_writable(_PS_ROOT_DIR_.DIRECTORY_SEPARATOR.dirname($f['filename'])) ) { $checks[] = dirname($f['filename']); } } } $checks = array_unique($checks); foreach($checks as $check) { $this->errors[] = sprintf( Tools::displayError('Please check rights for folder and files in %s'), $check ); } if(!unlink($file)) { $this->errors[] = sprintf( Tools::displayError('Cannot delete the archive %s.'), $file ); } } } else { $this->errors[] = Tools::displayError('The server does not have permissions for writing.').' ' .sprintf(Tools::displayError('Please check rights for %s'), dirname($file)); } } else { $this->errors[] = Tools::displayError('Language not found.'); } } else { $this->errors[] = Tools::displayError('Invalid parameter.'); } } /** * This method check each file (tpl or php file), get its sentences to translate, * compare with posted values and write in iso code translation file. * * @param string $file_name * @param array $files * @param string $theme_name * @param string $module_name * @param string|bool $dir * * @throws PrestaShopException */ protected function findAndWriteTranslationsIntoFile($file_name, $files, $theme_name, $module_name, $dir = false) { // These static vars allow to use file to write just one time. static $cache_file = array(); static $str_write = ''; static $array_check_duplicate = array(); // Set file_name in static var, this allow to open and wright the file just one time if(!isset($cache_file[$theme_name.'-'.$file_name])) { $str_write = ''; $cache_file[$theme_name.'-'.$file_name] = true; if(!Tools::file_exists_cache(dirname($file_name))) { Tools::mkdirAndIndex(dirname($file_name), 0777, true); } if(!Tools::file_exists_cache($file_name)) { file_put_contents($file_name, ''); } if(!is_writable($file_name)) { throw new PrestaShopException(sprintf( Tools::displayError('Cannot write to the theme\'s language file (%s). Please check writing permissions.'), $file_name )); } // this string is initialized one time for a file $str_write .= "userParseFile($content, $this->type_selected, $type_file, $module_name); // Write each translation on its module file $template_name = substr(basename($file), 0, -4); foreach($matches as $key) { if($theme_name) { $post_key = md5((string)strtolower($module_name).'_'.strtolower($theme_name).'_'.strtolower($template_name).'_'.md5((string)$key)); $pattern = '\'<{'.strtolower($module_name).'}'.strtolower($theme_name).'>'.strtolower($template_name).'_'.md5((string)$key).'\''; } else { $post_key = md5((string)strtolower($module_name).'_'.strtolower($template_name).'_'.md5((string)$key)); $pattern = '\'<{'.strtolower($module_name).'}prestashop>'.strtolower($template_name).'_'.md5((string)$key).'\''; } if(array_key_exists($post_key, $_POST) && !in_array($pattern, $array_check_duplicate)) { if($_POST[$post_key] == '') { continue; } $array_check_duplicate[] = $pattern; $str_write .= '$_MODULE['.$pattern.'] = \''.pSQL(str_replace(array("\r\n", "\r", "\n"), ' ', $_POST[$post_key])).'\';'."\n"; $this->total_expression++; } } } } if(isset($cache_file[$theme_name.'-'.$file_name]) && $str_write != " $file) { if($file[0] === '.' || in_array(substr($file, 0, strrpos($file, '.')), $this->all_iso_lang)) { unset($files[$key]); } elseif($type_clear === 'file' && !in_array(substr($file, strrpos($file, '.')), $arr_good_ext)) { unset($files[$key]); } elseif($type_clear === 'directory' && (!is_dir($path.$file) || in_array($file, $arr_exclude))) { unset($files[$key]); } } return $files; } /** * This method get translation for each files of a module, * compare with global $_MODULES array and fill AdminTranslations::modules_translations array * With key as English sentences and values as their iso code translations. * * @param array $files * @param string $theme_name * @param string $module_name * @param string|bool $dir */ protected function findAndFillTranslations($files, $theme_name, $module_name, $dir = false) { $name_var = $this->translations_informations[$this->type_selected]['var']; // Added for compatibility $$name_var = array_change_key_case($this->global_modules); // Thank to this var similar keys are not duplicate // in AndminTranslation::modules_translations array // see below $array_check_duplicate = array(); foreach($files as $file) { if((preg_match('/^(.*).tpl$/', $file) || preg_match('/^(.*).php$/', $file)) && Tools::file_exists_cache($file_path = $dir.$file) ) { // Get content for this file $content = file_get_contents($file_path); // Module files can now be ignored by adding this string in a file if(strpos($content, 'IGNORE_THIS_FILE_FOR_TRANSLATION') !== false) { continue; } // Get file type $type_file = substr($file, -4) == '.tpl' ? 'tpl' : 'php'; // Parse this content $matches = $this->userParseFile($content, $this->type_selected, $type_file, $module_name); unset($content); // Write each translation on its module file $template_name = substr(basename($file), 0, -4); foreach($matches as $key) { $md5_key = md5((string)$key); $module_key = '<{'.Tools::strtolower($module_name).'}'.($theme_name ? strtolower($theme_name) : '').'>' .Tools::strtolower($template_name).'_'.$md5_key; $default_key = '<{'.Tools::strtolower($module_name).'}prestashop>' .Tools::strtolower($template_name).'_'.$md5_key; // To avoid duplicate entry if(!in_array($module_key, $array_check_duplicate)) { $array_check_duplicate[] = $module_key; if(!isset($this->modules_translations[$theme_name][$module_name][$template_name][$key]['trad'])) { $this->total_expression++; } if($theme_name && array_key_exists($module_key, ${$name_var})) { $this->modules_translations [$theme_name] [$module_name] [$template_name] [$key] ['trad'] = html_entity_decode(${$name_var}[$module_key], ENT_COMPAT, 'UTF-8'); } elseif(array_key_exists($default_key, ${$name_var})) { $this->modules_translations [$theme_name] [$module_name] [$template_name] [$key] ['trad'] = html_entity_decode(${$name_var}[$default_key], ENT_COMPAT, 'UTF-8'); } else { $this->modules_translations [$theme_name] [$module_name] [$template_name] [$key] ['trad'] = ''; $this->missing_translations++; } $this->modules_translations [$theme_name] [$module_name] [$template_name] [$key] ['use_sprintf'] = $this->checkIfKeyUseSprintf($key); } } } } } /** * Get list of files which must be parsed by directory and by type of translations * * @return array : list of files by directory */ public function getFileToParseByTypeTranslation() { $directories = array(); switch ($this->type_selected) { case 'front': $directories['tpl'] = array(_PS_ALL_THEMES_DIR_ => scandir(_PS_ALL_THEMES_DIR_)); self::$ignore_folder[] = 'modules'; if(_PARENT_THEME_NAME_) { $directories['tpl'] = array_merge($directories['tpl'], $this->listFiles(_PS_ALL_THEMES_DIR_._PARENT_THEME_NAME_)); } $directories['tpl'] = array_merge($directories['tpl'], $this->listFiles(_PS_THEME_SELECTED_DIR_)); if(isset($directories['tpl'][_PS_THEME_SELECTED_DIR_.'pdf/'])) { unset($directories['tpl'][_PS_THEME_SELECTED_DIR_.'pdf/']); } if(Tools::file_exists_cache(_PS_THEME_OVERRIDE_DIR_)) { $directories['tpl'] = array_merge($directories['tpl'], $this->listFiles(_PS_THEME_OVERRIDE_DIR_)); } break; case 'back': $directories = array( 'php' => array( _PS_ADMIN_CONTROLLER_DIR_ => scandir(_PS_ADMIN_CONTROLLER_DIR_), _PS_OVERRIDE_DIR_.'controllers/admin/' => scandir(_PS_OVERRIDE_DIR_.'controllers/admin/'), _PS_CLASS_DIR_.'helper/' => scandir(_PS_CLASS_DIR_.'helper/'), _PS_CLASS_DIR_.'controller/' => array('AdminController.php'), _PS_CLASS_DIR_ => array('PaymentModule.php, order/OrderHistory.php') ), 'tpl' => $this->listFiles(_PS_ADMIN_DIR_.DIRECTORY_SEPARATOR.'themes/'), 'specific' => array( _PS_ADMIN_DIR_.DIRECTORY_SEPARATOR => array( 'header.inc.php', 'footer.inc.php', 'index.php', 'functions.php' ) ) ); // For translate the template which are overridden if(file_exists(_PS_OVERRIDE_DIR_.'controllers'.DIRECTORY_SEPARATOR.'admin'.DIRECTORY_SEPARATOR.'templates')) { $directories['tpl'] = array_merge( $directories['tpl'], $this->listFiles(_PS_OVERRIDE_DIR_.'controllers'.DIRECTORY_SEPARATOR.'admin'.DIRECTORY_SEPARATOR.'templates') ); } break; case 'errors': case 'confirmations': $directories['php'] = array( _PS_ROOT_DIR_.DIRECTORY_SEPARATOR => scandir(_PS_ROOT_DIR_), _PS_ADMIN_DIR_.DIRECTORY_SEPARATOR => scandir(_PS_ADMIN_DIR_.DIRECTORY_SEPARATOR), _PS_FRONT_CONTROLLER_DIR_ => scandir(_PS_FRONT_CONTROLLER_DIR_), _PS_ADMIN_CONTROLLER_DIR_ => scandir(_PS_ADMIN_CONTROLLER_DIR_), _PS_OVERRIDE_DIR_.'controllers/front/' => scandir(_PS_OVERRIDE_DIR_.'controllers/front/'), _PS_OVERRIDE_DIR_.'controllers/admin/' => scandir(_PS_OVERRIDE_DIR_.'controllers/admin/') ); // Get all files for folders classes/ and override/classes/ recursively $directories['php'] = array_merge( $directories['php'], $this->listFiles(_PS_CLASS_DIR_, array(), 'php') ); $directories['php'] = array_merge( $directories['php'], $this->listFiles(_PS_OVERRIDE_DIR_.'classes/', array(), 'php') ); break; case 'fields': $directories['php'] = $this->listFiles(_PS_CLASS_DIR_, array(), 'php'); break; case 'pdf': $tpl_theme = Tools::file_exists_cache(_PS_THEME_SELECTED_DIR_.'pdf/') ? scandir(_PS_THEME_SELECTED_DIR_.'pdf/') : array(); $directories = array( 'php' => array( _PS_CLASS_DIR_.'pdf/' => scandir(_PS_CLASS_DIR_.'pdf/'), _PS_OVERRIDE_DIR_.'classes/pdf/' => scandir(_PS_OVERRIDE_DIR_.'classes/pdf/') ), 'tpl' => array( _PS_PDF_DIR_ => scandir(_PS_PDF_DIR_), _PS_THEME_SELECTED_DIR_.'pdf/' => $tpl_theme ) ); $directories['tpl'] = array_merge($directories['tpl'], $this->getModulesHasPDF()); $directories['php'] = array_merge($directories['php'], $this->getModulesHasPDF(true)); break; case 'mails': $directories['php'] = array( _PS_FRONT_CONTROLLER_DIR_ => scandir(_PS_FRONT_CONTROLLER_DIR_), _PS_ADMIN_CONTROLLER_DIR_ => scandir(_PS_ADMIN_CONTROLLER_DIR_), _PS_OVERRIDE_DIR_.'controllers/front/' => scandir(_PS_OVERRIDE_DIR_.'controllers/front/'), _PS_OVERRIDE_DIR_.'controllers/admin/' => scandir(_PS_OVERRIDE_DIR_.'controllers/admin/'), _PS_ADMIN_DIR_.DIRECTORY_SEPARATOR => scandir(_PS_ADMIN_DIR_.DIRECTORY_SEPARATOR), _PS_ADMIN_DIR_.DIRECTORY_SEPARATOR.'tabs/' => scandir(_PS_ADMIN_DIR_.DIRECTORY_SEPARATOR.'/tabs') ); // Get all files for folders classes/ and override/classes/ recursively $directories['php'] = array_merge( $directories['php'], $this->listFiles(_PS_CLASS_DIR_, array(), 'php') ); $directories['php'] = array_merge( $directories['php'], $this->listFiles(_PS_OVERRIDE_DIR_.'classes/', array(), 'php') ); $directories['php'] = array_merge( $directories['php'], $this->getModulesHasMails() ); break; } return $directories; } /** * This method parse a file by type of translation and type file * * @param $content * @param $type_translation : front, back, errors, modules... * @param string|bool $type_file : (tpl|php) * @param string $module_name : name of the module * @return array */ protected function userParseFile($content, $type_translation, $type_file = false, $module_name = '') { switch ($type_translation) { case 'front': // Parsing Front office file $regex = '/\{l\s*s=([\'\"])'._PS_TRANS_PATTERN_.'\1(\s*sprintf=.*)?(\s*js=1)?\s*\}/U'; break; case 'back': // Parsing Back office file if($type_file == 'php') { $regex = '/this->l\((\')'._PS_TRANS_PATTERN_.'\'[\)|\,]/U'; } elseif($type_file == 'specific') { $regex = '/Translate::getAdminTranslation\((\')'._PS_TRANS_PATTERN_.'\'(?:,.*)*\)/U'; } else { $regex = '/\{l\s*s\s*=([\'\"])'._PS_TRANS_PATTERN_.'\1(\s*sprintf=.*)?(\s*js=1)?(\s*slashes=1)?.*\}/U'; } break; case 'errors': // Parsing file for all errors syntax $regex = '/Tools::displayError\((\')'._PS_TRANS_PATTERN_.'\'(,\s*(.+))?\)/U'; break; case 'confirmations': // Parsing file for all confirmations syntax $regex = '/Tools::displayConfirmation\((\')'._PS_TRANS_PATTERN_.'\'(,\s*(.+))?\)/U'; break; case 'modules': // Parsing modules file if($type_file == 'php') { $regex = '/->l\((\')'._PS_TRANS_PATTERN_.'\'(, ?\'(.+)\')?(, ?(.+))?\)/U'; } else { // In tpl file look for something that should contain mod='module_name' // according to the documentation $regex = '/\{l\s*s=([\'\"])'._PS_TRANS_PATTERN_.'\1.*\s+mod=\''.$module_name.'\'.*\}/U'; } break; case 'pdf': // Parsing PDF file if($type_file == 'php') { $regex = array( '/HTMLTemplate.*::l\((\')'._PS_TRANS_PATTERN_.'\'[\)|\,]/U', '/->l\((\')'._PS_TRANS_PATTERN_.'\'(, ?\'(.+)\')?(, ?(.+))?\)/U' ); } else { $regex = '/\{l\s*s=([\'\"])'._PS_TRANS_PATTERN_.'\1(\s*sprintf=.*)?(\s*js=1)?(\s*pdf=\'true\')?\s*\}/U'; } break; } if(!is_array($regex)) { $regex = array($regex); } $strings = array(); foreach($regex as $regex_row) { $matches = array(); $n = preg_match_all($regex_row, $content, $matches); for($i = 0; $i < $n; $i += 1) { $quote = $matches[1][$i]; $string = $matches[2][$i]; if($quote === '"') { // Escape single quotes because the core will do it // when looking for the translation of this string $string = str_replace('\'', '\\\'', $string); // Unescape double quotes $string = preg_replace('/\\\\+"/', '"', $string); } $strings[] = $string; } } return array_unique($strings); } /** * Get all translations informations for all type of translations * * array( * 'type' => array( * 'name' => string : title for the translation type, * 'var' => string : name of var for the translation file, * 'dir' => string : dir of translation file * 'file' => string : file name of translation file * ) * ) */ public function getTranslationsInformations() { $this->translations_informations = array( 'front' => array( 'name' => $this->l('Front office translations'), 'var' => '_LANG', 'dir' => defined('_PS_THEME_SELECTED_DIR_') ? _PS_THEME_SELECTED_DIR_.'lang/' : '', 'file' => $this->lang_selected->iso_code.'.php' ), 'back' => array( 'name' => $this->l('Back office translations'), 'var' => '_LANGADM', 'dir' => _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.'/', 'file' => 'admin.php' ), 'errors' => array( 'name' => $this->l('Error message translations'), 'var' => '_ERRORS', 'dir' => _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.'/', 'file' => 'errors.php' ), 'confirmations' => array( 'name' => $this->l('Confirmation message translations'), 'var' => '_CONFS', 'dir' => _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.'/', 'file' => 'confs.php' ), 'fields' => array( 'name' => $this->l('Field name translations'), 'var' => '_FIELDS', 'dir' => _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.'/', 'file' => 'fields.php' ), 'modules' => array( 'name' => $this->l('Installed modules translations'), 'var' => '_MODULES', 'dir' => _PS_MODULE_DIR_, 'file' => '' ), 'pdf' => array( 'name' => $this->l('PDF translations'), 'var' => '_LANGPDF', 'dir' => _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.'/', 'file' => 'pdf.php' ), 'mails' => array( 'name' => $this->l('Email templates translations'), 'var' => '_LANGMAIL', 'dir' => _PS_MAIL_DIR_.$this->lang_selected->iso_code.'/', 'file' => 'lang.php' ) ); if(defined('_PS_THEME_SELECTED_DIR_')) { $this->translations_informations['modules']['override'] = array( 'dir' => _PS_THEME_SELECTED_DIR_.'modules/', 'file' => '' ); $this->translations_informations['pdf']['override'] = array( 'dir' => _PS_THEME_SELECTED_DIR_.'pdf/lang/', 'file' => $this->lang_selected->iso_code.'.php' ); $this->translations_informations['mails']['override'] = array( 'dir' => _PS_THEME_SELECTED_DIR_.'mails/'.$this->lang_selected->iso_code.'/', 'file' => 'lang.php' ); } } /** * Get all informations on : languages, theme and the translation type. */ public function getInformations() { // Get all Languages $this->languages = Language::getLanguages(false); // Get all iso_code of languages foreach($this->languages as $language) { $this->all_iso_lang[] = $language['iso_code']; } // Get all themes $this->themes = Theme::getThemes(); // Get folder name of theme if(($theme = Tools::getValue('theme')) && !is_array($theme)) { $theme_exists = $this->theme_exists($theme); if(!$theme_exists) { throw new PrestaShopException(sprintf( Tools::displayError('Invalid theme "%s"'), Tools::safeOutput($theme) )); } $this->theme_selected = Tools::safeOutput($theme); } // Set the path of selected theme if(!defined('_PS_THEME_SELECTED_DIR_')) { if($this->theme_selected) { define('_PS_THEME_SELECTED_DIR_', _PS_ROOT_DIR_.'/themes/'.$this->theme_selected.'/'); } else { define('_PS_THEME_SELECTED_DIR_', ''); } } // Get type of translation if(($type = Tools::getValue('type')) && !is_array($type)) { $this->type_selected = strtolower(Tools::safeOutput($type)); } // Get selected language if(Tools::getValue('lang') || Tools::getValue('iso_code')) { $iso_code = Tools::getValue('lang') ? Tools::getValue('lang') : Tools::getValue('iso_code'); if(!Validate::isLangIsoCode($iso_code) || !in_array($iso_code, $this->all_iso_lang)) { throw new PrestaShopException(sprintf( Tools::displayError('Invalid iso code "%s"'), Tools::safeOutput($iso_code) )); } $this->lang_selected = new Language((int)Language::getIdByIso($iso_code)); } else { $this->lang_selected = new Language((int)Language::getIdByIso('en')); } // Get all information for translations $this->getTranslationsInformations(); } /** * AdminController::postProcess() override * @see AdminController::postProcess() */ public function postProcess() { $this->getInformations(); if(_PS_MODE_DEMO_) { $this->errors[] = Tools::displayError('This functionality has been disabled.'); return; } try { if(Tools::isSubmit('submitCopyLang')) { if($this->tabAccess['add'] == 1) { $this->submitCopyLang(); } else { $this->errors[] = Tools::displayError('You do not have permission to add this.'); } } elseif(Tools::isSubmit('submitExport')) { if($this->tabAccess['add'] == 1) { $this->submitExportLang(); } else { $this->errors[] = Tools::displayError('You do not have permission to add this.'); } } elseif(Tools::isSubmit('submitImport')) { if($this->tabAccess['add'] == 1) { $this->submitImportLang(); } else { $this->errors[] = Tools::displayError('You do not have permission to add this.'); } } elseif(Tools::isSubmit('submitAddLanguage')) { if($this->tabAccess['add'] == 1) { $this->submitAddLang(); } else { $this->errors[] = Tools::displayError('You do not have permission to add this.'); } } } catch (PrestaShopException $e) { $this->errors[] = $e->getMessage(); } } /** * This method redirect in the translation main page or in the translation page * * @param bool $save_and_stay : true if the user has clicked on the button "save and stay" * @param bool $conf : id of confirmation message */ protected function redirect($save_and_stay = false, $conf = false) { $conf = !$conf ? 4 : $conf; $url_base = self::$currentIndex.'&token='.$this->token.'&conf='.$conf; if($save_and_stay) { Tools::redirectAdmin($url_base.'&lang='.$this->lang_selected->iso_code .'&type='.$this->type_selected.'&theme='.$this->theme_selected); } else { Tools::redirectAdmin($url_base); } } protected function getMailPattern() { Tools::displayAsDeprecated('Email pattern is no longer used, emails are always saved like they are.'); // Let the indentation like it. return ' #title #content '; } /** * This method is used to write translation for mails. * This writes subject translation files * (in root/mails/lang_choosen/lang.php or root/_PS_THEMES_DIR_/mails/lang_choosen/lang.php) * and mails files. */ protected function submitTranslationsMails() { $arr_mail_content = array(); $arr_mail_path = array(); if(Tools::getValue('core_mail')) { $arr_mail_content['core_mail'] = Tools::getValue('core_mail'); } if(Tools::getValue('module_mail')) { $arr_mail_content['module_mail'] = Tools::getValue('module_mail'); } // Get path of directory for find a good path of translation file if(!$this->theme_selected) { $arr_mail_path['module_mail'] = $this->translations_informations['modules']['dir'].'{module}/mails/'.$this->lang_selected->iso_code.'/'; } else { $arr_mail_path['module_mail'] = $this->translations_informations['modules']['override']['dir'].'{module}/mails/'.$this->lang_selected->iso_code.'/'; } // Get path of directory for find a good path of translation file if(!$this->theme_selected) { $arr_mail_path['core_mail'] = $this->translations_informations[$this->type_selected]['dir']; } else { $arr_mail_path['core_mail'] = $this->translations_informations[$this->type_selected]['override']['dir']; } // Update subjects $array_subjects = array(); if(($subjects = Tools::getValue('subject')) && is_array($subjects)) { $array_subjects['core_and_modules'] = array( 'translations' => array(), 'path' => $arr_mail_path['core_mail'].'lang.php' ); foreach($subjects as $subject_translation) { $array_subjects['core_and_modules']['translations'] = array_merge( $array_subjects['core_and_modules']['translations'], $subject_translation ); } } if(!empty($array_subjects)) { foreach($array_subjects as $infos) { $this->writeSubjectTranslationFile($infos['translations'], $infos['path']); } } // Save each mail content foreach($arr_mail_content as $group_name => $all_content) { foreach($all_content as $type_content => $mails) { foreach($mails as $mail_name => $content) { $content = str_replace('\\', '', $content); $module_name = false; $module_name_pipe_pos = stripos($mail_name, '|'); if($module_name_pipe_pos) { $module_name = substr($mail_name, 0, $module_name_pipe_pos); if(!Validate::isModuleName($module_name)) { throw new PrestaShopException(sprintf(Tools::displayError('Invalid module name "%s"'), Tools::safeOutput($module_name))); } $mail_name = substr($mail_name, $module_name_pipe_pos + 1); if(!Validate::isTplName($mail_name)) { throw new PrestaShopException(sprintf(Tools::displayError('Invalid mail name "%s"'), Tools::safeOutput($mail_name))); } } if($type_content == 'html') { $content = str_replace(_PS_IMG_.'logo.jpg', '{shop_logo}', $content); $content = Tools::htmlentitiesUTF8($content); $content = htmlspecialchars_decode($content); // Replace correct end of line $content = str_replace("\r\n", PHP_EOL, $content); $title = ''; if(Tools::getValue('title_'.$group_name.'_'.$mail_name)) { $title = Tools::getValue('title_'.$group_name.'_'.$mail_name); } $content = preg_replace('/.*<\/title>/s', '<title>'.$title.'', $content); } if(Validate::isCleanHTML($content)) { $path = $arr_mail_path[$group_name]; if($module_name) { $path = str_replace('{module}', $module_name, $path); } if(!file_exists($path) && !mkdir($path, 0777, true)) { throw new PrestaShopException(sprintf(Tools::displayError('Directory "%s" cannot be created'), dirname($path))); } if ($type_content == 'html') { if(extension_loaded('tidy')) { $formated = tidy_repair_string($content, array('indent' => true, 'show-body-only' => false, 'indent-spaces' => 4, 'wrap' => 154)); } else { $formated = $content; } file_put_contents($path.$mail_name.'.'.$type_content, $formated); } else { file_put_contents($path.$mail_name.'.'.$type_content, strip_tags($content)); } } else { throw new PrestaShopException(Tools::displayError('Your HTML email templates cannot contain JavaScript code.')); } } } } if(Tools::isSubmit('submitTranslationsMailsAndStay')) { $this->redirect(true); } else { $this->redirect(); } } /** * Include file $dir/$file and return the var $var declared in it. * This create the file if not exists * * return array : translations */ public function fileExists() { $var = $this->translations_informations[$this->type_selected]['var']; $dir = $this->translations_informations[$this->type_selected]['dir']; $file = $this->translations_informations[$this->type_selected]['file']; $$var = array(); if(!Tools::file_exists_cache($dir)) { Tools::mkdirAndIndex($dir, 0755); } // First time if(!Tools::file_exists_cache($dir.$file)) { // FO if(_PARENT_THEME_NAME_ && ($var == '_LANG')) { // Get all existing active translations in parent theme $languages = Language::getLanguages(); foreach($languages as $key => $lang) { $lang_file = $lang['iso_code'].'.php'; if(!Tools::file_exists_cache($dir.$lang_file)) { $parent_lang_path = str_replace($this->theme_selected, _PARENT_THEME_NAME_, $dir); if(file_exists($parent_lang_path.$lang_file)) { if(!copy($parent_lang_path.$lang_file, $dir.$lang_file)) { throw new PrestaShopException('File "'.$lang_file.'" doesn\'t exists and cannot be created in '.$dir); } } } } } elseif(!file_put_contents($dir.$file, "")) { throw new PrestaShopException('File "'.$file.'" doesn\'t exists and cannot be created in '.$dir); } } if(!is_writable($dir.$file)) { $this->displayWarning(Tools::displayError('This file must be writable:').' '.$dir.$file); } // We can't load directly translation files because interactions with $GLOBALS $temp_dir = $dir.'temp'.$var.'.php'; file_put_contents($temp_dir, str_replace('global $'.$var.';', '', file_get_contents($dir.$file))); include($temp_dir); unlink($temp_dir); return $$var; } public function displayToggleButton($closed = false) { $str_output = ' '; return $str_output; } /* * Deprecated since 1.6.2.2 */ public function displayLimitPostWarning($count) { $return = array(); 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) ) { $return['error_type'] = 'suhosin'; $return['post.max_vars'] = ini_get('suhosin.post.max_vars'); $return['request.max_vars'] = ini_get('suhosin.request.max_vars'); $return['needed_limit'] = $count + 100; } elseif(ini_get('max_input_vars') && ini_get('max_input_vars') < $count) { $return['error_type'] = 'conf'; $return['max_input_vars'] = ini_get('max_input_vars'); $return['needed_limit'] = $count + 100; } return $return; } /** * Find sentence which use %d, %s, %%, %1$d, %1$s... * * @param $key : english sentence * @return array|bool return list of matches */ public function checkIfKeyUseSprintf($key) { if(preg_match_all('#(?:%%|%(?:[0-9]+\$)?[+-]?(?:[ 0]|\'.)?-?[0-9]*(?:\.[0-9]+)?[bcdeufFosxX])#', $key, $matches)) { return implode(', ', $matches[0]); } return false; } /** * This method generate the form for front translations */ public function initFormFront() { if(!$this->theme_exists(Tools::getValue('theme'))) { $this->errors[] = sprintf(Tools::displayError('Invalid theme "%s"'), Tools::getValue('theme')); return; } $missing_translations_front = array(); $name_var = $this->translations_informations[$this->type_selected]['var']; $$name_var = $this->fileExists(); // List templates to parse $files_by_directory = $this->getFileToParseByTypeTranslation(); $count = 0; $tabs_array = array(); foreach($files_by_directory['tpl'] as $dir => $files) { $prefix = ''; if($dir == _PS_THEME_OVERRIDE_DIR_) { $prefix = 'override_'; } foreach($files as $file) { if(preg_match('/^(.*).tpl$/', $file) && (Tools::file_exists_cache($file_path = $dir.$file)) ) { $prefix_key = $prefix.substr(basename($file), 0, -4); $new_lang = array(); // Get content for this file $content = file_get_contents($file_path); // Parse this content $matches = $this->userParseFile($content, $this->type_selected); // Get string translation foreach($matches as $key) { if(empty($key)) { $this->errors[] = sprintf( $this->l('Empty string found, please edit: "%s"'), $file_path ); $new_lang[$key] = ''; } else { // Caution ! front has underscore between prefix key and md5, back has not if(isset(${$name_var}[$prefix_key.'_'.md5((string)$key)])) { $new_lang[$key]['trad'] = stripslashes(html_entity_decode(${$name_var}[$prefix_key.'_'.md5((string)$key)], ENT_COMPAT, 'UTF-8')); } else { if(!isset($new_lang[$key]['trad'])) { $new_lang[$key]['trad'] = ''; if(!isset($missing_translations_front[$prefix_key])) { $missing_translations_front[$prefix_key] = 1; } else { $missing_translations_front[$prefix_key]++; } } } $new_lang[$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key); } } if(isset($tabs_array[$prefix_key])) { $tabs_array[$prefix_key] = array_merge($tabs_array[$prefix_key], $new_lang); } else { $tabs_array[$prefix_key] = $new_lang; } $count += count($new_lang); } } } $this->tpl_view_vars = array_merge($this->tpl_view_vars, array( 'missing_translations' => $missing_translations_front, 'count' => $count, 'cancel_url' => $this->context->link->getAdminLink('AdminTranslations'), 'tabsArray' => $tabs_array, )); $this->initToolbar(); $this->base_tpl_view = 'translation_form.tpl'; return parent::renderView(); } /** * This method generate the form for back translations */ public function initFormBack() { $translation_informations = $this->translations_informations[$this->type_selected]; $name_var = $translation_informations['var']; $$name_var = $this->fileExists(); $missing_translations_back = array(); $missing_translations_found = array(); $tab_array = array(); // Get all types of file (PHP, TPL...) and a list of files to parse by folder $files_per_directory = $this->getFileToParseByTypeTranslation(); foreach($files_per_directory['php'] as $dir => $files) { foreach($files as $file) { // Check if is a PHP file and if the override file exists if(preg_match('/^(.*)\.php$/', $file) && Tools::file_exists_cache($file_path = $dir.$file) && !in_array($file, self::$ignore_folder) ) { $prefix_key = basename($file); // -14 characters to remove the ending "Controller.php" from the filename if(strpos($file, 'Controller.php') !== false) { $prefix_key = basename(substr($file, 0, -14)); } elseif(strpos($file, 'Helper') !== false) { $prefix_key = 'Helper'; } if($prefix_key == 'Admin') { $prefix_key = 'AdminController'; } if($prefix_key == 'PaymentModule.php') { $prefix_key = 'PaymentModule'; } // Get content for this file $content = file_get_contents($file_path); // Parse this content $matches = $this->userParseFile($content, $this->type_selected, 'php'); foreach($matches as $key) { // Caution ! front has underscore between prefix key and md5, back has not if(isset(${$name_var}[$prefix_key.md5((string)$key)])) { $tabs_array[$prefix_key][$key]['trad'] = stripslashes(html_entity_decode( ${$name_var}[$prefix_key.md5((string)$key)], ENT_COMPAT, 'UTF-8' )); } else { if(!isset($tabs_array[$prefix_key][$key]['trad'])) { $tabs_array[$prefix_key][$key]['trad'] = ''; if(!isset($missing_translations_back[$prefix_key])) { $missing_translations_back[$prefix_key] = 1; $missing_translations_found[$prefix_key] = array(); $missing_translations_found[$prefix_key][] = $key; } elseif(!in_array($key, $missing_translations_found[$prefix_key])) { $missing_translations_back[$prefix_key]++; $missing_translations_found[$prefix_key][] = $key; } } } $tabs_array[$prefix_key][$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key); } } } } foreach($files_per_directory['specific'] as $dir => $files) { foreach($files as $file) { if(Tools::file_exists_cache($file_path = $dir.$file) && !in_array($file, self::$ignore_folder) ) { $prefix_key = 'index'; // Get content for this file $content = file_get_contents($file_path); // Parse this content $matches = $this->userParseFile($content, $this->type_selected, 'specific'); foreach($matches as $key) { // Caution ! front has underscore between prefix key and md5, back has not if(isset(${$name_var}[$prefix_key.md5((string)$key)])) { $tabs_array[$prefix_key][$key]['trad'] = stripslashes(html_entity_decode( ${$name_var}[$prefix_key.md5((string)$key)], ENT_COMPAT, 'UTF-8' )); } else { if(!isset($tabs_array[$prefix_key][$key]['trad'])) { $tabs_array[$prefix_key][$key]['trad'] = ''; if(!isset($missing_translations_back[$prefix_key])) { $missing_translations_back[$prefix_key] = 1; $missing_translations_found[$prefix_key] = array(); $missing_translations_found[$prefix_key][] = $key; } elseif(!in_array($key, $missing_translations_found[$prefix_key])) { $missing_translations_back[$prefix_key]++; $missing_translations_found[$prefix_key][] = $key; } } } $tabs_array[$prefix_key][$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key); } } } } foreach($files_per_directory['tpl'] as $dir => $files) { foreach($files as $file) { if(preg_match('/^(.*).tpl$/', $file) && Tools::file_exists_cache($file_path = $dir.$file) ) { // get controller name instead of file name $prefix_key = Tools::toCamelCase(str_replace( _PS_ADMIN_DIR_.DIRECTORY_SEPARATOR.'themes', '', $file_path ), true); $pos = strrpos($prefix_key, DIRECTORY_SEPARATOR); $tmp = substr($prefix_key, 0, $pos); if(preg_match('#controllers#', $tmp)) { $parent_class = explode(DIRECTORY_SEPARATOR, str_replace('/', DIRECTORY_SEPARATOR, $tmp)); $override = array_search('override', $parent_class); if($override !== false) { // Case override/controllers/admin/templates/controller_name $prefix_key = 'Admin'.ucfirst($parent_class[$override + 4]); } else { // Case admin_name/themes/theme_name/template/controllers/controller_name $key = array_search('controllers', $parent_class); $prefix_key = 'Admin'.ucfirst($parent_class[$key + 1]); } } else { $prefix_key = 'Admin'.ucfirst(substr($tmp, strrpos($tmp, DIRECTORY_SEPARATOR) + 1, $pos)); } // Adding list, form, option in Helper Translations $list_prefix_key = array(' AdminHelpers', 'AdminList', 'AdminView', 'AdminOptions', 'AdminForm', 'AdminCalendar', 'AdminTree', 'AdminUploader', 'AdminDataviz', 'AdminKpi', 'AdminModule_list', 'AdminModulesList' ); if(in_array($prefix_key, $list_prefix_key)) { $prefix_key = 'Helper'; } // Adding the folder backup/download/ in AdminBackup Translations if($prefix_key == 'AdminDownload') { $prefix_key = 'AdminBackup'; } // Use the prefix "AdminController" (like old php files 'header'...) if($prefix_key == 'Admin' || $prefix_key == 'AdminTemplate') { $prefix_key = 'AdminController'; } $new_lang = array(); // Get content for this file $content = file_get_contents($file_path); // Parse this content $matches = $this->userParseFile($content, $this->type_selected, 'tpl'); /* Get string translation for each tpl file */ foreach($matches as $english_string) { if(empty($english_string)) { $this->errors[] = sprintf( $this->l('There is an error in template, an empty string has been found. Please edit: "%s"'), $file_path ); $new_lang[$english_string] = ''; } else { $trans_key = $prefix_key.md5((string)$english_string); if(isset(${$name_var}[$trans_key])) { $new_lang[$english_string]['trad'] = html_entity_decode( ${$name_var}[$trans_key], ENT_COMPAT, 'UTF-8' ); } else { if(!isset($new_lang[$english_string]['trad'])) { $new_lang[$english_string]['trad'] = ''; if(!isset($missing_translations_back[$prefix_key])) { $missing_translations_back[$prefix_key] = 1; $missing_translations_found[$prefix_key] = array(); $missing_translations_found[$prefix_key][] = $english_string; } elseif(!in_array($english_string, $missing_translations_found[$prefix_key])) { $missing_translations_back[$prefix_key]++; $missing_translations_found[$prefix_key][] = $english_string; } } } $new_lang[$english_string]['use_sprintf'] = $this->checkIfKeyUseSprintf($english_string); } } if(isset($tabs_array[$prefix_key])) { $tabs_array[$prefix_key] = array_merge($tabs_array[$prefix_key], $new_lang); } else { $tabs_array[$prefix_key] = $new_lang; } } } } // Clean unused translations & save file $translations = array(); $file_path = $translation_informations['dir'].$translation_informations['file']; foreach($tabs_array as $prefix_key => $strings_to_translate) { foreach($strings_to_translate as $key => $field) { if($field['trad']) $translations[$prefix_key.md5((string)$key)] = pSQL(str_replace('\\\\', '', $field['trad']), true); } } ksort($translations); $text = " $field) { $text .= '$'.$name_var.'[\''.$key.'\'] = \''.str_replace('\\\\', '', $field).'\';'."\n"; } file_put_contents($file_path, $text); // Count will contain the number of expressions of the page $count = 0; foreach($tabs_array as $array) { $count += count($array); } $this->tpl_view_vars = array_merge($this->tpl_view_vars, array( 'count' => $count, 'cancel_url' => $this->context->link->getAdminLink('AdminTranslations'), 'tabsArray' => $tabs_array, 'missing_translations' => $missing_translations_back )); $this->initToolbar(); $this->base_tpl_view = 'translation_form.tpl'; return parent::renderView(); } /** * Check if directory and file exist and return an list of modules * * @return array List of modules * @throws PrestaShopException */ public function getListModules() { if(!Tools::file_exists_cache($this->translations_informations['modules']['dir'])) { throw new PrestaShopException(Tools::displayError('Fatal error: The module directory does not exist.') .'('.$this->translations_informations['modules']['dir'].')'); } if(!is_writable($this->translations_informations['modules']['dir'])) { throw new PrestaShopException(Tools::displayError('The module directory must be writable.')); } $modules = Module::getModulesInstalled(); // List of only BO modules $bo_modules = array( 'autoupgrade', 'cronjobs', 'dashactivity', 'dashgoals', 'dashproducts', 'dashtrends', 'emailgenerator', 'emarketing', 'graphnvd3', 'gridhtml', 'googleanalytics', 'gsitemap', 'imageregenerator', 'newsletter', 'pagesnotfound', 'pscleaner', 'secondimage', 'statsbestcategories', 'statsbestcustomers', 'statsbestmanufacturers', 'statsbestproducts', 'statsbestsuppliers', 'statsbestvouchers', 'statscarrier', 'statscatalog', 'statscheckup', 'statsdata', 'statsequipment', 'statsforecast', 'statslive', 'statsnewsletter', 'statsorigin', 'statspersonalinfos', 'statsproduct', 'statsregistrations', 'statssales', 'statssearch', 'statsstock', 'statsvisits', 'vatnumber', 'watermark' ); foreach($modules as $key => &$module) { // BO modules are not translated in FO theme if($this->theme_selected) { if($module['bo_only'] || in_array($module['name'], $bo_modules)) { unset($modules[$key]); continue; } } $module = $module['name']; } return $modules; } /** * This method generate the form for errors translations */ public function initFormErrors() { $translation_informations = $this->translations_informations[$this->type_selected]; $name_var = $translation_informations['var']; $$name_var = $this->fileExists(); $count_empty = array(); /* List files to parse */ $string_to_translate = array(); $file_by_directory = $this->getFileToParseByTypeTranslation(); if($modules = $this->getListModules()) { foreach($modules as $module) { if(is_dir(_PS_MODULE_DIR_.$module) && !in_array($module, self::$ignore_folder)) { $file_by_directory['php'] = array_merge( $file_by_directory['php'], $this->listFiles(_PS_MODULE_DIR_.$module.'/', array(), 'php') ); } } } foreach($file_by_directory['php'] as $dir => $files) { foreach($files as $file) { if(preg_match('/\.php$/', $file) && Tools::file_exists_cache($file_path = $dir.$file) && !in_array($file, self::$ignore_folder) ) { if(!filesize($file_path)) { continue; } // Get content for this file $content = file_get_contents($file_path); // Parse this content $matches = $this->userParseFile($content, $this->type_selected); foreach($matches as $key) { if(array_key_exists(md5((string)$key), ${$name_var})) { $string_to_translate[$key]['trad'] = html_entity_decode( ${$name_var}[md5((string)$key)], ENT_COMPAT, 'UTF-8' ); } else { $string_to_translate[$key]['trad'] = ''; if(!isset($count_empty[$key])) { $count_empty[$key] = 1; } } $string_to_translate[$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key); } } } } // Clean & save translations file $errors_file_path = $translation_informations['dir'].$translation_informations['file']; $translations = array(); foreach($string_to_translate as $key => $field) { if($field['trad']) $translations[md5((string)$key)] = pSQL(str_replace('\\\\', '', $field['trad']), true); } ksort($translations); $text = " $field) { $text .= '$'.$name_var.'[\''.$key.'\'] = \''.str_replace('\\\\', '', $field).'\';'."\n"; } file_put_contents($errors_file_path, $text); $this->tpl_view_vars = array_merge($this->tpl_view_vars, array( 'count' => count($string_to_translate), 'cancel_url' => $this->context->link->getAdminLink('AdminTranslations'), 'errorsArray' => $string_to_translate, 'missing_translations' => $count_empty )); $this->initToolbar(); $this->base_tpl_view = 'translation_errors.tpl'; return parent::renderView(); } /** * This method generate the form for confirmations translations */ public function initFormConfirmations() { $translation_informations = $this->translations_informations[$this->type_selected]; $name_var = $translation_informations['var']; $$name_var = $this->fileExists(); $count_empty = array(); /* List files to parse */ $string_to_translate = array(); $file_by_directory = $this->getFileToParseByTypeTranslation(); if($modules = $this->getListModules()) { foreach($modules as $module) { if(is_dir(_PS_MODULE_DIR_.$module) && !in_array($module, self::$ignore_folder)) { $file_by_directory['php'] = array_merge( $file_by_directory['php'], $this->listFiles(_PS_MODULE_DIR_.$module.'/', array(), 'php') ); } } } foreach($file_by_directory['php'] as $dir => $files) { foreach($files as $file) { if(preg_match('/\.php$/', $file) && Tools::file_exists_cache($file_path = $dir.$file) && !in_array($file, self::$ignore_folder) ) { if(!filesize($file_path)) { continue; } // Get content for this file $content = file_get_contents($file_path); // Parse this content $matches = $this->userParseFile($content, $this->type_selected); foreach($matches as $key) { if(array_key_exists(md5((string)$key), ${$name_var})) { $string_to_translate[$key]['trad'] = html_entity_decode( ${$name_var}[md5((string)$key)], ENT_COMPAT, 'UTF-8' ); } else { $string_to_translate[$key]['trad'] = ''; if(!isset($count_empty[$key])) { $count_empty[$key] = 1; } } $string_to_translate[$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key); } } } } // Clean & save translations file $confirmations_file_path = $translation_informations['dir'].$translation_informations['file']; $translations = array(); foreach($string_to_translate as $key => $field) { if($field['trad']) $translations[md5((string)$key)] = pSQL(str_replace('\\\\', '', $field['trad']), true); } ksort($translations); $text = " $field) { $text .= '$'.$name_var.'[\''.$key.'\'] = \''.str_replace('\\\\', '', $field).'\';'."\n"; } file_put_contents($confirmations_file_path, $text); $this->tpl_view_vars = array_merge($this->tpl_view_vars, array( 'count' => count($string_to_translate), 'cancel_url' => $this->context->link->getAdminLink('AdminTranslations'), 'confirmationsArray' => $string_to_translate, 'missing_translations' => $count_empty )); $this->initToolbar(); $this->base_tpl_view = 'translation_confirmations.tpl'; return parent::renderView(); } /** * This method generate the form for fields translations */ public function initFormFields() { $name_var = $this->translations_informations[$this->type_selected]['var']; $$name_var = $this->fileExists(); $missing_translations_fields = array(); $class_array = array(); $tabs_array = array(); $count = 0; $files_by_directory = $this->getFileToParseByTypeTranslation(); foreach($files_by_directory['php'] as $dir => $files) { foreach($files as $file) { $exclude_files = array( 'index.php', 'PrestaShopAutoload.php', 'StockManagerInterface.php', 'TaxManagerInterface.php', 'WebserviceOutputInterface.php', 'WebserviceSpecificManagementInterface.php' ); if(!preg_match('/\.php$/', $file) || in_array($file, $exclude_files)) { continue; } $class_name = substr($file, 0, -4); if(!class_exists($class_name, false) && !class_exists($class_name.'Core', false) ) { PrestaShopAutoload::getInstance()->load($class_name); } if(!is_subclass_of($class_name.'Core', 'ObjectModel')) { continue; } // PHP 8 if($class_name == 'Attribute') { $class_name = 'AttributeProduct'; } $class_array[$class_name] = call_user_func( array($class_name, 'getValidationRules'), $class_name ); } } foreach($class_array as $prefix_key => $rules) { if(isset($rules['validate'])) { foreach($rules['validate'] as $key => $value) { if(isset(${$name_var}[$prefix_key.'_'.md5((string)$key)])) { $tabs_array[$prefix_key][$key]['trad'] = html_entity_decode( ${$name_var}[$prefix_key.'_'.md5((string)$key)], ENT_COMPAT, 'UTF-8' ); $count++; } else { if(!isset($tabs_array[$prefix_key][$key]['trad'])) { $tabs_array[$prefix_key][$key]['trad'] = ''; if(!isset($missing_translations_fields[$prefix_key])) { $missing_translations_fields[$prefix_key] = 1; } else { $missing_translations_fields[$prefix_key]++; } $count++; } } } } if(isset($rules['validateLang'])) { foreach($rules['validateLang'] as $key => $value) { if(isset(${$name_var}[$prefix_key.'_'.md5((string)$key)])) { $tabs_array[$prefix_key][$key]['trad'] = ''; if(array_key_exists($prefix_key.'_'.md5((string)addslashes($key)), ${$name_var})) { $tabs_array[$prefix_key][$key]['trad'] = html_entity_decode( ${$name_var}[$prefix_key.'_'.md5((string)addslashes($key))], ENT_COMPAT, 'UTF-8' ); } $count++; } else { if(!isset($tabs_array[$prefix_key][$key]['trad'])) { $tabs_array[$prefix_key][$key]['trad'] = ''; if(!isset($missing_translations_fields[$prefix_key])) { $missing_translations_fields[$prefix_key] = 1; } else { $missing_translations_fields[$prefix_key]++; } $count++; } } } } } $this->tpl_view_vars = array_merge($this->tpl_view_vars, array( 'count' => $count, 'tabsArray' => $tabs_array, 'cancel_url' => $this->context->link->getAdminLink('AdminTranslations'), 'missing_translations' => $missing_translations_fields )); $this->initToolbar(); $this->base_tpl_view = 'translation_form.tpl'; return parent::renderView(); } /** * Get each informations for each mails found in the folder $dir. * * @since 1.4.0.14 * @param string $dir * @param string $group_name * @return array : list of mails */ public function getMailFiles($dir, $group_name = 'mail') { $arr_return = array(); if(Language::getIdByIso('en')) { $default_language = 'en'; } else { $default_language = Language::getIsoById((int)Configuration::get('PS_LANG_DEFAULT')); } if(!$default_language || !Validate::isLanguageIsoCode($default_language)) { return false; } if(!extension_loaded('tidy')) { $this->displayWarning(Tools::displayError('Warning: Tidy PHP extension is not loaded on your server, so mails will not be formated')); } // Very usefull to name input and textarea fields $arr_return['group_name'] = $group_name; $arr_return['missing'] = array(); $arr_return['empty_values'] = 0; $arr_return['total_filled'] = 0; $arr_return['directory'] = $dir; // Get path for english mail directory $dir_en = str_replace('/'.$this->lang_selected->iso_code.'/', '/'.$default_language.'/', $dir); if(Tools::file_exists_cache($dir_en)) { // Get all english files to compare with the language to translate foreach(scandir($dir_en) as $email_file) { // Clean unused old txt files if(strripos($email_file, '.txt') > 0) { unlink($dir_en.$email_file); if(file_exists($dir.$email_file)) { unlink($dir.$email_file); } } if(strripos($email_file, '.html') > 0) { $email_name = substr($email_file, 0, strripos($email_file, '.')); // Clean unused old html files if(in_array($email_name, array( 'header', 'header_order_conf', 'footer', 'password', 'employee_password', 'password_query' ) )) { unlink($dir_en.$email_file); if(file_exists($dir.$email_file)) { unlink($dir.$email_file); } continue; } $type = substr($email_file, strripos($email_file, '.') + 1); if(!isset($arr_return['files'][$email_name])) { $arr_return['files'] [$email_name] = array(); } // $email_file is from scandir ($dir), so we already know that file exists $arr_return['files'] [$email_name] [$type] ['en'] = $this->getMailContent($dir_en, $email_file); // Check if the file exists in the language to translate if(Tools::file_exists_cache($dir.'/'.$email_file)) { $arr_return['files'] [$email_name] [$type] [$this->lang_selected->iso_code] = $this->getMailContent($dir, $email_file); $this->total_expression++; $arr_return['total_filled']++; } else { // Try to load default core mail in selected language $selected_lang = Tools::getValue('lang'); if(file_exists(_PS_MAIL_DIR_.$selected_lang.'/'.$email_file) && copy(_PS_MAIL_DIR_.$selected_lang.'/'.$email_file, $dir.'/'.$email_file) ) { $arr_return['files'] [$email_name] [$type] [$this->lang_selected->iso_code] = $this->getMailContent($dir, $email_file); $this->total_expression++; $arr_return['total_filled']++; } else { $arr_return['missing'][$email_name] = true; $arr_return['files'] [$email_name] [$type] [$this->lang_selected->iso_code] = $this->getMailContent($dir_en, $email_file); $arr_return['empty_values']++; } } } } } else { $this->warnings[] = sprintf( Tools::displayError('A mail directory exists for the "%1$s" language, but not for the default language (%3$s) in %2$s'), $this->lang_selected->iso_code, str_replace(_PS_ROOT_DIR_, '', dirname($dir)), $default_language ); } return $arr_return; } /** * Get content of the mail file. * * @param string $dir * @param string $file * @return array : content of file */ protected function getMailContent($dir, $file) { $content = file_get_contents($dir.'/'.$file); if(Tools::strlen($content) === 0) { $content = ''; } return $content; } /** * Display mails in html format. * This was create for factorize the html displaying * @since 1.4.0.14 * * @param array $mails * @param array $all_subject_mail * @param Language $obj_lang * @param string $id_html Use for set html id attribute for the block * @param string $title Set the title for the block * @param string|bool $name_for_module Is not false define add a name for distinguish mails module * * @return string */ protected function displayMailContent($mails, $all_subject_mail, $obj_lang, $id_html, $title, $name_for_module = false) { $group_name = 'mail'; if(array_key_exists('group_name', $mails)) { $group_name = $mails['group_name']; } $str_header = '
'; $str_return = '
'; if(!empty($mails['files'])) { $header_alert = array(); $missing_translations = 0; $topic_already_displayed = array(); foreach($mails['files'] as $mail_name => $mail_files) { $subject = ''; $missing_translation = 0; $header_alert[$mail_name] = ''; if(array_key_exists('html', $mail_files)) { if(array_key_exists($mail_name, $all_subject_mail)) { foreach($all_subject_mail[$mail_name] as $subject_mail) { $subject_key = 'subject['.Tools::htmlentitiesUTF8($group_name).']['.Tools::htmlentitiesUTF8($subject_mail).']'; if(in_array($subject_key, $topic_already_displayed)) { continue; } $topic_already_displayed[] = $subject_key; $value_subject_mail = isset($mails['subject'][$subject_mail]) ? $mails['subject'][$subject_mail] : ''; if(empty($value_subject_mail) || empty(trim($value_subject_mail['trad']))) { $missing_translation++; } $subject .= '
'; if(!empty($value_subject_mail['trad'])) { $subject .= ''; } else { $subject .= ''; } $subject .= '

'.stripcslashes($subject_mail).'

'; $subject .= '
'; } if($missing_translation) { $header_alert[$mail_name] = ' '.$missing_translation.' '.$this->l('missing translation(s)').' '; $missing_translations += $missing_translation; } } else { $subject .= '
'.sprintf($this->l('No Subject was found for %s in the database.'), $mail_name).'
'; } $missing = isset($mails['missing'][$mail_name]) ? ''.$this->l('Missing file, copied from english language').'' : false; $str_return .= '
'.$missing.' '.$mail_name.' '.$header_alert[$mail_name].'
'; } } if($mails['empty_values'] == 0 && !$missing_translations) { $translation_missing_badge_type = 'badge-success'; } else { $translation_missing_badge_type = 'badge-danger'; } $str_header .= '

'.((int)$mails['empty_values'] + (int)$mails['total_filled']).' '.$title.' '.((int)$mails['empty_values'] + (int)$missing_translations).' '.$this->l('missing translation(s)').'

'; } else { $str_return .= '

'.$this->l('There was a problem getting the mail files.').'
'.sprintf( $this->l('English language files must exist in %s folder'), ''.preg_replace('@/[a-z]{2}(/?)$@', '/en$1', $mails['directory']).'' ).'

'; } $str_return .= '
'; return $str_header.$str_return; } /** * Just build the html structure for display html mails. * * @since 1.4.0.14 * @param array $content With english and language needed contents * @param string $lang ISO code of the needed language * @param string $url for The html page and displaying an outline * @param string $mail_name Name of the file to translate (same for txt and html files) * @param string $group_name Group name allow to distinguish each block of mail. * @param string|bool $name_for_module Is not false define add a name for distinguish mails module * * @return string */ protected function displayMailBlockHtml($content, $lang, $url, $mail_name, $group_name, $name_for_module = false) { $title = array(); $this->cleanMailContent($content, $lang, $title); $name_for_module = $name_for_module ? $name_for_module.'|' : ''; return '

'.(isset($title['en']) ? $title['en'] : '').'

'; } protected function displayMailEditor($content, $lang, $mail_name, $group_name, $name_for_module = false) { $title = array(); $this->cleanMailContent($content, $lang, $title); $name_for_module = $name_for_module ? $name_for_module.'|' : ''; return ' '; } protected function cleanMailContent(&$content, $lang, &$title) { if(stripos($content[$lang], '([^<]+)#Ui', $title[$language], $matches); $title[$language] = empty($matches[1])?'':$matches[1]; } $content[$lang] = str_replace('{shop_logo}', _PS_IMG_.'logo.jpg', $content[$lang]); $content[$lang] = preg_replace( '~({shop_name}) (.*)()~i', '
{shop_name}
', $content[$lang] ); } $content[$lang] = (isset($content[$lang]) ? Tools::htmlentitiesUTF8(stripslashes($content[$lang])) : ''); } /** * Check in each module if contains mails folder. * * @param bool $with_module_name * * @return array Array of modules which have mails */ public function getModulesHasMails($with_module_name = false) { $arr_modules = array(); foreach(scandir($this->translations_informations['modules']['dir']) as $module_dir) { if(is_file($this->translations_informations['modules']['dir'].$module_dir)) { if(!in_array($module_dir, array('.', '..', '.htaccess', 'index.php'))) { @unlink($this->translations_informations['modules']['dir'].$module_dir); } continue; } if(!in_array($module_dir, self::$ignore_folder)) { $dir = false; if($this->theme_selected && Tools::file_exists_cache($this->translations_informations['modules']['override']['dir'].$module_dir.'/mails/') ) { $dir = $this->translations_informations['modules']['override']['dir'].$module_dir.'/'; } elseif(Tools::file_exists_cache($this->translations_informations['modules']['dir'].$module_dir.'/mails/')) { $dir = $this->translations_informations['modules']['dir'].$module_dir.'/'; } if($dir !== false) { if($with_module_name) { $arr_modules[$module_dir] = $dir; } else { if($this->theme_selected) { $dir = $this->translations_informations['modules']['dir'].$module_dir.'/'; } $arr_modules[$dir] = scandir($dir); } } } } return $arr_modules; } /** * Check in each module if contains pdf folder. * * @param bool $classes * * @return array Array of modules which have pdf */ public function getModulesHasPDF($classes = false) { $arr_modules = array(); foreach(scandir($this->translations_informations['modules']['dir']) as $module_dir) { if(is_file($this->translations_informations['modules']['dir'].$module_dir)) { if(!in_array($module_dir, array('.', '..', '.htaccess', 'index.php'))) { @unlink(_PS_MODULE_DIR_.$module_dir); } continue; } if(!in_array($module_dir, self::$ignore_folder)) { $dir = false; if($classes) { if($this->theme_selected && Tools::file_exists_cache($this->translations_informations['modules']['override']['dir'].$module_dir.'/classes/pdf/') ) { $dir = $this->translations_informations['modules']['override']['dir'].$module_dir.'/classes/pdf/'; } elseif(Tools::file_exists_cache($this->translations_informations['modules']['dir'].$module_dir.'/classes/pdf/')) { $dir = $this->translations_informations['modules']['dir'].$module_dir.'/classes/pdf/'; } if($dir !== false) { $arr_modules[$dir] = scandir($dir); } } else { if($this->theme_selected && Tools::file_exists_cache($this->translations_informations['modules']['override']['dir'].$module_dir.'/pdf/') ) { $dir = $this->translations_informations['modules']['override']['dir'].$module_dir.'/pdf/'; } elseif(Tools::file_exists_cache($this->translations_informations['modules']['dir'].$module_dir.'/pdf/')) { $dir = $this->translations_informations['modules']['dir'].$module_dir.'/pdf/'; } if($dir !== false) { $arr_modules[$dir] = scandir($dir); } } } } return $arr_modules; } protected function getTinyMCEForMails($iso_lang) { $iso_tiny_mce = (Tools::file_exists_cache(_PS_ROOT_DIR_.'/js/tiny_mce/langs/'.$iso_lang.'.js') ? $iso_lang : 'en'); $ad = __PS_BASE_URI__.basename(_PS_ADMIN_DIR_); return ' '; } /** * This method generate the form for mails translations * * @param bool $no_display * * @return array|string */ public function initFormMails($no_display = false) { $module_mails = array(); // Get all mail subjects, this method parse each files in PhenixSuite $subject_mail = array(); $modules_has_mails = $this->getModulesHasMails(true); $files_by_directories = $this->getFileToParseByTypeTranslation(); if(!$this->theme_selected || !file_exists($this->translations_informations[$this->type_selected]['override']['dir']) ) { $this->copyMailFilesForAllLanguages(); } foreach($files_by_directories['php'] as $dir => $files) { foreach($files as $file) { // If file exist and is not in ignore_folder, in the next step we check if a folder or mail if(Tools::file_exists_cache($dir.$file) && !in_array($file, self::$ignore_folder)) { $subject_mail = $this->getSubjectMail($dir, $file, $subject_mail); } } } // Get path of directory for find a good path of translation file if($this->theme_selected) { if(Tools::file_exists_cache($this->translations_informations[$this->type_selected]['override']['dir'] .$this->translations_informations[$this->type_selected]['file']) ) { $i18n_dir = $this->translations_informations[$this->type_selected]['override']['dir']; } // Child theme elseif(_PARENT_THEME_NAME_) { $parent_dir = str_replace( $this->theme_selected, _PARENT_THEME_NAME_, $this->translations_informations[$this->type_selected]['override']['dir'] ); if(Tools::file_exists_cache($parent_dir.$this->translations_informations[$this->type_selected]['file'])) { // Copy current lang.php in child theme if not exists if(!file_exists(dirname($this->translations_informations[$this->type_selected]['override']['dir']))) { Tools::mkdirAndIndex($this->translations_informations[$this->type_selected]['override']['dir'], 0755, true); copy( $parent_dir.$this->translations_informations[$this->type_selected]['file'], $this->translations_informations[$this->type_selected]['override']['dir'] .$this->translations_informations[$this->type_selected]['file'] ); } // Add default language (en) if not exists if(!file_exists(dirname($this->translations_informations[$this->type_selected]['override']['dir']).'/en')) { Tools::mkdirAndIndex(dirname($this->translations_informations[$this->type_selected]['override']['dir']).'/en'); if($lang = Tools::getValue('lang')) { if(file_exists(str_replace($lang, 'en', $parent_dir.$this->translations_informations[$this->type_selected]['file']))) { $default_file = str_replace($lang, 'en', $parent_dir.$this->translations_informations[$this->type_selected]['file']); } else { $default_file = _PS_MAIL_DIR_.'/en/lang.php'; } if(file_exists($default_file)) { copy( $default_file, str_replace($lang, 'en', $this->translations_informations[$this->type_selected]['override']['dir'] .$this->translations_informations[$this->type_selected]['file']) ); } else { $this->errors[] = $this->l('Default translation file in english not found.'); } } } $i18n_dir = $parent_dir; } } } else { $i18n_dir = $this->translations_informations[$this->type_selected]['dir']; } $core_mails = $this->getMailFiles($i18n_dir, 'core_mail'); $core_mails['subject'] = $this->getSubjectMailContent($i18n_dir); foreach($modules_has_mails as $module_name => $module_path) { $module_path = rtrim($module_path, '/'); $module_mails[$module_name] = $this->getMailFiles( $module_path.'/mails/'.$this->lang_selected->iso_code.'/', 'module_mail' ); $module_mails[$module_name]['subject'] = $core_mails['subject']; $module_mails[$module_name]['display'] = $this->displayMailContent( $module_mails[$module_name], $subject_mail, $this->lang_selected, Tools::strtolower($module_name), $module_name, $module_name ); } if($no_display) { $empty = 0; $total = 0; $total += (int)$core_mails['total_filled']; $empty += (int)$core_mails['empty_values']; foreach($module_mails as $mod_infos) { $total += (int)$mod_infos['total_filled']; $empty += (int)$mod_infos['empty_values']; } return array('total' => $total, 'empty' => $empty); } $this->tpl_view_vars = array_merge($this->tpl_view_vars, array( 'limit_warning' => $this->displayLimitPostWarning($this->total_expression), 'mod_security_warning' => Tools::apacheModExists('mod_security'), 'tinyMCE' => $this->getTinyMCEForMails($this->lang_selected->iso_code), 'cancel_url' => $this->context->link->getAdminLink('AdminTranslations'), 'module_mails' => $module_mails, 'theme_name' => $this->theme_selected, 'mail_content' => $this->displayMailContent( $core_mails, $subject_mail, $this->lang_selected, 'core', $this->l('Core emails') ), )); $this->initToolbar(); $this->base_tpl_view = 'translation_mails.tpl'; return parent::renderView(); } public function copyMailFilesForAllLanguages() { $current_theme = Tools::safeOutput($this->context->theme->directory); $languages = Language::getLanguages(); foreach($languages as $key => $lang) { $dir_to_copy_iso = array(); $files_to_copy_iso = array(); $current_iso_code = $lang['iso_code']; $dir_to_copy_iso[] = _PS_MAIL_DIR_.$current_iso_code.'/'; $modules_has_mails = $this->getModulesHasMails(true); foreach($modules_has_mails as $module_name => $module_path) { if($pos = strpos($module_path, '/modules')) { if(Tools::file_exists_cache(_PS_ALL_THEMES_DIR_._PARENT_THEME_NAME_.'/modules/'.$module_name.'mails/'.$current_iso_code.'/')) { $dir_to_copy_iso[] = _PS_ALL_THEMES_DIR_._PARENT_THEME_NAME_.'/modules/'.$module_name.'mails/'.$current_iso_code.'/'; } elseif(Tools::file_exists_cache(_PS_ROOT_DIR_.substr($module_path, $pos).'mails/'.$current_iso_code.'/')) { $dir_to_copy_iso[] = _PS_ROOT_DIR_.substr($module_path, $pos).'mails/'.$current_iso_code.'/'; } } } foreach($dir_to_copy_iso as $dir) { if(!file_exists($dir)) { continue; } foreach(scandir($dir) as $file) { if(!in_array($file, self::$ignore_folder)) { $to = str_replace((strpos($dir, _PS_CORE_DIR_) !== false) ? _PS_CORE_DIR_ : _PS_ROOT_DIR_, _PS_ALL_THEMES_DIR_.$current_theme, $dir); $origin_dir = $dir; if(_PARENT_THEME_NAME_ && file_exists(_PS_ALL_THEMES_DIR_._PARENT_THEME_NAME_.'/mails/'.$current_iso_code.'/'.$file) ) { $origin_dir = _PS_ALL_THEMES_DIR_._PARENT_THEME_NAME_.'/mails/'.$current_iso_code.'/'; $to = _PS_ALL_THEMES_DIR_.$current_theme.'/mails/'.$current_iso_code.'/'; } $files_to_copy_iso[] = array( "from" => $origin_dir.$file, "to" => $to.$file ); } } } foreach($files_to_copy_iso as $file) { if(!file_exists($file['to'])) { $content = file_get_contents($file['from']); $stack = array(); $folder = dirname($file['to']); while(!is_dir($folder)) { array_push($stack, $folder); $folder = dirname($folder); } while($folder = array_pop($stack)) { Tools::mkdirAndIndex($folder); } $success = file_put_contents($file['to'], $content); if($success === false) { Tools::dieOrLog(sprintf( "%s cannot be copied to %s", $file['from'], $file['to'] ), false); } } } } return true; } /** * Get list of subjects of mails * * @param $dir * @param $file * @param $subject_mail * @return array : list of subjects of mails */ protected function getSubjectMail($dir, $file, $subject_mail) { $dir = rtrim($dir, '/'); // If is file and is not in ignore_folder if(is_file($dir.'/'.$file) && !in_array($file, self::$ignore_folder) && preg_match('/\.php$/', $file) ) { $content = file_get_contents($dir.'/'.$file); $content = str_replace("\n", ' ', $content); // Subject must match with a template, // therefore we first grep the Mail::Send() function then the Mail::l() inside. if(preg_match_all('/Mail::Send+(?:Core|)\(([^;]*)\)(?:\*\/|);/si', $content, $tab)) { for ($i = 0; isset($tab[1][$i]); $i++) { $tab2 = explode(',', $tab[1][$i]); if(is_array($tab2) && isset($tab2[1])) { $template = trim(str_replace('\'', '', $tab2[1])); foreach($tab2 as $tab3) { if(preg_match('/Mail::l\(\''._PS_TRANS_PATTERN_.'\'\)/Us', $tab3.')', $matches)) { if(!isset($subject_mail[$template])) { $subject_mail[$template] = array(); } if(!in_array($matches[1], $subject_mail[$template])) { $subject_mail[$template][] = $matches[1]; } } } } } } } // Or if is folder, we scan folder for check if found in folder and subfolder elseif(!in_array($file, self::$ignore_folder) && is_dir($dir.'/'.$file) ) { foreach(scandir($dir.'/'.$file) as $temp) { if($temp[0] != '.') { $subject_mail = $this->getSubjectMail($dir.'/'.$file, $temp, $subject_mail); } } } return $subject_mail; } /** * @param $directory : name of directory * @return array */ protected function getSubjectMailContent($directory) { $subject_mail_content = array(); if(Tools::file_exists_cache($directory.'/lang.php')) { if(function_exists('opcache_invalidate')) { opcache_invalidate($directory.'/lang.php', true); } include($directory.'/lang.php'); foreach($GLOBALS[$this->translations_informations[$this->type_selected]['var']] as $key => $subject) { $this->total_expression++; $subject = str_replace('\n', ' ', $subject); $subject = str_replace("\\'", "\'", $subject); $subject_mail_content[$key]['trad'] = htmlentities($subject, ENT_QUOTES, 'UTF-8'); $subject_mail_content[$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key); } } else { $this->errors[] = sprintf( $this->l('Email subject translation file not found in "%s".'), $directory ); } return $subject_mail_content; } protected function writeSubjectTranslationFile($sub, $path) { $tab = 'LANGMAIL'; if(!Tools::file_exists_cache(dirname($path))) { Tools::mkdirAndIndex(dirname($path), 0755); if(!file_put_contents($path, "")) { throw new PrestaShopException(sprintf( Tools::displayError('Cannot write language file for email subjects. Path is: %s'), $path )); } } include($path); foreach($sub as $key => $value) { $_LANGMAIL[pSQL($key)] = $value; } if($fd = @fopen($path, 'w')) { fwrite($fd, " $value) { fwrite($fd, '$_'.$tab.'[\''.pSQL(str_replace('\\\\', '', $key)).'\'] = \''.pSQL($value).'\';'."\n"); } fflush($fd); fclose($fd); } else { throw new PrestaShopException(sprintf( Tools::displayError('Cannot write language file for email subjects. Path is: %s'), $path )); } } /** * This get files to translate in module directory. * Recursive method allow to get each files for a module no matter his depth. * * @param string $path directory path to scan * @param array $array_files by reference - array which saved files to parse. * @param string $module_name module name * @param string $lang_file full path of translation file * @param bool $is_default */ protected function recursiveGetModuleFiles($path, &$array_files, $module_name, $lang_file, $is_default = false) { $files_module = array(); if(Tools::file_exists_cache($path)) { $files_module = scandir($path); } $files_for_module = $this->clearModuleFiles($files_module, 'file'); if(!empty($files_for_module)) { $array_files[] = array( 'file_name' => $lang_file, 'dir' => $path, 'files' => $files_for_module, 'module' => $module_name, 'is_default' => $is_default, 'theme' => $this->theme_selected, ); } $dir_module = $this->clearModuleFiles($files_module, 'directory', $path); if(!empty($dir_module)) { foreach($dir_module as $folder) { $this->recursiveGetModuleFiles( $path.$folder.'/', $array_files, $module_name, $lang_file, $is_default ); } } } /** * This method get translation in each translations file. * The file depend on $lang param. * * @param array $modules List of modules * @param string|null $root_dir path where it get each modules * @param string $lang ISO code of chosen language to translate * @param bool $is_default Set it if modules are located in root/phenixsuite/modules folder * This allow to distinguish overridden phenixsuite theme and original module * * @return array */ protected function getAllModuleFiles($modules, $root_dir = null, $lang = false, $is_default = false) { if(!$lang) { return array(); } $array_files = array(); $initial_root_dir = $root_dir; $translations_informations = $this->translations_informations[$this->type_selected]; $var = $translations_informations['var']; foreach($modules as $module) { $root_dir = $initial_root_dir; if($module[0] == '.') { continue; } // First we load the default translation file if($root_dir == null) { $i18n_dir = $translations_informations['dir']; if(is_dir($i18n_dir.$module)) { $root_dir = $i18n_dir; } $lang_file = $root_dir.$module.'/translations/'.$lang.'.php'; if(!Tools::file_exists_cache($root_dir.$module.'/translations/'.$lang.'.php') && Tools::file_exists_cache($root_dir.$module.'/'.$lang.'.php') ) { $lang_file = $root_dir.$module.'/'.$lang.'.php'; } if(file_exists($lang_file)) { $temp_dir = $root_dir.$module.DIRECTORY_SEPARATOR.'temp'.$var.'.php'; file_put_contents($temp_dir, str_replace('global $'.$var.';', '', str_replace('\\\\', '', file_get_contents($lang_file)))); include($temp_dir); unlink($temp_dir); if(is_array($_MODULE) && count($_MODULE)) { $this->global_modules = array_merge($this->global_modules, $_MODULE); } } // If a theme is selected, then the destination translation file must be in the theme if($this->theme_selected) { $lang_file = $translations_informations['override']['dir'].$module.'/translations/'.$lang.'.php'; } $this->recursiveGetModuleFiles( $root_dir.$module.'/', $array_files, $module, $lang_file, $is_default ); } $root_dir = $initial_root_dir; // Then we load the overriden translation file if($this->theme_selected && isset($translations_informations['override'])) { $lang_file = false; $i18n_dir = $translations_informations['override']['dir']; if(is_dir($i18n_dir.$module)) { $root_dir = $i18n_dir; } if(Tools::file_exists_cache($root_dir.$module.'/translations/'.$lang.'.php')) { $lang_file = $root_dir.$module.'/translations/'.$lang.'.php'; } elseif(Tools::file_exists_cache($root_dir.$module.'/'.$lang.'.php')) { $lang_file = $root_dir.$module.'/'.$lang.'.php'; } elseif(Tools::file_exists_cache(_PS_ALL_THEMES_DIR_._PARENT_THEME_NAME_.'/modules/'.$module.'/translations/'.$lang.'.php')) { $root_dir = _PS_ALL_THEMES_DIR_._PARENT_THEME_NAME_.'/modules/'; $lang_file = $root_dir.$module.'/translations/'.$lang.'.php'; // Update child theme first time Tools::mkdirAndIndex($i18n_dir.$module.'/translations/', 0755, true); file_put_contents( $i18n_dir.$module.'/translations/'.$lang.'.php', str_replace(_PARENT_THEME_NAME_, $this->theme_selected, file_get_contents($lang_file)) ); } if($lang_file) { $temp_dir = $root_dir.$module.DIRECTORY_SEPARATOR.'temp'.$var.'.php'; file_put_contents( $temp_dir, str_replace('global $'.$var.';', '', str_replace('\\\\', '', file_get_contents($lang_file))) ); include($temp_dir); unlink($temp_dir); if(is_array($_MODULE) && count($_MODULE)) { $this->global_modules = array_merge($this->global_modules, $_MODULE); } $this->recursiveGetModuleFiles( $root_dir.$module.'/', $array_files, $module, $lang_file, $is_default ); } } } return $array_files; } /** * This method generate the form for modules translations */ public function initFormModules() { // Get list of modules $modules = $this->getListModules(); if(!empty($modules)) { // Get all modules files and include all translation files $arr_files = $this->getAllModuleFiles( $modules, null, $this->lang_selected->iso_code, true ); foreach($arr_files as $value) { $this->findAndFillTranslations( $value['files'], $value['theme'], $value['module'], $value['dir'] ); } $this->tpl_view_vars = array_merge($this->tpl_view_vars, array( 'default_theme_name' => self::DEFAULT_THEME_NAME, 'count' => $this->total_expression, 'limit_warning' => $this->displayLimitPostWarning($this->total_expression), 'mod_security_warning' => Tools::apacheModExists('mod_security'), 'textarea_sized' => AdminTranslationsControllerCore::TEXTAREA_SIZED, 'cancel_url' => $this->context->link->getAdminLink('AdminTranslations'), 'modules_translations' => isset($this->modules_translations) ? $this->modules_translations : array(), 'missing_translations' => $this->missing_translations )); $this->initToolbar(); $this->base_tpl_view = 'translation_modules.tpl'; return parent::renderView(); } } /** * Parse PDF class * @since 1.4.5.0 * * @param string $file_path File to parse * @param string $file_type Type of file * @param array $lang_array Contains expression in the chosen language * @param string $tab name To use with the md5 key * @param array $tabs_array * @param array $count_missing * * @return array Array Containing all datas needed for building the translation form */ protected function parsePdfClass($file_path, $file_type, $lang_array, $tab, $tabs_array, &$count_missing) { // Get content for this file if(is_file($file_path)) { $content = file_get_contents($file_path); // Parse this content $matches = $this->userParseFile($content, $this->type_selected, $file_type); foreach($matches as $key) { if(stripslashes(array_key_exists($tab.md5((string)addslashes($key)), $lang_array))) { $tabs_array[$tab][$key]['trad'] = html_entity_decode( $lang_array[$tab.md5((string)addslashes($key))], ENT_COMPAT, 'UTF-8' ); } else { $tabs_array[$tab][$key]['trad'] = ''; if(!isset($count_missing[$tab])) { $count_missing[$tab] = 1; } else { $count_missing[$tab]++; } } $tabs_array[$tab][$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key); } } return $tabs_array; } /** * This method generate the form for PDF translations */ public function initFormPDF() { $name_var = $this->translations_informations[$this->type_selected]['var']; $$name_var = $this->fileExists(); $missing_translations_pdf = array(); $i18n_dir = $this->translations_informations[$this->type_selected]['dir']; $default_i18n_file = $i18n_dir.$this->translations_informations[$this->type_selected]['file']; if(!$this->theme_selected) { $i18n_file = $default_i18n_file; } else { $i18n_dir = $this->translations_informations[$this->type_selected]['override']['dir']; $i18n_file = $i18n_dir.$this->translations_informations[$this->type_selected]['override']['file']; } $this->checkDirAndCreate($i18n_file); if((!file_exists($i18n_file) && !is_writable($i18n_dir)) || (file_exists($i18n_file) && !is_writable($i18n_file)) ) { $this->errors[] = sprintf(Tools::displayError('Cannot write into the "%s"'), $i18n_file); } // Child theme if(_PARENT_THEME_NAME_ && $this->theme_selected && $name_var == '_LANGPDF' ) { // Get all existing translations in parent theme $languages = Language::getLanguages(); foreach($languages as $key => $lang) { $lang_file = $lang['iso_code'].'.php'; if(file_exists($i18n_dir) && !Tools::file_exists_cache($i18n_dir.$lang_file) ) { $parent_lang_path = str_replace($this->theme_selected, _PARENT_THEME_NAME_, $i18n_dir); if(file_exists($parent_lang_path.$lang_file)) { if(!file_exists($i18n_dir.'index.php')) { file_put_contents($i18n_dir.'index.php', Tools::getDefaultIndexContent($i18n_dir)); } if(!copy($parent_lang_path.$lang_file, $i18n_file)) { throw new PrestaShopException('File "'.$lang_file.'" doesn\'t exists and cannot be created in '.$i18n_dir); } } } } } if(file_exists($i18n_file)) { include($i18n_file); } // If the override's translation file is empty load the default file if(!isset(${$name_var}) || count(${$name_var}) == 0) { if(file_exists($default_i18n_file)) { include($default_i18n_file); } } $prefix_key = 'PDF'; $tabs_array = array($prefix_key => array()); $files_by_directory = $this->getFileToParseByTypeTranslation(); foreach($files_by_directory as $type => $directories) { foreach($directories as $dir => $files) { foreach($files as $file) { if(!in_array($file, self::$ignore_folder) && Tools::file_exists_cache($file_path = $dir.$file) ) { if($type == 'tpl') { if(Tools::file_exists_cache($file_path) && is_file($file_path)) { // Get content for this file $content = file_get_contents($file_path); // Parse this content $matches = $this->userParseFile($content, $this->type_selected, 'tpl'); foreach($matches as $key) { if(isset(${$name_var}[$prefix_key.md5((string)$key)])) { $tabs_array[$prefix_key][$key]['trad'] = html_entity_decode( ${$name_var}[$prefix_key.md5((string)$key)], ENT_COMPAT, 'UTF-8' ); } else { if(!isset($tabs_array[$prefix_key][$key]['trad'])) { $tabs_array[$prefix_key][$key]['trad'] = ''; if(!isset($missing_translations_pdf[$prefix_key])) { $missing_translations_pdf[$prefix_key] = 1; } else { $missing_translations_pdf[$prefix_key]++; } } } $tabs_array[$prefix_key][$key]['use_sprintf'] = $this->checkIfKeyUseSprintf($key); } } } elseif(Tools::file_exists_cache($file_path)) { $tabs_array = $this->parsePdfClass( $file_path, 'php', ${$name_var}, $prefix_key, $tabs_array, $missing_translations_pdf ); } } } } } $this->tpl_view_vars = array_merge($this->tpl_view_vars, array( 'count' => count($tabs_array['PDF']), 'tabsArray' => $tabs_array, 'cancel_url' => $this->context->link->getAdminLink('AdminTranslations'), 'missing_translations' => $missing_translations_pdf )); $this->initToolbar(); $this->base_tpl_view = 'translation_form.tpl'; return parent::renderView(); } /** * Recursively list files in directory $dir * * @param string $dir * @param array $list * @param string $file_ext * * @return array */ public function listFiles($dir, $list = array(), $file_ext = 'tpl') { $dir = rtrim($dir, '/').DIRECTORY_SEPARATOR; $to_parse = scandir($dir); // Copied (and kind of) adapted from AdminImages.php foreach($to_parse as $file) { if(!in_array($file, self::$ignore_folder)) { if(preg_match('#'.preg_quote($file_ext, '#').'$#i', $file)) { $list[$dir][] = $file; } elseif(is_dir($dir.$file)) { $list = $this->listFiles($dir.$file, $list, $file_ext); } } } return $list; } /** * Checks if theme exists * * @param string $theme * * @return bool */ protected function theme_exists($theme) { if(!is_array($this->themes)) { $this->themes = Theme::getThemes(); } $theme_exists = false; foreach($this->themes as $existing_theme) { if($existing_theme->directory == $theme) { return true; } } return false; } public static function getEmailHTML($email) { if(defined('_PS_HOST_MODE_') && strpos($email, _PS_MAIL_DIR_) !== false) { $email_file = $email; } elseif(__PS_BASE_URI__ != '/') { $email_file = str_replace(__PS_BASE_URI__, '', _PS_ROOT_DIR_.'/').$email; } else { $email_file = _PS_ROOT_DIR_.$email; } $email_html = file_get_contents($email_file); $email_html = str_replace('{shop_logo}', _PS_IMG_.'logo.jpg', $email_html); return $email_html; } }