Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
acp_styles.php
0001 <?php
0002 /**
0003 *
0004 * This file is part of the phpBB Forum Software package.
0005 *
0006 * @copyright (c) phpBB Limited <https://www.phpbb.com>
0007 * @license GNU General Public License, version 2 (GPL-2.0)
0008 *
0009 * For full copyright and license information, please see
0010 * the docs/CREDITS.txt file.
0011 *
0012 */
0013
0014 /**
0015 * @ignore
0016 */
0017 if (!defined('IN_PHPBB'))
0018 {
0019 exit;
0020 }
0021
0022 class acp_styles
0023 {
0024 public $u_action;
0025
0026 protected $u_base_action;
0027 protected $s_hidden_fields;
0028 protected $mode;
0029 protected $styles_path;
0030 protected $styles_path_absolute = 'styles';
0031 protected $default_style = 0;
0032 protected $styles_list_cols = 0;
0033 protected $reserved_style_names = array('adm', 'admin', 'all');
0034
0035 /** @var \phpbb\config\config */
0036 protected $config;
0037
0038 /** @var \phpbb\db\driver\driver_interface */
0039 protected $db;
0040
0041 /** @var \phpbb\user */
0042 protected $user;
0043
0044 /** @var \phpbb\template\template */
0045 protected $template;
0046
0047 /** @var \phpbb\request\request_interface */
0048 protected $request;
0049
0050 /** @var \phpbb\cache\driver\driver_interface */
0051 protected $cache;
0052
0053 /** @var \phpbb\auth\auth */
0054 protected $auth;
0055
0056 /** @var \phpbb\textformatter\cache_interface */
0057 protected $text_formatter_cache;
0058
0059 /** @var string */
0060 protected $phpbb_root_path;
0061
0062 /** @var string */
0063 protected $php_ext;
0064
0065 /** @var \phpbb\event\dispatcher_interface */
0066 protected $dispatcher;
0067
0068 public function main($id, $mode)
0069 {
0070 global $db, $user, $phpbb_admin_path, $phpbb_root_path, $phpEx, $template, $request, $cache, $auth, $config, $phpbb_dispatcher, $phpbb_container;
0071
0072 $this->db = $db;
0073 $this->user = $user;
0074 $this->template = $template;
0075 $this->request = $request;
0076 $this->cache = $cache;
0077 $this->auth = $auth;
0078 $this->text_formatter_cache = $phpbb_container->get('text_formatter.cache');
0079 $this->config = $config;
0080 $this->phpbb_root_path = $phpbb_root_path;
0081 $this->php_ext = $phpEx;
0082 $this->dispatcher = $phpbb_dispatcher;
0083
0084 $this->default_style = $config['default_style'];
0085 $this->styles_path = $this->phpbb_root_path . $this->styles_path_absolute . '/';
0086
0087 $this->u_base_action = append_sid("{$phpbb_admin_path}index.{$this->php_ext}", "i={$id}");
0088 $this->s_hidden_fields = array(
0089 'mode' => $mode,
0090 );
0091
0092 $this->user->add_lang('acp/styles');
0093
0094 $this->tpl_name = 'acp_styles';
0095 $this->page_title = 'ACP_CAT_STYLES';
0096 $this->mode = $mode;
0097
0098 $action = $this->request->variable('action', '');
0099 $post_actions = array('install', 'activate', 'deactivate', 'uninstall');
0100
0101 foreach ($post_actions as $key)
0102 {
0103 if ($this->request->is_set_post($key))
0104 {
0105 $action = $key;
0106 }
0107 }
0108
0109 // The uninstall action uses confirm_box() to verify the validity of the request,
0110 // so there is no need to check for a valid token here.
0111 if (in_array($action, $post_actions) && $action != 'uninstall')
0112 {
0113 $is_valid_request = check_link_hash($request->variable('hash', ''), $action) || check_form_key('styles_management');
0114
0115 if (!$is_valid_request)
0116 {
0117 trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING);
0118 }
0119 }
0120
0121 if ($action != '')
0122 {
0123 $this->s_hidden_fields['action'] = $action;
0124 }
0125
0126 $this->template->assign_vars(array(
0127 'U_ACTION' => $this->u_base_action,
0128 'S_HIDDEN_FIELDS' => build_hidden_fields($this->s_hidden_fields)
0129 )
0130 );
0131
0132 /**
0133 * Run code before ACP styles action execution
0134 *
0135 * @event core.acp_styles_action_before
0136 * @var int id Module ID
0137 * @var string mode Active module
0138 * @var string action Module that should be run
0139 * @since 3.1.7-RC1
0140 */
0141 $vars = array('id', 'mode', 'action');
0142 extract($this->dispatcher->trigger_event('core.acp_styles_action_before', compact($vars)));
0143
0144 // Execute actions
0145 switch ($action)
0146 {
0147 case 'install':
0148 $this->action_install();
0149 return;
0150 case 'uninstall':
0151 $this->action_uninstall();
0152 return;
0153 case 'activate':
0154 $this->action_activate();
0155 return;
0156 case 'deactivate':
0157 $this->action_deactivate();
0158 return;
0159 case 'details':
0160 $this->action_details();
0161 return;
0162 default:
0163 $this->frontend();
0164 }
0165 }
0166
0167 /**
0168 * Main page
0169 */
0170 protected function frontend()
0171 {
0172 add_form_key('styles_management');
0173
0174 // Check mode
0175 switch ($this->mode)
0176 {
0177 case 'style':
0178 $this->welcome_message('ACP_STYLES', 'ACP_STYLES_EXPLAIN');
0179 $this->show_installed();
0180 return;
0181 case 'install':
0182 $this->welcome_message('INSTALL_STYLES', 'INSTALL_STYLES_EXPLAIN');
0183 $this->show_available();
0184 return;
0185 }
0186 trigger_error($this->user->lang['NO_MODE'] . adm_back_link($this->u_action), E_USER_WARNING);
0187 }
0188
0189 /**
0190 * Install style(s)
0191 */
0192 protected function action_install()
0193 {
0194 // Get list of styles to install
0195 $dirs = $this->request_vars('dir', '', true);
0196
0197 // Get list of styles that can be installed
0198 $styles = $this->find_available(false);
0199
0200 // Install each style
0201 $messages = array();
0202 $installed_names = array();
0203 $installed_dirs = array();
0204 foreach ($dirs as $dir)
0205 {
0206 if (in_array($dir, $this->reserved_style_names))
0207 {
0208 $messages[] = $this->user->lang('STYLE_NAME_RESERVED', htmlspecialchars($dir, ENT_COMPAT));
0209 continue;
0210 }
0211
0212 $found = false;
0213 foreach ($styles as &$style)
0214 {
0215 // Check if:
0216 // 1. Directory matches directory we are looking for
0217 // 2. Style is not installed yet
0218 // 3. Style with same name or directory hasn't been installed already within this function
0219 if ($style['style_path'] == $dir && empty($style['_installed']) && !in_array($style['style_path'], $installed_dirs) && !in_array($style['style_name'], $installed_names))
0220 {
0221 // Install style
0222 $style['style_active'] = 1;
0223 $style['style_id'] = $this->install_style($style);
0224 $style['_installed'] = true;
0225 $found = true;
0226 $installed_names[] = $style['style_name'];
0227 $installed_dirs[] = $style['style_path'];
0228 $messages[] = sprintf($this->user->lang['STYLE_INSTALLED'], htmlspecialchars($style['style_name'], ENT_COMPAT));
0229 }
0230 }
0231 if (!$found)
0232 {
0233 $messages[] = sprintf($this->user->lang['STYLE_NOT_INSTALLED'], htmlspecialchars($dir, ENT_COMPAT));
0234 }
0235 }
0236
0237 // Invalidate the text formatter's cache for the new styles to take effect
0238 if (!empty($installed_names))
0239 {
0240 $this->text_formatter_cache->invalidate();
0241 }
0242
0243 // Show message
0244 if (!count($messages))
0245 {
0246 trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING);
0247 }
0248 $message = implode('<br />', $messages);
0249 $message .= '<br /><br /><a href="' . $this->u_base_action . '&mode=style' . '">« ' . $this->user->lang('STYLE_INSTALLED_RETURN_INSTALLED_STYLES') . '</a>';
0250 $message .= '<br /><br /><a href="' . $this->u_base_action . '&mode=install' . '">» ' . $this->user->lang('STYLE_INSTALLED_RETURN_UNINSTALLED_STYLES') . '</a>';
0251 trigger_error($message, E_USER_NOTICE);
0252 }
0253
0254 /**
0255 * Confirm styles removal
0256 */
0257 protected function action_uninstall()
0258 {
0259 // Get list of styles to uninstall
0260 $ids = $this->request_vars('id', 0, true);
0261
0262 // Don't remove prosilver, you can still deactivate it.
0263 $sql = 'SELECT style_id
0264 FROM ' . STYLES_TABLE . "
0265 WHERE style_name = '" . $this->db->sql_escape('prosilver') . "'";
0266 $result = $this->db->sql_query($sql);
0267 $prosilver_id = (int) $this->db->sql_fetchfield('style_id');
0268 $this->db->sql_freeresult($result);
0269
0270 if ($prosilver_id && in_array($prosilver_id, $ids))
0271 {
0272 trigger_error($this->user->lang('UNINSTALL_PROSILVER') . adm_back_link($this->u_action), E_USER_WARNING);
0273 }
0274
0275 // Check if confirmation box was submitted
0276 if (confirm_box(true))
0277 {
0278 // Uninstall
0279 $this->action_uninstall_confirmed($ids, $this->request->variable('confirm_delete_files', false));
0280 return;
0281 }
0282
0283 // Confirm box
0284 $s_hidden = build_hidden_fields(array(
0285 'action' => 'uninstall',
0286 'ids' => $ids
0287 ));
0288 $this->template->assign_var('S_CONFIRM_DELETE', true);
0289 confirm_box(false, $this->user->lang['CONFIRM_UNINSTALL_STYLES'], $s_hidden, 'acp_styles.html');
0290
0291 // Canceled - show styles list
0292 $this->frontend();
0293 }
0294
0295 /**
0296 * Uninstall styles(s)
0297 *
0298 * @param array $ids List of style IDs
0299 * @param bool $delete_files If true, script will attempt to remove files for selected styles
0300 */
0301 protected function action_uninstall_confirmed($ids, $delete_files)
0302 {
0303 global $user, $phpbb_log;
0304
0305 $default = $this->default_style;
0306 $uninstalled = array();
0307 $messages = array();
0308
0309 // Check styles list
0310 foreach ($ids as $id)
0311 {
0312 if (!$id)
0313 {
0314 trigger_error($this->user->lang['INVALID_STYLE_ID'] . adm_back_link($this->u_action), E_USER_WARNING);
0315 }
0316 if ($id == $default)
0317 {
0318 trigger_error($this->user->lang['UNINSTALL_DEFAULT'] . adm_back_link($this->u_action), E_USER_WARNING);
0319 }
0320 $uninstalled[$id] = false;
0321 }
0322
0323 // Order by reversed style_id, so parent styles would be removed after child styles
0324 // This way parent and child styles can be removed in same function call
0325 $sql = 'SELECT *
0326 FROM ' . STYLES_TABLE . '
0327 WHERE style_id IN (' . implode(', ', $ids) . ')
0328 ORDER BY style_id DESC';
0329 $result = $this->db->sql_query($sql);
0330
0331 $rows = $this->db->sql_fetchrowset($result);
0332 $this->db->sql_freeresult($result);
0333
0334 // Uinstall each style
0335 $uninstalled = array();
0336 foreach ($rows as $style)
0337 {
0338 $result = $this->uninstall_style($style, $delete_files);
0339
0340 if (is_string($result))
0341 {
0342 $messages[] = $result;
0343 continue;
0344 }
0345 $messages[] = sprintf($this->user->lang['STYLE_UNINSTALLED'], $style['style_name']);
0346 $uninstalled[] = $style['style_name'];
0347
0348 // Attempt to delete files
0349 if ($delete_files)
0350 {
0351 $messages[] = sprintf($this->user->lang[$this->delete_style_files($style['style_path']) ? 'DELETE_STYLE_FILES_SUCCESS' : 'DELETE_STYLE_FILES_FAILED'], $style['style_name']);
0352 }
0353 }
0354
0355 if (empty($messages))
0356 {
0357 // Nothing to uninstall?
0358 trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING);
0359 }
0360
0361 // Log action
0362 if (count($uninstalled))
0363 {
0364 $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_STYLE_DELETE', false, array(implode(', ', $uninstalled)));
0365 }
0366
0367 // Clear cache
0368 $this->cache->purge();
0369
0370 // Show message
0371 trigger_error(implode('<br />', $messages) . adm_back_link($this->u_action), E_USER_NOTICE);
0372 }
0373
0374 /**
0375 * Activate styles
0376 */
0377 protected function action_activate()
0378 {
0379 // Get list of styles to activate
0380 $ids = $this->request_vars('id', 0, true);
0381
0382 // Activate styles
0383 $sql = 'UPDATE ' . STYLES_TABLE . '
0384 SET style_active = 1
0385 WHERE style_id IN (' . implode(', ', $ids) . ')';
0386 $this->db->sql_query($sql);
0387
0388 // Purge cache
0389 $this->cache->destroy('sql', STYLES_TABLE);
0390
0391 // Show styles list
0392 $this->frontend();
0393 }
0394
0395 /**
0396 * Deactivate styles
0397 */
0398 protected function action_deactivate()
0399 {
0400 // Get list of styles to deactivate
0401 $ids = $this->request_vars('id', 0, true);
0402
0403 // Check for default style
0404 foreach ($ids as $id)
0405 {
0406 if ($id == $this->default_style)
0407 {
0408 trigger_error($this->user->lang['DEACTIVATE_DEFAULT'] . adm_back_link($this->u_action), E_USER_WARNING);
0409 }
0410 }
0411
0412 // Reset default style for users who use selected styles
0413 $sql = 'UPDATE ' . USERS_TABLE . '
0414 SET user_style = ' . (int) $this->default_style . '
0415 WHERE user_style IN (' . implode(', ', $ids) . ')';
0416 $this->db->sql_query($sql);
0417
0418 // Deactivate styles
0419 $sql = 'UPDATE ' . STYLES_TABLE . '
0420 SET style_active = 0
0421 WHERE style_id IN (' . implode(', ', $ids) . ')';
0422 $this->db->sql_query($sql);
0423
0424 // Purge cache
0425 $this->cache->destroy('sql', STYLES_TABLE);
0426
0427 // Show styles list
0428 $this->frontend();
0429 }
0430
0431 /**
0432 * Show style details
0433 */
0434 protected function action_details()
0435 {
0436 global $user, $phpbb_log;
0437
0438 $id = $this->request->variable('id', 0);
0439 if (!$id)
0440 {
0441 trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING);
0442 }
0443
0444 // Get all styles
0445 $styles = $this->get_styles();
0446 usort($styles, array($this, 'sort_styles'));
0447
0448 // Find current style
0449 $style = false;
0450 foreach ($styles as $row)
0451 {
0452 if ($row['style_id'] == $id)
0453 {
0454 $style = $row;
0455 break;
0456 }
0457 }
0458
0459 if ($style === false)
0460 {
0461 trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING);
0462 }
0463
0464 // Read style configuration file
0465 $style_cfg = $this->read_style_cfg($style['style_path']);
0466
0467 // Find all available parent styles
0468 $list = $this->find_possible_parents($styles, $id);
0469
0470 // Add form key
0471 $form_key = 'acp_styles';
0472 add_form_key($form_key);
0473
0474 // Change data
0475 if ($this->request->variable('update', false))
0476 {
0477 if (!check_form_key($form_key))
0478 {
0479 trigger_error($this->user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING);
0480 }
0481
0482 $update = array(
0483 'style_name' => trim($this->request->variable('style_name', $style['style_name'])),
0484 'style_parent_id' => $this->request->variable('style_parent', (int) $style['style_parent_id']),
0485 'style_active' => $this->request->variable('style_active', (int) $style['style_active']),
0486 );
0487 $update_action = $this->u_action . '&action=details&id=' . $id;
0488
0489 // Check style name
0490 if ($update['style_name'] != $style['style_name'])
0491 {
0492 if (!strlen($update['style_name']))
0493 {
0494 trigger_error($this->user->lang['STYLE_ERR_STYLE_NAME'] . adm_back_link($update_action), E_USER_WARNING);
0495 }
0496 foreach ($styles as $row)
0497 {
0498 if ($row['style_name'] == $update['style_name'])
0499 {
0500 trigger_error($this->user->lang['STYLE_ERR_NAME_EXIST'] . adm_back_link($update_action), E_USER_WARNING);
0501 }
0502 }
0503 }
0504 else
0505 {
0506 unset($update['style_name']);
0507 }
0508
0509 // Check parent style id
0510 if ($update['style_parent_id'] != $style['style_parent_id'])
0511 {
0512 if ($update['style_parent_id'] != 0)
0513 {
0514 $found = false;
0515 foreach ($list as $row)
0516 {
0517 if ($row['style_id'] == $update['style_parent_id'])
0518 {
0519 $found = true;
0520 $update['style_parent_tree'] = ($row['style_parent_tree'] != '' ? $row['style_parent_tree'] . '/' : '') . $row['style_path'];
0521 break;
0522 }
0523 }
0524 if (!$found)
0525 {
0526 trigger_error($this->user->lang['STYLE_ERR_INVALID_PARENT'] . adm_back_link($update_action), E_USER_WARNING);
0527 }
0528 }
0529 else
0530 {
0531 $update['style_parent_tree'] = '';
0532 }
0533 }
0534 else
0535 {
0536 unset($update['style_parent_id']);
0537 }
0538
0539 // Check style_active
0540 if ($update['style_active'] != $style['style_active'])
0541 {
0542 if (!$update['style_active'] && $this->default_style == $style['style_id'])
0543 {
0544 trigger_error($this->user->lang['DEACTIVATE_DEFAULT'] . adm_back_link($update_action), E_USER_WARNING);
0545 }
0546 }
0547 else
0548 {
0549 unset($update['style_active']);
0550 }
0551
0552 // Update data
0553 if (count($update))
0554 {
0555 $sql = 'UPDATE ' . STYLES_TABLE . '
0556 SET ' . $this->db->sql_build_array('UPDATE', $update) . "
0557 WHERE style_id = $id";
0558 $this->db->sql_query($sql);
0559
0560 $style = array_merge($style, $update);
0561
0562 if (isset($update['style_parent_id']))
0563 {
0564 // Update styles tree
0565 $styles = $this->get_styles();
0566 if ($this->update_styles_tree($styles, $style))
0567 {
0568 // Something was changed in styles tree, purge all cache
0569 $this->cache->purge();
0570 }
0571 }
0572
0573 $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_STYLE_EDIT_DETAILS', false, array($style['style_name']));
0574 }
0575
0576 // Update default style
0577 $default = $this->request->variable('style_default', 0);
0578 if ($default)
0579 {
0580 if (!$style['style_active'])
0581 {
0582 trigger_error($this->user->lang['STYLE_DEFAULT_CHANGE_INACTIVE'] . adm_back_link($update_action), E_USER_WARNING);
0583 }
0584 $this->config->set('default_style', $id);
0585 $this->cache->purge();
0586 }
0587
0588 // Show styles list
0589 $this->frontend();
0590 return;
0591 }
0592
0593 // Show page title
0594 $this->welcome_message('ACP_STYLES', null);
0595
0596 // Show parent styles
0597 foreach ($list as $row)
0598 {
0599 $this->template->assign_block_vars('parent_styles', array(
0600 'STYLE_ID' => $row['style_id'],
0601 'STYLE_NAME' => htmlspecialchars($row['style_name'], ENT_COMPAT),
0602 'LEVEL' => $row['level'],
0603 'SPACER' => str_repeat(' ', $row['level']),
0604 )
0605 );
0606 }
0607
0608 // Show style details
0609 $this->template->assign_vars(array(
0610 'S_STYLE_DETAILS' => true,
0611 'STYLE_ID' => $style['style_id'],
0612 'STYLE_NAME' => htmlspecialchars($style['style_name'], ENT_COMPAT),
0613 'STYLE_PATH' => htmlspecialchars($style['style_path'], ENT_COMPAT),
0614 'STYLE_VERSION' => htmlspecialchars($style_cfg['style_version'], ENT_COMPAT),
0615 'STYLE_COPYRIGHT' => strip_tags($style['style_copyright']),
0616 'STYLE_PARENT' => $style['style_parent_id'],
0617 'S_STYLE_ACTIVE' => $style['style_active'],
0618 'S_STYLE_DEFAULT' => ($style['style_id'] == $this->default_style)
0619 )
0620 );
0621 }
0622
0623 /**
0624 * List installed styles
0625 */
0626 protected function show_installed()
0627 {
0628 // Get all installed styles
0629 $styles = $this->get_styles();
0630
0631 if (!count($styles))
0632 {
0633 trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING);
0634 }
0635
0636 usort($styles, array($this, 'sort_styles'));
0637
0638 // Get users
0639 $users = $this->get_users();
0640
0641 // Add users counter to rows
0642 foreach ($styles as &$style)
0643 {
0644 $style['_users'] = isset($users[$style['style_id']]) ? $users[$style['style_id']] : 0;
0645 }
0646
0647 // Set up styles list variables
0648 // Addons should increase this number and update template variable
0649 $this->styles_list_cols = 5;
0650 $this->template->assign_var('STYLES_LIST_COLS', $this->styles_list_cols);
0651
0652 // Show styles list
0653 $this->show_styles_list($styles, 0, 0);
0654
0655 // Show styles with invalid inherits_id
0656 foreach ($styles as $style)
0657 {
0658 if (empty($style['_shown']))
0659 {
0660 $style['_note'] = sprintf($this->user->lang['REQUIRES_STYLE'], htmlspecialchars($style['style_parent_tree'], ENT_COMPAT));
0661 $this->list_style($style, 0);
0662 }
0663 }
0664
0665 // Add buttons
0666 $this->template->assign_block_vars('extra_actions', array(
0667 'ACTION_NAME' => 'activate',
0668 'L_ACTION' => $this->user->lang['STYLE_ACTIVATE'],
0669 )
0670 );
0671
0672 $this->template->assign_block_vars('extra_actions', array(
0673 'ACTION_NAME' => 'deactivate',
0674 'L_ACTION' => $this->user->lang['STYLE_DEACTIVATE'],
0675 )
0676 );
0677
0678 if (isset($this->style_counters) && $this->style_counters['total'] > 1)
0679 {
0680 $this->template->assign_block_vars('extra_actions', array(
0681 'ACTION_NAME' => 'uninstall',
0682 'L_ACTION' => $this->user->lang['STYLE_UNINSTALL'],
0683 )
0684 );
0685 }
0686 }
0687
0688 /**
0689 * Show list of styles that can be installed
0690 */
0691 protected function show_available()
0692 {
0693 // Get list of styles
0694 $styles = $this->find_available(true);
0695
0696 // Show styles
0697 if (empty($styles))
0698 {
0699 trigger_error($this->user->lang['NO_UNINSTALLED_STYLE'] . adm_back_link($this->u_base_action), E_USER_NOTICE);
0700 }
0701
0702 usort($styles, array($this, 'sort_styles'));
0703
0704 $this->styles_list_cols = 4;
0705 $this->template->assign_vars(array(
0706 'STYLES_LIST_COLS' => $this->styles_list_cols,
0707 'STYLES_LIST_HIDE_COUNT' => true
0708 )
0709 );
0710
0711 // Show styles
0712 foreach ($styles as &$style)
0713 {
0714 // Check if style has a parent style in styles list
0715 $has_parent = false;
0716 if ($style['_inherit_name'] != '')
0717 {
0718 foreach ($styles as $parent_style)
0719 {
0720 if ($parent_style['style_name'] == $style['_inherit_name'] && empty($parent_style['_shown']))
0721 {
0722 // Show parent style first
0723 $has_parent = true;
0724 }
0725 }
0726 }
0727 if (!$has_parent)
0728 {
0729 $this->list_style($style, 0);
0730 $this->show_available_child_styles($styles, $style['style_name'], 1);
0731 }
0732 }
0733
0734 // Show styles that do not have parent style in styles list
0735 foreach ($styles as $style)
0736 {
0737 if (empty($style['_shown']))
0738 {
0739 $this->list_style($style, 0);
0740 }
0741 }
0742
0743 // Add button
0744 if (isset($this->style_counters) && $this->style_counters['caninstall'] > 0)
0745 {
0746 $this->template->assign_block_vars('extra_actions', array(
0747 'ACTION_NAME' => 'install',
0748 'L_ACTION' => $this->user->lang['INSTALL_STYLES'],
0749 )
0750 );
0751 }
0752 }
0753
0754 /**
0755 * Find styles available for installation
0756 *
0757 * @param bool $all if true, function will return all installable styles. if false, function will return only styles that can be installed
0758 * @return array List of styles
0759 */
0760 protected function find_available($all)
0761 {
0762 // Get list of installed styles
0763 $installed = $this->get_styles();
0764
0765 $installed_dirs = array();
0766 $installed_names = array();
0767 foreach ($installed as $style)
0768 {
0769 $installed_dirs[] = $style['style_path'];
0770 $installed_names[$style['style_name']] = array(
0771 'path' => $style['style_path'],
0772 'id' => $style['style_id'],
0773 'parent' => $style['style_parent_id'],
0774 'tree' => (strlen($style['style_parent_tree']) ? $style['style_parent_tree'] . '/' : '') . $style['style_path'],
0775 );
0776 }
0777
0778 // Get list of directories
0779 $dirs = $this->find_style_dirs();
0780
0781 // Find styles that can be installed
0782 $styles = array();
0783 foreach ($dirs as $dir)
0784 {
0785 if (in_array($dir, $installed_dirs))
0786 {
0787 // Style is already installed
0788 continue;
0789 }
0790 $cfg = $this->read_style_cfg($dir);
0791 if ($cfg === false)
0792 {
0793 // Invalid style.cfg
0794 continue;
0795 }
0796
0797 // Style should be available for installation
0798 $parent = $cfg['parent'];
0799 $style = array(
0800 'style_id' => 0,
0801 'style_name' => $cfg['name'],
0802 'style_copyright' => $cfg['copyright'],
0803 'style_active' => 0,
0804 'style_path' => $dir,
0805 'bbcode_bitfield' => $cfg['template_bitfield'],
0806 'style_parent_id' => 0,
0807 'style_parent_tree' => '',
0808 // Extra values for styles list
0809 // All extra variable start with _ so they won't be confused with data that can be added to styles table
0810 '_inherit_name' => $parent,
0811 '_available' => true,
0812 '_note' => '',
0813 );
0814
0815 // Check style inheritance
0816 if ($parent != '')
0817 {
0818 if (isset($installed_names[$parent]))
0819 {
0820 // Parent style is installed
0821 $row = $installed_names[$parent];
0822 $style['style_parent_id'] = $row['id'];
0823 $style['style_parent_tree'] = $row['tree'];
0824 }
0825 else
0826 {
0827 // Parent style is not installed yet
0828 $style['_available'] = false;
0829 $style['_note'] = sprintf($this->user->lang['REQUIRES_STYLE'], htmlspecialchars($parent, ENT_COMPAT));
0830 }
0831 }
0832
0833 if ($all || $style['_available'])
0834 {
0835 $styles[] = $style;
0836 }
0837 }
0838
0839 return $styles;
0840 }
0841
0842 /**
0843 * Show styles list
0844 *
0845 * @param array $styles styles list
0846 * @param int $parent parent style id
0847 * @param int $level style inheritance level
0848 */
0849 protected function show_styles_list(&$styles, $parent, $level)
0850 {
0851 foreach ($styles as &$style)
0852 {
0853 if (empty($style['_shown']) && $style['style_parent_id'] == $parent)
0854 {
0855 $this->list_style($style, $level);
0856 $this->show_styles_list($styles, $style['style_id'], $level + 1);
0857 }
0858 }
0859 }
0860
0861 /**
0862 * Show available styles tree
0863 *
0864 * @param array $styles Styles list, passed as reference
0865 * @param string $name Name of parent style
0866 * @param int $level Styles tree level
0867 */
0868 protected function show_available_child_styles(&$styles, $name, $level)
0869 {
0870 foreach ($styles as &$style)
0871 {
0872 if (empty($style['_shown']) && $style['_inherit_name'] == $name)
0873 {
0874 $this->list_style($style, $level);
0875 $this->show_available_child_styles($styles, $style['style_name'], $level + 1);
0876 }
0877 }
0878 }
0879
0880 /**
0881 * Update styles tree
0882 *
0883 * @param array $styles Styles list, passed as reference
0884 * @param array|false $style Current style, false if root
0885 * @return bool True if something was updated, false if not
0886 */
0887 protected function update_styles_tree(&$styles, $style = false)
0888 {
0889 $parent_id = ($style === false) ? 0 : $style['style_id'];
0890 $parent_tree = ($style === false) ? '' : ($style['style_parent_tree'] == '' ? '' : $style['style_parent_tree']) . $style['style_path'];
0891 $update = false;
0892 $updated = false;
0893 foreach ($styles as &$row)
0894 {
0895 if ($row['style_parent_id'] == $parent_id)
0896 {
0897 if ($row['style_parent_tree'] != $parent_tree)
0898 {
0899 $row['style_parent_tree'] = $parent_tree;
0900 $update = true;
0901 }
0902 $updated |= $this->update_styles_tree($styles, $row);
0903 }
0904 }
0905 if ($update)
0906 {
0907 $sql = 'UPDATE ' . STYLES_TABLE . "
0908 SET style_parent_tree = '" . $this->db->sql_escape($parent_tree) . "'
0909 WHERE style_parent_id = {$parent_id}";
0910 $this->db->sql_query($sql);
0911 $updated = true;
0912 }
0913 return $updated;
0914 }
0915
0916 /**
0917 * Find all possible parent styles for style
0918 *
0919 * @param array $styles list of styles
0920 * @param int $id id of style
0921 * @param int $parent current parent style id
0922 * @param int $level current tree level
0923 * @return array Style ids, names and levels
0924 */
0925 protected function find_possible_parents($styles, $id = -1, $parent = 0, $level = 0)
0926 {
0927 $results = array();
0928 foreach ($styles as $style)
0929 {
0930 if ($style['style_id'] != $id && $style['style_parent_id'] == $parent)
0931 {
0932 $results[] = array(
0933 'style_id' => $style['style_id'],
0934 'style_name' => $style['style_name'],
0935 'style_path' => $style['style_path'],
0936 'style_parent_id' => $style['style_parent_id'],
0937 'style_parent_tree' => $style['style_parent_tree'],
0938 'level' => $level
0939 );
0940 $results = array_merge($results, $this->find_possible_parents($styles, $id, $style['style_id'], $level + 1));
0941 }
0942 }
0943 return $results;
0944 }
0945
0946 /**
0947 * Show item in styles list
0948 *
0949 * @param array $style style row
0950 * @param int $level style inheritance level
0951 */
0952 protected function list_style(&$style, $level)
0953 {
0954 // Mark row as shown
0955 if (!empty($style['_shown']))
0956 {
0957 return;
0958 }
0959
0960 $style['_shown'] = true;
0961
0962 $style_cfg = $this->read_style_cfg($style['style_path']);
0963
0964 // Generate template variables
0965 $actions = array();
0966 $row = array(
0967 // Style data
0968 'STYLE_ID' => $style['style_id'],
0969 'STYLE_NAME' => htmlspecialchars($style['style_name'], ENT_COMPAT),
0970 'STYLE_VERSION' => $style_cfg['style_version'] ?? '-',
0971 'STYLE_PHPBB_VERSION' => $style_cfg['phpbb_version'],
0972 'STYLE_PATH' => htmlspecialchars($style['style_path'], ENT_COMPAT),
0973 'STYLE_COPYRIGHT' => strip_tags($style['style_copyright']),
0974 'STYLE_ACTIVE' => $style['style_active'],
0975
0976 // Additional data
0977 'DEFAULT' => ($style['style_id'] && $style['style_id'] == $this->default_style),
0978 'USERS' => (isset($style['_users'])) ? $style['_users'] : '',
0979 'LEVEL' => $level,
0980 'PADDING' => (4 + 16 * $level),
0981 'SHOW_COPYRIGHT' => ($style['style_id']) ? false : true,
0982 'STYLE_PATH_FULL' => htmlspecialchars($this->styles_path_absolute . '/' . $style['style_path'], ENT_COMPAT) . '/',
0983
0984 // Comment to show below style
0985 'COMMENT' => (isset($style['_note'])) ? $style['_note'] : '',
0986
0987 // The following variables should be used by hooks to add custom HTML code
0988 'EXTRA' => '',
0989 'EXTRA_OPTIONS' => ''
0990 );
0991
0992 // Status specific data
0993 if ($style['style_id'])
0994 {
0995 // Style is installed
0996
0997 // Details
0998 $actions[] = array(
0999 'U_ACTION' => $this->u_action . '&action=details&id=' . $style['style_id'],
1000 'L_ACTION' => $this->user->lang['DETAILS']
1001 );
1002
1003 // Activate/Deactive
1004 $action_name = ($style['style_active'] ? 'de' : '') . 'activate';
1005
1006 $actions[] = array(
1007 'U_ACTION' => $this->u_action . '&action=' . $action_name . '&hash=' . generate_link_hash($action_name) . '&id=' . $style['style_id'],
1008 'L_ACTION' => $this->user->lang['STYLE_' . ($style['style_active'] ? 'DE' : '') . 'ACTIVATE']
1009 );
1010
1011 /* // Export
1012 $actions[] = array(
1013 'U_ACTION' => $this->u_action . '&action=export&hash=' . generate_link_hash('export') . '&id=' . $style['style_id'],
1014 'L_ACTION' => $this->user->lang['EXPORT']
1015 ); */
1016
1017 if ($style['style_name'] !== 'prosilver')
1018 {
1019 // Uninstall
1020 $actions[] = array(
1021 'U_ACTION' => $this->u_action . '&action=uninstall&hash=' . generate_link_hash('uninstall') . '&id=' . $style['style_id'],
1022 'L_ACTION' => $this->user->lang['STYLE_UNINSTALL']
1023 );
1024 }
1025
1026 // Preview
1027 $actions[] = array(
1028 'U_ACTION' => append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'style=' . $style['style_id']),
1029 'L_ACTION' => $this->user->lang['PREVIEW']
1030 );
1031 }
1032 else
1033 {
1034 // Style is not installed
1035 if (empty($style['_available']))
1036 {
1037 $actions[] = array(
1038 'HTML' => $this->user->lang['CANNOT_BE_INSTALLED']
1039 );
1040 }
1041 else
1042 {
1043 $actions[] = array(
1044 'U_ACTION' => $this->u_action . '&action=install&hash=' . generate_link_hash('install') . '&dir=' . urlencode($style['style_path']),
1045 'L_ACTION' => $this->user->lang['INSTALL_STYLE']
1046 );
1047 }
1048 }
1049
1050 // todo: add hook
1051
1052 // Assign template variables
1053 $this->template->assign_block_vars('styles_list', $row);
1054 foreach ($actions as $action)
1055 {
1056 $this->template->assign_block_vars('styles_list.actions', $action);
1057 }
1058
1059 // Increase counters
1060 $counter = ($style['style_id']) ? ($style['style_active'] ? 'active' : 'inactive') : (empty($style['_available']) ? 'cannotinstall' : 'caninstall');
1061 if (!isset($this->style_counters))
1062 {
1063 $this->style_counters = array(
1064 'total' => 0,
1065 'active' => 0,
1066 'inactive' => 0,
1067 'caninstall' => 0,
1068 'cannotinstall' => 0
1069 );
1070 }
1071 $this->style_counters[$counter]++;
1072 $this->style_counters['total']++;
1073 }
1074
1075 /**
1076 * Show welcome message
1077 *
1078 * @param string $title main title
1079 * @param string $description page description
1080 */
1081 protected function welcome_message($title, $description)
1082 {
1083 $this->template->assign_vars(array(
1084 'L_TITLE' => $this->user->lang[$title],
1085 'L_EXPLAIN' => (isset($this->user->lang[$description])) ? $this->user->lang[$description] : ''
1086 )
1087 );
1088 }
1089
1090 /**
1091 * Find all directories that have styles
1092 *
1093 * @return array Directory names
1094 */
1095 protected function find_style_dirs()
1096 {
1097 $styles = array();
1098
1099 $dp = @opendir($this->styles_path);
1100 if ($dp)
1101 {
1102 while (($file = readdir($dp)) !== false)
1103 {
1104 $dir = $this->styles_path . $file;
1105 if ($file[0] == '.' || !is_dir($dir))
1106 {
1107 continue;
1108 }
1109
1110 if (file_exists("{$dir}/style.cfg"))
1111 {
1112 $styles[] = $file;
1113 }
1114 }
1115 closedir($dp);
1116 }
1117
1118 return $styles;
1119 }
1120
1121 /**
1122 * Sort styles
1123 */
1124 public function sort_styles($style1, $style2)
1125 {
1126 if ($style1['style_active'] != $style2['style_active'])
1127 {
1128 return ($style1['style_active']) ? -1 : 1;
1129 }
1130 if (isset($style1['_available']) && $style1['_available'] != $style2['_available'])
1131 {
1132 return ($style1['_available']) ? -1 : 1;
1133 }
1134 return strcasecmp(isset($style1['style_name']) ? $style1['style_name'] : $style1['name'], isset($style2['style_name']) ? $style2['style_name'] : $style2['name']);
1135 }
1136
1137 /**
1138 * Read style configuration file
1139 *
1140 * @param string $dir style directory
1141 * @return array|bool Style data, false on error
1142 */
1143 protected function read_style_cfg($dir)
1144 {
1145 // This should never happen, we give them a red warning because of its relevance.
1146 if (!file_exists($this->styles_path . $dir . '/style.cfg'))
1147 {
1148 trigger_error($this->user->lang('NO_STYLE_CFG', $dir), E_USER_WARNING);
1149 }
1150
1151 static $required = array('name', 'phpbb_version', 'copyright');
1152
1153 $cfg = parse_cfg_file($this->styles_path . $dir . '/style.cfg');
1154
1155 // Check if it is a valid file
1156 foreach ($required as $key)
1157 {
1158 if (!isset($cfg[$key]))
1159 {
1160 return false;
1161 }
1162 }
1163
1164 // Check data
1165 if (!isset($cfg['parent']) || !is_string($cfg['parent']) || $cfg['parent'] == $cfg['name'])
1166 {
1167 $cfg['parent'] = '';
1168 }
1169 if (!isset($cfg['template_bitfield']))
1170 {
1171 $cfg['template_bitfield'] = $this->default_bitfield();
1172 }
1173
1174 return $cfg;
1175 }
1176
1177 /**
1178 * Install style
1179 *
1180 * @param array $style style data
1181 * @return int Style id
1182 */
1183 protected function install_style($style)
1184 {
1185 global $user, $phpbb_log;
1186
1187 // Generate row
1188 $sql_ary = array();
1189 foreach ($style as $key => $value)
1190 {
1191 if ($key != 'style_id' && substr($key, 0, 1) != '_')
1192 {
1193 $sql_ary[$key] = $value;
1194 }
1195 }
1196
1197 // Add to database
1198 $this->db->sql_transaction('begin');
1199
1200 $sql = 'INSERT INTO ' . STYLES_TABLE . '
1201 ' . $this->db->sql_build_array('INSERT', $sql_ary);
1202 $this->db->sql_query($sql);
1203
1204 $id = $this->db->sql_nextid();
1205
1206 $this->db->sql_transaction('commit');
1207
1208 $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_STYLE_ADD', false, array($sql_ary['style_name']));
1209
1210 return $id;
1211 }
1212
1213 /**
1214 * Lists all styles
1215 *
1216 * @return array Rows with styles data
1217 */
1218 protected function get_styles()
1219 {
1220 $sql = 'SELECT *
1221 FROM ' . STYLES_TABLE;
1222 $result = $this->db->sql_query($sql);
1223
1224 $rows = $this->db->sql_fetchrowset($result);
1225 $this->db->sql_freeresult($result);
1226
1227 return $rows;
1228 }
1229
1230 /**
1231 * Count users for each style
1232 *
1233 * @return array Styles in following format: [style_id] = number of users
1234 */
1235 protected function get_users()
1236 {
1237 $sql = 'SELECT user_style, COUNT(user_style) AS style_count
1238 FROM ' . USERS_TABLE . '
1239 GROUP BY user_style';
1240 $result = $this->db->sql_query($sql);
1241
1242 $style_count = array();
1243 while ($row = $this->db->sql_fetchrow($result))
1244 {
1245 $style_count[$row['user_style']] = $row['style_count'];
1246 }
1247 $this->db->sql_freeresult($result);
1248
1249 return $style_count;
1250 }
1251
1252 /**
1253 * Uninstall style
1254 *
1255 * @param array $style Style data
1256 * @return bool|string True on success, error message on error
1257 */
1258 protected function uninstall_style($style)
1259 {
1260 $id = $style['style_id'];
1261 $path = $style['style_path'];
1262
1263 // Check if style has child styles
1264 $sql = 'SELECT style_id
1265 FROM ' . STYLES_TABLE . '
1266 WHERE style_parent_id = ' . (int) $id . " OR style_parent_tree = '" . $this->db->sql_escape($path) . "'";
1267 $result = $this->db->sql_query($sql);
1268
1269 $conflict = $this->db->sql_fetchrow($result);
1270 $this->db->sql_freeresult($result);
1271
1272 if ($conflict !== false)
1273 {
1274 return sprintf($this->user->lang['STYLE_UNINSTALL_DEPENDENT'], $style['style_name']);
1275 }
1276
1277 // Change default style for users
1278 $sql = 'UPDATE ' . USERS_TABLE . '
1279 SET user_style = ' . (int) $this->default_style . '
1280 WHERE user_style = ' . $id;
1281 $this->db->sql_query($sql);
1282
1283 // Uninstall style
1284 $sql = 'DELETE FROM ' . STYLES_TABLE . '
1285 WHERE style_id = ' . $id;
1286 $this->db->sql_query($sql);
1287 return true;
1288 }
1289
1290 /**
1291 * Delete all files in style directory
1292 *
1293 * @param string $path Style directory
1294 * @param string $dir Directory to remove inside style's directory
1295 * @return bool True on success, false on error
1296 */
1297 protected function delete_style_files($path, $dir = '')
1298 {
1299 $dirname = $this->styles_path . $path . $dir;
1300 $result = true;
1301
1302 $dp = @opendir($dirname);
1303
1304 if ($dp)
1305 {
1306 while (($file = readdir($dp)) !== false)
1307 {
1308 if ($file == '.' || $file == '..')
1309 {
1310 continue;
1311 }
1312 $filename = $dirname . '/' . $file;
1313 if (is_dir($filename))
1314 {
1315 if (!$this->delete_style_files($path, $dir . '/' . $file))
1316 {
1317 $result = false;
1318 }
1319 }
1320 else
1321 {
1322 if (!@unlink($filename))
1323 {
1324 $result = false;
1325 }
1326 }
1327 }
1328 closedir($dp);
1329 }
1330 if (!@rmdir($dirname))
1331 {
1332 return false;
1333 }
1334
1335 return $result;
1336 }
1337
1338 /**
1339 * Get list of items from posted data
1340 *
1341 * @param string $name Variable name
1342 * @param string|int $default Default value for array
1343 * @param bool $error If true, error will be triggered if list is empty
1344 * @return array Items
1345 */
1346 protected function request_vars($name, $default, $error = false)
1347 {
1348 $item = $this->request->variable($name, $default);
1349 $items = $this->request->variable($name . 's', array($default));
1350
1351 if (count($items) == 1 && $items[0] == $default)
1352 {
1353 $items = array();
1354 }
1355
1356 if ($item != $default && !count($items))
1357 {
1358 $items[] = $item;
1359 }
1360
1361 if ($error && !count($items))
1362 {
1363 trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING);
1364 }
1365
1366 return $items;
1367 }
1368
1369 /**
1370 * Generates default bitfield
1371 *
1372 * This bitfield decides which bbcodes are defined in a template.
1373 *
1374 * @return string Bitfield
1375 */
1376 protected function default_bitfield()
1377 {
1378 static $value;
1379 if (isset($value))
1380 {
1381 return $value;
1382 }
1383
1384 // Hardcoded template bitfield to add for new templates
1385 $default_bitfield = '1111111111111';
1386
1387 $bitfield = new bitfield();
1388 for ($i = 0; $i < strlen($default_bitfield); $i++)
1389 {
1390 if ($default_bitfield[$i] == '1')
1391 {
1392 $bitfield->set($i);
1393 }
1394 }
1395
1396 return $bitfield->get_base64();
1397 }
1398
1399 }
1400