summaryrefslogblamecommitdiffstats
path: root/inc/module.inc.php
blob: b072c4a2d86dcd4764efa67d1a72df2490a46a44 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11

     

                        






                 
                         
           
                                       


                                                         
                                                                                                          

                               
                                                                             


                                                  
                                                                                 


                                             


                                                                                 
                                                                                    
                                                                                  
                             



                                                                 
                                                                                         



                                               
                                
                                                   
                 

                                                          
        
                                                                     





                                                                




                                                                         
                                                              




                                                                     
                                                                                                                                




                                                                 
                                          


           
                                                           
           
                                                                        

                               
                                
                                                     
                                                         
                                                 


                                                                



                                                               



                            


                                                                             
                                              






                                                     


                                                                     
                                                    

                               
                       
                                                     

                                                                                             

                         
                            


                            
                                           
         
                                            





                                                         
                                                                               












                                                                         

                                       
                                      


                                     
                                          

                                        
                          







                                                                                                                            
 
                                                  


                                                                      



                                                                          



                                                                               
                                                                                


                                                                           


                                    
                                                      



                                          
                                       


                                                                         
                                                                                               





                                               
                                                                  
         
















                                                                                                                                              
                                          


                                                       


                                                       
                                                                                           

                         
                            
         
 
                                                





                                              
                                                            

                                                   
                               









                                                                             
        
                                               



                                   
                                                
         
                                                                                                       
                                        
                                                         


                               
 
                                              
         
                                                                                                   


                                               
         
        
                                              



                                       
                                                 


                                                                    
 
                                          



                                       
                                        



                                                
                                           
         
                                                                     
                                                                                                                                
                                                                     
                         
                                              
                 
                          

         
                                       
         
                                                                     
                                                                                                                
                                                           
                         
                                          
                 
                          

         
 
<?php

declare(strict_types=1);

class Module
{
	/*
	 * Static
	 */

	/**
	 * @var ?Module[]
	 */
	private static $modules = null;

	/**
	 * @param string $name ID/Internal name of module
	 * @param bool $ignoreDepFail whether to return the module even if some of its dependencies failed
	 * @return false|Module
	 */
	public static function get(string $name, bool $ignoreDepFail = false)
	{
		if (!isset(self::$modules[$name]))
			return false;
		if (!self::resolveDeps(self::$modules[$name]) && !$ignoreDepFail)
			return false;
		return self::$modules[$name];
	}

	/**
	 * Check whether given module is available, that is, all dependencies are
	 * met. If the module is available, it will be activated, so all its classes
	 * are available through the autoloader, and any js or css is added to the
	 * final page output.
	 *
	 * @param string $moduleId module to check
	 * @return bool true if module is available and activated
	 */
	public static function isAvailable(string $moduleId, bool $activate = true): bool
	{
		$module = self::get($moduleId);
		if ($module === false)
			return false;
		if ($activate) {
			$module->activate(1, true);
		}
		return !$module->hasMissingDependencies();
	}
	
	private static function resolveDepsByName(string $name): bool
	{
		if (!isset(self::$modules[$name]))
			return false;
		return self::resolveDeps(self::$modules[$name]);
	}

	/**
	 * 
	 * @param \Module $mod the module to check
	 * @return boolean true iff module deps are all found and enabled
	 */
	private static function resolveDeps(Module $mod): bool
	{
		if (!$mod->depsChecked) {
			$mod->depsChecked = true;
			foreach ($mod->dependencies as $dep) {
				if (!self::resolveDepsByName($dep)) {
					trigger_error("Disabling module {$mod->name}: Dependency $dep failed.", E_USER_WARNING);
					$mod->depsMissing = true;
					return false;
				}
			}
		}
		return !$mod->depsMissing;
	}

	/**
	 * @return \Module[] List of valid, enabled modules
	 */
	public static function getEnabled(bool $sortById = false): array
	{
		$ret = array();
		$sort = array();
		foreach (self::$modules as $module) {
			if (self::resolveDeps($module)) {
				$ret[] = $module;
				if ($sortById) {
					$sort[] = $module->name;
				}
			}
		}
		if ($sortById) {
			array_multisort($sort, SORT_ASC, $ret);
		}
		return $ret;
	}

	/**
	 * @return \Module[] List of all modules, including with missing deps
	 */
	public static function getAll(): array
	{
		foreach (self::$modules as $module) {
			self::resolveDeps($module);
		}
		return self::$modules;
	}

	/**
	 * @return \Module[] List of modules that have been activated
	 */
	public static function getActivated(): array
	{
		$ret = array();
		$i = 0;
		foreach (self::$modules as $module) {
			if ($module->activated !== false) {
				$ret[sprintf('%05d_%d', $module->activated, $i++)] = $module;
			}
		}
		ksort($ret);
		return $ret;
	}

	public static function init(): void
	{
		if (self::$modules !== null)
			return;
		$dh = opendir('modules');
		if ($dh === false)
			return;
		self::$modules = array();
		while (($dir = readdir($dh)) !== false) {
			if (empty($dir) || preg_match('/[^a-zA-Z0-9_]/', $dir))
				continue;
			if (!is_file('modules/' . $dir . '/config.json'))
				continue;
			$name = strtolower($dir);
			self::$modules[$name] = new Module($dir);
		}
		closedir($dh);
	}

	/*
	 * Non-static
	 */

	/** @var ?string category id */
	private $category = null;
	private $clientPlugin = false;
	private $depsMissing = false;
	private $depsChecked = false;
	private $activated = false;
	private $directActivation = false;
	private $dependencies = array();
	private $name;
	private $collapse;
	/**
	 * @var array assoc list of 'filename.css' => true|false (true = always load, false = only if module is main module)
	 */
	private $css = array();
	/**
	 * @var array assoc list of 'filename.js' => true|false (true = always load, false = only if module is main module)
	 */
	private $scripts = array();

	private function __construct(string $name)
	{
		$file = 'modules/' . $name . '/config.json';
		$json = @json_decode(@file_get_contents($file), true);
		foreach (['dependencies', 'css', 'scripts'] as $key) {
			if (isset($json[$key]) && is_array($json[$key])) {
				$this->$key = $json[$key];
			}
		}
		if (isset($json['category']) && is_string($json['category'])) {
			$this->category = $json['category'];
		}
		$this->collapse = isset($json['collapse']) && $json['collapse'];
		if (isset($json['client-plugin'])) {
			$this->clientPlugin = (bool)$json['client-plugin'];
		}
		$this->name = $name;
	}
	
	public function hasMissingDependencies(): bool
	{
		return $this->depsMissing;
	}
	
	public function newPage(): Page
	{
		$modulePath = 'modules/' . $this->name . '/page.inc.php';
		if (!file_exists($modulePath)) {
			ErrorHandler::traceError("Module doesn't have a page: " . $modulePath);
		}
		require_once $modulePath;
		$class = 'Page_' . $this->name;
		return new $class();
	}

	public function activate(?int $depth, ?bool $direct): bool
	{
		if ($this->depsMissing)
			return false;
		if ($this->activated !== false && ($this->directActivation || !$direct))
			return true;
		if ($depth === null && $direct === null) {
			// This is the current page, always load its scripts
			$this->clientPlugin = true;
			$direct = true;
		}
		if ($this->activated === false) {
			spl_autoload_register(function ($class) {
				$file = 'modules/' . $this->name . '/inc/' . preg_replace('/[^a-z0-9]/', '', strtolower($class)) . '.inc.php';
				if (!file_exists($file))
					return;
				require_once $file;
			});
		}
		$this->activated = $depth;
		if ($direct) {
			$this->directActivation = true;
		}
		foreach ($this->dependencies as $dep) {
			$get = self::get($dep);
			if ($get !== false) {
				$get->activate($depth + 1, $direct && $this->clientPlugin);
			}
		}
		return true;
	}

	public function getDependencies(): array
	{
		$deps = array();
		$this->getDepsInternal($deps);
		return array_keys($deps);
	}

	private function getDepsInternal(array &$deps): void
	{
		if (!is_array($this->dependencies))
			return;
		foreach ($this->dependencies as $dep) {
			if (isset($deps[$dep])) // Handle cyclic dependencies
				continue;
			$deps[$dep] = true;
			$mod = self::get($dep);
			if ($mod === false)
				continue;
			$mod->getDepsInternal($deps);
		}
	}
	
	public function getIdentifier(): string
	{
		return $this->name;
	}

	public function getDisplayName(): string
	{
		$string = Dictionary::translateFileModule($this->name, 'module', 'module_name', false);
		if ($string === false) {
			return '!!' . $this->name . '!!';
		}
		return $string;
	}

	public function getPageTitle(): string
	{
		$val = Dictionary::translateFileModule($this->name, 'module', 'page_title', false);
		if ($val !== false)
			return $val;
		return $this->getDisplayName();
	}
	
	public function getCategory(): ?string
	{
		return $this->category;
	}
	
	public function getCategoryName(): string
	{
		return Dictionary::getCategoryName($this->category);
	}

	public function doCollapse(): bool
	{
		return $this->collapse;
	}

	public function getDir(): string
	{
		return 'modules/' . $this->name;
	}

	public function getScripts(): array
	{
		if ($this->directActivation && $this->clientPlugin) {
			if (!in_array('clientscript.js', $this->scripts) && file_exists($this->getDir() . '/clientscript.js')) {
				$this->scripts[] = 'clientscript.js';
			}
			return $this->scripts;
		}
		return [];
	}

	public function getCss(): array
	{
		if ($this->directActivation && $this->clientPlugin) {
			if (!in_array('style.css', $this->css) && file_exists($this->getDir() . '/style.css')) {
				$this->css[] = 'style.css';
			}
			return $this->css;
		}
		return [];
	}

}