| 
									
										
										
										
											2020-08-03 20:45:00 +00:00
										 |  |  | <?php | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // {{{ License
 | 
					
						
							|  |  |  | // This file is part of GNU social - https://www.gnu.org/software/social
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // GNU social is free software: you can redistribute it and/or modify
 | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by
 | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or
 | 
					
						
							|  |  |  | // (at your option) any later version.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // GNU social is distributed in the hope that it will be useful,
 | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					
						
							|  |  |  | // GNU Affero General Public License for more details.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License
 | 
					
						
							|  |  |  | // along with GNU social.  If not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							|  |  |  | // }}}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Module and plugin loader code, one of the main features of GNU social | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Loads plugins from `plugins/enabled`, instances them | 
					
						
							|  |  |  |  * and hooks its events | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @package   GNUsocial | 
					
						
							|  |  |  |  * @category  Modules | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2021-02-19 23:02:40 +00:00
										 |  |  |  * @author    Hugo Sales <hugo@hsal.es> | 
					
						
							|  |  |  |  * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org | 
					
						
							| 
									
										
										
										
											2020-08-03 20:45:00 +00:00
										 |  |  |  * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace App\Core; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | use App\Util\Formatting; | 
					
						
							| 
									
										
										
										
											2020-08-05 16:22:12 +00:00
										 |  |  | use AppendIterator; | 
					
						
							|  |  |  | use FilesystemIterator; | 
					
						
							| 
									
										
										
										
											2020-08-03 20:45:00 +00:00
										 |  |  | use Functional as F; | 
					
						
							| 
									
										
										
										
											2020-08-05 16:22:12 +00:00
										 |  |  | use RecursiveDirectoryIterator; | 
					
						
							|  |  |  | use RecursiveIteratorIterator; | 
					
						
							| 
									
										
										
										
											2020-09-07 23:53:44 +00:00
										 |  |  | use Symfony\Component\DependencyInjection\ContainerBuilder; | 
					
						
							|  |  |  | use Symfony\Component\DependencyInjection\Reference; | 
					
						
							| 
									
										
										
										
											2020-08-03 20:45:00 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | class ModuleManager | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2020-08-05 16:22:12 +00:00
										 |  |  |     public function __construct() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         if (!defined('CACHE_FILE')) { | 
					
						
							|  |  |  |             define('CACHE_FILE', INSTALLDIR . '/var/cache/module_manager.php'); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-03 20:45:00 +00:00
										 |  |  |     protected static $loader; | 
					
						
							|  |  |  |     public static function setLoader($l) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         self::$loader = $l; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     protected array $modules = []; | 
					
						
							|  |  |  |     protected array $events  = []; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 19:47:15 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Add the $fqcn class from $path as a module | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2020-08-03 20:45:00 +00:00
										 |  |  |     public function add(string $fqcn, string $path) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         list($type, $module) = preg_split('/\\\\/', $fqcn, 0, PREG_SPLIT_NO_EMPTY); | 
					
						
							|  |  |  |         self::$loader->addPsr4("\\{$type}\\{$module}\\", dirname($path)); | 
					
						
							|  |  |  |         $id                 = Formatting::camelCaseToSnakeCase($type . '.' . $module); | 
					
						
							|  |  |  |         $obj                = new $fqcn(); | 
					
						
							|  |  |  |         $this->modules[$id] = $obj; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 19:47:15 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Container-build-time step that preprocesses the registering of events | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2020-08-03 20:45:00 +00:00
										 |  |  |     public function preRegisterEvents() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         foreach ($this->modules as $id => $obj) { | 
					
						
							|  |  |  |             F\map(F\select(get_class_methods($obj), | 
					
						
							|  |  |  |                            F\ary(F\partial_right('App\Util\Formatting::startsWith', 'on'), 1)), | 
					
						
							|  |  |  |                   function (string $m) use ($obj) { | 
					
						
							| 
									
										
										
										
											2020-10-19 18:22:59 +00:00
										 |  |  |                       $ev = substr($m, 2); | 
					
						
							| 
									
										
										
										
											2020-08-03 20:45:00 +00:00
										 |  |  |                       $this->events[$ev] = $this->events[$ev] ?? []; | 
					
						
							|  |  |  |                       $this->events[$ev][] = [$obj, $m]; | 
					
						
							|  |  |  |                   } | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 19:47:15 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Compiler pass responsible for registering all modules | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2020-09-10 20:42:17 +00:00
										 |  |  |     public static function process(?ContainerBuilder $container = null) | 
					
						
							| 
									
										
										
										
											2020-08-05 16:22:12 +00:00
										 |  |  |     { | 
					
						
							|  |  |  |         $module_paths   = array_merge(glob(INSTALLDIR . '/components/*/*.php'), glob(INSTALLDIR . '/plugins/*/*.php')); | 
					
						
							|  |  |  |         $module_manager = new self(); | 
					
						
							| 
									
										
										
										
											2020-09-07 23:53:44 +00:00
										 |  |  |         $entity_paths   = []; | 
					
						
							| 
									
										
										
										
											2020-08-05 16:22:12 +00:00
										 |  |  |         foreach ($module_paths as $path) { | 
					
						
							|  |  |  |             $type   = ucfirst(preg_replace('%' . INSTALLDIR . '/(component|plugin)s/.*%', '\1', $path)); | 
					
						
							| 
									
										
										
										
											2020-09-07 23:53:44 +00:00
										 |  |  |             $dir    = dirname($path); | 
					
						
							| 
									
										
										
										
											2021-02-19 23:02:40 +00:00
										 |  |  |             $module = basename($dir); // component or plugin
 | 
					
						
							| 
									
										
										
										
											2020-08-05 16:22:12 +00:00
										 |  |  |             $fqcn   = "\\{$type}\\{$module}\\{$module}"; | 
					
						
							|  |  |  |             $module_manager->add($fqcn, $path); | 
					
						
							| 
									
										
										
										
											2020-09-10 20:42:17 +00:00
										 |  |  |             if (!is_null($container) && file_exists($dir = $dir . '/Entity') && is_dir($dir)) { | 
					
						
							| 
									
										
										
										
											2020-09-07 23:53:44 +00:00
										 |  |  |                 $entity_paths[] = $dir; | 
					
						
							| 
									
										
										
										
											2020-09-10 20:42:17 +00:00
										 |  |  |                 $container->findDefinition('doctrine.orm.default_metadata_driver')->addMethodCall( | 
					
						
							| 
									
										
										
										
											2020-09-07 23:53:44 +00:00
										 |  |  |                     'addDriver', | 
					
						
							| 
									
										
										
										
											2021-02-19 23:02:40 +00:00
										 |  |  |                     [new Reference('app.schemadef_driver'), "{$type}\\{$module}\\Entity"] | 
					
						
							| 
									
										
										
										
											2020-09-07 23:53:44 +00:00
										 |  |  |                 ); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-08-05 16:22:12 +00:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-10 20:42:17 +00:00
										 |  |  |         if (!is_null($container)) { | 
					
						
							| 
									
										
										
										
											2021-02-19 23:02:40 +00:00
										 |  |  |             $container->findDefinition('app.schemadef_driver') | 
					
						
							| 
									
										
										
										
											2020-09-10 20:42:17 +00:00
										 |  |  |                       ->addMethodCall('addPaths', ['$paths' => $entity_paths]); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-09-07 23:53:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-05 16:22:12 +00:00
										 |  |  |         $module_manager->preRegisterEvents(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         file_put_contents(CACHE_FILE, "<?php\nreturn " . var_export($module_manager, true) . ';'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 19:47:15 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Serialize this class, for dumping into the cache | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param mixed $state | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2020-08-03 20:45:00 +00:00
										 |  |  |     public static function __set_state($state) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $obj          = new self(); | 
					
						
							|  |  |  |         $obj->modules = $state['modules']; | 
					
						
							|  |  |  |         $obj->events  = $state['events']; | 
					
						
							|  |  |  |         return $obj; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 19:47:15 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Load the modules at runtime. In production requires the cache | 
					
						
							|  |  |  |      * file to exist, in dev it rebuilds this cache | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2020-08-03 20:45:00 +00:00
										 |  |  |     public function loadModules() | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2020-08-05 16:22:12 +00:00
										 |  |  |         if ($_ENV['APP_ENV'] == 'prod' && !file_exists(CACHE_FILE)) { | 
					
						
							|  |  |  |             throw new Exception('The application needs to be compiled before using in production'); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             $rdi = new AppendIterator(); | 
					
						
							|  |  |  |             $rdi->append(new RecursiveIteratorIterator(new RecursiveDirectoryIterator(INSTALLDIR . '/components', FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS))); | 
					
						
							|  |  |  |             $rdi->append(new RecursiveIteratorIterator(new RecursiveDirectoryIterator(INSTALLDIR . '/plugins',    FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS))); | 
					
						
							|  |  |  |             $time = file_exists(CACHE_FILE) ? filemtime(CACHE_FILE) : 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (F\some($rdi, function ($e) use ($time) { return $e->getMTime() > $time; })) { | 
					
						
							| 
									
										
										
										
											2020-09-10 20:42:17 +00:00
										 |  |  |                 Log::info('Rebuilding plugin cache at runtime. This means we can\'t update DB definitions'); | 
					
						
							| 
									
										
										
										
											2020-08-05 16:22:12 +00:00
										 |  |  |                 self::process(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $obj = require CACHE_FILE; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-03 20:45:00 +00:00
										 |  |  |         foreach ($obj->events as $event => $callables) { | 
					
						
							|  |  |  |             foreach ($callables as $callable) { | 
					
						
							|  |  |  |                 Event::addHandler($event, $callable); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |