diff --git a/admin/themes/default/template/controllers/translations/helpers/view/translation_mails.tpl b/admin/themes/default/template/controllers/translations/helpers/view/translation_mails.tpl index da8358912..5475edf88 100644 --- a/admin/themes/default/template/controllers/translations/helpers/view/translation_mails.tpl +++ b/admin/themes/default/template/controllers/translations/helpers/view/translation_mails.tpl @@ -54,6 +54,7 @@ + {/literal} diff --git a/controllers/admin/AdminTranslationsController.php b/controllers/admin/AdminTranslationsController.php index e56831c50..25248bfe6 100644 --- a/controllers/admin/AdminTranslationsController.php +++ b/controllers/admin/AdminTranslationsController.php @@ -1480,6 +1480,12 @@ class AdminTranslationsControllerCore extends AdminController } else { $this->errors[] = Tools::displayError('You do not have permission to edit this.'); } + } elseif (Tools::isSubmit('submitResetTranslationsMails')) { + if ($this->tabAccess['edit'] === 1) { + $this->submitResetTranslationsMails(); + } 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(); @@ -1663,6 +1669,51 @@ class AdminTranslationsControllerCore extends AdminController } } + protected function submitResetTranslationsMails() + { + if (!$this->theme_selected) { + return; + } + + $iso_code = $this->lang_selected->iso_code; + $module_name = false; + + $mail_name = Tools::getValue('reset_mail_name'); + if (($pos = stripos($mail_name, '|')) !== false) { + $module_name = substr($mail_name, 0, $pos); + $mail_name = substr($mail_name, $pos + 1); + } + if (!Validate::isTplName($mail_name) || ($module_name && !Validate::isModuleName($module_name))) { + $this->errors[] = Tools::displayError('Invalid mail template to reset.'); + return; + } + + if ($module_name) { + $core_dir = rtrim($this->translations_informations['modules']['dir'], '/').'/'.$module_name.'/mails/'.$iso_code.'/'; + $dest_dir = $this->theme_selected ? $this->translations_informations['modules']['override']['dir'].$module_name.'/mails/'.$iso_code.'/' : $core_dir; + } else { + $core_dir = $this->translations_informations['mails']['dir']; + $dest_dir = $this->theme_selected ? $this->translations_informations['mails']['override']['dir'] : $core_dir; + } + + if (!Tools::file_exists_cache($core_dir)) { + $this->errors[] = Tools::displayError('No core mail template found to reset from.'); + return; + } + + if (!file_exists($dest_dir) && !mkdir($dest_dir, 0777, true)) { + $this->errors[] = (sprintf(Tools::displayError('Directory "%s" cannot be created'), $dest_dir)); + } + + foreach (array($mail_name.'.html', $mail_name.'.txt') as $file) { + if (Tools::file_exists_cache($core_dir.$file)) { + file_put_contents($dest_dir.$file, file_get_contents($core_dir.$file)); + } + } + + $this->redirect(true); + } + /** * Include file $dir/$file and return the var $var declared in it. * This create the file if not exists @@ -2362,9 +2413,12 @@ class AdminTranslationsControllerCore extends AdminController $str_return .= ''; } + $mail_available_variables = $this->displayMailAvailableVariables($mail_files); + if (array_key_exists('txt', $mail_files)) { $str_return .= '
'; $str_return .= $this->displayMailBlockTxt($mail_files['txt'], $obj_lang->iso_code, $mail_name, $group_name, $name_for_module); + $str_return .= $mail_available_variables; $str_return .= '
'; } @@ -2372,6 +2426,7 @@ class AdminTranslationsControllerCore extends AdminController if (isset($mail_files['html'])) { $str_return .= $this->displayMailEditor($mail_files['html'], $obj_lang->iso_code, $url_mail, $mail_name, $group_name, $name_for_module); } + $str_return .= $mail_available_variables; $str_return .= ''; $str_return .= ''; @@ -2389,6 +2444,70 @@ class AdminTranslationsControllerCore extends AdminController $str_return .= ''; return $str_return; } + + /** + * Build the "Available variables" tag list for a mail template, so the + * admin can see which {variable} placeholders the template supports and + * click one to insert it into the editor. + * + * @param array $mail_files Same structure as $mails['files'][$mail_name] + * + * @return string + */ + protected function displayMailAvailableVariables($mail_files) + { + $variables = $this->getMailTemplateVariables($mail_files); + if (empty($variables)) { + return ''; + } + + // Bootstrap's .label is display:inline with vertical padding, so it can't take a + // vertical margin; without extra line-height here, wrapped rows visually overlap. + $str_return = '
'; + foreach ($variables as $variable) { + $tag = '{'.$variable.'}'; + $str_return .= ''.Tools::htmlentitiesUTF8($tag).' '; + } + $str_return .= '
'; + + return $str_return; + } + + /** + * Extract the unique {variable} placeholders used in a mail template's + * core content. Reads the 'en' key populated by getMailFiles(), which + * actually holds whichever language resolved as the shop's default + * (English, or PS_LANG_DEFAULT when English isn't an installed language) + * - falls back to any other available language content in the rare case + * that key isn't set. + * + * @param array $mail_files Same structure as $mails['files'][$mail_name] + * + * @return array Variable names, without braces, sorted alphabetically + */ + protected function getMailTemplateVariables($mail_files) + { + $content = ''; + foreach (array('html', 'txt') as $type) { + if (empty($mail_files[$type]) || !is_array($mail_files[$type])) { + continue; + } + if (isset($mail_files[$type]['en'])) { + $content .= ' '.$mail_files[$type]['en']; + } else { + $content .= ' '.reset($mail_files[$type]); + } + } + + $variables = array(); + if ($content !== '' && preg_match_all('/\{([a-zA-Z0-9_]+)\}/', $content, $matches)) { + $variables = array_unique($matches[1]); + sort($variables); + } + + return $variables; + } + /** * Just build the html structure for display txt mails * @since 1.4.0.14 @@ -3070,9 +3189,33 @@ class AdminTranslationsControllerCore extends AdminController } $sanitizedFilePath = realpath($email_file); - $permittedMailDir = realpath(_PS_MAIL_DIR_) . DIRECTORY_SEPARATOR; + if ($sanitizedFilePath === false) { + return false; + } - if ($sanitizedFilePath === false || $permittedMailDir === false || strpos($sanitizedFilePath, $permittedMailDir) !== 0) { + // Mail templates can also live under a theme's or a module's own + // "mails" override directory (themes//mails//, + // modules//mails//, or their theme-override copies), + // not just the core mails/ directory - so every permitted root below + // is additionally required to have a "mails" path segment. + $permitted_roots = array( + realpath(_PS_MAIL_DIR_), + realpath(_PS_ROOT_DIR_.'/themes/'), + realpath(_PS_ROOT_DIR_.'/modules/'), + ); + + $is_permitted = false; + foreach ($permitted_roots as $permitted_root) { + if ($permitted_root !== false + && strpos($sanitizedFilePath, $permitted_root.DIRECTORY_SEPARATOR) === 0 + && strpos($sanitizedFilePath, DIRECTORY_SEPARATOR.'mails'.DIRECTORY_SEPARATOR) !== false + ) { + $is_permitted = true; + break; + } + } + + if (!$is_permitted) { return false; }