Textpattern | PHP Cross Reference | Content Management Systems |
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 <head> 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 <head> 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
Body
title
Description
Body
title
Description
Body
title
Body
title