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.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

acp_styles.php

Zuletzt modifiziert: 02.04.2025, 15:02 - Dateigröße: 35.39 KiB


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 . '&amp;mode=style' . '">&laquo; ' . $this->user->lang('STYLE_INSTALLED_RETURN_INSTALLED_STYLES') . '</a>';
0250          $message .= '<br /><br /><a href="' . $this->u_base_action . '&amp;mode=install' . '">&raquo; ' . $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 . '&amp;action=details&amp;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('&nbsp; ', $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 . '&amp;action=details&amp;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 . '&amp;action=' . $action_name . '&amp;hash=' . generate_link_hash($action_name) . '&amp;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 . '&amp;action=export&amp;hash=' . generate_link_hash('export') . '&amp;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 . '&amp;action=uninstall&amp;hash=' . generate_link_hash('uninstall') . '&amp;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 . '&amp;action=install&amp;hash=' . generate_link_hash('install') . '&amp;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