artabro/wire/core/AdminTheme.php

541 lines
14 KiB
PHP
Raw Permalink Normal View History

2024-08-27 11:35:37 +02:00
<?php namespace ProcessWire;
/**
* ProcessWire Admin Theme Module
*
* An abstract module intended as a base for admin themes.
*
* See the Module interface (Module.php) for details about each method.
*
* This file is licensed under the MIT license.
* https://processwire.com/about/license/mit/
*
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
* https://processwire.com
*
* @property int|string $version Current admin theme version
* @property string $url URL to admin theme
* @property string $path Disk path to admin theme
*
* @method void install()
* @method void uninstall()
* @method array getExtraMarkup()
*
*/
abstract class AdminTheme extends WireData implements Module {
/**
* Per the Module interface, return an array of information about the Module
*
*/
public static function getModuleInfo() {
return array(
'title' => '', // printable name/title of module
'version' => 1, // version number of module
'summary' => '', // 1 sentence summary of module
'href' => '', // URL to more information (optional)
// all admin themes should have this as their autoload selector:
'autoload' => 'template=admin',
'singular' => true
);
}
/**
* Current admin theme version (cached from module info)
*
* @var int
*
*/
protected $version = 0;
/**
* Keeps track of quantity of admin themes installed so that we know when to add profile field
*
*/
protected static $numAdminThemes = 0;
/**
* Additional classes for body tag
*
* @var array
*
*/
protected $bodyClasses = array();
/**
* General purpose classes indexed by name
*
* @var array
*
*/
protected $classes = array();
/**
* Extra markup regions
*
* @var array
*
*/
protected $extraMarkup = array(
'head' => '',
'notices' => '',
'body' => '',
'masthead' => '',
'content' => '',
'footer' => '',
'sidebar' => '', // sidebar not used in all admin themes
);
/**
* URLs to place in link prerender tags
*
* @var array
*
*/
protected $preRenderURLs = array();
/**
* Construct
*
*/
public function __construct() {
// placeholder
parent::__construct();
}
/**
* Initialize the admin theme system and determine which admin theme should be used
*
* All admin themes must call this init() method to register themselves.
*
* Note: this should be called after API ready.
*
*/
public function init() {
self::$numAdminThemes++;
$info = $this->wire()->modules->getModuleInfo($this);
$this->version = $info['version'];
$page = $this->wire()->page;
// if module has been called when it shouldn't (per the 'autoload' conditional)
// then module author probably forgot the right 'autoload' string, so this
// serves as secondary stopgap to keep this module from loading when it shouldn't.
if(!$page || $page->template->name !== 'admin') return;
if(self::$numAdminThemes > 1 && !$this->wire()->fields->get('admin_theme')) $this->install();
// if admin theme has already been set, then no need to continue
if($this->wire('adminTheme')) return;
$config = $this->wire()->config;
$user = $this->wire()->user;
$adminTheme = $user->admin_theme; /** @var string $adminTheme */
$isCurrent = false;
if($adminTheme) {
// there is user specified admin theme
// check if this is the one that should be used
if($adminTheme == $this->className()) {
$isCurrent = true;
$this->setCurrent();
}
} else if($config->defaultAdminTheme == $this->className()) {
// there is no user specified admin theme, so use this one
$isCurrent = true;
$this->setCurrent();
}
if($isCurrent) $this->initConfig();
}
/**
* Initialize configuration properties and JS config for when this is current admin theme
*
* @since 3.0.173
*
*/
protected function initConfig() {
$config = $this->wire()->config;
$user = $this->wire()->user;
$session = $this->wire()->session;
$page = $this->wire()->page;
$urls = $config->urls;
// adjust $config adminThumbOptions[scale] for auto detect when requested
$o = $config->adminThumbOptions;
if($o && isset($o['scale']) && $o['scale'] === 1) {
$o['scale'] = $session->get('hidpi') ? 0.5 : 1.0;
$config->adminThumbOptions = $o;
}
$config->jsConfig('urls', array(
'root' => $urls->root,
'admin' => $urls->admin,
'modules' => $urls->modules,
'core' => $urls->core,
'files' => $urls->files,
'templates' => $urls->templates,
'adminTemplates' => $urls->adminTemplates,
));
$config->js('modals', true); // share at render time
$config->jsConfig('debug', $config->debug);
if($user) {
$userInfo = array(
'id' => $user->id,
'name' => $user->name,
'roles' => array(),
);
$roles = $user->isLoggedin() ? $user->roles : null;
$guestRoleID = $config->guestUserRolePageID;
if($roles) foreach($roles as $role) {
if($role->id !== $guestRoleID) $userInfo['roles'][] = $role->name;
}
$config->jsConfig('user', $userInfo);
}
if($page) {
$config->jsConfig('page', array(
'id' => $page->id,
'name' => $page->name,
'process' => (string) $page->process,
));
}
if($session->get('hidpi')) $this->addBodyClass('hidpi-device');
if($session->get('touch')) $this->addBodyClass('touch-device');
$this->addBodyClass($this->className());
}
/**
* Get property
*
* @param string $key
* @return int|mixed|null|string
*
*/
public function get($key) {
if($key === 'version') return $this->version;
if($key === 'url') return $this->url();
if($key === 'path') return $this->path();
return parent::get($key);
}
/**
* Get URL to this admin theme
*
* @return string
* @since 3.0.171
*
*/
public function url() {
return $this->wire()->config->urls($this->className());
}
/**
* Get disk path to this admin theme
*
* @return string
* @since 3.0.171
*
*/
public function path() {
$config = $this->wire()->config;
$path = $config->paths($this->className());
if(empty($path)) {
$class = $this->className();
$path = $config->paths->modules . "AdminTheme/$class/";
if(!is_dir($path)) {
$path = $config->paths->siteModules . "$class/";
if(!is_dir($path)) $path = __DIR__;
}
}
return $path;
}
/**
* Get predefined translated label by key for labels shared among admin themes
*
* @param string $key
* @param string $val Value to return if label not available
* @return string
* @since 3.0.162
*
*/
public function getLabel($key, $val = '') {
switch($key) {
case 'search-help': return $this->_('help'); // Localized term to type for search-engine help (3+ chars)
case 'search-tip': return $this->_('Try “help”'); // // Search tip (indicating your translated “help” term)
case 'advanced-mode': return $this->_('Advanced Mode');
case 'debug': return $this->_('Debug');
}
return $val;
}
/**
* Returns true if this admin theme is the one that will be used for this request
*
*/
public function isCurrent() {
$adminTheme = $this->wire()->adminTheme;
return $adminTheme && $adminTheme->className() === $this->className();
}
/**
* Set this admin theme as the current one
*
*/
protected function setCurrent() {
$config = $this->wire()->config;
$name = $this->className();
$config->paths->set('adminTemplates', $config->paths->get($name));
$config->urls->set('adminTemplates', $config->urls->get($name));
$config->set('defaultAdminTheme', $name);
$this->wire('adminTheme', $this);
}
/**
* Enables hooks to append extra markup to various sections of the admin page
*
* @return array Associative array containing the following properties, any of
* which may be populated or empty:
* - head
* - body
* - masthead
* - content
* - footer
* - sidebar
*
*/
public function ___getExtraMarkup() {
$parts = $this->extraMarkup;
$isLoggedin = $this->wire()->user->isLoggedin();
if($isLoggedin && $this->wire()->modules->isInstalled('InputfieldCKEditor')
&& $this->wire()->process instanceof WirePageEditor) {
// necessary for when CKEditor is loaded via ajax
$script = 'script';
$parts['head'] .= "<$script>" .
"window.CKEDITOR_BASEPATH='" . $this->wire()->config->urls('InputfieldCKEditor') .
'ckeditor-' . InputfieldCKEditor::CKEDITOR_VERSION . "/';</$script>";
}
/*
if($isLoggedin && $this->wire('config')->advanced) {
$parts['footer'] = "<p class='AdvancedMode'><i class='fa fa-flask'></i> " . $this->_('Advanced Mode') . "</p>";
}
*/
foreach($this->preRenderURLs as $url) {
$parts['head'] .= "<link rel='prerender' href='$url'>";
}
return $parts;
}
/**
* Add extra markup to a region in the admin theme
*
* @param string $name
* @param string $value
*
*/
public function addExtraMarkup($name, $value) {
if(!empty($this->extraMarkup[$name])) {
$this->extraMarkup[$name] .= "\n$value";
} else {
$this->extraMarkup[$name] = $value;
}
}
/**
* Add a <body> class to the admin theme
*
* @param string $className
*
*/
public function addBodyClass($className) {
$this->addClass('body', $className);
}
/**
* Get the body[class] attribute string
*
* @return string
*
*/
public function getBodyClass() {
return $this->getClass('body');
}
/**
* Return class for a given named item or blank if none available
*
* Omit the first argument to return all classes in an array.
*
* @param string $name Tag or item name, i.e. “input”, or omit to return all defined [tags=classes]
* @param bool $getArray Specify true to return array of class name(s) rather than string (default=false). $name argument required.
* @return string|array Returns string or array of class names, or array of all [tags=classes] or $tagName argument is empty.
*
*/
public function getClass($name = '', $getArray = false) {
if(empty($name)) {
return $this->classes;
} else if(isset($this->classes[$name])) {
return $getArray ? explode(' ', $this->classes[$name]) : $this->classes[$name];
} else {
return $getArray ? array() : '';
}
}
/**
* Add class for given named item
*
* Default behavior is to merge classes if existing classes are already present for given item $name.
*
* #pw-internal
*
* @param string $name
* @param string|array $class
* @param bool $replace Specify true to replace any existing classes rather than merging them
*
*/
public function addClass($name, $class, $replace = false) {
if(is_array($class)) {
foreach($class as $c) {
$this->addClass($name, $c);
}
} else if(!$replace && isset($this->classes[$name])) {
$classes = $this->classes[$name];
if(strpos($classes, $class) !== false) {
// avoid re-adding class if it is already present
if(array_search($class, explode(' ', $classes)) !== false) return;
}
$this->classes[$name] = trim($classes . ' ' . ltrim($class));
} else {
$this->classes[$name] = trim($class);
}
}
/**
* Set classes for multiple tags
*
* #pw-internal
*
* @param array $classes Array of strings (class names) where keys are tag names
* @param bool $replace Specify true to replace any existing classes rather than merge them (default=false)
*
*/
public function setClasses(array $classes, $replace = false) {
if($replace || empty($this->classes)) {
$this->classes = $classes;
} else {
foreach($classes as $name => $class) {
$this->addClass($name, $class);
}
}
}
/**
* Install the admin theme
*
* Other admin themes using an install() method must call this install before their own.
*
*/
public function ___install() {
// if we are the only admin theme installed, no need to add an admin_theme field
if(self::$numAdminThemes == 0) return;
$modules = $this->wire()->modules;
// install a field for selecting the admin theme from the user's profile
/** @var Field $field */
$field = $this->wire()->fields->get('admin_theme');
$toUseNote = $this->_('To use this theme, select it from your user profile.');
// we already have this field installed, no need to continue
if($field) {
$this->message($toUseNote);
} else {
// this will be the 2nd admin theme installed, so add a field that lets them select admin theme
/** @var Field $field */
$field = $this->wire(new Field());
$field->name = 'admin_theme';
$field->type = $modules->get('FieldtypeModule');
$field->set('moduleTypes', array('AdminTheme'));
$field->set('labelField', 'title');
$field->set('inputfieldClass', 'InputfieldRadios');
$field->label = 'Admin Theme';
$field->flags = Field::flagSystem;
try {
$field->save();
} catch(\Exception $e) {
// $this->error("Error creating 'admin_theme' field: " . $e->getMessage());
}
}
if($field && $field->id) {
/** @var Fieldgroup $fieldgroup */
$fieldgroup = $this->wire()->fieldgroups->get('user');
if(!$fieldgroup->hasField($field)) {
$fieldgroup->add($field);
$fieldgroup->save();
$this->message($this->_('Installed field "admin_theme" and added to user profile settings.'));
$this->message($toUseNote);
}
// make this field one that the user is allowed to configure in their profile
$data = $modules->getModuleConfigData('ProcessProfile');
$data['profileFields'][] = 'admin_theme';
$modules->saveModuleConfigData('ProcessProfile', $data);
}
}
/**
* Set a pre-render URL or get currently pre-render URL(s)
*
* #pw-internal
*
* @param string $url
* @return array
*
*/
public function preRenderURL($url = '') {
if(!empty($url)) $this->preRenderURLs[] = $url;
return $this->preRenderURLs;
}
public function ___uninstall() {
$defaultAdminTheme = $this->wire()->config->defaultAdminTheme;
if($defaultAdminTheme == $this->className()) {
throw new WireException(
"Cannot uninstall this admin theme because \$config->defaultAdminTheme = '$defaultAdminTheme'; " .
"Please add this setting with a different value in /site/config.php"
);
}
/*
if(self::$numAdminThemes > 1) return;
// this is the last installed admin theme
$field = $this->wire('fields')->get('admin_theme');
$field->flags = Field::flagSystemOverride;
$field->flags = 0;
$field->save();
$fieldgroup = $this->wire('fieldgroups')->get('user');
$fieldgroup->remove($field);
$fieldgroup->save();
$this->wire('fields')->delete($field);
$this->message($this->_('Removed field "admin_theme" from system.'));
*/
}
}