diff --git a/phpBB/adm/style/acp_language.html b/phpBB/adm/style/acp_language.html index 5e3ac9532a..bcaca05019 100644 --- a/phpBB/adm/style/acp_language.html +++ b/phpBB/adm/style/acp_language.html @@ -24,6 +24,14 @@
{LANG_ISO}
+
+
+
{{ LANG_PHPBB_VERSION }}
+
+
+
+
{{ LANG_VERSION }}
+
diff --git a/phpBB/adm/style/acp_styles.html b/phpBB/adm/style/acp_styles.html index 81c24fe98a..3ce3b23ac1 100644 --- a/phpBB/adm/style/acp_styles.html +++ b/phpBB/adm/style/acp_styles.html @@ -127,7 +127,7 @@
{styles_list.COMMENT}
- +
{L_STYLE_PATH}{L_COLON} {styles_list.STYLE_PATH_FULL}
diff --git a/phpBB/includes/acp/acp_language.php b/phpBB/includes/acp/acp_language.php index b2d732d4a8..a647128624 100644 --- a/phpBB/includes/acp/acp_language.php +++ b/phpBB/includes/acp/acp_language.php @@ -19,48 +19,116 @@ if (!defined('IN_PHPBB')) exit; } +use phpbb\config\config; +use phpbb\db\driver\driver_interface; +use phpbb\event\dispatcher; +use phpbb\language\language; +use phpbb\language\language_file_helper; +use phpbb\log\log_interface; +use phpbb\request\request_interface; +use phpbb\template\template; +use phpbb\user; + class acp_language { var $u_action; - var $main_files; - var $language_header = ''; - var $lang_header = ''; var $language_file = ''; var $language_directory = ''; - function main($id, $mode) + /** @var config Config class */ + protected $config; + + /** @var driver_interface DBAL driver */ + protected $db; + + /** @var dispatcher Event dispatcher */ + protected $dispatcher; + + /** @var language Language class */ + protected $language; + + /** @var language_file_helper Language file helper */ + protected $language_helper; + + /** @var log_interface Logging class */ + protected $log; + + /** @var request_interface */ + protected $request; + + /** @var template Template class */ + protected $template; + + /** @var user User class */ + protected $user; + + /** @var string phpBB root path */ + protected $phpbb_root_path; + + /** @var string PHP file extension */ + protected $php_ext; + + /** @var string Page title */ + public $page_title = 'ACP_LANGUAGE_PACKS'; + + /** @var string Template name */ + public $tpl_name = 'acp_language'; + + /** + * acp_language constructor + */ + public function __construct() { global $config, $db, $user, $template, $phpbb_log, $phpbb_container; global $phpbb_root_path, $phpEx, $request, $phpbb_dispatcher; + $this->config = $config; + $this->db = $db; + $this->dispatcher = $phpbb_dispatcher; + $this->language = $phpbb_container->get('language'); + $this->language_helper = $phpbb_container->get('language.helper.language_file'); + $this->log = $phpbb_log; + $this->request = $request; + $this->template = $template; + $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $phpEx; + } + + /** + * Main handler for acp_language + * + * @param int $id Module ID + * @param string $mode Module mode + */ + public function main($id, $mode) + { if (!function_exists('validate_language_iso_name')) { - include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); } // Check and set some common vars - $action = (isset($_POST['update_details'])) ? 'update_details' : ''; - $action = (isset($_POST['remove_store'])) ? 'details' : $action; + $action = $this->request->is_set_post('update_details') ? 'update_details' : ''; + $action = $this->request->is_set_post('remove_store') ? 'details' : $action; - $submit = (empty($action) && !isset($_POST['update']) && !isset($_POST['test_connection'])) ? false : true; - $action = (empty($action)) ? $request->variable('action', '') : $action; + $submit = (empty($action) && !$this->request->is_set_post('update') && !$this->request->is_set_post('test_connection')) ? false : true; + $action = (empty($action)) ? $this->request->variable('action', '') : $action; $form_name = 'acp_lang'; add_form_key('acp_lang'); - $lang_id = $request->variable('id', 0); + $lang_id = $this->request->variable('id', 0); - $selected_lang_file = $request->variable('language_file', '|common.' . $phpEx); + $selected_lang_file = $this->request->variable('language_file', '|common.' . $this->php_ext); list($this->language_directory, $this->language_file) = explode('|', $selected_lang_file); $this->language_directory = basename($this->language_directory); $this->language_file = basename($this->language_file); - $user->add_lang('acp/language'); - $this->tpl_name = 'acp_language'; - $this->page_title = 'ACP_LANGUAGE_PACKS'; + $this->language->add_lang('acp/language'); switch ($action) { @@ -68,41 +136,41 @@ class acp_language if (!$submit || !check_form_key($form_name)) { - trigger_error($user->lang['FORM_INVALID']. adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('FORM_INVALID'). adm_back_link($this->u_action), E_USER_WARNING); } if (!$lang_id) { - trigger_error($user->lang['NO_LANG_ID'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('NO_LANG_ID') . adm_back_link($this->u_action), E_USER_WARNING); } $sql = 'SELECT * FROM ' . LANG_TABLE . " WHERE lang_id = $lang_id"; - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); $sql_ary = array( - 'lang_english_name' => $request->variable('lang_english_name', $row['lang_english_name']), - 'lang_local_name' => $request->variable('lang_local_name', $row['lang_local_name'], true), - 'lang_author' => $request->variable('lang_author', $row['lang_author'], true), + 'lang_english_name' => $this->request->variable('lang_english_name', $row['lang_english_name']), + 'lang_local_name' => $this->request->variable('lang_local_name', $row['lang_local_name'], true), + 'lang_author' => $this->request->variable('lang_author', $row['lang_author'], true), ); - $db->sql_query('UPDATE ' . LANG_TABLE . ' - SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + $this->db->sql_query('UPDATE ' . LANG_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' WHERE lang_id = ' . $lang_id); - $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_LANGUAGE_PACK_UPDATED', false, array($sql_ary['lang_english_name'])); + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_LANGUAGE_PACK_UPDATED', false, array($sql_ary['lang_english_name'])); - trigger_error($user->lang['LANGUAGE_DETAILS_UPDATED'] . adm_back_link($this->u_action)); + trigger_error($this->language->lang('LANGUAGE_DETAILS_UPDATED') . adm_back_link($this->u_action)); break; case 'details': if (!$lang_id) { - trigger_error($user->lang['NO_LANG_ID'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('NO_LANG_ID') . adm_back_link($this->u_action), E_USER_WARNING); } $this->page_title = 'LANGUAGE_PACK_DETAILS'; @@ -110,39 +178,52 @@ class acp_language $sql = 'SELECT * FROM ' . LANG_TABLE . ' WHERE lang_id = ' . $lang_id; - $result = $db->sql_query($sql); - $lang_entries = $db->sql_fetchrow($result); - $db->sql_freeresult($result); + $result = $this->db->sql_query($sql); + $lang_entries = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); if (!$lang_entries) { - trigger_error($user->lang['LANGUAGE_PACK_NOT_EXIST'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('LANGUAGE_PACK_NOT_EXIST') . adm_back_link($this->u_action), E_USER_WARNING); } $lang_iso = $lang_entries['lang_iso']; - $template->assign_vars(array( - 'S_DETAILS' => true, - 'U_ACTION' => $this->u_action . "&action=details&id=$lang_id", - 'U_BACK' => $this->u_action, + try + { + $lang_cfg = $this->language_helper->get_language_data_from_composer_file($this->phpbb_root_path . 'language/' . $lang_iso . '/composer.json'); + } + catch (\DomainException $e) + { + trigger_error($this->language->lang('LANGUAGE_PACK_NOT_EXIST') . adm_back_link($this->u_action), E_USER_WARNING); + } - 'LANG_LOCAL_NAME' => $lang_entries['lang_local_name'], - 'LANG_ENGLISH_NAME' => $lang_entries['lang_english_name'], - 'LANG_ISO' => $lang_iso, - 'LANG_AUTHOR' => $lang_entries['lang_author'], - 'L_MISSING_FILES' => $user->lang('THOSE_MISSING_LANG_FILES', $lang_entries['lang_local_name']), - 'L_MISSING_VARS_EXPLAIN' => $user->lang('THOSE_MISSING_LANG_VARIABLES', $lang_entries['lang_local_name']), + $this->language->add_lang('acp/extensions'); + + $this->template->assign_vars(array( + 'S_DETAILS' => true, + 'U_ACTION' => $this->u_action . "&action=details&id=$lang_id", + 'U_BACK' => $this->u_action, + + 'LANG_LOCAL_NAME' => $lang_entries['lang_local_name'], + 'LANG_ENGLISH_NAME' => $lang_entries['lang_english_name'], + 'LANG_ISO' => $lang_iso, + 'LANG_VERSION' => $lang_cfg['version'], + 'LANG_PHPBB_VERSION' => $lang_cfg['phpbb_version'], + 'LANG_AUTHOR' => $lang_entries['lang_author'], + 'L_MISSING_FILES' => $this->language->lang('THOSE_MISSING_LANG_FILES', $lang_entries['lang_local_name']), + 'L_MISSING_VARS_EXPLAIN' => $this->language->lang('THOSE_MISSING_LANG_VARIABLES', $lang_entries['lang_local_name']), )); // If current lang is different from the default lang, then highlight missing files and variables - if ($lang_iso != $config['default_lang']) + if ($lang_iso != $this->config['default_lang']) { try { $iterator = new \RecursiveIteratorIterator( new \phpbb\recursive_dot_prefix_filter_iterator( new \RecursiveDirectoryIterator( - $phpbb_root_path . 'language/' . $config['default_lang'] . '/', + $this->phpbb_root_path . 'language/' . $this->config['default_lang'] . '/', \FilesystemIterator::SKIP_DOTS ) ), @@ -160,21 +241,21 @@ class acp_language $relative_path = $iterator->getInnerIterator()->getSubPathname(); $relative_path = str_replace(DIRECTORY_SEPARATOR, '/', $relative_path); - if (file_exists($phpbb_root_path . 'language/' . $lang_iso . '/' . $relative_path)) + if (file_exists($this->phpbb_root_path . 'language/' . $lang_iso . '/' . $relative_path)) { - if (substr($relative_path, 0 - strlen($phpEx)) === $phpEx) + if (substr($relative_path, -strlen($this->php_ext)) === $this->php_ext) { - $missing_vars = $this->compare_language_files($config['default_lang'], $lang_iso, $relative_path); + $missing_vars = $this->compare_language_files($this->config['default_lang'], $lang_iso, $relative_path); if (!empty($missing_vars)) { - $template->assign_block_vars('missing_varfile', array( + $this->template->assign_block_vars('missing_varfile', array( 'FILE_NAME' => $relative_path, )); foreach ($missing_vars as $var) { - $template->assign_block_vars('missing_varfile.variable', array( + $this->template->assign_block_vars('missing_varfile.variable', array( 'VAR_NAME' => $var, )); } @@ -183,7 +264,7 @@ class acp_language } else { - $template->assign_block_vars('missing_files', array( + $this->template->assign_block_vars('missing_files', array( 'FILE_NAME' => $relative_path, )); } @@ -196,40 +277,40 @@ class acp_language if (!$lang_id) { - trigger_error($user->lang['NO_LANG_ID'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('NO_LANG_ID') . adm_back_link($this->u_action), E_USER_WARNING); } $sql = 'SELECT * FROM ' . LANG_TABLE . ' WHERE lang_id = ' . $lang_id; - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); - if ($row['lang_iso'] == $config['default_lang']) + if ($row['lang_iso'] == $this->config['default_lang']) { - trigger_error($user->lang['NO_REMOVE_DEFAULT_LANG'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('NO_REMOVE_DEFAULT_LANG') . adm_back_link($this->u_action), E_USER_WARNING); } if (confirm_box(true)) { - $db->sql_query('DELETE FROM ' . LANG_TABLE . ' WHERE lang_id = ' . $lang_id); + $this->db->sql_query('DELETE FROM ' . LANG_TABLE . ' WHERE lang_id = ' . $lang_id); $sql = 'UPDATE ' . USERS_TABLE . " - SET user_lang = '" . $db->sql_escape($config['default_lang']) . "' - WHERE user_lang = '" . $db->sql_escape($row['lang_iso']) . "'"; - $db->sql_query($sql); + SET user_lang = '" . $this->db->sql_escape($this->config['default_lang']) . "' + WHERE user_lang = '" . $this->db->sql_escape($row['lang_iso']) . "'"; + $this->db->sql_query($sql); // We also need to remove the translated entries for custom profile fields - we want clean tables, don't we? $sql = 'DELETE FROM ' . PROFILE_LANG_TABLE . ' WHERE lang_id = ' . $lang_id; - $db->sql_query($sql); + $this->db->sql_query($sql); $sql = 'DELETE FROM ' . PROFILE_FIELDS_LANG_TABLE . ' WHERE lang_id = ' . $lang_id; - $db->sql_query($sql); + $this->db->sql_query($sql); - $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_LANGUAGE_PACK_DELETED', false, array($row['lang_english_name'])); + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_LANGUAGE_PACK_DELETED', false, array($row['lang_english_name'])); - $delete_message = sprintf($user->lang['LANGUAGE_PACK_DELETED'], $row['lang_english_name']); + $delete_message = $this->language->lang('LANGUAGE_PACK_DELETED', $row['lang_english_name']); $lang_iso = $row['lang_iso']; /** * Run code after language deleted @@ -240,7 +321,7 @@ class acp_language * @since 3.2.2-RC1 */ $vars = array('lang_iso', 'delete_message'); - extract($phpbb_dispatcher->trigger_event('core.acp_language_after_delete', compact($vars))); + extract($this->dispatcher->trigger_event('core.acp_language_after_delete', compact($vars))); trigger_error($delete_message . adm_back_link($this->u_action)); } @@ -252,49 +333,48 @@ class acp_language 'action' => $action, 'id' => $lang_id, ); - confirm_box(false, $user->lang('DELETE_LANGUAGE_CONFIRM', $row['lang_english_name']), build_hidden_fields($s_hidden_fields)); + confirm_box(false, $this->language->lang('DELETE_LANGUAGE_CONFIRM', $row['lang_english_name']), build_hidden_fields($s_hidden_fields)); } break; case 'install': - if (!check_link_hash($request->variable('hash', ''), 'acp_language')) + if (!check_link_hash($this->request->variable('hash', ''), 'acp_language')) { - trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); } - $lang_iso = $request->variable('iso', ''); + $lang_iso = $this->request->variable('iso', ''); $lang_iso = basename($lang_iso); - if (!$lang_iso || !file_exists("{$phpbb_root_path}language/$lang_iso/iso.txt")) + if (!$lang_iso || !file_exists("{$this->phpbb_root_path}language/$lang_iso/composer.json")) { - trigger_error($user->lang['LANGUAGE_PACK_NOT_EXIST'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('LANGUAGE_PACK_NOT_EXIST') . adm_back_link($this->u_action), E_USER_WARNING); } - $file = file("{$phpbb_root_path}language/$lang_iso/iso.txt"); - - $lang_pack = array( - 'iso' => $lang_iso, - 'name' => trim(htmlspecialchars($file[0], ENT_COMPAT)), - 'local_name'=> trim(htmlspecialchars($file[1], ENT_COMPAT, 'UTF-8')), - 'author' => trim(htmlspecialchars($file[2], ENT_COMPAT, 'UTF-8')) - ); - unset($file); + try + { + $lang_pack = $this->language_helper->get_language_data_from_composer_file("{$this->phpbb_root_path}language/$lang_iso/composer.json"); + } + catch (\DomainException $e) + { + trigger_error($this->language->lang('LANGUAGE_PACK_NOT_EXIST') . adm_back_link($this->u_action), E_USER_WARNING); + } $sql = 'SELECT lang_iso FROM ' . LANG_TABLE . " - WHERE lang_iso = '" . $db->sql_escape($lang_iso) . "'"; - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); + WHERE lang_iso = '" . $this->db->sql_escape($lang_iso) . "'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); if ($row) { - trigger_error($user->lang['LANGUAGE_PACK_ALREADY_INSTALLED'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('LANGUAGE_PACK_ALREADY_INSTALLED') . adm_back_link($this->u_action), E_USER_WARNING); } if (!$lang_pack['name'] || !$lang_pack['local_name']) { - trigger_error($user->lang['INVALID_LANGUAGE_PACK'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('INVALID_LANGUAGE_PACK') . adm_back_link($this->u_action), E_USER_WARNING); } // Add language pack @@ -306,16 +386,16 @@ class acp_language 'lang_author' => $lang_pack['author'] ); - $db->sql_query('INSERT INTO ' . LANG_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); - $lang_id = $db->sql_nextid(); + $this->db->sql_query('INSERT INTO ' . LANG_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary)); + $lang_id = $this->db->sql_nextid(); // Now let's copy the default language entries for custom profile fields for this new language - makes admin's life easier. $sql = 'SELECT lang_id FROM ' . LANG_TABLE . " - WHERE lang_iso = '" . $db->sql_escape($config['default_lang']) . "'"; - $result = $db->sql_query($sql); - $default_lang_id = (int) $db->sql_fetchfield('lang_id'); - $db->sql_freeresult($result); + WHERE lang_iso = '" . $this->db->sql_escape($this->config['default_lang']) . "'"; + $result = $this->db->sql_query($sql); + $default_lang_id = (int) $this->db->sql_fetchfield('lang_id'); + $this->db->sql_freeresult($result); // We want to notify the admin that custom profile fields need to be updated for the new language. $notify_cpf_update = false; @@ -327,33 +407,33 @@ class acp_language $sql = 'SELECT field_id, lang_name, lang_explain, lang_default_value FROM ' . PROFILE_LANG_TABLE . ' WHERE lang_id = ' . $default_lang_id; - $result = $db->sql_query($sql); + $result = $this->db->sql_query($sql); - while ($row = $db->sql_fetchrow($result)) + while ($row = $this->db->sql_fetchrow($result)) { $row['lang_id'] = $lang_id; - $db->sql_query('INSERT INTO ' . PROFILE_LANG_TABLE . ' ' . $db->sql_build_array('INSERT', $row)); + $this->db->sql_query('INSERT INTO ' . PROFILE_LANG_TABLE . ' ' . $this->db->sql_build_array('INSERT', $row)); $notify_cpf_update = true; } - $db->sql_freeresult($result); + $this->db->sql_freeresult($result); $sql = 'SELECT field_id, option_id, field_type, lang_value FROM ' . PROFILE_FIELDS_LANG_TABLE . ' WHERE lang_id = ' . $default_lang_id; - $result = $db->sql_query($sql); + $result = $this->db->sql_query($sql); - while ($row = $db->sql_fetchrow($result)) + while ($row = $this->db->sql_fetchrow($result)) { $row['lang_id'] = $lang_id; - $db->sql_query('INSERT INTO ' . PROFILE_FIELDS_LANG_TABLE . ' ' . $db->sql_build_array('INSERT', $row)); + $this->db->sql_query('INSERT INTO ' . PROFILE_FIELDS_LANG_TABLE . ' ' . $this->db->sql_build_array('INSERT', $row)); $notify_cpf_update = true; } - $db->sql_freeresult($result); + $this->db->sql_freeresult($result); - $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_LANGUAGE_PACK_INSTALLED', false, array($lang_pack['name'])); + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_LANGUAGE_PACK_INSTALLED', false, array($lang_pack['name'])); - $message = sprintf($user->lang['LANGUAGE_PACK_INSTALLED'], $lang_pack['name']); - $message .= ($notify_cpf_update) ? '

' . $user->lang['LANGUAGE_PACK_CPF_UPDATE'] : ''; + $message = $this->language->lang('LANGUAGE_PACK_INSTALLED', $lang_pack['name']); + $message .= ($notify_cpf_update) ? '

' . $this->language->lang('LANGUAGE_PACK_CPF_UPDATE') : ''; trigger_error($message . adm_back_link($this->u_action)); break; @@ -362,28 +442,28 @@ class acp_language $sql = 'SELECT user_lang, COUNT(user_lang) AS lang_count FROM ' . USERS_TABLE . ' GROUP BY user_lang'; - $result = $db->sql_query($sql); + $result = $this->db->sql_query($sql); $lang_count = array(); - while ($row = $db->sql_fetchrow($result)) + while ($row = $this->db->sql_fetchrow($result)) { $lang_count[$row['user_lang']] = $row['lang_count']; } - $db->sql_freeresult($result); + $this->db->sql_freeresult($result); $sql = 'SELECT * FROM ' . LANG_TABLE . ' ORDER BY lang_english_name'; - $result = $db->sql_query($sql); + $result = $this->db->sql_query($sql); $installed = array(); - while ($row = $db->sql_fetchrow($result)) + while ($row = $this->db->sql_fetchrow($result)) { $installed[] = $row['lang_iso']; - $tagstyle = ($row['lang_iso'] == $config['default_lang']) ? '*' : ''; + $tagstyle = ($row['lang_iso'] == $this->config['default_lang']) ? '*' : ''; - $template->assign_block_vars('lang', array( + $this->template->assign_block_vars('lang', array( 'U_DETAILS' => $this->u_action . "&action=details&id={$row['lang_id']}", 'U_DOWNLOAD' => $this->u_action . "&action=download&id={$row['lang_id']}", 'U_DELETE' => $this->u_action . "&action=delete&id={$row['lang_id']}", @@ -395,13 +475,11 @@ class acp_language 'USED_BY' => (isset($lang_count[$row['lang_iso']])) ? $lang_count[$row['lang_iso']] : 0, )); } - $db->sql_freeresult($result); + $this->db->sql_freeresult($result); $new_ary = $iso = array(); - /** @var \phpbb\language\language_file_helper $language_helper */ - $language_helper = $phpbb_container->get('language.helper.language_file'); - $iso = $language_helper->get_available_languages(); + $iso = $this->language_helper->get_available_languages(); foreach ($iso as $lang_array) { @@ -419,7 +497,7 @@ class acp_language { foreach ($new_ary as $iso => $lang_ary) { - $template->assign_block_vars('notinst', array( + $this->template->assign_block_vars('notinst', array( 'ISO' => htmlspecialchars($lang_ary['iso'], ENT_COMPAT), 'LOCAL_NAME' => htmlspecialchars($lang_ary['local_name'], ENT_COMPAT, 'UTF-8'), 'NAME' => htmlspecialchars($lang_ary['name'], ENT_COMPAT, 'UTF-8'), @@ -436,10 +514,8 @@ class acp_language */ function compare_language_files($source_lang, $dest_lang, $file) { - global $phpbb_root_path; - - $source_file = $phpbb_root_path . 'language/' . $source_lang . '/' . $file; - $dest_file = $phpbb_root_path . 'language/' . $dest_lang . '/' . $file; + $source_file = $this->phpbb_root_path . 'language/' . $source_lang . '/' . $file; + $dest_file = $this->phpbb_root_path . 'language/' . $dest_lang . '/' . $file; if (!file_exists($dest_file)) { diff --git a/phpBB/includes/acp/acp_styles.php b/phpBB/includes/acp/acp_styles.php index fc33519882..a1b1e62a3b 100644 --- a/phpBB/includes/acp/acp_styles.php +++ b/phpBB/includes/acp/acp_styles.php @@ -38,8 +38,8 @@ class acp_styles /** @var \phpbb\db\driver\driver_interface */ protected $db; - /** @var \phpbb\user */ - protected $user; + /** @var \phpbb\language\language */ + protected $language; /** @var \phpbb\template\template */ protected $template; @@ -67,10 +67,10 @@ class acp_styles public function main($id, $mode) { - global $db, $user, $phpbb_admin_path, $phpbb_root_path, $phpEx, $template, $request, $cache, $auth, $config, $phpbb_dispatcher, $phpbb_container; + global $db, $phpbb_admin_path, $phpbb_root_path, $phpEx, $template, $request, $cache, $auth, $config, $phpbb_dispatcher, $phpbb_container; $this->db = $db; - $this->user = $user; + $this->language = $phpbb_container->get('language'); $this->template = $template; $this->request = $request; $this->cache = $cache; @@ -89,7 +89,7 @@ class acp_styles 'mode' => $mode, ); - $this->user->add_lang('acp/styles'); + $this->language->add_lang('acp/styles'); $this->tpl_name = 'acp_styles'; $this->page_title = 'ACP_CAT_STYLES'; @@ -114,7 +114,7 @@ class acp_styles if (!$is_valid_request) { - trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); } } @@ -183,7 +183,7 @@ class acp_styles $this->show_available(); return; } - trigger_error($this->user->lang['NO_MODE'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('NO_MODE') . adm_back_link($this->u_action), E_USER_WARNING); } /** @@ -205,7 +205,7 @@ class acp_styles { if (in_array($dir, $this->reserved_style_names)) { - $messages[] = $this->user->lang('STYLE_NAME_RESERVED', htmlspecialchars($dir, ENT_COMPAT)); + $messages[] = $this->language->lang('STYLE_NAME_RESERVED', htmlspecialchars($dir, ENT_COMPAT)); continue; } @@ -225,12 +225,12 @@ class acp_styles $found = true; $installed_names[] = $style['style_name']; $installed_dirs[] = $style['style_path']; - $messages[] = sprintf($this->user->lang['STYLE_INSTALLED'], htmlspecialchars($style['style_name'], ENT_COMPAT)); + $messages[] = $this->language->lang('STYLE_INSTALLED', htmlspecialchars($style['style_name'], ENT_COMPAT)); } } if (!$found) { - $messages[] = sprintf($this->user->lang['STYLE_NOT_INSTALLED'], htmlspecialchars($dir, ENT_COMPAT)); + $messages[] = $this->language->lang('STYLE_NOT_INSTALLED', htmlspecialchars($dir, ENT_COMPAT)); } } @@ -243,11 +243,11 @@ class acp_styles // Show message if (!count($messages)) { - trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('NO_MATCHING_STYLES_FOUND') . adm_back_link($this->u_action), E_USER_WARNING); } $message = implode('
', $messages); - $message .= '

« ' . $this->user->lang('STYLE_INSTALLED_RETURN_INSTALLED_STYLES') . ''; - $message .= '

» ' . $this->user->lang('STYLE_INSTALLED_RETURN_UNINSTALLED_STYLES') . ''; + $message .= '

« ' . $this->language->lang('STYLE_INSTALLED_RETURN_INSTALLED_STYLES') . ''; + $message .= '

» ' . $this->language->lang('STYLE_INSTALLED_RETURN_UNINSTALLED_STYLES') . ''; trigger_error($message, E_USER_NOTICE); } @@ -269,7 +269,7 @@ class acp_styles if ($prosilver_id && in_array($prosilver_id, $ids)) { - trigger_error($this->user->lang('UNINSTALL_PROSILVER') . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('UNINSTALL_PROSILVER') . adm_back_link($this->u_action), E_USER_WARNING); } // Check if confirmation box was submitted @@ -286,7 +286,7 @@ class acp_styles 'ids' => $ids )); $this->template->assign_var('S_CONFIRM_DELETE', true); - confirm_box(false, $this->user->lang['CONFIRM_UNINSTALL_STYLES'], $s_hidden, 'acp_styles.html'); + confirm_box(false, $this->language->lang('CONFIRM_UNINSTALL_STYLES'), $s_hidden, 'acp_styles.html'); // Canceled - show styles list $this->frontend(); @@ -311,11 +311,11 @@ class acp_styles { if (!$id) { - trigger_error($this->user->lang['INVALID_STYLE_ID'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('INVALID_STYLE_ID') . adm_back_link($this->u_action), E_USER_WARNING); } if ($id == $default) { - trigger_error($this->user->lang['UNINSTALL_DEFAULT'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('UNINSTALL_DEFAULT') . adm_back_link($this->u_action), E_USER_WARNING); } $uninstalled[$id] = false; } @@ -342,20 +342,20 @@ class acp_styles $messages[] = $result; continue; } - $messages[] = sprintf($this->user->lang['STYLE_UNINSTALLED'], $style['style_name']); + $messages[] = $this->language->lang('STYLE_UNINSTALLED', $style['style_name']); $uninstalled[] = $style['style_name']; // Attempt to delete files if ($delete_files) { - $messages[] = sprintf($this->user->lang[$this->delete_style_files($style['style_path']) ? 'DELETE_STYLE_FILES_SUCCESS' : 'DELETE_STYLE_FILES_FAILED'], $style['style_name']); + $messages[] = $this->language->lang($this->delete_style_files($style['style_path']) ? 'DELETE_STYLE_FILES_SUCCESS' : 'DELETE_STYLE_FILES_FAILED', $style['style_name']); } } if (empty($messages)) { // Nothing to uninstall? - trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('NO_MATCHING_STYLES_FOUND') . adm_back_link($this->u_action), E_USER_WARNING); } // Log action @@ -405,7 +405,7 @@ class acp_styles { if ($id == $this->default_style) { - trigger_error($this->user->lang['DEACTIVATE_DEFAULT'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('DEACTIVATE_DEFAULT') . adm_back_link($this->u_action), E_USER_WARNING); } } @@ -438,7 +438,7 @@ class acp_styles $id = $this->request->variable('id', 0); if (!$id) { - trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('NO_MATCHING_STYLES_FOUND') . adm_back_link($this->u_action), E_USER_WARNING); } // Get all styles @@ -458,11 +458,11 @@ class acp_styles if ($style === false) { - trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('NO_MATCHING_STYLES_FOUND') . adm_back_link($this->u_action), E_USER_WARNING); } // Read style configuration file - $style_cfg = $this->read_style_cfg($style['style_path']); + $style_cfg = $this->read_style_composer_file($style['style_path']); // Find all available parent styles $list = $this->find_possible_parents($styles, $id); @@ -476,7 +476,7 @@ class acp_styles { if (!check_form_key($form_key)) { - trigger_error($this->user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); } $update = array( @@ -491,13 +491,13 @@ class acp_styles { if (!strlen($update['style_name'])) { - trigger_error($this->user->lang['STYLE_ERR_STYLE_NAME'] . adm_back_link($update_action), E_USER_WARNING); + trigger_error($this->language->lang('STYLE_ERR_STYLE_NAME') . adm_back_link($update_action), E_USER_WARNING); } foreach ($styles as $row) { if ($row['style_name'] == $update['style_name']) { - trigger_error($this->user->lang['STYLE_ERR_NAME_EXIST'] . adm_back_link($update_action), E_USER_WARNING); + trigger_error($this->language->lang('STYLE_ERR_NAME_EXIST') . adm_back_link($update_action), E_USER_WARNING); } } } @@ -523,7 +523,7 @@ class acp_styles } if (!$found) { - trigger_error($this->user->lang['STYLE_ERR_INVALID_PARENT'] . adm_back_link($update_action), E_USER_WARNING); + trigger_error($this->language->lang('STYLE_ERR_INVALID_PARENT') . adm_back_link($update_action), E_USER_WARNING); } } else @@ -541,7 +541,7 @@ class acp_styles { if (!$update['style_active'] && $this->default_style == $style['style_id']) { - trigger_error($this->user->lang['DEACTIVATE_DEFAULT'] . adm_back_link($update_action), E_USER_WARNING); + trigger_error($this->language->lang('DEACTIVATE_DEFAULT') . adm_back_link($update_action), E_USER_WARNING); } } else @@ -579,7 +579,7 @@ class acp_styles { if (!$style['style_active']) { - trigger_error($this->user->lang['STYLE_DEFAULT_CHANGE_INACTIVE'] . adm_back_link($update_action), E_USER_WARNING); + trigger_error($this->language->lang('STYLE_DEFAULT_CHANGE_INACTIVE') . adm_back_link($update_action), E_USER_WARNING); } $this->config->set('default_style', $id); $this->cache->purge(); @@ -611,13 +611,12 @@ class acp_styles 'STYLE_ID' => $style['style_id'], 'STYLE_NAME' => htmlspecialchars($style['style_name'], ENT_COMPAT), 'STYLE_PATH' => htmlspecialchars($style['style_path'], ENT_COMPAT), - 'STYLE_VERSION' => htmlspecialchars($style_cfg['style_version'], ENT_COMPAT), + 'STYLE_VERSION' => htmlspecialchars($style_cfg['version'], ENT_COMPAT), 'STYLE_COPYRIGHT' => strip_tags($style['style_copyright']), 'STYLE_PARENT' => $style['style_parent_id'], 'S_STYLE_ACTIVE' => $style['style_active'], 'S_STYLE_DEFAULT' => ($style['style_id'] == $this->default_style) - ) - ); + )); } /** @@ -630,7 +629,7 @@ class acp_styles if (!count($styles)) { - trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('NO_MATCHING_STYLES_FOUND') . adm_back_link($this->u_action), E_USER_WARNING); } usort($styles, array($this, 'sort_styles')); @@ -657,7 +656,7 @@ class acp_styles { if (empty($style['_shown'])) { - $style['_note'] = sprintf($this->user->lang['REQUIRES_STYLE'], htmlspecialchars($style['style_parent_tree'], ENT_COMPAT)); + $style['_note'] = $this->language->lang('REQUIRES_STYLE', htmlspecialchars($style['style_parent_tree'], ENT_COMPAT)); $this->list_style($style, 0); } } @@ -665,13 +664,13 @@ class acp_styles // Add buttons $this->template->assign_block_vars('extra_actions', array( 'ACTION_NAME' => 'activate', - 'L_ACTION' => $this->user->lang['STYLE_ACTIVATE'], + 'L_ACTION' => $this->language->lang('STYLE_ACTIVATE'), ) ); $this->template->assign_block_vars('extra_actions', array( 'ACTION_NAME' => 'deactivate', - 'L_ACTION' => $this->user->lang['STYLE_DEACTIVATE'], + 'L_ACTION' => $this->language->lang('STYLE_DEACTIVATE'), ) ); @@ -679,7 +678,7 @@ class acp_styles { $this->template->assign_block_vars('extra_actions', array( 'ACTION_NAME' => 'uninstall', - 'L_ACTION' => $this->user->lang['STYLE_UNINSTALL'], + 'L_ACTION' => $this->language->lang('STYLE_UNINSTALL'), ) ); } @@ -696,7 +695,7 @@ class acp_styles // Show styles if (empty($styles)) { - trigger_error($this->user->lang['NO_UNINSTALLED_STYLE'] . adm_back_link($this->u_base_action), E_USER_NOTICE); + trigger_error($this->language->lang('NO_UNINSTALLED_STYLE') . adm_back_link($this->u_base_action), E_USER_NOTICE); } usort($styles, array($this, 'sort_styles')); @@ -711,6 +710,12 @@ class acp_styles // Show styles foreach ($styles as &$style) { + if (!$style['_available'] && !empty($style['_invalid'])) + { + $this->list_invalid($style); + continue; + } + // Check if style has a parent style in styles list $has_parent = false; if ($style['_inherit_name'] != '') @@ -745,7 +750,7 @@ class acp_styles { $this->template->assign_block_vars('extra_actions', array( 'ACTION_NAME' => 'install', - 'L_ACTION' => $this->user->lang['INSTALL_STYLES'], + 'L_ACTION' => $this->language->lang('INSTALL_STYLES'), ) ); } @@ -787,22 +792,33 @@ class acp_styles // Style is already installed continue; } - $cfg = $this->read_style_cfg($dir); - if ($cfg === false) + + try { - // Invalid style.cfg + $style_data = $this->read_style_composer_file($dir); + } + catch (\DomainException $e) + { + // Invalid composer.json + $style = array( + '_available' => false, + '_invalid' => true, + 'style_path' => $dir, + ); + $styles[] = $style; + continue; } // Style should be available for installation - $parent = $cfg['parent']; + $parent = $style_data['extra']['parent-style']; $style = array( 'style_id' => 0, - 'style_name' => $cfg['name'], - 'style_copyright' => $cfg['copyright'], + 'style_name' => $style_data['extra']['display-name'], + 'style_copyright' => $style_data['license'], 'style_active' => 0, 'style_path' => $dir, - 'bbcode_bitfield' => $cfg['template_bitfield'], + 'bbcode_bitfield' => $style_data['extra']['template-bitfield'], 'style_parent_id' => 0, 'style_parent_tree' => '', // Extra values for styles list @@ -826,7 +842,7 @@ class acp_styles { // Parent style is not installed yet $style['_available'] = false; - $style['_note'] = sprintf($this->user->lang['REQUIRES_STYLE'], htmlspecialchars($parent, ENT_COMPAT)); + $style['_note'] = $this->language->lang('REQUIRES_STYLE', htmlspecialchars($parent, ENT_COMPAT)); } } @@ -949,7 +965,7 @@ class acp_styles * @param array $style style row * @param int $level style inheritance level */ - protected function list_style(&$style, $level) + protected function list_style(array &$style, int $level) : void { // Mark row as shown if (!empty($style['_shown'])) @@ -959,35 +975,35 @@ class acp_styles $style['_shown'] = true; - $style_cfg = $this->read_style_cfg($style['style_path']); + $style_cfg = $this->read_style_composer_file($style['style_path']); // Generate template variables - $actions = array(); - $row = array( + $actions = []; + $row = [ // Style data 'STYLE_ID' => $style['style_id'], 'STYLE_NAME' => htmlspecialchars($style['style_name'], ENT_COMPAT), - 'STYLE_VERSION' => $style_cfg['style_version'] ?? '-', - 'STYLE_PHPBB_VERSION' => $style_cfg['phpbb_version'], + 'STYLE_VERSION' => $style_cfg['version'] ?? '-', + 'STYLE_PHPBB_VERSION' => $style_cfg['extra']['phpbb-version'] ?? '', 'STYLE_PATH' => htmlspecialchars($style['style_path'], ENT_COMPAT), 'STYLE_COPYRIGHT' => strip_tags($style['style_copyright']), 'STYLE_ACTIVE' => $style['style_active'], // Additional data 'DEFAULT' => ($style['style_id'] && $style['style_id'] == $this->default_style), - 'USERS' => (isset($style['_users'])) ? $style['_users'] : '', + 'USERS' => $style['_users'] ?? '', 'LEVEL' => $level, 'PADDING' => (4 + 16 * $level), 'SHOW_COPYRIGHT' => ($style['style_id']) ? false : true, 'STYLE_PATH_FULL' => htmlspecialchars($this->styles_path_absolute . '/' . $style['style_path'], ENT_COMPAT) . '/', // Comment to show below style - 'COMMENT' => (isset($style['_note'])) ? $style['_note'] : '', + 'COMMENT' => $style['_note'] ?? '', // The following variables should be used by hooks to add custom HTML code 'EXTRA' => '', 'EXTRA_OPTIONS' => '' - ); + ]; // Status specific data if ($style['style_id']) @@ -995,60 +1011,52 @@ class acp_styles // Style is installed // Details - $actions[] = array( + $actions[] = [ 'U_ACTION' => $this->u_action . '&action=details&id=' . $style['style_id'], - 'L_ACTION' => $this->user->lang['DETAILS'] - ); + 'L_ACTION' => $this->language->lang('DETAILS') + ]; - // Activate/Deactive + // Activate/Deactivate $action_name = ($style['style_active'] ? 'de' : '') . 'activate'; - $actions[] = array( + $actions[] = [ 'U_ACTION' => $this->u_action . '&action=' . $action_name . '&hash=' . generate_link_hash($action_name) . '&id=' . $style['style_id'], - 'L_ACTION' => $this->user->lang['STYLE_' . ($style['style_active'] ? 'DE' : '') . 'ACTIVATE'] - ); - -/* // Export - $actions[] = array( - 'U_ACTION' => $this->u_action . '&action=export&hash=' . generate_link_hash('export') . '&id=' . $style['style_id'], - 'L_ACTION' => $this->user->lang['EXPORT'] - ); */ + 'L_ACTION' => $this->language->lang('STYLE_' . ($style['style_active'] ? 'DE' : '') . 'ACTIVATE') + ]; if ($style['style_name'] !== 'prosilver') { // Uninstall - $actions[] = array( + $actions[] = [ 'U_ACTION' => $this->u_action . '&action=uninstall&hash=' . generate_link_hash('uninstall') . '&id=' . $style['style_id'], - 'L_ACTION' => $this->user->lang['STYLE_UNINSTALL'] - ); + 'L_ACTION' => $this->language->lang('STYLE_UNINSTALL') + ]; } // Preview - $actions[] = array( + $actions[] = [ 'U_ACTION' => append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'style=' . $style['style_id']), - 'L_ACTION' => $this->user->lang['PREVIEW'] - ); + 'L_ACTION' => $this->language->lang('PREVIEW') + ]; } else { // Style is not installed if (empty($style['_available'])) { - $actions[] = array( - 'HTML' => $this->user->lang['CANNOT_BE_INSTALLED'] - ); + $actions[] = [ + 'HTML' => $this->language->lang('CANNOT_BE_INSTALLED') + ]; } else { - $actions[] = array( + $actions[] = [ 'U_ACTION' => $this->u_action . '&action=install&hash=' . generate_link_hash('install') . '&dir=' . urlencode($style['style_path']), - 'L_ACTION' => $this->user->lang['INSTALL_STYLE'] - ); + 'L_ACTION' => $this->language->lang('INSTALL_STYLE') + ]; } } - // todo: add hook - // Assign template variables $this->template->assign_block_vars('styles_list', $row); foreach ($actions as $action) @@ -1060,18 +1068,54 @@ class acp_styles $counter = ($style['style_id']) ? ($style['style_active'] ? 'active' : 'inactive') : (empty($style['_available']) ? 'cannotinstall' : 'caninstall'); if (!isset($this->style_counters)) { - $this->style_counters = array( + $this->style_counters = [ 'total' => 0, 'active' => 0, 'inactive' => 0, 'caninstall' => 0, 'cannotinstall' => 0 - ); + ]; } $this->style_counters[$counter]++; $this->style_counters['total']++; } + /** + * List invalid style + * + * @param array $style Array with info about style to display as invalid + */ + protected function list_invalid(&$style) + { + $style['_shown'] = true; + + $row = [ + // Style data + 'STYLE_INVALID' => true, + 'STYLE_NAME' => $this->language->lang('INVALID_STYLE_MESSAGE', $style['style_path']), + ]; + + $this->template->assign_block_vars('styles_list', $row); + + $this->template->assign_block_vars('styles_list.actions', [ + 'HTML' => $this->language->lang('CANNOT_BE_INSTALLED') + ]); + + // Increase counters + if (!isset($this->style_counters)) + { + $this->style_counters = [ + 'total' => 0, + 'active' => 0, + 'inactive' => 0, + 'caninstall' => 0, + 'cannotinstall' => 0 + ]; + } + $this->style_counters['cannotinstall']++; + $this->style_counters['total']++; + } + /** * Show welcome message * @@ -1080,11 +1124,10 @@ class acp_styles */ protected function welcome_message($title, $description) { - $this->template->assign_vars(array( - 'L_TITLE' => $this->user->lang[$title], - 'L_EXPLAIN' => (isset($this->user->lang[$description])) ? $this->user->lang[$description] : '' - ) - ); + $this->template->assign_vars([ + 'L_TITLE' => $this->language->lang($title), + 'L_EXPLAIN' => $this->language->is_set($description) ? $this->language->lang($description) : '' + ]); } /** @@ -1107,7 +1150,7 @@ class acp_styles continue; } - if (file_exists("{$dir}/style.cfg")) + if (file_exists("{$dir}/composer.json")) { $styles[] = $file; } @@ -1135,43 +1178,45 @@ class acp_styles } /** - * Read style configuration file - * - * @param string $dir style directory - * @return array|bool Style data, false on error - */ - protected function read_style_cfg($dir) + * Read style composer.json file + * + * @param string $dir style directory + * + * @return array Style data + * @throws \DomainException in case of error + */ + protected function read_style_composer_file($dir) { // This should never happen, we give them a red warning because of its relevance. - if (!file_exists($this->styles_path . $dir . '/style.cfg')) + if (!file_exists($this->styles_path . $dir . '/composer.json')) { - trigger_error($this->user->lang('NO_STYLE_CFG', $dir), E_USER_WARNING); + trigger_error($this->language->lang('NO_STYLE_CFG', $dir), E_USER_WARNING); } - static $required = array('name', 'phpbb_version', 'copyright'); + $json = file_get_contents($this->styles_path . $dir . '/composer.json'); + $style_data = \phpbb\json\sanitizer::decode($json); - $cfg = parse_cfg_file($this->styles_path . $dir . '/style.cfg'); - - // Check if it is a valid file - foreach ($required as $key) + if (!is_array($style_data) || !isset($style_data['type']) || $style_data['type'] !== 'phpbb-style') { - if (!isset($cfg[$key])) - { - return false; - } + throw new \DomainException('NO_VALID_STYLE'); + } + + if (!isset($style_data['extra'])) + { + $style_data['extra'] = array(); } // Check data - if (!isset($cfg['parent']) || !is_string($cfg['parent']) || $cfg['parent'] == $cfg['name']) + if (!isset($style_data['extra']['parent-style']) || !is_string($style_data['extra']['parent-style']) || $style_data['extra']['parent-style'] === $style_data['name']) { - $cfg['parent'] = ''; + $style_data['extra']['parent-style'] = ''; } - if (!isset($cfg['template_bitfield'])) + if (!isset($style_data['extra']['template-bitfield'])) { - $cfg['template_bitfield'] = $this->default_bitfield(); + $style_data['extra']['template-bitfield'] = $this->default_bitfield(); } - return $cfg; + return $style_data; } /** @@ -1271,7 +1316,7 @@ class acp_styles if ($conflict !== false) { - return sprintf($this->user->lang['STYLE_UNINSTALL_DEPENDENT'], $style['style_name']); + return $this->language->lang('STYLE_UNINSTALL_DEPENDENT', $style['style_name']); } // Change default style for users @@ -1360,7 +1405,7 @@ class acp_styles if ($error && !count($items)) { - trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->language->lang('NO_MATCHING_STYLES_FOUND') . adm_back_link($this->u_action), E_USER_WARNING); } return $items; diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 72df25cc03..fca78b2ec1 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -2668,63 +2668,6 @@ function build_hidden_fields($field_ary, $specialchar = false, $stripslashes = f return $s_hidden_fields; } -/** -* Parse cfg file -*/ -function parse_cfg_file($filename, $lines = false) -{ - $parsed_items = array(); - - if ($lines === false) - { - $lines = file($filename); - } - - foreach ($lines as $line) - { - $line = trim($line); - - if (!$line || $line[0] == '#' || ($delim_pos = strpos($line, '=')) === false) - { - continue; - } - - // Determine first occurrence, since in values the equal sign is allowed - $key = htmlspecialchars(strtolower(trim(substr($line, 0, $delim_pos))), ENT_COMPAT); - $value = trim(substr($line, $delim_pos + 1)); - - if (in_array($value, array('off', 'false', '0'))) - { - $value = false; - } - else if (in_array($value, array('on', 'true', '1'))) - { - $value = true; - } - else if (!trim($value)) - { - $value = ''; - } - else if (($value[0] == "'" && $value[strlen($value) - 1] == "'") || ($value[0] == '"' && $value[strlen($value) - 1] == '"')) - { - $value = htmlspecialchars(substr($value, 1, strlen($value)-2), ENT_COMPAT); - } - else - { - $value = htmlspecialchars($value, ENT_COMPAT); - } - - $parsed_items[$key] = $value; - } - - if (isset($parsed_items['parent']) && isset($parsed_items['name']) && $parsed_items['parent'] == $parsed_items['name']) - { - unset($parsed_items['parent']); - } - - return $parsed_items; -} - /** * Return a nicely formatted backtrace. * diff --git a/phpBB/includes/functions_compatibility.php b/phpBB/includes/functions_compatibility.php index 0dd2dd74bb..2a53b2ec3a 100644 --- a/phpBB/includes/functions_compatibility.php +++ b/phpBB/includes/functions_compatibility.php @@ -881,3 +881,65 @@ function phpbb_check_and_display_sql_report(\phpbb\request\request_interface $re $controller_helper->display_sql_report(); } + +/** + * Parse cfg file + * @param string $filename + * @param bool|array $lines + * @return array + * + * @deprecated 4.0.0-a1 (To be removed: 5.0.0) + */ +function parse_cfg_file($filename, $lines = false) +{ + $parsed_items = array(); + + if ($lines === false) + { + $lines = file($filename); + } + + foreach ($lines as $line) + { + $line = trim($line); + + if (!$line || $line[0] == '#' || ($delim_pos = strpos($line, '=')) === false) + { + continue; + } + + // Determine first occurrence, since in values the equal sign is allowed + $key = htmlspecialchars(strtolower(trim(substr($line, 0, $delim_pos))), ENT_COMPAT); + $value = trim(substr($line, $delim_pos + 1)); + + if (in_array($value, array('off', 'false', '0'))) + { + $value = false; + } + else if (in_array($value, array('on', 'true', '1'))) + { + $value = true; + } + else if (!trim($value)) + { + $value = ''; + } + else if (($value[0] == "'" && $value[strlen($value) - 1] == "'") || ($value[0] == '"' && $value[strlen($value) - 1] == '"')) + { + $value = htmlspecialchars(substr($value, 1, strlen($value)-2), ENT_COMPAT); + } + else + { + $value = htmlspecialchars($value, ENT_COMPAT); + } + + $parsed_items[$key] = $value; + } + + if (isset($parsed_items['parent']) && isset($parsed_items['name']) && $parsed_items['parent'] == $parsed_items['name']) + { + unset($parsed_items['parent']); + } + + return $parsed_items; +} diff --git a/phpBB/language/en/acp/language.php b/phpBB/language/en/acp/language.php index d14491ae75..27bf209bba 100644 --- a/phpBB/language/en/acp/language.php +++ b/phpBB/language/en/acp/language.php @@ -60,6 +60,7 @@ $lang = array_merge($lang, array( 'LANG_ENGLISH_NAME' => 'English name', 'LANG_ISO_CODE' => 'ISO code', 'LANG_LOCAL_NAME' => 'Local name', + 'LANG_VERSION' => 'Language version', 'MISSING_LANG_FILES' => 'Missing language files', 'MISSING_LANG_VARIABLES' => 'Missing language variables', diff --git a/phpBB/language/en/acp/styles.php b/phpBB/language/en/acp/styles.php index 44be3c11cd..0adc95f3dc 100644 --- a/phpBB/language/en/acp/styles.php +++ b/phpBB/language/en/acp/styles.php @@ -54,6 +54,7 @@ $lang = array_merge($lang, [ 'INSTALL_STYLES' => 'Install styles', 'INSTALL_STYLES_EXPLAIN' => 'Here you can install new styles.
If you cannot find a specific style in list below, check to make sure style is already installed. If it is not installed, check if it was uploaded correctly.', 'INVALID_STYLE_ID' => 'Invalid style ID.', + 'INVALID_STYLE_MESSAGE' => 'The directory %1$s does not contain a valid style.', 'NO_MATCHING_STYLES_FOUND' => 'No styles match your query.', 'NO_UNINSTALLED_STYLE' => 'No uninstalled styles detected.', diff --git a/phpBB/language/en/composer.json b/phpBB/language/en/composer.json new file mode 100644 index 0000000000..bbb2da4ae1 --- /dev/null +++ b/phpBB/language/en/composer.json @@ -0,0 +1,27 @@ +{ + "name": "phpbb/phpbb-language-en", + "description": "phpBB Forum Software default language", + "type": "phpbb-language", + "version": "4.0.0-a1-dev", + "homepage": "https://www.phpbb.com", + "license": "GPL-2.0", + "authors": [ + { + "name": "phpBB Limited", + "email": "operations@phpbb.com", + "homepage": "https://www.phpbb.com/go/authors" + } + ], + "support": { + "issues": "https://tracker.phpbb.com", + "forum": "https://www.phpbb.com/community/", + "wiki": "https://wiki.phpbb.com", + "irc": "irc://irc.freenode.org/phpbb" + }, + "extra": { + "language-iso": "en", + "english-name": "British English", + "local-name": "British English", + "phpbb-version": "4.0.0-a1-dev" + } +} diff --git a/phpBB/language/en/iso.txt b/phpBB/language/en/iso.txt deleted file mode 100644 index 2e45cc56d0..0000000000 --- a/phpBB/language/en/iso.txt +++ /dev/null @@ -1,3 +0,0 @@ -British English -British English -phpBB Limited \ No newline at end of file diff --git a/phpBB/phpbb/cache/service.php b/phpBB/phpbb/cache/service.php index 2d4e55bfbb..bce0c8d6d8 100644 --- a/phpBB/phpbb/cache/service.php +++ b/phpBB/phpbb/cache/service.php @@ -13,6 +13,8 @@ namespace phpbb\cache; +use phpbb\json\sanitizer as json_sanitizer; + /** * Class for grabbing/handling cached entries */ @@ -344,7 +346,7 @@ class service $parsed_array = array(); } - $filename = $this->phpbb_root_path . 'styles/' . $style['style_path'] . '/style.cfg'; + $filename = $this->phpbb_root_path . 'styles/' . $style['style_path'] . '/composer.json'; if (!file_exists($filename)) { @@ -354,7 +356,8 @@ class service if (!isset($parsed_array['filetime']) || (($this->config['load_tplcompile'] && @filemtime($filename) > $parsed_array['filetime']))) { // Re-parse cfg file - $parsed_array = parse_cfg_file($filename); + $json = file_get_contents($filename); + $parsed_array = json_sanitizer::decode($json); $parsed_array['filetime'] = @filemtime($filename); $this->driver->put('_cfg_' . $style['style_path'], $parsed_array); diff --git a/phpBB/phpbb/db/migration/data/v310/style_update_p1.php b/phpBB/phpbb/db/migration/data/v310/style_update_p1.php index a7e30a9cb7..be66dcd8e9 100644 --- a/phpBB/phpbb/db/migration/data/v310/style_update_p1.php +++ b/phpBB/phpbb/db/migration/data/v310/style_update_p1.php @@ -13,6 +13,8 @@ namespace phpbb\db\migration\data\v310; +use phpbb\json\sanitizer as json_sanitizer; + class style_update_p1 extends \phpbb\db\migration\migration { public function effectively_installed() @@ -69,13 +71,26 @@ class style_update_p1 extends \phpbb\db\migration\migration $skip_dirs = array('.', '..', 'prosilver'); foreach ($iterator as $fileinfo) { - if ($fileinfo->isDir() && !in_array($fileinfo->getFilename(), $skip_dirs) && file_exists($fileinfo->getPathname() . '/style.cfg')) + if ($fileinfo->isDir() && !in_array($fileinfo->getFilename(), $skip_dirs)) { - $style_cfg = parse_cfg_file($fileinfo->getPathname() . '/style.cfg'); - if (isset($style_cfg['phpbb_version']) && version_compare($style_cfg['phpbb_version'], '3.1.0-dev', '>=')) + if (file_exists($fileinfo->getPathname() . '/style.cfg')) { - // 3.1 style - $available_styles[] = $fileinfo->getFilename(); + $style_cfg = parse_cfg_file($fileinfo->getPathname() . '/style.cfg'); + if (isset($style_cfg['phpbb_version']) && version_compare($style_cfg['phpbb_version'], '3.1.0-dev', '>=')) + { + // 3.1 - 3.3 style + $available_styles[] = $fileinfo->getFilename(); + } + } + else if (file_exists($fileinfo->getPathname() . '/composer.json')) + { + $json = file_get_contents($fileinfo->getPathname() . '/composer.json'); + $style_data = json_sanitizer::decode($json); + if (isset($style_data['extra']['phpbb-version']) && version_compare($style_data['extra']['phpbb-version'], '4.0.0-dev', '>=')) + { + // 4.x style + $available_styles[] = $fileinfo->getFilename(); + } } } } diff --git a/phpBB/phpbb/db/migration/data/v31x/style_update.php b/phpBB/phpbb/db/migration/data/v31x/style_update.php index a5f99b5d28..0c3f53c4a9 100644 --- a/phpBB/phpbb/db/migration/data/v31x/style_update.php +++ b/phpBB/phpbb/db/migration/data/v31x/style_update.php @@ -52,8 +52,8 @@ class style_update extends \phpbb\db\migration\migration // Try to parse config file $cfg = parse_cfg_file($this->phpbb_root_path . 'styles/prosilver/style.cfg'); - // Stop running this if prosilver cfg file can't be read - if (empty($cfg)) + // Stop running this if both prosilver cfg file and composer.json file can't be read + if (empty($cfg) && !file_exists($this->phpbb_root_path . 'styles/prosilver/composer.json')) { throw new \RuntimeException('No styles available and could not fall back to prosilver.'); } @@ -123,7 +123,7 @@ class style_update extends \phpbb\db\migration\migration continue; } - if (file_exists("{$dir}/style.cfg")) + if (file_exists("{$dir}/composer.json") || file_exists("{$dir}/style.cfg")) { $styles[] = $file; } diff --git a/phpBB/phpbb/json/sanitizer.php b/phpBB/phpbb/json/sanitizer.php new file mode 100644 index 0000000000..4db1602055 --- /dev/null +++ b/phpBB/phpbb/json/sanitizer.php @@ -0,0 +1,59 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +declare(strict_types=1); + +namespace phpbb\json; + +use phpbb\request\type_cast_helper; + +/** + * JSON sanitizer class + */ +class sanitizer +{ + /** + * Sanitize json data + * + * @param array $data Data to sanitize + * + * @return array Sanitized data + */ + static public function sanitize(array $data) : array + { + if (!empty($data)) + { + $json_sanitizer = function (&$value) + { + $type_cast_helper = new type_cast_helper(); + $type_cast_helper->set_var($value, $value, gettype($value), true); + }; + array_walk_recursive($data, $json_sanitizer); + } + + return $data; + } + + /** + * Decode and sanitize json data + * + * @param string $json JSON data string + * + * @return array Data array + */ + static public function decode(string $json) : array + { + $data = json_decode($json, true); + return !empty($data) ? self::sanitize($data) : []; + } +} diff --git a/phpBB/phpbb/language/language_file_helper.php b/phpBB/phpbb/language/language_file_helper.php index 85de034fb8..e6966a3710 100644 --- a/phpBB/phpbb/language/language_file_helper.php +++ b/phpBB/phpbb/language/language_file_helper.php @@ -13,6 +13,8 @@ namespace phpbb\language; +use DomainException; +use phpbb\json\sanitizer as json_sanitizer; use Symfony\Component\Finder\Finder; /** @@ -28,9 +30,9 @@ class language_file_helper /** * Constructor * - * @param string $phpbb_root_path Path to phpBB's root + * @param string $phpbb_root_path Path to phpBB's root */ - public function __construct($phpbb_root_path) + public function __construct(string $phpbb_root_path) { $this->phpbb_root_path = $phpbb_root_path; } @@ -39,13 +41,16 @@ class language_file_helper * Returns available languages * * @return array + * + * @throws DomainException When one of the languages in language directory + * could not be loaded or have invalid composer.json data */ - public function get_available_languages() + public function get_available_languages() : array { // Find available language packages $finder = new Finder(); $finder->files() - ->name('iso.txt') + ->name('composer.json') ->depth('== 1') ->followLinks() ->in($this->phpbb_root_path . 'language'); @@ -53,20 +58,63 @@ class language_file_helper $available_languages = array(); foreach ($finder as $file) { - $path = $file->getRelativePath(); - $info = explode("\n", $file->getContents()); + $json = $file->getContents(); + $data = json_sanitizer::decode($json); - $available_languages[] = array( - // Get the name of the directory containing iso.txt - 'iso' => $path, - - // Recover data from file - 'name' => trim($info[0]), - 'local_name' => trim($info[1]), - 'author' => trim($info[2]) - ); + $available_languages[] = $this->get_language_data_from_json($data); } return $available_languages; } + + /** + * Collect some data from the composer.json file + * + * @param string $path + * @return array + * + * @throws DomainException When unable to language data from composer.json + */ + public function get_language_data_from_composer_file(string $path) : array + { + $json_data = file_get_contents($path); + return $this->get_language_data_from_json(json_sanitizer::decode($json_data)); + } + + /** + * Collect some data from the composer.json data + * + * @param array $data + * @return array + * + * @throws DomainException When composer.json data is invalid for language files + */ + protected function get_language_data_from_json(array $data) : array + { + if (!isset($data['extra']['language-iso']) || !isset($data['extra']['english-name']) || !isset($data['extra']['local-name'])) + { + throw new DomainException('INVALID_LANGUAGE_PACK'); + } + + $authors = []; + if (isset($data['authors'])) + { + foreach ($data['authors'] as $author) + { + if (isset($author['name']) && $author['name'] !== '') + { + $authors[] = $author['name']; + } + } + } + + return [ + 'iso' => $data['extra']['language-iso'], + 'name' => $data['extra']['english-name'], + 'local_name' => $data['extra']['local-name'], + 'author' => implode(', ', $authors), + 'version' => $data['version'], + 'phpbb_version' => $data['extra']['phpbb-version'], + ]; + } } diff --git a/phpBB/phpbb/version_helper.php b/phpBB/phpbb/version_helper.php index a73fbfbfbe..61737865ea 100644 --- a/phpBB/phpbb/version_helper.php +++ b/phpBB/phpbb/version_helper.php @@ -14,6 +14,7 @@ namespace phpbb; use phpbb\exception\version_check_exception; +use phpbb\json\sanitizer as json_sanitizer; /** * Class to handle version checking and comparison @@ -389,17 +390,8 @@ class version_helper throw new version_check_exception($error_string); } - $info = json_decode($info, true); - // Sanitize any data we retrieve from a server - if (!empty($info)) - { - $json_sanitizer = function (&$value, $key) { - $type_cast_helper = new \phpbb\request\type_cast_helper(); - $type_cast_helper->set_var($value, $value, gettype($value), true); - }; - array_walk_recursive($info, $json_sanitizer); - } + $info = json_sanitizer::decode($info); if (empty($info['stable']) && empty($info['unstable'])) { diff --git a/phpBB/styles/prosilver/composer.json b/phpBB/styles/prosilver/composer.json new file mode 100644 index 0000000000..51024f2285 --- /dev/null +++ b/phpBB/styles/prosilver/composer.json @@ -0,0 +1,26 @@ +{ + "name": "phpbb/phpbb-style-prosilver", + "description": "phpBB Forum Software default style", + "type": "phpbb-style", + "version": "4.0.0-a1-dev", + "homepage": "https://www.phpbb.com", + "license": "GPL-2.0", + "authors": [ + { + "name": "phpBB Limited", + "email": "operations@phpbb.com", + "homepage": "https://www.phpbb.com/go/authors" + } + ], + "support": { + "issues": "https://tracker.phpbb.com", + "forum": "https://www.phpbb.com/community/", + "wiki": "https://wiki.phpbb.com", + "irc": "irc://irc.freenode.org/phpbb" + }, + "extra": { + "display-name": "prosilver", + "phpbb-version": "4.0.0-a1-dev", + "parent-style": "" + } +} diff --git a/phpBB/styles/prosilver/style.cfg b/phpBB/styles/prosilver/style.cfg deleted file mode 100644 index 0f795e6a60..0000000000 --- a/phpBB/styles/prosilver/style.cfg +++ /dev/null @@ -1,32 +0,0 @@ -# -# phpBB Style Configuration File -# -# This file is part of the phpBB Forum Software package. -# -# @copyright (c) phpBB Limited -# @license GNU General Public License, version 2 (GPL-2.0) -# -# For full copyright and license information, please see -# the docs/CREDITS.txt file. -# -# At the left is the name, please do not change this -# At the right the value is entered -# -# Values get trimmed, if you want to add a space in front or at the end of -# the value, then enclose the value with single or double quotes. -# Single and double quotes do not need to be escaped. -# -# - -# General Information about this style -name = prosilver -copyright = © phpBB Limited, 2007 -style_version = 4.0.0-a1-dev -phpbb_version = 4.0.0-a1-dev - -# Defining a different template bitfield -# template_bitfield = //g= - -# Parent style -# Set value to empty or to this style's name if this style does not have a parent style -parent = prosilver diff --git a/tests/json/sanitizer_test.php b/tests/json/sanitizer_test.php new file mode 100644 index 0000000000..267dcaf774 --- /dev/null +++ b/tests/json/sanitizer_test.php @@ -0,0 +1,35 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +use phpbb\json\sanitizer as json_sanitizer; + +class phpbb_json_sanitizer_test extends phpbb_test_case +{ + public function data_decode() + { + return [ + [false, []], + ['', []], + ['{ "name": "phpbb/phpbb-style-prosilver"}', ['name' => 'phpbb/phpbb-style-prosilver']], + ['{ "name":[[ "phpbb/phpbb-style-prosilver"}', []], + ]; + } + + /** + * @dataProvider data_decode + */ + public function test_decode_data($input, $output) + { + $this->assertEquals($output, json_sanitizer::decode($input)); + } +}