+
+ public function getModule(?string $module_class = null): ICanHandleRequests
+ {
+ $module_parameters = [$this->server];
+ /**
+ * ROUTING
+ *
+ * From the request URL, routing consists of obtaining the name of a BaseModule-extending class of which the
+ * post() and/or content() static methods can be respectively called to produce a data change or an output.
+ **/
+ try {
+ $module_class = $module_class ?? $this->getModuleClass();
+ $module_parameters[] = $this->parameters;
+ } catch (MethodNotAllowedException $e) {
+ $module_class = MethodNotAllowed::class;
+ } catch (NotFoundException $e) {
+ $moduleName = $this->args->getModuleName();
+ // Then we try addon-provided modules that we wrap in the LegacyModule class
+ if (Addon::isEnabled($moduleName) && file_exists("addon/{$moduleName}/{$moduleName}.php")) {
+ //Check if module is an app and if public access to apps is allowed or not
+ $privateapps = $this->config->get('config', 'private_addons', false);
+ if ((!local_user()) && Hook::isAddonApp($moduleName) && $privateapps) {
+ throw new MethodNotAllowedException($this->l10n->t("You must be logged in to use addons. "));
+ } else {
+ include_once "addon/{$moduleName}/{$moduleName}.php";
+ if (function_exists($moduleName . '_module')) {
+ $module_parameters[] = "addon/{$moduleName}/{$moduleName}.php";
+ $module_class = LegacyModule::class;
+ }
+ }
+ }
+
+ /* Finally, we look for a 'standard' program module in the 'mod' directory
+ * We emulate a Module class through the LegacyModule class
+ */
+ if (!$module_class && file_exists("mod/{$moduleName}.php")) {
+ $module_parameters[] = "mod/{$moduleName}.php";
+ $module_class = LegacyModule::class;
+ }
+
+ $module_class = $module_class ?: PageNotFound::class;
+ }
+
+ $stamp = microtime(true);
+
+ try {
+ /** @var ICanHandleRequests $module */
+ return $this->dice->create($module_class, $module_parameters);
+ } finally {
+ if ($this->dice_profiler_threshold > 0) {
+ $dur = floatval(microtime(true) - $stamp);
+ if ($dur >= $this->dice_profiler_threshold) {
+ $this->logger->warning('Dice module creation lasts too long.', ['duration' => round($dur, 3), 'module' => $module_class, 'parameters' => $module_parameters]);
+ }
+ }
+ }
+ }
+
+ /**
+ * If a base routes file path has been provided, we can load routes from it if the cache misses.
+ *
+ * @return array
+ * @throws HTTPException\InternalServerErrorException
+ */
+ private function getDispatchData()
+ {
+ $dispatchData = [];
+
+ if ($this->baseRoutesFilepath) {
+ $dispatchData = require $this->baseRoutesFilepath;
+ if (!is_array($dispatchData)) {
+ throw new HTTPException\InternalServerErrorException('Invalid base routes file');
+ }
+ }
+
+ $this->loadRoutes($dispatchData);
+
+ return $this->routeCollector->getData();
+ }
+
+ /**
+ * We cache the dispatch data for speed, as computing the current routes (version 2020.09)
+ * takes about 850ms for each requests.
+ *
+ * The cached "routerDispatchData" lasts for a day, and must be cleared manually when there
+ * is any changes in the enabled addons list.
+ *
+ * Additionally, we check for the base routes file last modification time to automatically
+ * trigger re-computing the dispatch data.
+ *
+ * @return array|mixed
+ * @throws HTTPException\InternalServerErrorException
+ */
+ private function getCachedDispatchData()
+ {
+ $routerDispatchData = $this->cache->get('routerDispatchData');
+ $lastRoutesFileModifiedTime = $this->cache->get('lastRoutesFileModifiedTime');
+ $forceRecompute = false;
+
+ if ($this->baseRoutesFilepath) {
+ $routesFileModifiedTime = filemtime($this->baseRoutesFilepath);
+ $forceRecompute = $lastRoutesFileModifiedTime != $routesFileModifiedTime;
+ }
+
+ if (!$forceRecompute && $routerDispatchData) {
+ return $routerDispatchData;
+ }
+
+ if (!$this->lock->acquire('getCachedDispatchData', 0)) {
+ // Immediately return uncached data when we can't aquire a lock
+ return $this->getDispatchData();
+ }
+
+ $routerDispatchData = $this->getDispatchData();
+
+ $this->cache->set('routerDispatchData', $routerDispatchData, Duration::DAY);
+ if (!empty($routesFileModifiedTime)) {
+ $this->cache->set('lastRoutesFileModifiedTime', $routesFileModifiedTime, Duration::MONTH);
+ }
+
+ if ($this->lock->isLocked('getCachedDispatchData')) {
+ $this->lock->release('getCachedDispatchData');
+ }
+
+ return $routerDispatchData;
+ }