Open Monograph Press  3.3.0
ThemePlugin.inc.php
1 <?php
2 
16 import('lib.pkp.classes.plugins.LazyLoadPlugin');
17 
18 define('LESS_FILENAME_SUFFIX', '.less');
19 define('THEME_OPTION_PREFIX', 'themeOption_');
20 
21 abstract class ThemePlugin extends LazyLoadPlugin {
28  public $styles = array();
29 
36  public $scripts = array();
37 
43  public $options = array();
44 
50  public $menuAreas = array();
51 
57  public $parent;
58 
67  protected $_optionValues = null;
68 
72  function register($category, $path, $mainContextId = null) {
73  if (!parent::register($category, $path, $mainContextId)) return false;
74 
75  // Don't perform any futher operations if theme is not currently active
76  if (!$this->isActive()) {
77  return true;
78  }
79 
80  // Themes must initialize their functionality after all theme plugins
81  // have been loaded in order to make use of parent/child theme
82  // relationships
83  HookRegistry::register('PluginRegistry::categoryLoaded::themes', array($this, 'themeRegistered'));
84  HookRegistry::register('PluginRegistry::categoryLoaded::themes', array($this, 'initAfter'));
85 
86  // Allow themes to override plugin template files
87  HookRegistry::register('TemplateResource::getFilename', array($this, '_overridePluginTemplates'));
88 
89  return true;
90  }
91 
98  public function themeRegistered($themes) {
99 
100  // Don't fully initialize the theme until OJS is installed, so that
101  // there are no requests to the database before it exists
102  if (defined('SESSION_DISABLE_INIT')) {
103  return;
104  }
105 
106  $this->init();
107  }
108 
116  public abstract function init();
117 
124  public function initAfter() {
125  $this->_registerTemplates();
126  $this->_registerStyles();
127  $this->_registerScripts();
128  }
129 
139  public function isActive() {
140  if (defined('SESSION_DISABLE_INIT')) return false;
141  $request = Application::get()->getRequest();
142  $context = $request->getContext();
143  if (is_a($context, 'Context')) {
144  $activeTheme = $context->getData('themePluginPath');
145  } else {
146  $site = $request->getSite();
147  $activeTheme = $site->getData('themePluginPath');
148  }
149 
150  return $activeTheme == basename($this->getPluginPath());
151  }
152 
173  public function addStyle($name, $style, $args = array()) {
174 
175  // Pass a file path for LESS files
176  if (substr($style, (strlen(LESS_FILENAME_SUFFIX) * -1)) === LESS_FILENAME_SUFFIX) {
177  $args['style'] = $this->_getBaseDir($style);
178 
179  // Pass a URL for other files
180  } elseif (empty($args['inline'])) {
181  if (isset($args['baseUrl'])) {
182  $args['style'] = $args['baseUrl'] . $style;
183  } else {
184  $args['style'] = $this->_getBaseUrl($style);
185  }
186 
187  // Leave inlined styles alone
188  } else {
189  $args['style'] = $style;
190  }
191 
192  // Generate file paths for any additional LESS files to compile with
193  // this style
194  if (isset($args['addLess'])) {
195  foreach ($args['addLess'] as &$file) {
196  $file = $this->_getBaseDir($file);
197  }
198  }
199 
200  $this->styles[$name] = $args;
201  }
202 
211  public function modifyStyle($name, $args = array()) {
212 
213  $style = &$this->getStyle($name);
214 
215  if (empty($style)) {
216  return;
217  }
218 
219  if (isset($args['addLess'])) {
220  foreach ($args['addLess'] as &$file) {
221  $file = $this->_getBaseDir($file);
222  }
223  }
224 
225  if (isset($args['style']) && !isset($args['inline'])) {
226  $args['style'] = substr($args['style'], (strlen(LESS_FILENAME_SUFFIX) * -1)) == LESS_FILENAME_SUFFIX ? $this->_getBaseDir($args['style']) : $this->_getBaseUrl($args['style']);
227  }
228 
229  $style = array_merge_recursive($style, $args);
230  }
231 
238  public function removeStyle($name) {
239 
240  if (isset($this->styles[$name])) {
241  unset($this->styles[$name]);
242  return true;
243  }
244 
245  return $this->parent ? $this->parent->removeStyle($name) : false;
246  }
247 
254  public function &getStyle($name) {
255 
256  // Search this theme
257  if (isset($this->styles[$name])) {
258  $style = &$this->styles[$name];
259  return $style;
260  }
261 
262  // If no parent theme, no style was found
263  if (!isset($this->parent)) {
264  $style = null;
265  return $style;
266  }
267 
268  return $this->parent->getStyle($name);
269  }
270 
286  public function addScript($name, $script, $args = array()) {
287 
288  if (!empty($args['inline'])) {
289  $args['script'] = $script;
290  } elseif (isset($args['baseUrl'])) {
291  $args['script'] = $args['baseUrl'] . $script;
292  } else {
293  $args['script'] = $this->_getBaseUrl($script);
294  }
295 
296  $this->scripts[$name] = $args;
297  }
298 
307  public function modifyScript($name, $args = array()) {
308 
309  $script = &$this->getScript($name);
310 
311  if (empty($script)) {
312  return;
313  }
314 
315  if (isset($args['path'])) {
316  $args['path'] = $this->_getBaseUrl($args['path']);
317  }
318 
319  $script = array_merge( $script, $args );
320  }
321 
328  public function removeScript($name) {
329 
330  if (isset($this->scripts[$name])) {
331  unset($this->scripts[$name]);
332  return true;
333  }
334 
335  return $this->parent ? $this->parent->removeScript($name) : false;
336  }
337 
344  public function &getScript($name) {
345 
346  // Search this theme
347  if (isset($this->scripts[$name])) {
348  $style = &$this->scripts[$name];
349  return $style;
350  }
351 
352  // If no parent theme, no script was found
353  if (!isset($this->parent)) {
354  return;
355  }
356 
357  return $this->parent->getScript($name);
358  }
359 
375  public function addOption($name, $type, $args = array()) {
376 
377  if (!empty($this->options[$name])) {
378  return;
379  }
380 
381  // Convert theme option types from before v3.2
382  if (in_array($type, ['text', 'colour', 'radio'])) {
383  if (isset($args['label'])) {
384  $args['label'] = __($args['label']);
385  }
386  if (isset($args['description'])) {
387  $args['description'] = __($args['description']);
388  }
389  switch ($type) {
390  case 'text':
391  $type = 'FieldText';
392  break;
393  case 'colour':
394  $type = 'FieldColor';
395  break;
396  case 'radio':
397  $type = 'FieldOptions';
398  $args['type'] = 'radio';
399  if (!empty($args['options'])) {
400  $options = [];
401  foreach ($args['options'] as $optionValue => $optionLabel) {
402  $options[] = ['value' => $optionValue, 'label' => __($optionLabel)];
403  }
404  $args['options'] = $options;
405  }
406  break;
407  }
408  }
409 
410  $class = 'PKP\components\forms\\' . $type;
411  try {
412  $this->options[$name] = new $class($name, $args);
413  } catch (Exception $e) {
414  $class = 'APP\components\forms\\' . $type;
415  try {
416  $this->options[$name] = new $class($name, $args);
417  } catch (Exception $e) {
418  throw new Exception(sprintf(
419  'The %s class was not found for the theme option, %s, defined by %s or one of its parent themes.',
420  $type,
421  $name,
422  $this->getDisplayName()
423  ));
424  }
425  }
426  }
427 
436  public function getOption($name) {
437 
438  // Check if this is a valid option
439  if (!isset($this->options[$name])) {
440  return $this->parent ? $this->parent->getOption($name) : false;
441  }
442 
443  // Retrieve option values if they haven't been loaded yet
444  if (is_null($this->_optionValues)) {
445  $context = Application::get()->getRequest()->getContext();
446  $contextId = $context ? $context->getId() : CONTEXT_ID_NONE;
447  $this->_optionValues = $this->getOptionValues($contextId);
448  }
449 
450  if (isset($this->_optionValues[$name])) {
451  return $this->_optionValues[$name];
452  }
453 
454  // Return a default if no value is set
455  if (isset($this->options[$name])) {
456  $option = $this->options[$name];
457  } elseif ($this->parent) {
458  $option = $this->parent->getOption($name);
459  }
460  return isset($option->default) ? $option->default : null;
461  }
462 
473  public function getOptionConfig($name) {
474 
475  if (isset($this->options[$name])) {
476  return $this->options[$name];
477  }
478 
479  return $this->parent ? $this->parent->getOptionConfig($name) : false;
480  }
481 
490  public function getOptionsConfig() {
491 
492  if (!$this->parent) {
493  return $this->options;
494  }
495 
496  return array_merge(
497  $this->parent->getOptionsConfig(),
498  $this->options
499  );
500  }
501 
512  public function modifyOptionsConfig($name, $args = array()) {
513  $option = $this->getOption($name);
514  foreach ($args as $key => $value) {
515  if (property_exists($option, $key)) {
516  $option->{$key} = $value;
517  }
518  }
519  }
520 
527  public function removeOption($name) {
528 
529  if (isset($this->options[$name])) {
530  unset($this->options[$name]);
531  return true;
532  }
533 
534  return $this->parent ? $this->parent->removeOption($name) : false;
535  }
536 
546  public function getOptionValues($contextId) {
547 
548  $pluginSettingsDAO = DAORegistry::getDAO('PluginSettingsDAO');
549 
550  $return = [];
551  $values = $pluginSettingsDAO->getPluginSettings($contextId, $this->getName());
552  foreach ($this->options as $optionName => $optionConfig) {
553  $value = isset($values[$optionName]) ? $values[$optionName] : null;
554  // Convert values stored in the db to the type of the default value
555  if (!is_null($optionConfig->default)) {
556  switch (gettype($optionConfig->default)) {
557  case 'boolean':
558  $value = !$value || $value === 'false' ? false : true;
559  break;
560  case 'integer':
561  $value = (int) $value;
562  break;
563  case 'array':
564  $value = $value === null ? [] : unserialize($value);
565  break;
566  }
567  }
568  $return[$optionName] = $value;
569  }
570 
571  if (!$this->parent) {
572  return $return;
573  }
574 
575  return array_merge(
576  $this->parent->getOptionValues($contextId),
577  $return
578  );
579  }
580 
601  public function validateOptions($options, $themePluginPath, $contextId, $request) {
602  return [];
603  }
604 
613  public function saveOption($name, $value, $contextId = null) {
614 
615  $option = !empty($this->options[$name]) ? $this->options[$name] : null;
616 
617  if (is_null($option)) {
618  return $this->parent ? $this->parent->saveOption($name, $value, $contextId) : false;
619  }
620 
621  if (is_null($contextId)) {
622  $context = Application::get()->getRequest()->getContext();
623  $contextId = $context->getId();
624  }
625 
626  $pluginSettingsDao = DAORegistry::getDAO('PluginSettingsDAO'); /* @var $pluginSettingsDao PluginSettingsDAO */
627 
628  // Remove setting row for empty string values (but not all falsey values)
629  if ($value === '') {
630  $pluginSettingsDao->deleteSetting($contextId, $this->getName(), $name);
631  } else {
632  $type = $pluginSettingsDao->getType($value);
633  $value = $pluginSettingsDao->convertToDb($value, $type);
634  $this->updateSetting($contextId, $name, $value, $type);
635  }
636 
637  }
638 
644  public function addMenuArea($menuAreas) {
645 
646  if (!is_array($menuAreas)) {
647  $menuAreas = array($menuAreas);
648  }
649 
650  $this->menuAreas = array_merge($this->menuAreas, $menuAreas);
651  }
652 
659  public function removeMenuArea($menuArea) {
660 
661  $index = array_search($menuArea, $this->menuAreas);
662  if ($index !== false) {
663  array_splice($this->menuAreas, $index, 1);
664  return true;
665  }
666 
667  return $this->parent ? $this->parent->removeMenuArea($menuArea) : false;
668  }
669 
676  public function getMenuAreas($existingAreas = array()) {
677 
678  $existingAreas = array_unique(array_merge($this->menuAreas, $existingAreas));
679 
680  return $this->parent ? $this->parent->getMenuAreas($existingAreas) : $existingAreas;
681  }
682 
689  public function setParent($parent) {
690  $parent = PluginRegistry::getPlugin('themes', $parent);
691 
692  if (!is_a($parent, 'ThemePlugin')) {
693  return;
694  }
695 
696  $this->parent = $parent;
697  $this->parent->init();
698  }
699 
705  private function _registerTemplates() {
706 
707  // Register parent theme template directory
708  if (isset($this->parent) && is_a($this->parent, 'ThemePlugin')) {
709  $this->parent->_registerTemplates();
710  }
711 
712  // Register this theme's template directory
713  $request = Application::get()->getRequest();
714  $templateManager = TemplateManager::getManager($request);
715  $templateManager->addTemplateDir($this->_getBaseDir('templates'));
716  }
717 
725  private function _registerStyles() {
726 
727  if (isset($this->parent)) {
728  $this->parent->_registerStyles();
729  }
730 
731  $request = Application::get()->getRequest();
732  $dispatcher = $request->getDispatcher();
733  $templateManager = TemplateManager::getManager($request);
734 
735  foreach($this->styles as $name => $data) {
736 
737  if (empty($data['style'])) {
738  continue;
739  }
740 
741  // Compile LESS files
742  if ($dispatcher && substr($data['style'], (strlen(LESS_FILENAME_SUFFIX) * -1)) == LESS_FILENAME_SUFFIX) {
743  $styles = $dispatcher->url(
744  $request,
745  ROUTE_COMPONENT,
746  null,
747  'page.PageHandler',
748  'css',
749  null,
750  array(
751  'name' => $name,
752  )
753  );
754  } else {
755  $styles = $data['style'];
756  }
757 
758  unset($data['style']);
759 
760  $templateManager->addStylesheet($name, $styles, $data);
761  }
762  }
763 
771  public function _registerScripts() {
772 
773  if (isset($this->parent)) {
774  $this->parent->_registerScripts();
775  }
776 
777  $request = Application::get()->getRequest();
778  $templateManager = TemplateManager::getManager($request);
779 
780  foreach($this->scripts as $name => $data) {
781  $script = $data['script'];
782  unset($data['script']);
783  $templateManager->addJavaScript($name, $script, $data);
784  }
785  }
786 
797  public function _getBaseUrl($path = '') {
798  $request = Application::get()->getRequest();
799  $path = empty($path) ? '' : DIRECTORY_SEPARATOR . $path;
800  return $request->getBaseUrl() . DIRECTORY_SEPARATOR . $this->getPluginPath() . $path;
801  }
802 
809  public function _getBaseDir($path = '') {
810  $path = empty($path) ? '' : DIRECTORY_SEPARATOR . $path;
811  return Core::getBaseDir() . DIRECTORY_SEPARATOR . $this->getPluginPath() . $path;
812  }
813 
827  public function isColourDark($color, $limit = 130) {
828  $color = str_replace('#', '', $color);
829  $r = hexdec(substr($color, 0, 2));
830  $g = hexdec(substr($color, 2, 2));
831  $b = hexdec(substr($color, 4, 2));
832  $contrast = sqrt(
833  $r * $r * .241 +
834  $g * $g * .691 +
835  $b * $b * .068
836  );
837  return $contrast < $limit;
838  }
839 }
ThemePlugin\removeMenuArea
removeMenuArea($menuArea)
Definition: ThemePlugin.inc.php:677
ThemePlugin\$styles
$styles
Definition: ThemePlugin.inc.php:31
ThemePlugin
Abstract class for theme plugins.
Definition: ThemePlugin.inc.php:21
Plugin\getDisplayName
getDisplayName()
ThemePlugin\isColourDark
isColourDark($color, $limit=130)
Definition: ThemePlugin.inc.php:845
ThemePlugin\getStyle
& getStyle($name)
Definition: ThemePlugin.inc.php:272
ThemePlugin\getOptionValues
getOptionValues($contextId)
Definition: ThemePlugin.inc.php:564
ThemePlugin\addMenuArea
addMenuArea($menuAreas)
Definition: ThemePlugin.inc.php:662
ThemePlugin\getScript
& getScript($name)
Definition: ThemePlugin.inc.php:362
DAORegistry\getDAO
static & getDAO($name, $dbconn=null)
Definition: DAORegistry.inc.php:57
ThemePlugin\_getBaseDir
_getBaseDir($path='')
Definition: ThemePlugin.inc.php:827
ThemePlugin\setParent
setParent($parent)
Definition: ThemePlugin.inc.php:707
ThemePlugin\removeOption
removeOption($name)
Definition: ThemePlugin.inc.php:545
ThemePlugin\$_optionValues
$_optionValues
Definition: ThemePlugin.inc.php:85
ThemePlugin\modifyOptionsConfig
modifyOptionsConfig($name, $args=array())
Definition: ThemePlugin.inc.php:530
ThemePlugin\removeStyle
removeStyle($name)
Definition: ThemePlugin.inc.php:256
ThemePlugin\getOption
getOption($name)
Definition: ThemePlugin.inc.php:454
ThemePlugin\addScript
addScript($name, $script, $args=array())
Definition: ThemePlugin.inc.php:304
ThemePlugin\addOption
addOption($name, $type, $args=array())
Definition: ThemePlugin.inc.php:393
ThemePlugin\themeRegistered
themeRegistered($themes)
Definition: ThemePlugin.inc.php:116
ThemePlugin\saveOption
saveOption($name, $value, $contextId=null)
Definition: ThemePlugin.inc.php:631
LazyLoadPlugin
Definition: LazyLoadPlugin.inc.php:19
ThemePlugin\removeScript
removeScript($name)
Definition: ThemePlugin.inc.php:346
ThemePlugin\_registerScripts
_registerScripts()
Definition: ThemePlugin.inc.php:789
ThemePlugin\$scripts
$scripts
Definition: ThemePlugin.inc.php:42
ThemePlugin\$parent
$parent
Definition: ThemePlugin.inc.php:72
ThemePlugin\isActive
isActive()
Definition: ThemePlugin.inc.php:157
PKPTemplateManager\getManager
static & getManager($request=null)
Definition: PKPTemplateManager.inc.php:1239
ThemePlugin\validateOptions
validateOptions($options, $themePluginPath, $contextId, $request)
Definition: ThemePlugin.inc.php:619
ThemePlugin\_getBaseUrl
_getBaseUrl($path='')
Definition: ThemePlugin.inc.php:815
ThemePlugin\getOptionConfig
getOptionConfig($name)
Definition: ThemePlugin.inc.php:491
Plugin\getPluginPath
getPluginPath()
Definition: Plugin.inc.php:330
ThemePlugin\modifyStyle
modifyStyle($name, $args=array())
Definition: ThemePlugin.inc.php:229
Plugin\$request
$request
Definition: Plugin.inc.php:68
PluginRegistry\getPlugin
static getPlugin($category, $name)
Definition: PluginRegistry.inc.php:85
ThemePlugin\getMenuAreas
getMenuAreas($existingAreas=array())
Definition: ThemePlugin.inc.php:694
HookRegistry\register
static register($hookName, $callback, $hookSequence=HOOK_SEQUENCE_NORMAL)
Definition: HookRegistry.inc.php:70
Core\getBaseDir
static getBaseDir()
Definition: Core.inc.php:37
ThemePlugin\addStyle
addStyle($name, $style, $args=array())
Definition: ThemePlugin.inc.php:191
PKPApplication\get
static get()
Definition: PKPApplication.inc.php:235
ThemePlugin\modifyScript
modifyScript($name, $args=array())
Definition: ThemePlugin.inc.php:325
ThemePlugin\$options
$options
Definition: ThemePlugin.inc.php:52
ThemePlugin\$menuAreas
$menuAreas
Definition: ThemePlugin.inc.php:62
ThemePlugin\getOptionsConfig
getOptionsConfig()
Definition: ThemePlugin.inc.php:508
ThemePlugin\initAfter
initAfter()
Definition: ThemePlugin.inc.php:142
ThemePlugin\init
init()