Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 27 |
CRAP | |
0.00% |
0 / 885 |
| acp_styles | |
0.00% |
0 / 1 |
|
0.00% |
0 / 27 |
36290 | |
0.00% |
0 / 881 |
| main | |
0.00% |
0 / 1 |
182 | |
0.00% |
0 / 72 |
|||
| frontend | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 15 |
|||
| action_install | |
0.00% |
0 / 1 |
132 | |
0.00% |
0 / 45 |
|||
| action_uninstall | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 25 |
|||
| action_uninstall_confirmed | |
0.00% |
0 / 1 |
110 | |
0.00% |
0 / 51 |
|||
| action_activate | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 9 |
|||
| action_deactivate | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 20 |
|||
| action_details | |
0.00% |
0 / 1 |
702 | |
0.00% |
0 / 149 |
|||
| show_installed | |
0.00% |
0 / 1 |
72 | |
0.00% |
0 / 42 |
|||
| show_available | |
0.00% |
0 / 1 |
156 | |
0.00% |
0 / 48 |
|||
| find_available | |
0.00% |
0 / 1 |
110 | |
0.00% |
0 / 62 |
|||
| show_styles_list | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 10 |
|||
| show_available_child_styles | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 10 |
|||
| update_styles_tree | |
0.00% |
0 / 1 |
72 | |
0.00% |
0 / 27 |
|||
| find_possible_parents | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 19 |
|||
| list_style | |
0.00% |
0 / 1 |
272 | |
0.00% |
0 / 82 |
|||
| welcome_message | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 7 |
|||
| find_style_dirs | |
0.00% |
0 / 1 |
42 | |
0.00% |
0 / 21 |
|||
| sort_styles | |
0.00% |
0 / 1 |
72 | |
0.00% |
0 / 11 |
|||
| read_style_cfg | |
0.00% |
0 / 1 |
72 | |
0.00% |
0 / 24 |
|||
| install_style | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 19 |
|||
| get_styles | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 8 |
|||
| get_users | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 13 |
|||
| uninstall_style | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 22 |
|||
| delete_style_files | |
0.00% |
0 / 1 |
90 | |
0.00% |
0 / 36 |
|||
| request_vars | |
0.00% |
0 / 1 |
56 | |
0.00% |
0 / 17 |
|||
| default_bitfield | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 17 |
|||
| <?php | |
| /** | |
| * | |
| * This file is part of the phpBB Forum Software package. | |
| * | |
| * @copyright (c) phpBB Limited <https://www.phpbb.com> | |
| * @license GNU General Public License, version 2 (GPL-2.0) | |
| * | |
| * For full copyright and license information, please see | |
| * the docs/CREDITS.txt file. | |
| * | |
| */ | |
| /** | |
| * @ignore | |
| */ | |
| if (!defined('IN_PHPBB')) | |
| { | |
| exit; | |
| } | |
| class acp_styles | |
| { | |
| public $u_action; | |
| protected $u_base_action; | |
| protected $s_hidden_fields; | |
| protected $mode; | |
| protected $styles_path; | |
| protected $styles_path_absolute = 'styles'; | |
| protected $default_style = 0; | |
| protected $styles_list_cols = 0; | |
| protected $reserved_style_names = array('adm', 'admin', 'all'); | |
| /** @var \phpbb\config\config */ | |
| protected $config; | |
| /** @var \phpbb\db\driver\driver_interface */ | |
| protected $db; | |
| /** @var \phpbb\user */ | |
| protected $user; | |
| /** @var \phpbb\template\template */ | |
| protected $template; | |
| /** @var \phpbb\request\request_interface */ | |
| protected $request; | |
| /** @var \phpbb\cache\driver\driver_interface */ | |
| protected $cache; | |
| /** @var \phpbb\auth\auth */ | |
| protected $auth; | |
| /** @var \phpbb\textformatter\cache_interface */ | |
| protected $text_formatter_cache; | |
| /** @var string */ | |
| protected $phpbb_root_path; | |
| /** @var string */ | |
| protected $php_ext; | |
| /** @var \phpbb\event\dispatcher_interface */ | |
| protected $dispatcher; | |
| public function main($id, $mode) | |
| { | |
| global $db, $user, $phpbb_admin_path, $phpbb_root_path, $phpEx, $template, $request, $cache, $auth, $config, $phpbb_dispatcher, $phpbb_container; | |
| $this->db = $db; | |
| $this->user = $user; | |
| $this->template = $template; | |
| $this->request = $request; | |
| $this->cache = $cache; | |
| $this->auth = $auth; | |
| $this->text_formatter_cache = $phpbb_container->get('text_formatter.cache'); | |
| $this->config = $config; | |
| $this->phpbb_root_path = $phpbb_root_path; | |
| $this->php_ext = $phpEx; | |
| $this->dispatcher = $phpbb_dispatcher; | |
| $this->default_style = $config['default_style']; | |
| $this->styles_path = $this->phpbb_root_path . $this->styles_path_absolute . '/'; | |
| $this->u_base_action = append_sid("{$phpbb_admin_path}index.{$this->php_ext}", "i={$id}"); | |
| $this->s_hidden_fields = array( | |
| 'mode' => $mode, | |
| ); | |
| $this->user->add_lang('acp/styles'); | |
| $this->tpl_name = 'acp_styles'; | |
| $this->page_title = 'ACP_CAT_STYLES'; | |
| $this->mode = $mode; | |
| $action = $this->request->variable('action', ''); | |
| $post_actions = array('install', 'activate', 'deactivate', 'uninstall'); | |
| foreach ($post_actions as $key) | |
| { | |
| if ($this->request->is_set_post($key)) | |
| { | |
| $action = $key; | |
| } | |
| } | |
| // The uninstall action uses confirm_box() to verify the validity of the request, | |
| // so there is no need to check for a valid token here. | |
| if (in_array($action, $post_actions) && $action != 'uninstall') | |
| { | |
| $is_valid_request = check_link_hash($request->variable('hash', ''), $action) || check_form_key('styles_management'); | |
| if (!$is_valid_request) | |
| { | |
| trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); | |
| } | |
| } | |
| if ($action != '') | |
| { | |
| $this->s_hidden_fields['action'] = $action; | |
| } | |
| $this->template->assign_vars(array( | |
| 'U_ACTION' => $this->u_base_action, | |
| 'S_HIDDEN_FIELDS' => build_hidden_fields($this->s_hidden_fields) | |
| ) | |
| ); | |
| /** | |
| * Run code before ACP styles action execution | |
| * | |
| * @event core.acp_styles_action_before | |
| * @var int id Module ID | |
| * @var string mode Active module | |
| * @var string action Module that should be run | |
| * @since 3.1.7-RC1 | |
| */ | |
| $vars = array('id', 'mode', 'action'); | |
| extract($this->dispatcher->trigger_event('core.acp_styles_action_before', compact($vars))); | |
| // Execute actions | |
| switch ($action) | |
| { | |
| case 'install': | |
| $this->action_install(); | |
| return; | |
| case 'uninstall': | |
| $this->action_uninstall(); | |
| return; | |
| case 'activate': | |
| $this->action_activate(); | |
| return; | |
| case 'deactivate': | |
| $this->action_deactivate(); | |
| return; | |
| case 'details': | |
| $this->action_details(); | |
| return; | |
| default: | |
| $this->frontend(); | |
| } | |
| } | |
| /** | |
| * Main page | |
| */ | |
| protected function frontend() | |
| { | |
| add_form_key('styles_management'); | |
| // Check mode | |
| switch ($this->mode) | |
| { | |
| case 'style': | |
| $this->welcome_message('ACP_STYLES', 'ACP_STYLES_EXPLAIN'); | |
| $this->show_installed(); | |
| return; | |
| case 'install': | |
| $this->welcome_message('INSTALL_STYLES', 'INSTALL_STYLES_EXPLAIN'); | |
| $this->show_available(); | |
| return; | |
| } | |
| trigger_error($this->user->lang['NO_MODE'] . adm_back_link($this->u_action), E_USER_WARNING); | |
| } | |
| /** | |
| * Install style(s) | |
| */ | |
| protected function action_install() | |
| { | |
| // Get list of styles to install | |
| $dirs = $this->request_vars('dir', '', true); | |
| // Get list of styles that can be installed | |
| $styles = $this->find_available(false); | |
| // Install each style | |
| $messages = array(); | |
| $installed_names = array(); | |
| $installed_dirs = array(); | |
| foreach ($dirs as $dir) | |
| { | |
| if (in_array($dir, $this->reserved_style_names)) | |
| { | |
| $messages[] = $this->user->lang('STYLE_NAME_RESERVED', htmlspecialchars($dir)); | |
| continue; | |
| } | |
| $found = false; | |
| foreach ($styles as &$style) | |
| { | |
| // Check if: | |
| // 1. Directory matches directory we are looking for | |
| // 2. Style is not installed yet | |
| // 3. Style with same name or directory hasn't been installed already within this function | |
| if ($style['style_path'] == $dir && empty($style['_installed']) && !in_array($style['style_path'], $installed_dirs) && !in_array($style['style_name'], $installed_names)) | |
| { | |
| // Install style | |
| $style['style_active'] = 1; | |
| $style['style_id'] = $this->install_style($style); | |
| $style['_installed'] = true; | |
| $found = true; | |
| $installed_names[] = $style['style_name']; | |
| $installed_dirs[] = $style['style_path']; | |
| $messages[] = sprintf($this->user->lang['STYLE_INSTALLED'], htmlspecialchars($style['style_name'])); | |
| } | |
| } | |
| if (!$found) | |
| { | |
| $messages[] = sprintf($this->user->lang['STYLE_NOT_INSTALLED'], htmlspecialchars($dir)); | |
| } | |
| } | |
| // Invalidate the text formatter's cache for the new styles to take effect | |
| if (!empty($installed_names)) | |
| { | |
| $this->text_formatter_cache->invalidate(); | |
| } | |
| // Show message | |
| if (!count($messages)) | |
| { | |
| trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); | |
| } | |
| $message = implode('<br />', $messages); | |
| $message .= '<br /><br /><a href="' . $this->u_base_action . '&mode=style' . '">« ' . $this->user->lang('STYLE_INSTALLED_RETURN_INSTALLED_STYLES') . '</a>'; | |
| $message .= '<br /><br /><a href="' . $this->u_base_action . '&mode=install' . '">» ' . $this->user->lang('STYLE_INSTALLED_RETURN_UNINSTALLED_STYLES') . '</a>'; | |
| trigger_error($message, E_USER_NOTICE); | |
| } | |
| /** | |
| * Confirm styles removal | |
| */ | |
| protected function action_uninstall() | |
| { | |
| // Get list of styles to uninstall | |
| $ids = $this->request_vars('id', 0, true); | |
| // Don't remove prosilver, you can still deactivate it. | |
| $sql = 'SELECT style_id | |
| FROM ' . STYLES_TABLE . " | |
| WHERE style_name = '" . $this->db->sql_escape('prosilver') . "'"; | |
| $result = $this->db->sql_query($sql); | |
| $prosilver_id = (int) $this->db->sql_fetchfield('style_id'); | |
| $this->db->sql_freeresult($result); | |
| if ($prosilver_id && in_array($prosilver_id, $ids)) | |
| { | |
| trigger_error($this->user->lang('UNINSTALL_PROSILVER') . adm_back_link($this->u_action), E_USER_WARNING); | |
| } | |
| // Check if confirmation box was submitted | |
| if (confirm_box(true)) | |
| { | |
| // Uninstall | |
| $this->action_uninstall_confirmed($ids, $this->request->variable('confirm_delete_files', false)); | |
| return; | |
| } | |
| // Confirm box | |
| $s_hidden = build_hidden_fields(array( | |
| 'action' => 'uninstall', | |
| 'ids' => $ids | |
| )); | |
| $this->template->assign_var('S_CONFIRM_DELETE', true); | |
| confirm_box(false, $this->user->lang['CONFIRM_UNINSTALL_STYLES'], $s_hidden, 'acp_styles.html'); | |
| // Canceled - show styles list | |
| $this->frontend(); | |
| } | |
| /** | |
| * Uninstall styles(s) | |
| * | |
| * @param array $ids List of style IDs | |
| * @param bool $delete_files If true, script will attempt to remove files for selected styles | |
| */ | |
| protected function action_uninstall_confirmed($ids, $delete_files) | |
| { | |
| global $user, $phpbb_log; | |
| $default = $this->default_style; | |
| $uninstalled = array(); | |
| $messages = array(); | |
| // Check styles list | |
| foreach ($ids as $id) | |
| { | |
| if (!$id) | |
| { | |
| trigger_error($this->user->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); | |
| } | |
| $uninstalled[$id] = false; | |
| } | |
| // Order by reversed style_id, so parent styles would be removed after child styles | |
| // This way parent and child styles can be removed in same function call | |
| $sql = 'SELECT * | |
| FROM ' . STYLES_TABLE . ' | |
| WHERE style_id IN (' . implode(', ', $ids) . ') | |
| ORDER BY style_id DESC'; | |
| $result = $this->db->sql_query($sql); | |
| $rows = $this->db->sql_fetchrowset($result); | |
| $this->db->sql_freeresult($result); | |
| // Uinstall each style | |
| $uninstalled = array(); | |
| foreach ($rows as $style) | |
| { | |
| $result = $this->uninstall_style($style, $delete_files); | |
| if (is_string($result)) | |
| { | |
| $messages[] = $result; | |
| continue; | |
| } | |
| $messages[] = sprintf($this->user->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']); | |
| } | |
| } | |
| if (empty($messages)) | |
| { | |
| // Nothing to uninstall? | |
| trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); | |
| } | |
| // Log action | |
| if (count($uninstalled)) | |
| { | |
| $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_STYLE_DELETE', false, array(implode(', ', $uninstalled))); | |
| } | |
| // Clear cache | |
| $this->cache->purge(); | |
| // Show message | |
| trigger_error(implode('<br />', $messages) . adm_back_link($this->u_action), E_USER_NOTICE); | |
| } | |
| /** | |
| * Activate styles | |
| */ | |
| protected function action_activate() | |
| { | |
| // Get list of styles to activate | |
| $ids = $this->request_vars('id', 0, true); | |
| // Activate styles | |
| $sql = 'UPDATE ' . STYLES_TABLE . ' | |
| SET style_active = 1 | |
| WHERE style_id IN (' . implode(', ', $ids) . ')'; | |
| $this->db->sql_query($sql); | |
| // Purge cache | |
| $this->cache->destroy('sql', STYLES_TABLE); | |
| // Show styles list | |
| $this->frontend(); | |
| } | |
| /** | |
| * Deactivate styles | |
| */ | |
| protected function action_deactivate() | |
| { | |
| // Get list of styles to deactivate | |
| $ids = $this->request_vars('id', 0, true); | |
| // Check for default style | |
| foreach ($ids as $id) | |
| { | |
| if ($id == $this->default_style) | |
| { | |
| trigger_error($this->user->lang['DEACTIVATE_DEFAULT'] . adm_back_link($this->u_action), E_USER_WARNING); | |
| } | |
| } | |
| // Reset default style for users who use selected styles | |
| $sql = 'UPDATE ' . USERS_TABLE . ' | |
| SET user_style = ' . (int) $this->default_style . ' | |
| WHERE user_style IN (' . implode(', ', $ids) . ')'; | |
| $this->db->sql_query($sql); | |
| // Deactivate styles | |
| $sql = 'UPDATE ' . STYLES_TABLE . ' | |
| SET style_active = 0 | |
| WHERE style_id IN (' . implode(', ', $ids) . ')'; | |
| $this->db->sql_query($sql); | |
| // Purge cache | |
| $this->cache->destroy('sql', STYLES_TABLE); | |
| // Show styles list | |
| $this->frontend(); | |
| } | |
| /** | |
| * Show style details | |
| */ | |
| protected function action_details() | |
| { | |
| global $user, $phpbb_log; | |
| $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); | |
| } | |
| // Get all styles | |
| $styles = $this->get_styles(); | |
| usort($styles, array($this, 'sort_styles')); | |
| // Find current style | |
| $style = false; | |
| foreach ($styles as $row) | |
| { | |
| if ($row['style_id'] == $id) | |
| { | |
| $style = $row; | |
| break; | |
| } | |
| } | |
| if ($style === false) | |
| { | |
| trigger_error($this->user->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']); | |
| // Find all available parent styles | |
| $list = $this->find_possible_parents($styles, $id); | |
| // Add form key | |
| $form_key = 'acp_styles'; | |
| add_form_key($form_key); | |
| // Change data | |
| if ($this->request->variable('update', false)) | |
| { | |
| if (!check_form_key($form_key)) | |
| { | |
| trigger_error($this->user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); | |
| } | |
| $update = array( | |
| 'style_name' => trim($this->request->variable('style_name', $style['style_name'])), | |
| 'style_parent_id' => $this->request->variable('style_parent', (int) $style['style_parent_id']), | |
| 'style_active' => $this->request->variable('style_active', (int) $style['style_active']), | |
| ); | |
| $update_action = $this->u_action . '&action=details&id=' . $id; | |
| // Check style name | |
| if ($update['style_name'] != $style['style_name']) | |
| { | |
| if (!strlen($update['style_name'])) | |
| { | |
| trigger_error($this->user->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); | |
| } | |
| } | |
| } | |
| else | |
| { | |
| unset($update['style_name']); | |
| } | |
| // Check parent style id | |
| if ($update['style_parent_id'] != $style['style_parent_id']) | |
| { | |
| if ($update['style_parent_id'] != 0) | |
| { | |
| $found = false; | |
| foreach ($list as $row) | |
| { | |
| if ($row['style_id'] == $update['style_parent_id']) | |
| { | |
| $found = true; | |
| $update['style_parent_tree'] = ($row['style_parent_tree'] != '' ? $row['style_parent_tree'] . '/' : '') . $row['style_path']; | |
| break; | |
| } | |
| } | |
| if (!$found) | |
| { | |
| trigger_error($this->user->lang['STYLE_ERR_INVALID_PARENT'] . adm_back_link($update_action), E_USER_WARNING); | |
| } | |
| } | |
| else | |
| { | |
| $update['style_parent_tree'] = ''; | |
| } | |
| } | |
| else | |
| { | |
| unset($update['style_parent_id']); | |
| } | |
| // Check style_active | |
| if ($update['style_active'] != $style['style_active']) | |
| { | |
| 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); | |
| } | |
| } | |
| else | |
| { | |
| unset($update['style_active']); | |
| } | |
| // Update data | |
| if (count($update)) | |
| { | |
| $sql = 'UPDATE ' . STYLES_TABLE . ' | |
| SET ' . $this->db->sql_build_array('UPDATE', $update) . " | |
| WHERE style_id = $id"; | |
| $this->db->sql_query($sql); | |
| $style = array_merge($style, $update); | |
| if (isset($update['style_parent_id'])) | |
| { | |
| // Update styles tree | |
| $styles = $this->get_styles(); | |
| if ($this->update_styles_tree($styles, $style)) | |
| { | |
| // Something was changed in styles tree, purge all cache | |
| $this->cache->purge(); | |
| } | |
| } | |
| $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_STYLE_EDIT_DETAILS', false, array($style['style_name'])); | |
| } | |
| // Update default style | |
| $default = $this->request->variable('style_default', 0); | |
| if ($default) | |
| { | |
| if (!$style['style_active']) | |
| { | |
| trigger_error($this->user->lang['STYLE_DEFAULT_CHANGE_INACTIVE'] . adm_back_link($update_action), E_USER_WARNING); | |
| } | |
| $this->config->set('default_style', $id); | |
| $this->cache->purge(); | |
| } | |
| // Show styles list | |
| $this->frontend(); | |
| return; | |
| } | |
| // Show page title | |
| $this->welcome_message('ACP_STYLES', null); | |
| // Show parent styles | |
| foreach ($list as $row) | |
| { | |
| $this->template->assign_block_vars('parent_styles', array( | |
| 'STYLE_ID' => $row['style_id'], | |
| 'STYLE_NAME' => htmlspecialchars($row['style_name']), | |
| 'LEVEL' => $row['level'], | |
| 'SPACER' => str_repeat(' ', $row['level']), | |
| ) | |
| ); | |
| } | |
| // Show style details | |
| $this->template->assign_vars(array( | |
| 'S_STYLE_DETAILS' => true, | |
| 'STYLE_ID' => $style['style_id'], | |
| 'STYLE_NAME' => htmlspecialchars($style['style_name']), | |
| 'STYLE_PATH' => htmlspecialchars($style['style_path']), | |
| 'STYLE_VERSION' => htmlspecialchars($style_cfg['style_version']), | |
| '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) | |
| ) | |
| ); | |
| } | |
| /** | |
| * List installed styles | |
| */ | |
| protected function show_installed() | |
| { | |
| // Get all installed styles | |
| $styles = $this->get_styles(); | |
| if (!count($styles)) | |
| { | |
| trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); | |
| } | |
| usort($styles, array($this, 'sort_styles')); | |
| // Get users | |
| $users = $this->get_users(); | |
| // Add users counter to rows | |
| foreach ($styles as &$style) | |
| { | |
| $style['_users'] = isset($users[$style['style_id']]) ? $users[$style['style_id']] : 0; | |
| } | |
| // Set up styles list variables | |
| // Addons should increase this number and update template variable | |
| $this->styles_list_cols = 4; | |
| $this->template->assign_var('STYLES_LIST_COLS', $this->styles_list_cols); | |
| // Show styles list | |
| $this->show_styles_list($styles, 0, 0); | |
| // Show styles with invalid inherits_id | |
| foreach ($styles as $style) | |
| { | |
| if (empty($style['_shown'])) | |
| { | |
| $style['_note'] = sprintf($this->user->lang['REQUIRES_STYLE'], htmlspecialchars($style['style_parent_tree'])); | |
| $this->list_style($style, 0); | |
| } | |
| } | |
| // Add buttons | |
| $this->template->assign_block_vars('extra_actions', array( | |
| 'ACTION_NAME' => 'activate', | |
| 'L_ACTION' => $this->user->lang['STYLE_ACTIVATE'], | |
| ) | |
| ); | |
| $this->template->assign_block_vars('extra_actions', array( | |
| 'ACTION_NAME' => 'deactivate', | |
| 'L_ACTION' => $this->user->lang['STYLE_DEACTIVATE'], | |
| ) | |
| ); | |
| if (isset($this->style_counters) && $this->style_counters['total'] > 1) | |
| { | |
| $this->template->assign_block_vars('extra_actions', array( | |
| 'ACTION_NAME' => 'uninstall', | |
| 'L_ACTION' => $this->user->lang['STYLE_UNINSTALL'], | |
| ) | |
| ); | |
| } | |
| } | |
| /** | |
| * Show list of styles that can be installed | |
| */ | |
| protected function show_available() | |
| { | |
| // Get list of styles | |
| $styles = $this->find_available(true); | |
| // Show styles | |
| if (empty($styles)) | |
| { | |
| trigger_error($this->user->lang['NO_UNINSTALLED_STYLE'] . adm_back_link($this->u_base_action), E_USER_NOTICE); | |
| } | |
| usort($styles, array($this, 'sort_styles')); | |
| $this->styles_list_cols = 3; | |
| $this->template->assign_vars(array( | |
| 'STYLES_LIST_COLS' => $this->styles_list_cols, | |
| 'STYLES_LIST_HIDE_COUNT' => true | |
| ) | |
| ); | |
| // Show styles | |
| foreach ($styles as &$style) | |
| { | |
| // Check if style has a parent style in styles list | |
| $has_parent = false; | |
| if ($style['_inherit_name'] != '') | |
| { | |
| foreach ($styles as $parent_style) | |
| { | |
| if ($parent_style['style_name'] == $style['_inherit_name'] && empty($parent_style['_shown'])) | |
| { | |
| // Show parent style first | |
| $has_parent = true; | |
| } | |
| } | |
| } | |
| if (!$has_parent) | |
| { | |
| $this->list_style($style, 0); | |
| $this->show_available_child_styles($styles, $style['style_name'], 1); | |
| } | |
| } | |
| // Show styles that do not have parent style in styles list | |
| foreach ($styles as $style) | |
| { | |
| if (empty($style['_shown'])) | |
| { | |
| $this->list_style($style, 0); | |
| } | |
| } | |
| // Add button | |
| if (isset($this->style_counters) && $this->style_counters['caninstall'] > 0) | |
| { | |
| $this->template->assign_block_vars('extra_actions', array( | |
| 'ACTION_NAME' => 'install', | |
| 'L_ACTION' => $this->user->lang['INSTALL_STYLES'], | |
| ) | |
| ); | |
| } | |
| } | |
| /** | |
| * Find styles available for installation | |
| * | |
| * @param bool $all if true, function will return all installable styles. if false, function will return only styles that can be installed | |
| * @return array List of styles | |
| */ | |
| protected function find_available($all) | |
| { | |
| // Get list of installed styles | |
| $installed = $this->get_styles(); | |
| $installed_dirs = array(); | |
| $installed_names = array(); | |
| foreach ($installed as $style) | |
| { | |
| $installed_dirs[] = $style['style_path']; | |
| $installed_names[$style['style_name']] = array( | |
| 'path' => $style['style_path'], | |
| 'id' => $style['style_id'], | |
| 'parent' => $style['style_parent_id'], | |
| 'tree' => (strlen($style['style_parent_tree']) ? $style['style_parent_tree'] . '/' : '') . $style['style_path'], | |
| ); | |
| } | |
| // Get list of directories | |
| $dirs = $this->find_style_dirs(); | |
| // Find styles that can be installed | |
| $styles = array(); | |
| foreach ($dirs as $dir) | |
| { | |
| if (in_array($dir, $installed_dirs)) | |
| { | |
| // Style is already installed | |
| continue; | |
| } | |
| $cfg = $this->read_style_cfg($dir); | |
| if ($cfg === false) | |
| { | |
| // Invalid style.cfg | |
| continue; | |
| } | |
| // Style should be available for installation | |
| $parent = $cfg['parent']; | |
| $style = array( | |
| 'style_id' => 0, | |
| 'style_name' => $cfg['name'], | |
| 'style_copyright' => $cfg['copyright'], | |
| 'style_active' => 0, | |
| 'style_path' => $dir, | |
| 'bbcode_bitfield' => $cfg['template_bitfield'], | |
| 'style_parent_id' => 0, | |
| 'style_parent_tree' => '', | |
| // Extra values for styles list | |
| // All extra variable start with _ so they won't be confused with data that can be added to styles table | |
| '_inherit_name' => $parent, | |
| '_available' => true, | |
| '_note' => '', | |
| ); | |
| // Check style inheritance | |
| if ($parent != '') | |
| { | |
| if (isset($installed_names[$parent])) | |
| { | |
| // Parent style is installed | |
| $row = $installed_names[$parent]; | |
| $style['style_parent_id'] = $row['id']; | |
| $style['style_parent_tree'] = $row['tree']; | |
| } | |
| else | |
| { | |
| // Parent style is not installed yet | |
| $style['_available'] = false; | |
| $style['_note'] = sprintf($this->user->lang['REQUIRES_STYLE'], htmlspecialchars($parent)); | |
| } | |
| } | |
| if ($all || $style['_available']) | |
| { | |
| $styles[] = $style; | |
| } | |
| } | |
| return $styles; | |
| } | |
| /** | |
| * Show styles list | |
| * | |
| * @param array $styles styles list | |
| * @param int $parent parent style id | |
| * @param int $level style inheritance level | |
| */ | |
| protected function show_styles_list(&$styles, $parent, $level) | |
| { | |
| foreach ($styles as &$style) | |
| { | |
| if (empty($style['_shown']) && $style['style_parent_id'] == $parent) | |
| { | |
| $this->list_style($style, $level); | |
| $this->show_styles_list($styles, $style['style_id'], $level + 1); | |
| } | |
| } | |
| } | |
| /** | |
| * Show available styles tree | |
| * | |
| * @param array $styles Styles list, passed as reference | |
| * @param string $name Name of parent style | |
| * @param int $level Styles tree level | |
| */ | |
| protected function show_available_child_styles(&$styles, $name, $level) | |
| { | |
| foreach ($styles as &$style) | |
| { | |
| if (empty($style['_shown']) && $style['_inherit_name'] == $name) | |
| { | |
| $this->list_style($style, $level); | |
| $this->show_available_child_styles($styles, $style['style_name'], $level + 1); | |
| } | |
| } | |
| } | |
| /** | |
| * Update styles tree | |
| * | |
| * @param array $styles Styles list, passed as reference | |
| * @param array|false $style Current style, false if root | |
| * @return bool True if something was updated, false if not | |
| */ | |
| protected function update_styles_tree(&$styles, $style = false) | |
| { | |
| $parent_id = ($style === false) ? 0 : $style['style_id']; | |
| $parent_tree = ($style === false) ? '' : ($style['style_parent_tree'] == '' ? '' : $style['style_parent_tree']) . $style['style_path']; | |
| $update = false; | |
| $updated = false; | |
| foreach ($styles as &$row) | |
| { | |
| if ($row['style_parent_id'] == $parent_id) | |
| { | |
| if ($row['style_parent_tree'] != $parent_tree) | |
| { | |
| $row['style_parent_tree'] = $parent_tree; | |
| $update = true; | |
| } | |
| $updated |= $this->update_styles_tree($styles, $row); | |
| } | |
| } | |
| if ($update) | |
| { | |
| $sql = 'UPDATE ' . STYLES_TABLE . " | |
| SET style_parent_tree = '" . $this->db->sql_escape($parent_tree) . "' | |
| WHERE style_parent_id = {$parent_id}"; | |
| $this->db->sql_query($sql); | |
| $updated = true; | |
| } | |
| return $updated; | |
| } | |
| /** | |
| * Find all possible parent styles for style | |
| * | |
| * @param array $styles list of styles | |
| * @param int $id id of style | |
| * @param int $parent current parent style id | |
| * @param int $level current tree level | |
| * @return array Style ids, names and levels | |
| */ | |
| protected function find_possible_parents($styles, $id = -1, $parent = 0, $level = 0) | |
| { | |
| $results = array(); | |
| foreach ($styles as $style) | |
| { | |
| if ($style['style_id'] != $id && $style['style_parent_id'] == $parent) | |
| { | |
| $results[] = array( | |
| 'style_id' => $style['style_id'], | |
| 'style_name' => $style['style_name'], | |
| 'style_path' => $style['style_path'], | |
| 'style_parent_id' => $style['style_parent_id'], | |
| 'style_parent_tree' => $style['style_parent_tree'], | |
| 'level' => $level | |
| ); | |
| $results = array_merge($results, $this->find_possible_parents($styles, $id, $style['style_id'], $level + 1)); | |
| } | |
| } | |
| return $results; | |
| } | |
| /** | |
| * Show item in styles list | |
| * | |
| * @param array $style style row | |
| * @param int $level style inheritance level | |
| */ | |
| protected function list_style(&$style, $level) | |
| { | |
| // Mark row as shown | |
| if (!empty($style['_shown'])) | |
| { | |
| return; | |
| } | |
| $style['_shown'] = true; | |
| // Generate template variables | |
| $actions = array(); | |
| $row = array( | |
| // Style data | |
| 'STYLE_ID' => $style['style_id'], | |
| 'STYLE_NAME' => htmlspecialchars($style['style_name']), | |
| 'STYLE_PHPBB_VERSION' => $this->read_style_cfg($style['style_path'])['phpbb_version'], | |
| 'STYLE_PATH' => htmlspecialchars($style['style_path']), | |
| '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'] : '', | |
| 'LEVEL' => $level, | |
| 'PADDING' => (4 + 16 * $level), | |
| 'SHOW_COPYRIGHT' => ($style['style_id']) ? false : true, | |
| 'STYLE_PATH_FULL' => htmlspecialchars($this->styles_path_absolute . '/' . $style['style_path']) . '/', | |
| // Comment to show below style | |
| 'COMMENT' => (isset($style['_note'])) ? $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']) | |
| { | |
| // Style is installed | |
| // Details | |
| $actions[] = array( | |
| 'U_ACTION' => $this->u_action . '&action=details&id=' . $style['style_id'], | |
| 'L_ACTION' => $this->user->lang['DETAILS'] | |
| ); | |
| // Activate/Deactive | |
| $action_name = ($style['style_active'] ? 'de' : '') . 'activate'; | |
| $actions[] = array( | |
| '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'] | |
| ); */ | |
| if ($style['style_name'] !== 'prosilver') | |
| { | |
| // Uninstall | |
| $actions[] = array( | |
| 'U_ACTION' => $this->u_action . '&action=uninstall&hash=' . generate_link_hash('uninstall') . '&id=' . $style['style_id'], | |
| 'L_ACTION' => $this->user->lang['STYLE_UNINSTALL'] | |
| ); | |
| } | |
| // Preview | |
| $actions[] = array( | |
| 'U_ACTION' => append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'style=' . $style['style_id']), | |
| 'L_ACTION' => $this->user->lang['PREVIEW'] | |
| ); | |
| } | |
| else | |
| { | |
| // Style is not installed | |
| if (empty($style['_available'])) | |
| { | |
| $actions[] = array( | |
| 'HTML' => $this->user->lang['CANNOT_BE_INSTALLED'] | |
| ); | |
| } | |
| else | |
| { | |
| $actions[] = array( | |
| 'U_ACTION' => $this->u_action . '&action=install&hash=' . generate_link_hash('install') . '&dir=' . urlencode($style['style_path']), | |
| 'L_ACTION' => $this->user->lang['INSTALL_STYLE'] | |
| ); | |
| } | |
| } | |
| // todo: add hook | |
| // Assign template variables | |
| $this->template->assign_block_vars('styles_list', $row); | |
| foreach ($actions as $action) | |
| { | |
| $this->template->assign_block_vars('styles_list.actions', $action); | |
| } | |
| // Increase counters | |
| $counter = ($style['style_id']) ? ($style['style_active'] ? 'active' : 'inactive') : (empty($style['_available']) ? 'cannotinstall' : 'caninstall'); | |
| if (!isset($this->style_counters)) | |
| { | |
| $this->style_counters = array( | |
| 'total' => 0, | |
| 'active' => 0, | |
| 'inactive' => 0, | |
| 'caninstall' => 0, | |
| 'cannotinstall' => 0 | |
| ); | |
| } | |
| $this->style_counters[$counter]++; | |
| $this->style_counters['total']++; | |
| } | |
| /** | |
| * Show welcome message | |
| * | |
| * @param string $title main title | |
| * @param string $description page description | |
| */ | |
| 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] : '' | |
| ) | |
| ); | |
| } | |
| /** | |
| * Find all directories that have styles | |
| * | |
| * @return array Directory names | |
| */ | |
| protected function find_style_dirs() | |
| { | |
| $styles = array(); | |
| $dp = @opendir($this->styles_path); | |
| if ($dp) | |
| { | |
| while (($file = readdir($dp)) !== false) | |
| { | |
| $dir = $this->styles_path . $file; | |
| if ($file[0] == '.' || !is_dir($dir)) | |
| { | |
| continue; | |
| } | |
| if (file_exists("{$dir}/style.cfg")) | |
| { | |
| $styles[] = $file; | |
| } | |
| } | |
| closedir($dp); | |
| } | |
| return $styles; | |
| } | |
| /** | |
| * Sort styles | |
| */ | |
| public function sort_styles($style1, $style2) | |
| { | |
| if ($style1['style_active'] != $style2['style_active']) | |
| { | |
| return ($style1['style_active']) ? -1 : 1; | |
| } | |
| if (isset($style1['_available']) && $style1['_available'] != $style2['_available']) | |
| { | |
| return ($style1['_available']) ? -1 : 1; | |
| } | |
| return strcasecmp(isset($style1['style_name']) ? $style1['style_name'] : $style1['name'], isset($style2['style_name']) ? $style2['style_name'] : $style2['name']); | |
| } | |
| /** | |
| * Read style configuration file | |
| * | |
| * @param string $dir style directory | |
| * @return array|bool Style data, false on error | |
| */ | |
| protected function read_style_cfg($dir) | |
| { | |
| // This should never happen, we give them a red warning because of its relevance. | |
| if (!file_exists($this->styles_path . $dir . '/style.cfg')) | |
| { | |
| trigger_error($this->user->lang('NO_STYLE_CFG', $dir), E_USER_WARNING); | |
| } | |
| static $required = array('name', 'phpbb_version', 'copyright'); | |
| $cfg = parse_cfg_file($this->styles_path . $dir . '/style.cfg'); | |
| // Check if it is a valid file | |
| foreach ($required as $key) | |
| { | |
| if (!isset($cfg[$key])) | |
| { | |
| return false; | |
| } | |
| } | |
| // Check data | |
| if (!isset($cfg['parent']) || !is_string($cfg['parent']) || $cfg['parent'] == $cfg['name']) | |
| { | |
| $cfg['parent'] = ''; | |
| } | |
| if (!isset($cfg['template_bitfield'])) | |
| { | |
| $cfg['template_bitfield'] = $this->default_bitfield(); | |
| } | |
| return $cfg; | |
| } | |
| /** | |
| * Install style | |
| * | |
| * @param array $style style data | |
| * @return int Style id | |
| */ | |
| protected function install_style($style) | |
| { | |
| global $user, $phpbb_log; | |
| // Generate row | |
| $sql_ary = array(); | |
| foreach ($style as $key => $value) | |
| { | |
| if ($key != 'style_id' && substr($key, 0, 1) != '_') | |
| { | |
| $sql_ary[$key] = $value; | |
| } | |
| } | |
| // Add to database | |
| $this->db->sql_transaction('begin'); | |
| $sql = 'INSERT INTO ' . STYLES_TABLE . ' | |
| ' . $this->db->sql_build_array('INSERT', $sql_ary); | |
| $this->db->sql_query($sql); | |
| $id = $this->db->sql_nextid(); | |
| $this->db->sql_transaction('commit'); | |
| $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_STYLE_ADD', false, array($sql_ary['style_name'])); | |
| return $id; | |
| } | |
| /** | |
| * Lists all styles | |
| * | |
| * @return array Rows with styles data | |
| */ | |
| protected function get_styles() | |
| { | |
| $sql = 'SELECT * | |
| FROM ' . STYLES_TABLE; | |
| $result = $this->db->sql_query($sql); | |
| $rows = $this->db->sql_fetchrowset($result); | |
| $this->db->sql_freeresult($result); | |
| return $rows; | |
| } | |
| /** | |
| * Count users for each style | |
| * | |
| * @return array Styles in following format: [style_id] = number of users | |
| */ | |
| protected function get_users() | |
| { | |
| $sql = 'SELECT user_style, COUNT(user_style) AS style_count | |
| FROM ' . USERS_TABLE . ' | |
| GROUP BY user_style'; | |
| $result = $this->db->sql_query($sql); | |
| $style_count = array(); | |
| while ($row = $this->db->sql_fetchrow($result)) | |
| { | |
| $style_count[$row['user_style']] = $row['style_count']; | |
| } | |
| $this->db->sql_freeresult($result); | |
| return $style_count; | |
| } | |
| /** | |
| * Uninstall style | |
| * | |
| * @param array $style Style data | |
| * @return bool|string True on success, error message on error | |
| */ | |
| protected function uninstall_style($style) | |
| { | |
| $id = $style['style_id']; | |
| $path = $style['style_path']; | |
| // Check if style has child styles | |
| $sql = 'SELECT style_id | |
| FROM ' . STYLES_TABLE . ' | |
| WHERE style_parent_id = ' . (int) $id . " OR style_parent_tree = '" . $this->db->sql_escape($path) . "'"; | |
| $result = $this->db->sql_query($sql); | |
| $conflict = $this->db->sql_fetchrow($result); | |
| $this->db->sql_freeresult($result); | |
| if ($conflict !== false) | |
| { | |
| return sprintf($this->user->lang['STYLE_UNINSTALL_DEPENDENT'], $style['style_name']); | |
| } | |
| // Change default style for users | |
| $sql = 'UPDATE ' . USERS_TABLE . ' | |
| SET user_style = ' . (int) $this->default_style . ' | |
| WHERE user_style = ' . $id; | |
| $this->db->sql_query($sql); | |
| // Uninstall style | |
| $sql = 'DELETE FROM ' . STYLES_TABLE . ' | |
| WHERE style_id = ' . $id; | |
| $this->db->sql_query($sql); | |
| return true; | |
| } | |
| /** | |
| * Delete all files in style directory | |
| * | |
| * @param string $path Style directory | |
| * @param string $dir Directory to remove inside style's directory | |
| * @return bool True on success, false on error | |
| */ | |
| protected function delete_style_files($path, $dir = '') | |
| { | |
| $dirname = $this->styles_path . $path . $dir; | |
| $result = true; | |
| $dp = @opendir($dirname); | |
| if ($dp) | |
| { | |
| while (($file = readdir($dp)) !== false) | |
| { | |
| if ($file == '.' || $file == '..') | |
| { | |
| continue; | |
| } | |
| $filename = $dirname . '/' . $file; | |
| if (is_dir($filename)) | |
| { | |
| if (!$this->delete_style_files($path, $dir . '/' . $file)) | |
| { | |
| $result = false; | |
| } | |
| } | |
| else | |
| { | |
| if (!@unlink($filename)) | |
| { | |
| $result = false; | |
| } | |
| } | |
| } | |
| closedir($dp); | |
| } | |
| if (!@rmdir($dirname)) | |
| { | |
| return false; | |
| } | |
| return $result; | |
| } | |
| /** | |
| * Get list of items from posted data | |
| * | |
| * @param string $name Variable name | |
| * @param string|int $default Default value for array | |
| * @param bool $error If true, error will be triggered if list is empty | |
| * @return array Items | |
| */ | |
| protected function request_vars($name, $default, $error = false) | |
| { | |
| $item = $this->request->variable($name, $default); | |
| $items = $this->request->variable($name . 's', array($default)); | |
| if (count($items) == 1 && $items[0] == $default) | |
| { | |
| $items = array(); | |
| } | |
| if ($item != $default && !count($items)) | |
| { | |
| $items[] = $item; | |
| } | |
| if ($error && !count($items)) | |
| { | |
| trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); | |
| } | |
| return $items; | |
| } | |
| /** | |
| * Generates default bitfield | |
| * | |
| * This bitfield decides which bbcodes are defined in a template. | |
| * | |
| * @return string Bitfield | |
| */ | |
| protected function default_bitfield() | |
| { | |
| static $value; | |
| if (isset($value)) | |
| { | |
| return $value; | |
| } | |
| // Hardcoded template bitfield to add for new templates | |
| $default_bitfield = '1111111111111'; | |
| $bitfield = new bitfield(); | |
| for ($i = 0; $i < strlen($default_bitfield); $i++) | |
| { | |
| if ($default_bitfield[$i] == '1') | |
| { | |
| $bitfield->set($i); | |
| } | |
| } | |
| return $bitfield->get_base64(); | |
| } | |
| } |