| 
									
										
										
										
											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; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-21 22:30:24 +01:00
										 |  |  | use App\Kernel; | 
					
						
							| 
									
										
										
										
											2020-08-03 20:45:00 +00:00
										 |  |  | 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; | 
					
						
							| 
									
										
										
										
											2021-08-21 22:30:24 +01:00
										 |  |  | use Symfony\Component\Config\Loader\LoaderInterface; | 
					
						
							| 
									
										
										
										
											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; | 
					
						
							| 
									
										
										
										
											2021-07-22 12:36:16 +00:00
										 |  |  |     /** @codeCoverageIgnore */ | 
					
						
							| 
									
										
										
										
											2020-08-03 20:45:00 +00:00
										 |  |  |     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) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-07-22 12:36:16 +00:00
										 |  |  |         [$type, $module] = preg_split('/\\\\/', $fqcn, 0, PREG_SPLIT_NO_EMPTY); | 
					
						
							| 
									
										
										
										
											2020-08-03 20:45:00 +00:00
										 |  |  |         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)) { | 
					
						
							| 
									
										
										
										
											2021-07-22 12:36:16 +00:00
										 |  |  |                 // Happens at compile time, so it's hard to do integration testing. However,
 | 
					
						
							|  |  |  |                 // everything would break if this did :')
 | 
					
						
							|  |  |  |                 // @codeCoverageIgnoreStart
 | 
					
						
							| 
									
										
										
										
											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
										 |  |  |                 ); | 
					
						
							| 
									
										
										
										
											2021-07-22 12:36:16 +00:00
										 |  |  |                 // @codeCoverageIgnoreEnd
 | 
					
						
							| 
									
										
										
										
											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-07-22 12:36:16 +00:00
										 |  |  |             // @codeCoverageIgnoreStart
 | 
					
						
							| 
									
										
										
										
											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]); | 
					
						
							| 
									
										
										
										
											2021-07-22 12:36:16 +00:00
										 |  |  |             // @codeCoverageIgnoreEnd
 | 
					
						
							| 
									
										
										
										
											2020-09-10 20:42:17 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											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() | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-07-22 12:36:16 +00:00
										 |  |  |         if ($_ENV['APP_ENV'] === 'prod' && !file_exists(CACHE_FILE)) { | 
					
						
							|  |  |  |             // @codeCoverageIgnoreStart
 | 
					
						
							| 
									
										
										
										
											2021-09-06 17:34:27 +01:00
										 |  |  |             throw new \Exception('The application needs to be compiled before using in production'); | 
					
						
							| 
									
										
										
										
											2021-07-22 12:36:16 +00:00
										 |  |  |         // @codeCoverageIgnoreEnd
 | 
					
						
							| 
									
										
										
										
											2020-08-05 16:22:12 +00:00
										 |  |  |         } 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; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 12:36:16 +00:00
										 |  |  |             if ($_ENV['APP_ENV'] === 'test' || 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; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-21 22:30:24 +01:00
										 |  |  |         foreach ($obj->modules as $module) { | 
					
						
							|  |  |  |             $module->loadConfig(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-03 20:45:00 +00:00
										 |  |  |         foreach ($obj->events as $event => $callables) { | 
					
						
							|  |  |  |             foreach ($callables as $callable) { | 
					
						
							|  |  |  |                 Event::addHandler($event, $callable); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-08-21 22:30:24 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Load Module settings and setup Twig template load paths | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * Happens at "compile time" | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @codeCoverageIgnore | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static function configureContainer(ContainerBuilder $container, LoaderInterface $loader): array | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $template_modules = array_merge(glob(INSTALLDIR . '/components/*/templates'), glob(INSTALLDIR . '/plugins/*/templates')); | 
					
						
							|  |  |  |         // Regular template location
 | 
					
						
							|  |  |  |         $templates = ['%kernel.project_dir%/templates' => 'default_path', '%kernel.project_dir%/public' => 'public_path']; | 
					
						
							|  |  |  |         // Path => alias
 | 
					
						
							|  |  |  |         foreach ($template_modules as $mod) { | 
					
						
							|  |  |  |             $templates[$mod] = null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         $container->loadFromExtension('twig', ['paths' => $templates]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $modules    = array_merge(glob(INSTALLDIR . '/components/*'), glob(INSTALLDIR . '/plugins/*')); | 
					
						
							|  |  |  |         $parameters = []; | 
					
						
							|  |  |  |         foreach ($modules as $mod) { | 
					
						
							|  |  |  |             $path = "{$mod}/config" . Kernel::CONFIG_EXTS; | 
					
						
							|  |  |  |             $loader->load($path, 'glob'); // Is supposed to, but doesn't return anything that would let us identify if loading worked
 | 
					
						
							|  |  |  |             foreach (explode(',', substr(Kernel::CONFIG_EXTS, 2, -1)) as $ext) { | 
					
						
							|  |  |  |                 if (file_exists("{$mod}/config.{$ext}")) { | 
					
						
							|  |  |  |                     $parameters[basename(strtolower($mod))] = basename(dirname(strtolower($mod))); | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return $parameters; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-08-03 20:45:00 +00:00
										 |  |  | } |