Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/vendors/Textpattern/Admin/Theme.php - 478 lines - 12367 bytes - Summary - Text - Print

Description: Base for admin-side themes.

   1  <?php
   2  
   3  /*
   4   * Textpattern Content Management System
   5   * https://textpattern.com/
   6   *
   7   * Copyright (C) 2020 The Textpattern Development Team
   8   *
   9   * This file is part of Textpattern.
  10   *
  11   * Textpattern is free software; you can redistribute it and/or
  12   * modify it under the terms of the GNU General Public License
  13   * as published by the Free Software Foundation, version 2.
  14   *
  15   * Textpattern is distributed in the hope that it will be useful,
  16   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18   * GNU General Public License for more details.
  19   *
  20   * You should have received a copy of the GNU General Public License
  21   * along with Textpattern. If not, see <https://www.gnu.org/licenses/>.
  22   */
  23  
  24  /**
  25   * Base for admin-side themes.
  26   *
  27   * @package Admin\Theme
  28   */
  29  
  30  namespace Textpattern\Admin;
  31  
  32  if (!defined('THEME')) {
  33      /**
  34       * Relative path to themes directory.
  35       */
  36  
  37      define('THEME', 'admin-themes'.DS);
  38  }
  39  
  40  /**
  41   * Admin-side theme.
  42   *
  43   * @package Admin\Theme
  44   */
  45  
  46  abstract class Theme
  47  {
  48      /**
  49       * The theme name.
  50       *
  51       * @var string
  52       */
  53  
  54      public $name;
  55  
  56      /**
  57       * Stores a menu.
  58       *
  59       * @var array
  60       */
  61  
  62      public $menu;
  63  
  64      /**
  65       * Theme location.
  66       *
  67       * @var string
  68       */
  69  
  70      public $url;
  71  
  72      public $cssPath;
  73      public $jsPath;
  74  
  75      /**
  76       * Just a popup window.
  77       *
  78       * @var bool
  79       */
  80  
  81      public $is_popup;
  82  
  83      /**
  84       * Stores an activity message.
  85       *
  86       * @var bool
  87       * @see \Textpattern\Admin\Theme::announce()
  88       * @see \Textpattern\Admin\Theme::announce_async()
  89       */
  90  
  91      public $message;
  92  
  93      /**
  94       * Constructor.
  95       *
  96       * @param string $name Theme name
  97       */
  98  
  99      public function __construct($name)
 100      {
 101          $this->name = $name;
 102          $this->menu = array();
 103          $this->url = THEME.rawurlencode($name).'/';
 104          $this->is_popup = false;
 105          $this->message = '';
 106          $this->cssPath = 'assets'.DS.'css';
 107          $this->jsPath = 'assets'.DS.'js';
 108      }
 109  
 110      /**
 111       * Gets a theme's source path.
 112       *
 113       * @param  string $name Theme name
 114       * @return string Source file path for named theme
 115       */
 116  
 117      public static function path($name)
 118      {
 119          return txpath.DS.THEME.$name.DS.$name.'.php';
 120      }
 121  
 122      /**
 123       * Theme factory.
 124       *
 125       * @param  string $name Theme name
 126       * @return \Textpattern\Admin\Theme|bool An initialised theme object or FALSE on failure
 127       */
 128  
 129      public static function factory($name)
 130      {
 131          $path = Theme::path($name);
 132  
 133          if (is_readable($path)) {
 134              require_once($path);
 135          } else {
 136              return false;
 137          }
 138  
 139          $t = "{$name}_theme";
 140  
 141          if (class_exists($t)) {
 142              return new $t($name);
 143          } else {
 144              return false;
 145          }
 146      }
 147  
 148      /**
 149       * Initialise the theme singleton.
 150       *
 151       * @param  string $name Theme name
 152       * @return \Textpattern\Admin\Theme A valid theme object
 153       */
 154  
 155      public static function init($name = '')
 156      {
 157          static $instance;
 158  
 159          if ($name === '') {
 160              $name = pluggable_ui('admin_side', 'theme_name', get_pref('theme_name', 'hive'));
 161          }
 162  
 163          if ($instance && is_object($instance) && ($name == $instance->name)) {
 164              return $instance;
 165          } else {
 166              $instance = null;
 167          }
 168  
 169          $instance = Theme::factory($name);
 170  
 171          if (!$instance) {
 172              set_pref('theme_name', 'hive');
 173              die(gTxt('cannot_instantiate_theme', array(
 174                  '{name}'  => $name,
 175                  '{class}' => "{$name}_theme",
 176                  '{path}'  => Theme::path($name),
 177              )));
 178          }
 179  
 180          return $instance;
 181      }
 182  
 183      /**
 184       * Get a list of all theme names.
 185       *
 186       * @param int $format
 187       *   0 - names
 188       *   1 - name => title              Used for selectInput
 189       *   2 - name => manifest(array)    Now not used, reserved
 190       *
 191       * @return array of all available theme names
 192       */
 193  
 194      public static function names($format = 0)
 195      {
 196          $out = array();
 197  
 198          if ($files = glob(txpath.DS.THEME.'*'.DS.'manifest.json')) {
 199              $DS = preg_quote(DS);
 200  
 201              foreach ($files as $file) {
 202                  $file = realpath($file);
 203                  if (preg_match('%^(.*'.$DS.'(\w+))'.$DS.'manifest\.json$%', $file, $mm) && $manifest = json_decode(txp_get_contents($file), true)) {
 204                      if (@$manifest['txp-type'] == 'textpattern-admin-theme' && is_file($mm[1].DS.$mm[2].'.php')) {
 205                          $manifest['title'] = empty($manifest['title']) ? ucwords($mm[2]) : $manifest['title'];
 206                          if ($format == 1) {
 207                              $out[$mm[2]] = $manifest['title'];
 208                          } elseif ($format == 2) {
 209                              $out[$mm[2]] = $manifest;
 210                          } else {
 211                              $out[] = $mm[2];
 212                          }
 213                      }
 214                  }
 215              }
 216          }
 217  
 218          return $out;
 219      }
 220  
 221      /**
 222       * Inherit from an ancestor theme.
 223       *
 224       * @param  string $name Name of ancestor theme
 225       * @return bool TRUE on success, FALSE on unavailable/invalid ancestor theme
 226       */
 227  
 228      public static function based_on($name)
 229      {
 230          global $production_status;
 231          $theme = Theme::factory($name);
 232  
 233          if (!$theme) {
 234              set_pref('theme_name', 'hive');
 235  
 236              if ($production_status === 'debug') {
 237                  echo gTxt('cannot_instantiate_theme', array(
 238                      '{name}'  => $name,
 239                      '{class}' => "{$name}_theme",
 240                      '{path}'  => Theme::path($name),
 241                  ));
 242              }
 243  
 244              return false;
 245          }
 246  
 247          return true;
 248      }
 249  
 250      /**
 251       * Sets Textpattern's menu structure, message contents and other application
 252       * states.
 253       *
 254       * @param  string $area     Currently active top level menu
 255       * @param  string $event    Currently active second level menu
 256       * @param  bool   $is_popup Just a popup window for tag builder et cetera
 257       * @param  array  $message  The contents of the notification message pane
 258       * @return \Textpattern\Admin\Theme This theme object
 259       */
 260  
 261      public function set_state($area, $event, $is_popup, $message)
 262      {
 263          $this->is_popup = $is_popup;
 264          $this->message = $message;
 265  
 266          if ($is_popup) {
 267              return $this;
 268          }
 269  
 270          // Use legacy areas() for b/c.
 271          $areas = areas();
 272          $defaults = array(
 273              'content'      => 'article',
 274              'presentation' => 'page',
 275              'admin'        => 'admin',
 276          );
 277  
 278          if (empty($areas['start'])) {
 279              unset($areas['start']);
 280          }
 281  
 282          if (empty($areas['extensions'])) {
 283              unset($areas['extensions']);
 284          }
 285  
 286          $dflt_tab = get_pref('default_event', '');
 287  
 288          foreach ($areas as $ar => $items) {
 289              $l_ = gTxt('tab_'.$ar);
 290              $e_ = (array_key_exists($ar, $defaults)) ? $defaults[$ar] : reset($areas[$ar]);
 291              $i_ = array();
 292  
 293              if (has_privs('tab.'.$ar)) {
 294                  if (!has_privs($e_)) {
 295                      $e_ = '';
 296                  }
 297  
 298                  foreach ($items as $a => $b) {
 299                      if (has_privs($b)) {
 300                          if ($e_ === '') {
 301                              $e_ = $b;
 302                          }
 303  
 304                          if ($b == $dflt_tab) {
 305                              $this->menu[$ar]['event'] = $dflt_tab;
 306                          }
 307  
 308                          $i_[] = array(
 309                              'label'  => $a,
 310                              'event'  => $b,
 311                              'active' => ($b == $event),
 312                          );
 313                      }
 314                  }
 315  
 316                  if ($e_) {
 317                      $this->menu[$ar] = array(
 318                          'label'  => $l_,
 319                          'event'  => $e_,
 320                          'active' => ($ar == $area),
 321                          'items'  => $i_,
 322                      );
 323                  }
 324              }
 325          }
 326  
 327          return $this;
 328      }
 329  
 330      /**
 331       * HTML &lt;head&gt; section.
 332       *
 333       * Returned value is rendered into the head element of
 334       * all admin pages.
 335       *
 336       * @return string
 337       */
 338  
 339      abstract public function html_head();
 340  
 341      /**
 342       * HTML &lt;head&gt; custom section.
 343       */
 344  
 345      public function html_head_custom()
 346      {
 347          $out = '';
 348          $prefs = $this->manifest('prefs');
 349  
 350          if (!empty($prefs['textpattern'])) {
 351              $content = json_encode($prefs['textpattern'], TEXTPATTERN_JSON);
 352              $out .= script_js("textpattern.prefs = jQuery.extend(textpattern.prefs, {$content})").n;
 353          }
 354  
 355          if (!empty($prefs['style'])) {
 356              $content = $prefs['style'];
 357              $out .= "<style>\n{$content}\n</style>".n;
 358          }
 359  
 360          // Custom CSS (see theme README for usage instructions).
 361          if (defined('admin_custom_css')) {
 362              $custom_css = admin_custom_css;
 363          } else {
 364              $custom_css = 'custom.css';
 365          }
 366  
 367          $custom = empty($this->cssPath) ? $custom_css : $this->cssPath.DS.$custom_css;
 368          if (file_exists(txpath.DS.THEME.$this->name.DS.$custom)) {
 369              $out .= '<link rel="stylesheet" href="'.$this->url.$custom.'">'.n;
 370          }
 371  
 372          // Custom JavaScript (see theme README for usage instructions).
 373          if (defined('admin_custom_js')) {
 374              $custom_js = admin_custom_js;
 375          } else {
 376              $custom_js = 'custom.js';
 377          }
 378  
 379          $custom = empty($this->jsPath) ? $custom_js : $this->jsPath.DS.$custom_js;
 380          if (file_exists(txpath.DS.THEME.$this->name.DS.$custom)) {
 381              $out .= '<script src="'.$this->url.$custom.'"></script>'.n;
 382          }
 383  
 384          return $out;
 385      }
 386  
 387      /**
 388       * Draw the theme's header.
 389       *
 390       * @return string
 391       */
 392  
 393      abstract public function header();
 394  
 395      /**
 396       * Draw the theme's footer.
 397       *
 398       * @return string
 399       */
 400  
 401      abstract public function footer();
 402  
 403      /**
 404       * Output notification message for synchronous HTML views.
 405       *
 406       * @param   array $thing Message text and status flag
 407       * @param   bool  $modal If TRUE, immediate user interaction suggested
 408       * @return  string HTML
 409       * @example
 410       * global $theme;
 411       * echo $theme->announce(array('my_message', E_ERROR));
 412       */
 413  
 414      public function announce($thing = array('', 0), $modal = false)
 415      {
 416          return $this->_announce($thing, false, $modal);
 417      }
 418  
 419      /**
 420       * Output notification message for asynchronous JavaScript views.
 421       *
 422       * @param   array $thing Message text and status flag
 423       * @param   bool  $modal If TRUE, immediate user interaction suggested
 424       * @return  string JavaScript
 425       * @since   4.5.0
 426       * @example
 427       * global $theme;
 428       * echo script_js(
 429       *     $theme->announce_async(array('my_message', E_ERROR))
 430       * );
 431       */
 432  
 433      public function announce_async($thing = array('', 0), $modal = false)
 434      {
 435          return $this->_announce($thing, true, $modal);
 436      }
 437  
 438      /**
 439       * Output notification message for synchronous HTML and asynchronous JavaScript views.
 440       */
 441  
 442      private function _announce($thing, $async, $modal)
 443      {
 444          // $thing[0]: message text.
 445          // $thing[1]: message type, defaults to "success" unless empty or a different flag is set.
 446  
 447          if ($thing === '') {
 448              return '';
 449          }
 450  
 451          if (!is_array($thing) || !isset($thing[1])) {
 452              $thing = array($thing, 0);
 453          }
 454  
 455          if ($modal) {
 456              $js = 'window.alert("'.escape_js(strip_tags($thing[0])).'")';
 457          } else {
 458              // Try to inject $html into the message pane no matter when _announce()'s output is printed.
 459              $thing = json_encode($thing, TEXTPATTERN_JSON);
 460              $js = "textpattern.Console.addMessage({$thing})";
 461          }
 462  
 463          return $async ? $js : script_js(str_replace('</', '<\/', $js));
 464      }
 465  
 466      /**
 467       * Return details of this theme.
 468       *
 469       * All returned items are optional.
 470       *
 471       * @return array
 472       */
 473  
 474      public function manifest($type = 'manifest')
 475      {
 476          return json_decode(txp_get_contents($this->url.$type.'.json'), true);
 477      }
 478  }

title

Description

title

Description

title

Description

title

title

Body