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));
+ }
+}