'', // 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'] = "
" . $this->_('Advanced Mode') . "
"; } */ foreach($this->preRenderURLs as $url) { $parts['head'] .= ""; } 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 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.')); */ } }