Browse Source

Add Arguments & Modules class

pull/7508/head
Philipp Holzer 1 month ago
parent
commit
0af9747c6c
No account linked to committer's email address

+ 5
- 1
index.php View File

@@ -18,4 +18,8 @@ $dice = (new Dice())->addRules(include __DIR__ . '/static/dependencies.config.ph
18 18
 
19 19
 $a = \Friendica\BaseObject::getApp();
20 20
 
21
-$a->runFrontend();
21
+$a->runFrontend(
22
+	$dice->create(\Friendica\App\Module::class),
23
+	$dice->create(\Friendica\App\Router::class),
24
+	$dice->create(\Friendica\Core\Config\PConfiguration::class)
25
+);

+ 83
- 248
src/App.php View File

@@ -8,9 +8,11 @@ use Detection\MobileDetect;
8 8
 use DOMDocument;
9 9
 use DOMXPath;
10 10
 use Exception;
11
+use Friendica\App\Arguments;
12
+use Friendica\App\Module;
11 13
 use Friendica\Core\Config\Cache\ConfigCache;
12 14
 use Friendica\Core\Config\Configuration;
13
-use Friendica\Core\Hook;
15
+use Friendica\Core\Config\PConfiguration;
14 16
 use Friendica\Core\L10n\L10n;
15 17
 use Friendica\Core\System;
16 18
 use Friendica\Core\Theme;
@@ -18,6 +20,7 @@ use Friendica\Database\Database;
18 20
 use Friendica\Database\DBA;
19 21
 use Friendica\Model\Profile;
20 22
 use Friendica\Module\Login;
23
+use Friendica\Module\Special\HTTPException as ModuleHTTPException;
21 24
 use Friendica\Network\HTTPException;
22 25
 use Friendica\Util\BaseURL;
23 26
 use Friendica\Util\ConfigFileLoader;
@@ -41,7 +44,7 @@ use Psr\Log\LoggerInterface;
41 44
  */
42 45
 class App
43 46
 {
44
-	public $module_class = null;
47
+	/** @deprecated 2019.09 - use App\Arguments->getQueryString() */
45 48
 	public $query_string = '';
46 49
 	public $page = [];
47 50
 	public $profile;
@@ -53,9 +56,13 @@ class App
53 56
 	public $page_contact;
54 57
 	public $content;
55 58
 	public $data = [];
59
+	/** @deprecated 2019.09 - use App\Arguments->getCommand() */
56 60
 	public $cmd = '';
61
+	/** @deprecated 2019.09 - use App\Arguments->getArgv() or Arguments->get() */
57 62
 	public $argv;
63
+	/** @deprecated 2019.09 - use App\Arguments->getArgc() */
58 64
 	public $argc;
65
+	/** @deprecated 2019.09 - Use App\Module->getName() instead */
59 66
 	public $module;
60 67
 	public $timezone;
61 68
 	public $interactive = true;
@@ -93,6 +100,8 @@ class App
93 100
 
94 101
 	/**
95 102
 	 * @var bool true, if the call is from an backend node (f.e. worker)
103
+	 *
104
+	 * @deprecated 2019.09 - use App\Module->isBackend() instead
96 105
 	 */
97 106
 	private $isBackend;
98 107
 
@@ -136,6 +145,16 @@ class App
136 145
 	 */
137 146
 	private $l10n;
138 147
 
148
+	/**
149
+	 * @var App\Arguments
150
+	 */
151
+	private $args;
152
+
153
+	/**
154
+	 * @var App\Module
155
+	 */
156
+	private $moduleClass;
157
+
139 158
 	/**
140 159
 	 * Returns the current config cache of this node
141 160
 	 *
@@ -197,6 +216,16 @@ class App
197 216
 		return $this->mode;
198 217
 	}
199 218
 
219
+	/**
220
+	 * Returns the Database of the Application
221
+	 *
222
+	 * @return Database
223
+	 */
224
+	public function getDBA()
225
+	{
226
+		return $this->database;
227
+	}
228
+
200 229
 	/**
201 230
 	 * Register a stylesheet file path to be included in the <head> tag of every page.
202 231
 	 * Inclusion is done in App->initHead().
@@ -247,7 +276,7 @@ class App
247 276
 	 *
248 277
 	 * @throws Exception if the Basepath is not usable
249 278
 	 */
250
-	public function __construct(Database $database, Configuration $config, App\Mode $mode, App\Router $router, BaseURL $baseURL, LoggerInterface $logger, Profiler $profiler, L10n $l10n)
279
+	public function __construct(Database $database, Configuration $config, App\Mode $mode, App\Router $router, BaseURL $baseURL, LoggerInterface $logger, Profiler $profiler, L10n $l10n, Arguments $args, Module $module)
251 280
 	{
252 281
 		$this->database = $database;
253 282
 		$this->config   = $config;
@@ -257,6 +286,8 @@ class App
257 286
 		$this->profiler = $profiler;
258 287
 		$this->logger   = $logger;
259 288
 		$this->l10n     = $l10n;
289
+		$this->args = $args;
290
+		$this->isBackend = $module->isBackend();
260 291
 
261 292
 		$this->profiler->reset();
262 293
 
@@ -273,59 +304,10 @@ class App
273 304
 			. $this->getBasePath() . DIRECTORY_SEPARATOR . 'library' . PATH_SEPARATOR
274 305
 			. $this->getBasePath());
275 306
 
276
-		if (!empty($_SERVER['QUERY_STRING']) && strpos($_SERVER['QUERY_STRING'], 'pagename=') === 0) {
277
-			$this->query_string = substr($_SERVER['QUERY_STRING'], 9);
278
-		} elseif (!empty($_SERVER['QUERY_STRING']) && strpos($_SERVER['QUERY_STRING'], 'q=') === 0) {
279
-			$this->query_string = substr($_SERVER['QUERY_STRING'], 2);
280
-		}
281
-
282
-		// removing trailing / - maybe a nginx problem
283
-		$this->query_string = ltrim($this->query_string, '/');
284
-
285
-		if (!empty($_GET['pagename'])) {
286
-			$this->cmd = trim($_GET['pagename'], '/\\');
287
-		} elseif (!empty($_GET['q'])) {
288
-			$this->cmd = trim($_GET['q'], '/\\');
289
-		}
290
-
291
-		// fix query_string
292
-		$this->query_string = str_replace($this->cmd . '&', $this->cmd . '?', $this->query_string);
293
-
294
-		// unix style "homedir"
295
-		if (substr($this->cmd, 0, 1) === '~') {
296
-			$this->cmd = 'profile/' . substr($this->cmd, 1);
297
-		}
298
-
299
-		// Diaspora style profile url
300
-		if (substr($this->cmd, 0, 2) === 'u/') {
301
-			$this->cmd = 'profile/' . substr($this->cmd, 2);
302
-		}
303
-
304
-		/*
305
-		 * Break the URL path into C style argc/argv style arguments for our
306
-		 * modules. Given "http://example.com/module/arg1/arg2", $this->argc
307
-		 * will be 3 (integer) and $this->argv will contain:
308
-		 *   [0] => 'module'
309
-		 *   [1] => 'arg1'
310
-		 *   [2] => 'arg2'
311
-		 *
312
-		 *
313
-		 * There will always be one argument. If provided a naked domain
314
-		 * URL, $this->argv[0] is set to "home".
315
-		 */
316
-
317
-		$this->argv = explode('/', $this->cmd);
318
-		$this->argc = count($this->argv);
319
-		if ((array_key_exists('0', $this->argv)) && strlen($this->argv[0])) {
320
-			$this->module = str_replace('.', '_', $this->argv[0]);
321
-			$this->module = str_replace('-', '_', $this->module);
322
-		} else {
323
-			$this->argc = 1;
324
-			$this->argv = ['home'];
325
-			$this->module = 'home';
326
-		}
327
-
328
-		$this->isBackend = $this->isBackend || $this->checkBackend($this->module);
307
+		$this->cmd = $args->getCommand();
308
+		$this->argv = $args->getArgv();
309
+		$this->argc = $args->getArgc();
310
+		$this->query_string = $args->getQueryString();
329 311
 
330 312
 		// Detect mobile devices
331 313
 		$mobile_detect = new MobileDetect();
@@ -346,10 +328,6 @@ class App
346 328
 	 */
347 329
 	public function reload()
348 330
 	{
349
-		$this->isBackend = basename($_SERVER['PHP_SELF'], '.php') !== 'index';
350
-
351
-		$this->getMode()->determine($this->getBasePath());
352
-
353 331
 		if ($this->getMode()->has(App\Mode::DBAVAILABLE)) {
354 332
 			$this->profiler->update($this->config);
355 333
 
@@ -399,6 +377,8 @@ class App
399 377
 	 * @param bool $ssl Whether to append http or https under BaseURL::SSL_POLICY_SELFSIGN
400 378
 	 *
401 379
 	 * @return string Friendica server base URL
380
+	 *
381
+	 * @deprecated 2019.09 - use BaseUrl->get($ssl) instead
402 382
 	 */
403 383
 	public function getBaseURL($ssl = false)
404 384
 	{
@@ -453,9 +433,9 @@ class App
453 433
 	 * - Infinite scroll data
454 434
 	 * - head.tpl template
455 435
 	 */
456
-	public function initHead()
436
+	private function initHead(App\Module $module, PConfiguration $pconfig)
457 437
 	{
458
-		$interval = ((local_user()) ? Core\PConfig::get(local_user(), 'system', 'update_interval') : 40000);
438
+		$interval = ((local_user()) ? $pconfig->get(local_user(), 'system', 'update_interval') : 40000);
459 439
 
460 440
 		// If the update is 'deactivated' set it to the highest integer number (~24 days)
461 441
 		if ($interval < 0) {
@@ -467,8 +447,8 @@ class App
467 447
 		}
468 448
 
469 449
 		// Default title: current module called
470
-		if (empty($this->page['title']) && $this->module) {
471
-			$this->page['title'] = ucfirst($this->module);
450
+		if (empty($this->page['title']) && $module->getName()) {
451
+			$this->page['title'] = ucfirst($module->getName());
472 452
 		}
473 453
 
474 454
 		// Prepend the sitename to the page title
@@ -520,7 +500,7 @@ class App
520 500
 	 * - Registered footer scripts (through App->registerFooterScript())
521 501
 	 * - footer.tpl template
522 502
 	 */
523
-	public function initFooter()
503
+	private function initFooter()
524 504
 	{
525 505
 		// If you're just visiting, let javascript take you home
526 506
 		if (!empty($_SESSION['visitor_home'])) {
@@ -595,58 +575,6 @@ class App
595 575
 			$this->getBaseURL();
596 576
 	}
597 577
 
598
-	/**
599
-	 * @brief Checks if the site is called via a backend process
600
-	 *
601
-	 * This isn't a perfect solution. But we need this check very early.
602
-	 * So we cannot wait until the modules are loaded.
603
-	 *
604
-	 * @param string $module
605
-	 * @return bool
606
-	 */
607
-	private function checkBackend($module) {
608
-		static $backends = [
609
-			'_well_known',
610
-			'api',
611
-			'dfrn_notify',
612
-			'feed',
613
-			'fetch',
614
-			'followers',
615
-			'following',
616
-			'hcard',
617
-			'hostxrd',
618
-			'inbox',
619
-			'manifest',
620
-			'nodeinfo',
621
-			'noscrape',
622
-			'objects',
623
-			'outbox',
624
-			'poco',
625
-			'post',
626
-			'proxy',
627
-			'pubsub',
628
-			'pubsubhubbub',
629
-			'receive',
630
-			'rsd_xml',
631
-			'salmon',
632
-			'statistics_json',
633
-			'xrd',
634
-		];
635
-
636
-		// Check if current module is in backend or backend flag is set
637
-		return in_array($module, $backends);
638
-	}
639
-
640
-	/**
641
-	 * Returns true, if the call is from a backend node (f.e. from a worker)
642
-	 *
643
-	 * @return bool Is it a known backend?
644
-	 */
645
-	public function isBackend()
646
-	{
647
-		return $this->isBackend;
648
-	}
649
-
650 578
 	/**
651 579
 	 * @brief Checks if the maximum number of database processes is reached
652 580
 	 *
@@ -740,7 +668,7 @@ class App
740 668
 	 */
741 669
 	public function isMaxLoadReached()
742 670
 	{
743
-		if ($this->isBackend()) {
671
+		if ($this->isBackend) {
744 672
 			$process = 'backend';
745 673
 			$maxsysload = intval($this->config->get('system', 'maxloadavg'));
746 674
 			if ($maxsysload < 1) {
@@ -930,21 +858,13 @@ class App
930 858
 	}
931 859
 
932 860
 	/**
933
-	 * Returns the value of a argv key
934
-	 * TODO there are a lot of $a->argv usages in combination with defaults() which can be replaced with this method
935
-	 *
936
-	 * @param int $position the position of the argument
937
-	 * @param mixed $default the default value if not found
861
+	 * @deprecated use Arguments->get() instead
938 862
 	 *
939
-	 * @return mixed returns the value of the argument
863
+	 * @see App\Arguments
940 864
 	 */
941 865
 	public function getArgumentValue($position, $default = '')
942 866
 	{
943
-		if (array_key_exists($position, $this->argv)) {
944
-			return $this->argv[$position];
945
-		}
946
-
947
-		return $default;
867
+		return $this->args->get($position, $default);
948 868
 	}
949 869
 
950 870
 	/**
@@ -973,9 +893,13 @@ class App
973 893
 	 * request and a representation of the response.
974 894
 	 *
975 895
 	 * This probably should change to limit the size of this monster method.
896
+	 *
897
+	 * @param App\Module $module The determined module
976 898
 	 */
977
-	public function runFrontend()
899
+	public function runFrontend(App\Module $module, App\Router $router, PConfiguration $pconfig)
978 900
 	{
901
+		$moduleName = $module->getName();
902
+
979 903
 		try {
980 904
 			// Missing DB connection: ERROR
981 905
 			if ($this->getMode()->has(App\Mode::LOCALCONFIGPRESENT) && !$this->getMode()->has(App\Mode::DBAVAILABLE)) {
@@ -985,7 +909,7 @@ class App
985 909
 			// Max Load Average reached: ERROR
986 910
 			if ($this->isMaxProcessesReached() || $this->isMaxLoadReached()) {
987 911
 				header('Retry-After: 120');
988
-				header('Refresh: 120; url=' . $this->getBaseURL() . "/" . $this->query_string);
912
+				header('Refresh: 120; url=' . $this->baseURL->get() . "/" . $this->args->getQueryString());
989 913
 
990 914
 				throw new HTTPException\ServiceUnavailableException('The node is currently overloaded. Please try again later.');
991 915
 			}
@@ -993,7 +917,7 @@ class App
993 917
 			if (!$this->getMode()->isInstall()) {
994 918
 				// Force SSL redirection
995 919
 				if ($this->baseURL->checkRedirectHttps()) {
996
-					System::externalRedirect($this->getBaseURL() . '/' . $this->query_string);
920
+					System::externalRedirect($this->baseURL->get() . '/' . $this->args->getQueryString());
997 921
 				}
998 922
 
999 923
 				Core\Session::init();
@@ -1001,7 +925,7 @@ class App
1001 925
 			}
1002 926
 
1003 927
 			// Exclude the backend processes from the session management
1004
-			if (!$this->isBackend()) {
928
+			if (!$module->isBackend()) {
1005 929
 				$stamp1 = microtime(true);
1006 930
 				session_start();
1007 931
 				$this->profiler->saveTimestamp($stamp1, 'parser', Core\System::callstack());
@@ -1021,7 +945,6 @@ class App
1021 945
 
1022 946
 			// ZRL
1023 947
 			if (!empty($_GET['zrl']) && $this->getMode()->isNormal()) {
1024
-				$this->query_string = Model\Profile::stripZrls($this->query_string);
1025 948
 				if (!local_user()) {
1026 949
 					// Only continue when the given profile link seems valid
1027 950
 					// Valid profile links contain a path with "/profile/" and no query parameters
@@ -1044,11 +967,10 @@ class App
1044 967
 
1045 968
 			if (!empty($_GET['owt']) && $this->getMode()->isNormal()) {
1046 969
 				$token = $_GET['owt'];
1047
-				$this->query_string = Model\Profile::stripQueryParam($this->query_string, 'owt');
1048 970
 				Model\Profile::openWebAuthInit($token);
1049 971
 			}
1050 972
 
1051
-			Module\Login::sessionAuth();
973
+			Login::sessionAuth();
1052 974
 
1053 975
 			if (empty($_SESSION['authenticated'])) {
1054 976
 				header('X-Account-Management-Status: none');
@@ -1066,9 +988,9 @@ class App
1066 988
 
1067 989
 			// in install mode, any url loads install module
1068 990
 			// but we need "view" module for stylesheet
1069
-			if ($this->getMode()->isInstall() && $this->module !== 'install') {
991
+			if ($this->getMode()->isInstall() && $moduleName !== 'install') {
1070 992
 				$this->internalRedirect('install');
1071
-			} elseif (!$this->getMode()->isInstall() && !$this->getMode()->has(App\Mode::MAINTENANCEDISABLED) && $this->module !== 'maintenance') {
993
+			} elseif (!$this->getMode()->isInstall() && !$this->getMode()->has(App\Mode::MAINTENANCEDISABLED) && $moduleName !== 'maintenance') {
1072 994
 				$this->internalRedirect('maintenance');
1073 995
 			} else {
1074 996
 				$this->checkURL();
@@ -1091,152 +1013,65 @@ class App
1091 1013
 			];
1092 1014
 
1093 1015
 			// Compatibility with the Android Diaspora client
1094
-			if ($this->module == 'stream') {
1016
+			if ($moduleName == 'stream') {
1095 1017
 				$this->internalRedirect('network?order=post');
1096 1018
 			}
1097 1019
 
1098
-			if ($this->module == 'conversations') {
1020
+			if ($moduleName == 'conversations') {
1099 1021
 				$this->internalRedirect('message');
1100 1022
 			}
1101 1023
 
1102
-			if ($this->module == 'commented') {
1024
+			if ($moduleName == 'commented') {
1103 1025
 				$this->internalRedirect('network?order=comment');
1104 1026
 			}
1105 1027
 
1106
-			if ($this->module == 'liked') {
1028
+			if ($moduleName == 'liked') {
1107 1029
 				$this->internalRedirect('network?order=comment');
1108 1030
 			}
1109 1031
 
1110
-			if ($this->module == 'activity') {
1032
+			if ($moduleName == 'activity') {
1111 1033
 				$this->internalRedirect('network?conv=1');
1112 1034
 			}
1113 1035
 
1114
-			if (($this->module == 'status_messages') && ($this->cmd == 'status_messages/new')) {
1036
+			if (($moduleName == 'status_messages') && ($this->args->getCommand() == 'status_messages/new')) {
1115 1037
 				$this->internalRedirect('bookmarklet');
1116 1038
 			}
1117 1039
 
1118
-			if (($this->module == 'user') && ($this->cmd == 'user/edit')) {
1040
+			if (($moduleName == 'user') && ($this->args->getCommand() == 'user/edit')) {
1119 1041
 				$this->internalRedirect('settings');
1120 1042
 			}
1121 1043
 
1122
-			if (($this->module == 'tag_followings') && ($this->cmd == 'tag_followings/manage')) {
1044
+			if (($moduleName == 'tag_followings') && ($this->args->getCommand() == 'tag_followings/manage')) {
1123 1045
 				$this->internalRedirect('search');
1124 1046
 			}
1125 1047
 
1126
-			// Compatibility with the Firefox App
1127
-			if (($this->module == "users") && ($this->cmd == "users/sign_in")) {
1128
-				$this->module = "login";
1129
-			}
1130
-
1131
-			/*
1132
-			 * ROUTING
1133
-			 *
1134
-			 * From the request URL, routing consists of obtaining the name of a BaseModule-extending class of which the
1135
-			 * post() and/or content() static methods can be respectively called to produce a data change or an output.
1136
-			 */
1137
-
1138
-			// First we try explicit routes defined in App\Router
1139
-			$this->router->collectRoutes();
1140
-
1141
-			$data = $this->router->getRouteCollector();
1142
-			Hook::callAll('route_collection', $data);
1143
-
1144
-			$this->module_class = $this->router->getModuleClass($this->cmd);
1145
-
1146
-			// Then we try addon-provided modules that we wrap in the LegacyModule class
1147
-			if (!$this->module_class && Core\Addon::isEnabled($this->module) && file_exists("addon/{$this->module}/{$this->module}.php")) {
1148
-				//Check if module is an app and if public access to apps is allowed or not
1149
-				$privateapps = $this->config->get('config', 'private_addons', false);
1150
-				if ((!local_user()) && Core\Hook::isAddonApp($this->module) && $privateapps) {
1151
-					info($this->l10n->t("You must be logged in to use addons. "));
1152
-				} else {
1153
-					include_once "addon/{$this->module}/{$this->module}.php";
1154
-					if (function_exists($this->module . '_module')) {
1155
-						LegacyModule::setModuleFile("addon/{$this->module}/{$this->module}.php");
1156
-						$this->module_class = LegacyModule::class;
1157
-					}
1158
-				}
1159
-			}
1160
-
1161
-			/* Finally, we look for a 'standard' program module in the 'mod' directory
1162
-			 * We emulate a Module class through the LegacyModule class
1163
-			 */
1164
-			if (!$this->module_class && file_exists("mod/{$this->module}.php")) {
1165
-				LegacyModule::setModuleFile("mod/{$this->module}.php");
1166
-				$this->module_class = LegacyModule::class;
1167
-			}
1168
-
1169
-			/* The URL provided does not resolve to a valid module.
1170
-			 *
1171
-			 * On Dreamhost sites, quite often things go wrong for no apparent reason and they send us to '/internal_error.html'.
1172
-			 * We don't like doing this, but as it occasionally accounts for 10-20% or more of all site traffic -
1173
-			 * we are going to trap this and redirect back to the requested page. As long as you don't have a critical error on your page
1174
-			 * this will often succeed and eventually do the right thing.
1175
-			 *
1176
-			 * Otherwise we are going to emit a 404 not found.
1177
-			 */
1178
-			if (!$this->module_class) {
1179
-				// Stupid browser tried to pre-fetch our Javascript img template. Don't log the event or return anything - just quietly exit.
1180
-				if (!empty($_SERVER['QUERY_STRING']) && preg_match('/{[0-9]}/', $_SERVER['QUERY_STRING']) !== 0) {
1181
-					exit();
1182
-				}
1183
-
1184
-				if (!empty($_SERVER['QUERY_STRING']) && ($_SERVER['QUERY_STRING'] === 'q=internal_error.html') && isset($dreamhost_error_hack)) {
1185
-					Core\Logger::log('index.php: dreamhost_error_hack invoked. Original URI =' . $_SERVER['REQUEST_URI']);
1186
-					$this->internalRedirect($_SERVER['REQUEST_URI']);
1187
-				}
1188
-
1189
-				Core\Logger::log('index.php: page not found: ' . $_SERVER['REQUEST_URI'] . ' ADDRESS: ' . $_SERVER['REMOTE_ADDR'] . ' QUERY: ' . $_SERVER['QUERY_STRING'], Core\Logger::DEBUG);
1190
-
1191
-				$this->module_class = Module\PageNotFound::class;
1192
-			}
1193
-
1194 1048
 			// Initialize module that can set the current theme in the init() method, either directly or via App->profile_uid
1195
-			$this->page['page_title'] = $this->module;
1196
-
1197
-			$placeholder = '';
1198
-
1199
-			Core\Hook::callAll($this->module . '_mod_init', $placeholder);
1200
-
1201
-			call_user_func([$this->module_class, 'init']);
1202
-
1203
-			// "rawContent" is especially meant for technical endpoints.
1204
-			// This endpoint doesn't need any theme initialization or other comparable stuff.
1205
-			call_user_func([$this->module_class, 'rawContent']);
1049
+			$this->page['page_title'] = $moduleName;
1206 1050
 
1207
-			// Load current theme info after module has been initialized as theme could have been set in module
1208
-			$theme_info_file = 'view/theme/' . $this->getCurrentTheme() . '/theme.php';
1209
-			if (file_exists($theme_info_file)) {
1210
-				require_once $theme_info_file;
1211
-			}
1051
+			// determine the module class and save it to the module instance
1052
+			// @todo there's an implicit dependency due SESSION::start(), so it has to be called here (yet)
1053
+			$module = $module->determineClass($this->args, $router, $this->config);
1212 1054
 
1213
-			if (function_exists(str_replace('-', '_', $this->getCurrentTheme()) . '_init')) {
1214
-				$func = str_replace('-', '_', $this->getCurrentTheme()) . '_init';
1215
-				$func($this);
1216
-			}
1055
+			// Let the module run it's internal process (init, get, post, ...)
1056
+			$module->run($this->l10n, $this, $this->logger, $this->getCurrentTheme(), $_SERVER, $_POST);
1217 1057
 
1218
-			if ($_SERVER['REQUEST_METHOD'] === 'POST') {
1219
-				Core\Hook::callAll($this->module . '_mod_post', $_POST);
1220
-				call_user_func([$this->module_class, 'post']);
1221
-			}
1222
-
1223
-			Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder);
1224
-			call_user_func([$this->module_class, 'afterpost']);
1225 1058
 		} catch(HTTPException $e) {
1226
-			Module\Special\HTTPException::rawContent($e);
1059
+			ModuleHTTPException::rawContent($e);
1227 1060
 		}
1228 1061
 
1229 1062
 		$content = '';
1230 1063
 
1231 1064
 		try {
1065
+			$moduleClass = $module->getClassName();
1066
+
1232 1067
 			$arr = ['content' => $content];
1233
-			Core\Hook::callAll($this->module . '_mod_content', $arr);
1068
+			Core\Hook::callAll($moduleClass . '_mod_content', $arr);
1234 1069
 			$content = $arr['content'];
1235
-			$arr = ['content' => call_user_func([$this->module_class, 'content'])];
1236
-			Core\Hook::callAll($this->module . '_mod_aftercontent', $arr);
1070
+			$arr = ['content' => call_user_func([$moduleClass, 'content'])];
1071
+			Core\Hook::callAll($moduleClass . '_mod_aftercontent', $arr);
1237 1072
 			$content .= $arr['content'];
1238 1073
 		} catch(HTTPException $e) {
1239
-			$content = Module\Special\HTTPException::content($e);
1074
+			$content = ModuleHTTPException::content($e);
1240 1075
 		}
1241 1076
 
1242 1077
 		// initialise content region
@@ -1253,7 +1088,7 @@ class App
1253 1088
 		 * all the module functions have executed so that all
1254 1089
 		 * theme choices made by the modules can take effect.
1255 1090
 		 */
1256
-		$this->initHead();
1091
+		$this->initHead($module, $pconfig);
1257 1092
 
1258 1093
 		/* Build the page ending -- this is stuff that goes right before
1259 1094
 		 * the closing </body> tag
@@ -1265,7 +1100,7 @@ class App
1265 1100
 		}
1266 1101
 
1267 1102
 		// Add the navigation (menu) template
1268
-		if ($this->module != 'install' && $this->module != 'maintenance') {
1103
+		if ($moduleName != 'install' && $moduleName != 'maintenance') {
1269 1104
 			$this->page['htmlhead'] .= Core\Renderer::replaceMacros(Core\Renderer::getMarkupTemplate('nav_head.tpl'), []);
1270 1105
 			$this->page['nav']       = Content\Nav::build($this);
1271 1106
 		}

+ 194
- 0
src/App/Arguments.php View File

@@ -0,0 +1,194 @@
1
+<?php
2
+
3
+namespace Friendica\App;
4
+
5
+/**
6
+ * Determine all arguments of the current call, including
7
+ * - The whole querystring (except the pagename/q parameter)
8
+ * - The command
9
+ * - The arguments (C-Style based)
10
+ * - The count of arguments
11
+ */
12
+class Arguments
13
+{
14
+	/**
15
+	 * @var string The complete query string
16
+	 */
17
+	private $queryString;
18
+	/**
19
+	 * @var string The current Friendica command
20
+	 */
21
+	private $command;
22
+	/**
23
+	 * @var array The arguments of the current execution
24
+	 */
25
+	private $argv;
26
+	/**
27
+	 * @var int The count of arguments
28
+	 */
29
+	private $argc;
30
+
31
+	public function __construct(string $queryString = '', string $command = '', array $argv = [Module::DEFAULT], int $argc = 1)
32
+	{
33
+		$this->queryString = $queryString;
34
+		$this->command     = $command;
35
+		$this->argv        = $argv;
36
+		$this->argc        = $argc;
37
+	}
38
+
39
+	/**
40
+	 * @return string The whole query string of this call
41
+	 */
42
+	public function getQueryString()
43
+	{
44
+		return $this->queryString;
45
+	}
46
+
47
+	/**
48
+	 * @return string The whole command of this call
49
+	 */
50
+	public function getCommand()
51
+	{
52
+		return $this->command;
53
+	}
54
+
55
+	/**
56
+	 * @return array All arguments of this call
57
+	 */
58
+	public function getArgv()
59
+	{
60
+		return $this->argv;
61
+	}
62
+
63
+	/**
64
+	 * @return int The count of arguments of this call
65
+	 */
66
+	public function getArgc()
67
+	{
68
+		return $this->argc;
69
+	}
70
+
71
+	/**
72
+	 * Returns the value of a argv key
73
+	 * @todo there are a lot of $a->argv usages in combination with defaults() which can be replaced with this method
74
+	 *
75
+	 * @param int   $position the position of the argument
76
+	 * @param mixed $default  the default value if not found
77
+	 *
78
+	 * @return mixed returns the value of the argument
79
+	 */
80
+	public function get(int $position, $default = '')
81
+	{
82
+		return $this->has($position) ? $this->argv[$position] : $default;
83
+	}
84
+
85
+	/**
86
+	 * @param int $position
87
+	 *
88
+	 * @return bool if the argument position exists
89
+	 */
90
+	public function has(int $position)
91
+	{
92
+		return array_key_exists($position, $this->argv);
93
+	}
94
+
95
+	/**
96
+	 * Determine the arguments of the current call
97
+	 *
98
+	 * @param array $server The $_SERVER variable
99
+	 * @param array $get    The $_GET variable
100
+	 *
101
+	 * @return Arguments The determined arguments
102
+	 */
103
+	public function determine(array $server, array $get)
104
+	{
105
+		$queryString = '';
106
+
107
+		if (!empty($server['QUERY_STRING']) && strpos($server['QUERY_STRING'], 'pagename=') === 0) {
108
+			$queryString = substr($server['QUERY_STRING'], 9);
109
+		} elseif (!empty($server['QUERY_STRING']) && strpos($server['QUERY_STRING'], 'q=') === 0) {
110
+			$queryString = substr($server['QUERY_STRING'], 2);
111
+		}
112
+
113
+		// eventually strip ZRL
114
+		$queryString = $this->stripZRLs($queryString);
115
+
116
+		// eventually strip OWT
117
+		$queryString = $this->stripQueryParam($queryString, 'owt');
118
+
119
+		// removing trailing / - maybe a nginx problem
120
+		$queryString = ltrim($queryString, '/');
121
+
122
+		if (!empty($get['pagename'])) {
123
+			$command = trim($get['pagename'], '/\\');
124
+		} elseif (!empty($get['q'])) {
125
+			$command = trim($get['q'], '/\\');
126
+		} else {
127
+			$command = Module::DEFAULT;
128
+		}
129
+
130
+
131
+		// fix query_string
132
+		if (!empty($command)) {
133
+			$queryString = str_replace(
134
+				$command . '&',
135
+				$command . '?',
136
+				$queryString
137
+			);
138
+		}
139
+
140
+		// unix style "homedir"
141
+		if (substr($command, 0, 1) === '~') {
142
+			$command = 'profile/' . substr($command, 1);
143
+		}
144
+
145
+		// Diaspora style profile url
146
+		if (substr($command, 0, 2) === 'u/') {
147
+			$command = 'profile/' . substr($command, 2);
148
+		}
149
+
150
+		/*
151
+		 * Break the URL path into C style argc/argv style arguments for our
152
+		 * modules. Given "http://example.com/module/arg1/arg2", $this->argc
153
+		 * will be 3 (integer) and $this->argv will contain:
154
+		 *   [0] => 'module'
155
+		 *   [1] => 'arg1'
156
+		 *   [2] => 'arg2'
157
+		 *
158
+		 *
159
+		 * There will always be one argument. If provided a naked domain
160
+		 * URL, $this->argv[0] is set to "home".
161
+		 */
162
+
163
+		$argv = explode('/', $command);
164
+		$argc = count($argv);
165
+
166
+
167
+		return new Arguments($queryString, $command, $argv, $argc);
168
+	}
169
+
170
+	/**
171
+	 * Strip zrl parameter from a string.
172
+	 *
173
+	 * @param string $queryString The input string.
174
+	 *
175
+	 * @return string The zrl.
176
+	 */
177
+	public function stripZRLs(string $queryString)
178
+	{
179
+		return preg_replace('/[?&]zrl=(.*?)(&|$)/ism', '$2', $queryString);
180
+	}
181
+
182
+	/**
183
+	 * Strip query parameter from a string.
184
+	 *
185
+	 * @param string $queryString The input string.
186
+	 * @param string $param
187
+	 *
188
+	 * @return string The query parameter.
189
+	 */
190
+	public function stripQueryParam(string $queryString, string $param)
191
+	{
192
+		return preg_replace('/[?&]' . $param . '=(.*?)(&|$)/ism', '$2', $queryString);
193
+	}
194
+}

+ 22
- 44
src/App/Mode.php View File

@@ -24,27 +24,9 @@ class Mode
24 24
 	 */
25 25
 	private $mode;
26 26
 
27
-	/**
28
-	 * @var string the basepath of the application
29
-	 */
30
-	private $basepath;
31
-
32
-	/**
33
-	 * @var Database
34
-	 */
35
-	private $database;
36
-
37
-	/**
38
-	 * @var ConfigCache
39
-	 */
40
-	private $configCache;
41
-
42
-	public function __construct(BasePath $basepath, Database $database, ConfigCache $configCache)
27
+	public function __construct(int $mode = 0)
43 28
 	{
44
-		$this->basepath    = $basepath->getPath();
45
-		$this->database    = $database;
46
-		$this->configCache = $configCache;
47
-		$this->mode        = 0;
29
+		$this->mode = $mode;
48 30
 	}
49 31
 
50 32
 	/**
@@ -54,50 +36,46 @@ class Mode
54 36
 	 * - App::MODE_MAINTENANCE: The maintenance mode has been set
55 37
 	 * - App::MODE_NORMAL     : Normal run with all features enabled
56 38
 	 *
57
-	 * @param string $basePath the Basepath of the Application
58
-	 *
59
-	 * @return Mode returns itself
39
+	 * @return Mode returns the determined mode
60 40
 	 *
61 41
 	 * @throws \Exception
62 42
 	 */
63
-	public function determine($basePath = null)
43
+	public function determine(BasePath $basepath, Database $database, ConfigCache $configCache)
64 44
 	{
65
-		if (!empty($basePath)) {
66
-			$this->basepath = $basePath;
67
-		}
45
+		$mode = 0;
68 46
 
69
-		$this->mode = 0;
47
+		$basepathName = $basepath->getPath();
70 48
 
71
-		if (!file_exists($this->basepath . '/config/local.config.php')
72
-		    && !file_exists($this->basepath . '/config/local.ini.php')
73
-		    && !file_exists($this->basepath . '/.htconfig.php')) {
74
-			return $this;
49
+		if (!file_exists($basepathName . '/config/local.config.php')
50
+		    && !file_exists($basepathName . '/config/local.ini.php')
51
+		    && !file_exists($basepathName . '/.htconfig.php')) {
52
+			return new Mode($mode);
75 53
 		}
76 54
 
77
-		$this->mode |= Mode::LOCALCONFIGPRESENT;
55
+		$mode |= Mode::LOCALCONFIGPRESENT;
78 56
 
79
-		if (!$this->database->connected()) {
80
-			return $this;
57
+		if (!$database->connected()) {
58
+			return new Mode($mode);
81 59
 		}
82 60
 
83
-		$this->mode |= Mode::DBAVAILABLE;
61
+		$mode |= Mode::DBAVAILABLE;
84 62
 
85
-		if ($this->database->fetchFirst("SHOW TABLES LIKE 'config'") === false) {
86
-			return $this;
63
+		if ($database->fetchFirst("SHOW TABLES LIKE 'config'") === false) {
64
+			return new Mode($mode);
87 65
 		}
88 66
 
89
-		$this->mode |= Mode::DBCONFIGAVAILABLE;
67
+		$mode |= Mode::DBCONFIGAVAILABLE;
90 68
 
91
-		if (!empty($this->configCache->get('system', 'maintenance')) ||
69
+		if (!empty($configCache->get('system', 'maintenance')) ||
92 70
 		    // Don't use Config or Configuration here because we're possibly BEFORE initializing the Configuration,
93 71
 		    // so this could lead to a dependency circle
94
-		    !empty($this->database->selectFirst('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])['v'])) {
95
-			return $this;
72
+		    !empty($database->selectFirst('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])['v'])) {
73
+			return new Mode($mode);
96 74
 		}
97 75
 
98
-		$this->mode |= Mode::MAINTENANCEDISABLED;
76
+		$mode |= Mode::MAINTENANCEDISABLED;
99 77
 
100
-		return $this;
78
+		return new Mode($mode);
101 79
 	}
102 80
 
103 81
 	/**

+ 279
- 0
src/App/Module.php View File

@@ -0,0 +1,279 @@
1
+<?php
2
+
3
+namespace Friendica\App;
4
+
5
+use Friendica\App;
6
+use Friendica\BaseObject;
7
+use Friendica\Core;
8
+use Friendica\LegacyModule;
9
+use Friendica\Module\Home;
10
+use Friendica\Module\PageNotFound;
11
+use Psr\Log\LoggerInterface;
12
+
13
+/**
14
+ * Holds the common context of the current, loaded module
15
+ */
16
+class Module
17
+{
18
+	const DEFAULT = 'home';
19
+	const DEFAULT_CLASS = Home::class;
20
+	/**
21
+	 * A list of modules, which are backend methods
22
+	 *
23
+	 * @var array
24
+	 */
25
+	const BACKEND_MODULES = [
26
+		'_well_known',
27
+		'api',
28
+		'dfrn_notify',
29
+		'feed',
30
+		'fetch',
31
+		'followers',
32
+		'following',
33
+		'hcard',
34
+		'hostxrd',
35
+		'inbox',
36
+		'manifest',
37
+		'nodeinfo',
38
+		'noscrape',
39
+		'objects',
40
+		'outbox',
41
+		'poco',
42
+		'post',
43
+		'proxy',
44
+		'pubsub',
45
+		'pubsubhubbub',
46
+		'receive',
47
+		'rsd_xml',
48
+		'salmon',
49
+		'statistics_json',
50
+		'xrd',
51
+	];
52
+
53
+	/**
54
+	 * @var string The module name
55
+	 */
56
+	private $module;
57
+
58
+	/**
59
+	 * @var BaseObject The module class
60
+	 */
61
+	private $module_class;
62
+
63
+	/**
64
+	 * @var bool true, if the module is a backend module
65
+	 */
66
+	private $isBackend;
67
+
68
+	/**
69
+	 * @var bool true, if the loaded addon is private, so we have to print out not allowed
70
+	 */
71
+	private $printNotAllowedAddon;
72
+
73
+	/**
74
+	 * @return string
75
+	 */
76
+	public function getName()
77
+	{
78
+		return $this->module;
79
+	}
80
+
81
+	/**
82
+	 * @return string The base class name
83
+	 */
84
+	public function getClassName()
85
+	{
86
+		return $this->module_class;
87
+	}
88
+
89
+	/**
90
+	 * @return bool
91
+	 */
92
+	public function isBackend()
93
+	{
94
+		return $this->isBackend;
95
+	}
96
+
97
+	public function __construct(string $module = self::DEFAULT, string $moduleClass = self::DEFAULT_CLASS, bool $isBackend = false, bool $printNotAllowedAddon = false)
98
+	{
99
+		$this->module       = $module;
100
+		$this->module_class = $moduleClass;
101
+		$this->isBackend    = $isBackend;
102
+		$this->printNotAllowedAddon = $printNotAllowedAddon;
103
+	}
104
+
105
+	/**
106
+	 * Determines the current module based on the App arguments and the server variable
107
+	 *
108
+	 * @param Arguments $args   The Friendica arguments
109
+	 * @param array     $server The $_SERVER variable
110
+	 *
111
+	 * @return Module The module with the determined module
112
+	 */
113
+	public function determineModule(Arguments $args, array $server)
114
+	{
115
+		if ($args->getArgc() > 0) {
116
+			$module = str_replace('.', '_', $args->get(0));
117
+			$module = str_replace('-', '_', $module);
118
+		} else {
119
+			$module = self::DEFAULT;
120
+		}
121
+
122
+		// Compatibility with the Firefox App
123
+		if (($module == "users") && ($args->getCommand() == "users/sign_in")) {
124
+			$module = "login";
125
+		}
126
+
127
+		$isBackend = $this->checkBackend($module, $server);
128
+
129
+		return new Module($module, $this->module_class, $isBackend, $this->printNotAllowedAddon);
130
+	}
131
+
132
+	/**
133
+	 * Determine the class of the current module
134
+	 *
135
+	 * @param Arguments                 $args   The Friendica execution arguments
136
+	 * @param Router                    $router The Friendica routing instance
137
+	 * @param Core\Config\Configuration $config The Friendica Configuration
138
+	 *
139
+	 * @return Module The determined module of this call
140
+	 *
141
+	 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
142
+	 */
143
+	public function determineClass(Arguments $args, Router $router, Core\Config\Configuration $config)
144
+	{
145
+		$printNotAllowedAddon = false;
146
+
147
+		/**
148
+		 * ROUTING
149
+		 *
150
+		 * From the request URL, routing consists of obtaining the name of a BaseModule-extending class of which the
151
+		 * post() and/or content() static methods can be respectively called to produce a data change or an output.
152
+		 **/
153
+
154
+		// First we try explicit routes defined in App\Router
155
+		$router->collectRoutes();
156
+
157
+		$data = $router->getRouteCollector();
158
+		Core\Hook::callAll('route_collection', $data);
159
+
160
+		$module_class = $router->getModuleClass($args->getCommand());
161
+
162
+		// Then we try addon-provided modules that we wrap in the LegacyModule class
163
+		if (!$module_class && Core\Addon::isEnabled($this->module) && file_exists("addon/{$this->module}/{$this->module}.php")) {
164
+			//Check if module is an app and if public access to apps is allowed or not
165
+			$privateapps = $config->get('config', 'private_addons', false);
166
+			if ((!local_user()) && Core\Hook::isAddonApp($this->module) && $privateapps) {
167
+				$printNotAllowedAddon = true;
168
+			} else {
169
+				include_once "addon/{$this->module}/{$this->module}.php";
170
+				if (function_exists($this->module . '_module')) {
171
+					LegacyModule::setModuleFile("addon/{$this->module}/{$this->module}.php");
172
+					$module_class = LegacyModule::class;
173
+				}
174
+			}
175
+		}
176
+
177
+		/* Finally, we look for a 'standard' program module in the 'mod' directory
178
+		 * We emulate a Module class through the LegacyModule class
179
+		 */
180
+		if (!$module_class && file_exists("mod/{$this->module}.php")) {
181
+			LegacyModule::setModuleFile("mod/{$this->module}.php");
182
+			$module_class = LegacyModule::class;
183
+		}
184
+
185
+		$module_class = !isset($module_class) ? PageNotFound::class : $module_class;
186
+
187
+		return new Module($this->module, $module_class, $this->isBackend, $printNotAllowedAddon);
188
+	}
189
+
190
+	/**
191
+	 * Run the determined module class and calls all hooks applied to
192
+	 *
193
+	 * @param Core\L10n\L10n $l10n         The L10n instance
194
+	 * @param App            $app          The whole Friendica app (for method arguments)
195
+	 * @param LoggerInterface           $logger The Friendica logger
196
+	 * @param string         $currentTheme The chosen theme
197
+	 * @param array          $server       The $_SERVER variable
198
+	 * @param array          $post         The $_POST variables
199
+	 *
200
+	 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
201
+	 */
202
+	public function run(Core\L10n\L10n $l10n, App $app,  LoggerInterface $logger, string $currentTheme, array $server, array $post)
203
+	{
204
+		if ($this->printNotAllowedAddon) {
205
+			info($l10n->t("You must be logged in to use addons. "));
206
+		}
207
+
208
+		/* The URL provided does not resolve to a valid module.
209
+		 *
210
+		 * On Dreamhost sites, quite often things go wrong for no apparent reason and they send us to '/internal_error.html'.
211
+		 * We don't like doing this, but as it occasionally accounts for 10-20% or more of all site traffic -
212
+		 * we are going to trap this and redirect back to the requested page. As long as you don't have a critical error on your page
213
+		 * this will often succeed and eventually do the right thing.
214
+		 *
215
+		 * Otherwise we are going to emit a 404 not found.
216
+		 */
217
+		if ($this->module_class === PageNotFound::class) {
218
+			$queryString = $server['QUERY_STRING'];
219
+			// Stupid browser tried to pre-fetch our Javascript img template. Don't log the event or return anything - just quietly exit.
220
+			if (!empty($queryString) && preg_match('/{[0-9]}/', $queryString) !== 0) {
221
+				exit();
222
+			}
223
+
224
+			if (!empty($queryString) && ($queryString === 'q=internal_error.html') && isset($dreamhost_error_hack)) {
225
+				$logger->info('index.php: dreamhost_error_hack invoked.', ['Original URI' => $server['REQUEST_URI']]);
226
+				$app->internalRedirect($server['REQUEST_URI']);
227
+			}
228
+
229
+			$logger->debug('index.php: page not found.', ['request_uri' => $server['REQUEST_URI'], 'address' => $server['REMOTE_ADDR'], 'query' => $server['QUERY_STRING']]);
230
+		}
231
+
232
+		$placeholder = '';
233
+
234
+		Core\Hook::callAll($this->module . '_mod_init', $placeholder);
235
+
236
+		call_user_func([$this->module_class, 'init']);
237
+
238
+		// "rawContent" is especially meant for technical endpoints.
239
+		// This endpoint doesn't need any theme initialization or other comparable stuff.
240
+		call_user_func([$this->module_class, 'rawContent']);
241
+
242
+		// Load current theme info after module has been initialized as theme could have been set in module
243
+		$theme_info_file = 'view/theme/' . $currentTheme . '/theme.php';
244
+		if (file_exists($theme_info_file)) {
245
+			require_once $theme_info_file;
246
+		}
247
+
248
+		if (function_exists(str_replace('-', '_', $currentTheme) . '_init')) {
249
+			$func = str_replace('-', '_', $currentTheme) . '_init';
250
+			$func($app);
251
+		}
252
+
253
+		if ($server['REQUEST_METHOD'] === 'POST') {
254
+			Core\Hook::callAll($this->module . '_mod_post', $post);
255
+			call_user_func([$this->module_class, 'post']);
256
+		}
257
+
258
+		Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder);
259
+		call_user_func([$this->module_class, 'afterpost']);
260
+	}
261
+
262
+	/**
263
+	 * @brief Checks if the site is called via a backend process
264
+	 *
265
+	 * This isn't a perfect solution. But we need this check very early.
266
+	 * So we cannot wait until the modules are loaded.
267
+	 *
268
+	 * @param string $module The determined module
269
+	 * @param array  $server The $_SERVER variable
270
+	 *
271
+	 * @return bool True, if the current module is called at backend
272
+	 */
273
+	private function checkBackend($module, array $server)
274
+	{
275
+		// Check if current module is in backend or backend flag is set
276
+		return basename(($server['PHP_SELF'] ?? ''), '.php') !== 'index' &&
277
+		       in_array($module, Module::BACKEND_MODULES);
278
+	}
279
+}

+ 1
- 3
src/Core/Installer.php View File

@@ -11,7 +11,6 @@ use Friendica\Database\Database;
11 11
 use Friendica\Database\DBStructure;
12 12
 use Friendica\Object\Image;
13 13
 use Friendica\Util\Network;
14
-use Friendica\Util\Profiler;
15 14
 use Friendica\Util\Strings;
16 15
 
17 16
 /**
@@ -591,8 +590,7 @@ class Installer
591 590
 	/**
592 591
 	 * Checking the Database connection and if it is available for the current installation
593 592
 	 *
594
-	 * @param ConfigCache $configCache The configuration cache
595
-	 * @param Profiler    $profiler    The profiler of this app
593
+	 * @param Database $dba
596 594
 	 *
597 595
 	 * @return bool true if the check was successful, otherwise false
598 596
 	 * @throws Exception

+ 0
- 23
src/Model/Profile.php View File

@@ -1224,29 +1224,6 @@ class Profile
1224 1224
 		return $uid;
1225 1225
 	}
1226 1226
 
1227
-	/**
1228
-	* Strip zrl parameter from a string.
1229
-	*
1230
-	* @param string $s The input string.
1231
-	* @return string The zrl.
1232
-	*/
1233
-	public static function stripZrls($s)
1234
-	{
1235
-		return preg_replace('/[\?&]zrl=(.*?)([\?&]|$)/is', '', $s);
1236
-	}
1237
-
1238
-	/**
1239
-	 * Strip query parameter from a string.
1240
-	 *
1241
-	 * @param string $s The input string.
1242
-	 * @param        $param
1243
-	 * @return string The query parameter.
1244
-	 */
1245
-	public static function stripQueryParam($s, $param)
1246
-	{
1247
-		return preg_replace('/[\?&]' . $param . '=(.*?)(&|$)/ism', '$2', $s);
1248
-	}
1249
-
1250 1227
 	/**
1251 1228
 	 * search for Profiles
1252 1229
 	 *

+ 2
- 2
src/Module/Install.php View File

@@ -111,7 +111,7 @@ class Install extends BaseModule
111 111
 				self::checkSetting($configCache, $_POST, 'database', 'database', '');
112 112
 
113 113
 				// If we cannot connect to the database, return to the previous step
114
-				if (!self::$installer->checkDB($configCache, $a->getProfiler())) {
114
+				if (!self::$installer->checkDB($a->getDBA())) {
115 115
 					self::$currentWizardStep = self::DATABASE_CONFIG;
116 116
 				}
117 117
 
@@ -135,7 +135,7 @@ class Install extends BaseModule
135 135
 				self::checkSetting($configCache, $_POST, 'config', 'admin_email', '');
136 136
 
137 137
 				// If we cannot connect to the database, return to the Database config wizard
138
-				if (!self::$installer->checkDB($configCache, $a->getProfiler())) {
138
+				if (!self::$installer->checkDB($a->getDBA())) {
139 139
 					self::$currentWizardStep = self::DATABASE_CONFIG;
140 140
 					return;
141 141
 				}

+ 32
- 20
static/dependencies.config.php View File

@@ -27,14 +27,14 @@ use Psr\Log\LoggerInterface;
27 27
  *
28 28
  */
29 29
 return [
30
-	'*' => [
30
+	'*'                             => [
31 31
 		// marks all class result as shared for other creations, so there's just
32 32
 		// one instance for the whole execution
33 33
 		'shared' => true,
34 34
 	],
35
-	'$basepath' => [
36
-		'instanceOf' => Util\BasePath::class,
37
-		'call' => [
35
+	'$basepath'                     => [
36
+		'instanceOf'      => Util\BasePath::class,
37
+		'call'            => [
38 38
 			['getPath', [], Dice::CHAIN_CALL],
39 39
 		],
40 40
 		'constructParams' => [
@@ -42,14 +42,14 @@ return [
42 42
 			$_SERVER
43 43
 		]
44 44
 	],
45
-	Util\BasePath::class => [
45
+	Util\BasePath::class            => [
46 46
 		'constructParams' => [
47 47
 			dirname(__FILE__, 2),
48 48
 			$_SERVER
49 49
 		]
50 50
 	],
51
-	Util\ConfigFileLoader::class => [
52
-		'shared' => true,
51
+	Util\ConfigFileLoader::class    => [
52
+		'shared'          => true,
53 53
 		'constructParams' => [
54 54
 			[Dice::INSTANCE => '$basepath'],
55 55
 		],
@@ -60,24 +60,24 @@ return [
60 60
 			['createCache', [], Dice::CHAIN_CALL],
61 61
 		],
62 62
 	],
63
-	App\Mode::class => [
64
-		'call'   => [
63
+	App\Mode::class                 => [
64
+		'call' => [
65 65
 			['determine', [], Dice::CHAIN_CALL],
66 66
 		],
67 67
 	],
68
-	Config\Configuration::class => [
68
+	Config\Configuration::class     => [
69 69
 		'instanceOf' => Factory\ConfigFactory::class,
70
-		'call' => [
70
+		'call'       => [
71 71
 			['createConfig', [], Dice::CHAIN_CALL],
72 72
 		],
73 73
 	],
74
-	Config\PConfiguration::class => [
74
+	Config\PConfiguration::class    => [
75 75
 		'instanceOf' => Factory\ConfigFactory::class,
76
-		'call' => [
76
+		'call'       => [
77 77
 			['createPConfig', [], Dice::CHAIN_CALL],
78 78
 		]
79 79
 	],
80
-	Database::class => [
80
+	Database::class                 => [
81 81
 		'constructParams' => [
82 82
 			[DICE::INSTANCE => \Psr\Log\NullLogger::class],
83 83
 			$_SERVER,
@@ -89,7 +89,7 @@ return [
89 89
 	 * Same as:
90 90
 	 *   $baseURL = new Util\BaseURL($configuration, $_SERVER);
91 91
 	 */
92
-	Util\BaseURL::class => [
92
+	Util\BaseURL::class             => [
93 93
 		'constructParams' => [
94 94
 			$_SERVER,
95 95
 		],
@@ -106,34 +106,46 @@ return [
106 106
 	 *    $app = $dice->create(App::class, [], ['$channel' => 'index']);
107 107
 	 *    and is automatically passed as an argument with the same name
108 108
 	 */
109
-	LoggerInterface::class    => [
109
+	LoggerInterface::class          => [
110 110
 		'instanceOf' => Factory\LoggerFactory::class,
111 111
 		'call'       => [
112 112
 			['create', [], Dice::CHAIN_CALL],
113 113
 		],
114 114
 	],
115
-	'$devLogger'              => [
115
+	'$devLogger'                    => [
116 116
 		'instanceOf' => Factory\LoggerFactory::class,
117 117
 		'call'       => [
118 118
 			['createDev', [], Dice::CHAIN_CALL],
119 119
 		]
120 120
 	],
121
-	Cache\ICache::class       => [
121
+	Cache\ICache::class             => [
122 122
 		'instanceOf' => Factory\CacheFactory::class,
123 123
 		'call'       => [
124 124
 			['create', [], Dice::CHAIN_CALL],
125 125
 		],
126 126
 	],
127
-	Cache\IMemoryCache::class => [
127
+	Cache\IMemoryCache::class       => [
128 128
 		'instanceOf' => Factory\CacheFactory::class,
129 129
 		'call'       => [
130 130
 			['create', [], Dice::CHAIN_CALL],
131 131
 		],
132 132
 	],
133
-	ILock::class              => [
133
+	ILock::class                    => [
134 134
 		'instanceOf' => Factory\LockFactory::class,
135 135
 		'call'       => [
136 136
 			['create', [], Dice::CHAIN_CALL],
137 137
 		],
138 138
 	],
139
+	App\Arguments::class => [
140
+		'instanceOf' => App\Arguments::class,
141
+		'call' => [
142
+			['determine', [$_SERVER, $_GET], Dice::CHAIN_CALL],
143
+		],
144
+	],
145
+	App\Module::class => [
146
+		'instanceOf' => App\Module::class,
147
+		'call' => [
148
+			['determineModule', [$_SERVER], Dice::CHAIN_CALL],
149
+		],
150
+	],
139 151
 ];

+ 242
- 0
tests/src/App/ArgumentsTest.php View File

@@ -0,0 +1,242 @@
1
+<?php
2
+
3
+namespace Friendica\Test\src\App;
4
+
5
+use Friendica\App;
6
+use PHPUnit\Framework\TestCase;
7
+
8
+class ArgumentsTest extends TestCase
9
+{
10
+	private function assertArguments(array $assert, App\Arguments $arguments)
11
+	{
12
+		$this->assertEquals($assert['queryString'], $arguments->getQueryString());
13
+		$this->assertEquals($assert['command'], $arguments->getCommand());
14
+		$this->assertEquals($assert['argv'], $arguments->getArgv());
15
+		$this->assertEquals($assert['argc'], $arguments->getArgc());
16
+		$this->assertCount($assert['argc'], $arguments->getArgv());
17
+	}
18
+
19
+	/**
20
+	 * Test the default argument without any determinations
21
+	 */
22
+	public function testDefault()
23
+	{
24
+		$arguments = new App\Arguments();
25
+
26
+		$this->assertArguments([
27
+			'queryString' => '',
28
+			'command'     => '',
29
+			'argv'        => ['home'],
30
+			'argc'        => 1,
31
+		],
32
+			$arguments);
33
+	}
34
+
35
+	public function dataArguments()
36
+	{
37
+		return [
38
+			'withPagename'         => [
39
+				'assert' => [
40
+					'queryString' => 'profile/test/it?arg1=value1&arg2=value2',
41
+					'command'     => 'profile/test/it',
42
+					'argv'        => ['profile', 'test', 'it'],
43
+					'argc'        => 3,
44
+				],
45
+				'server' => [
46
+					'QUERY_STRING' => 'pagename=profile/test/it?arg1=value1&arg2=value2',
47
+				],
48
+				'get'    => [
49
+					'pagename' => 'profile/test/it',
50
+				],
51
+			],
52
+			'withQ'                => [
53
+				'assert' => [
54
+					'queryString' => 'profile/test/it?arg1=value1&arg2=value2',
55
+					'command'     => 'profile/test/it',
56
+					'argv'        => ['profile', 'test', 'it'],
57
+					'argc'        => 3,
58
+				],
59
+				'server' => [
60
+					'QUERY_STRING' => 'q=profile/test/it?arg1=value1&arg2=value2',
61
+				],
62
+				'get'    => [
63
+					'q' => 'profile/test/it',
64
+				],
65
+			],
66
+			'withWrongDelimiter'   => [
67
+				'assert' => [
68
+					'queryString' => 'profile/test/it?arg1=value1&arg2=value2',
69
+					'command'     => 'profile/test/it',
70
+					'argv'        => ['profile', 'test', 'it'],
71
+					'argc'        => 3,
72
+				],
73
+				'server' => [
74
+					'QUERY_STRING' => 'pagename=profile/test/it&arg1=value1&arg2=value2',
75
+				],
76
+				'get'    => [
77
+					'pagename' => 'profile/test/it',
78
+				],
79
+			],
80
+			'withUnixHomeDir'      => [
81
+				'assert' => [
82
+					'queryString' => '~test/it?arg1=value1&arg2=value2',
83
+					'command'     => 'profile/test/it',
84
+					'argv'        => ['profile', 'test', 'it'],
85
+					'argc'        => 3,
86
+				],
87
+				'server' => [
88
+					'QUERY_STRING' => 'pagename=~test/it?arg1=value1&arg2=value2',
89
+				],
90
+				'get'    => [
91
+					'pagename' => '~test/it',
92
+				],
93
+			],
94
+			'withDiasporaHomeDir'  => [
95
+				'assert' => [
96
+					'queryString' => 'u/test/it?arg1=value1&arg2=value2',
97
+					'command'     => 'profile/test/it',
98
+					'argv'        => ['profile', 'test', 'it'],
99
+					'argc'        => 3,
100
+				],
101
+				'server' => [
102
+					'QUERY_STRING' => 'pagename=u/test/it?arg1=value1&arg2=value2',
103
+				],
104
+				'get'    => [
105
+					'pagename' => 'u/test/it',
106
+				],
107
+			],
108
+			'withTrailingSlash'    => [
109
+				'assert' => [
110
+					'queryString' => 'profile/test/it?arg1=value1&arg2=value2/',
111
+					'command'     => 'profile/test/it',
112
+					'argv'        => ['profile', 'test', 'it'],
113
+					'argc'        => 3,
114
+				],
115
+				'server' => [
116
+					'QUERY_STRING' => 'pagename=profile/test/it?arg1=value1&arg2=value2/',
117
+				],
118
+				'get'    => [
119
+					'pagename' => 'profile/test/it',
120
+				],
121
+			],
122
+			'withWrongQueryString' => [
123
+				'assert' => [
124
+					// empty query string?!
125
+					'queryString' => '',
126
+					'command'     => 'profile/test/it',
127
+					'argv'        => ['profile', 'test', 'it'],
128
+					'argc'        => 3,
129
+				],
130
+				'server' => [
131
+					'QUERY_STRING' => 'wrong=profile/test/it?arg1=value1&arg2=value2/',
132
+				],
133
+				'get'    => [
134
+					'pagename' => 'profile/test/it',
135
+				],
136
+			],
137
+			'withMissingPageName'  => [
138
+				'assert' => [
139
+					'queryString' => 'notvalid/it?arg1=value1&arg2=value2/',
140
+					'command'     => App\Module::DEFAULT,
141
+					'argv'        => [App\Module::DEFAULT],
142
+					'argc'        => 1,
143
+				],
144
+				'server' => [
145
+					'QUERY_STRING' => 'pagename=notvalid/it?arg1=value1&arg2=value2/',
146
+				],
147
+				'get'    => [
148
+				],
149
+			],
150
+		];
151
+	}
152
+
153
+	/**
154
+	 * Test all variants of argument determination
155
+	 *
156
+	 * @dataProvider dataArguments
157
+	 */
158
+	public function testDetermine(array $assert, array $server, array $get)
159
+	{
160
+		$arguments = (new App\Arguments())
161
+			->determine($server, $get);
162
+
163
+		$this->assertArguments($assert, $arguments);
164
+	}
165
+
166
+	/**
167
+	 * Test if the get/has methods are working for the determined arguments
168
+	 *
169
+	 * @dataProvider dataArguments
170
+	 */
171
+	public function testGetHas(array $assert, array $server, array $get)
172
+	{
173
+		$arguments = (new App\Arguments())
174
+			->determine($server, $get);
175
+
176
+		for ($i = 0; $i < $arguments->getArgc(); $i++) {
177
+			$this->assertTrue($arguments->has($i));
178
+			$this->assertEquals($assert['argv'][$i], $arguments->get($i));
179
+		}
180
+
181
+		$this->assertFalse($arguments->has($arguments->getArgc()));
182
+		$this->assertEmpty($arguments->get($arguments->getArgc()));
183
+		$this->assertEquals('default', $arguments->get($arguments->getArgc(), 'default'));
184
+	}
185
+
186
+	public function dataStripped()
187
+	{
188
+		return [
189
+			'strippedZRLFirst'  => [
190
+				'assert' => '?arg1=value1',
191
+				'input'  => '?zrl=nope&arg1=value1',
192
+			],
193
+			'strippedZRLLast'   => [
194
+				'assert' => '?arg1=value1',
195
+				'input'  => '?arg1=value1&zrl=nope',
196
+			],
197
+			'strippedZTLMiddle' => [
198
+				'assert' => '?arg1=value1&arg2=value2',
199
+				'input'  => '?arg1=value1&zrl=nope&arg2=value2',
200
+			],
201
+			'strippedOWTFirst'  => [
202
+				'assert' => '?arg1=value1',
203
+				'input'  => '?owt=test&arg1=value1',
204
+			],
205
+			'strippedOWTLast'   => [
206
+				'assert' => '?arg1=value1',
207
+				'input'  => '?arg1=value1&owt=test',
208
+			],
209
+			'strippedOWTMiddle' => [
210
+				'assert' => '?arg1=value1&arg2=value2',
211
+				'input'  => '?arg1=value1&owt=test&arg2=value2',
212
+			],
213
+		];
214
+	}
215
+
216
+	/**
217
+	 * Test the ZRL and OWT stripping
218
+	 *
219
+	 * @dataProvider dataStripped
220
+	 */
221
+	public function testStrippedQueries(string $assert, string $input)
222
+	{
223
+		$command = 'test/it';
224
+
225
+		$arguments = (new App\Arguments())
226
+			->determine(['QUERY_STRING' => 'q=' . $command . $input,], ['pagename' => $command]);
227
+
228
+		$this->assertEquals($command . $assert, $arguments->getQueryString());
229
+	}
230
+
231
+	/**
232
+	 * Test that arguments are immutable
233
+	 */
234
+	public function testImmutable()
235
+	{
236
+		$argument = new App\Arguments();
237
+
238
+		$argNew = $argument->determine([], []);
239
+
240
+		$this->assertNotSame($argument, $argNew);
241
+	}
242
+}

+ 32
- 15
tests/src/App/ModeTest.php View File

@@ -38,22 +38,20 @@ class ModeTest extends MockedTest
38 38
 		$this->setUpVfsDir();
39 39
 
40 40
 		$this->basePathMock = \Mockery::mock(BasePath::class);
41
-		$this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once();
42
-
43 41
 		$this->databaseMock = \Mockery::mock(Database::class);
44 42
 		$this->configCacheMock = \Mockery::mock(Config\Cache\ConfigCache::class);
45 43
 	}
46 44
 
47 45
 	public function testItEmpty()
48 46
 	{
49
-		$mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock);
47
+		$mode = new Mode();
50 48
 		$this->assertTrue($mode->isInstall());
51 49
 		$this->assertFalse($mode->isNormal());
52 50
 	}
53 51
 
54 52
 	public function testWithoutConfig()
55 53
 	{
56
-		$mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock);
54
+		$this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once();
57 55
 
58 56
 		$this->assertTrue($this->root->hasChild('config/local.config.php'));
59 57
 
@@ -61,7 +59,7 @@ class ModeTest extends MockedTest
61 59
 
62 60
 		$this->assertFalse($this->root->hasChild('config/local.config.php'));
63 61
 
64
-		$mode->determine();
62
+		$mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
65 63
 
66 64
 		$this->assertTrue($mode->isInstall());
67 65
 		$this->assertFalse($mode->isNormal());
@@ -71,10 +69,11 @@ class ModeTest extends MockedTest
71 69
 
72 70
 	public function testWithoutDatabase()
73 71
 	{
72
+		$this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once();
73
+
74 74
 		$this->databaseMock->shouldReceive('connected')->andReturn(false)->once();
75 75
 
76
-		$mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock);
77
-		$mode->determine();
76
+		$mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
78 77
 
79 78
 		$this->assertFalse($mode->isNormal());
80 79
 		$this->assertTrue($mode->isInstall());
@@ -85,12 +84,13 @@ class ModeTest extends MockedTest
85 84
 
86 85
 	public function testWithoutDatabaseSetup()
87 86
 	{
87
+		$this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once();
88
+
88 89
 		$this->databaseMock->shouldReceive('connected')->andReturn(true)->once();
89 90
 		$this->databaseMock->shouldReceive('fetchFirst')
90 91
 		                   ->with('SHOW TABLES LIKE \'config\'')->andReturn(false)->once();
91 92
 
92
-		$mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock);
93
-		$mode->determine();
93
+		$mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
94 94
 
95 95
 		$this->assertFalse($mode->isNormal());
96 96
 		$this->assertTrue($mode->isInstall());
@@ -100,14 +100,15 @@ class ModeTest extends MockedTest
100 100
 
101 101
 	public function testWithMaintenanceMode()
102 102
 	{
103
+		$this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once();
104
+
103 105
 		$this->databaseMock->shouldReceive('connected')->andReturn(true)->once();
104 106
 		$this->databaseMock->shouldReceive('fetchFirst')
105 107
 		                   ->with('SHOW TABLES LIKE \'config\'')->andReturn(true)->once();
106 108
 		$this->configCacheMock->shouldReceive('get')->with('system', 'maintenance')
107 109
 		                      ->andReturn(true)->once();
108 110
 
109
-		$mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock);
110
-		$mode->determine();
111
+		$mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
111 112
 
112 113
 		$this->assertFalse($mode->isNormal());
113 114
 		$this->assertFalse($mode->isInstall());
@@ -118,6 +119,8 @@ class ModeTest extends MockedTest
118 119
 
119 120
 	public function testNormalMode()
120 121
 	{
122
+		$this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once();
123
+
121 124
 		$this->databaseMock->shouldReceive('connected')->andReturn(true)->once();
122 125
 		$this->databaseMock->shouldReceive('fetchFirst')
123 126
 		                   ->with('SHOW TABLES LIKE \'config\'')->andReturn(true)->once();
@@ -127,8 +130,7 @@ class ModeTest extends MockedTest
127 130
 		                   ->with('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])
128 131
 		                   ->andReturn(['v' => null])->once();
129 132
 
130
-		$mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock);
131
-		$mode->determine();
133
+		$mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
132 134
 
133 135
 		$this->assertTrue($mode->isNormal());
134 136
 		$this->assertFalse($mode->isInstall());
@@ -142,6 +144,8 @@ class ModeTest extends MockedTest
142 144
 	 */
143 145
 	public function testDisabledMaintenance()
144 146
 	{
147
+		$this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once();
148
+
145 149
 		$this->databaseMock->shouldReceive('connected')->andReturn(true)->once();
146 150
 		$this->databaseMock->shouldReceive('fetchFirst')
147 151
 		                   ->with('SHOW TABLES LIKE \'config\'')->andReturn(true)->once();
@@ -151,8 +155,7 @@ class ModeTest extends MockedTest
151 155
 		                   ->with('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])
152 156
 		                   ->andReturn(['v' => '0'])->once();
153 157
 
154
-		$mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock);
155
-		$mode->determine();
158
+		$mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
156 159
 
157 160
 		$this->assertTrue($mode->isNormal());
158 161
 		$this->assertFalse($mode->isInstall());
@@ -160,4 +163,18 @@ class ModeTest extends MockedTest
160 163
 		$this->assertTrue($mode->has(Mode::DBCONFIGAVAILABLE));
161 164
 		$this->assertTrue($mode->has(Mode::MAINTENANCEDISABLED));
162 165
 	}
166
+
167
+	/**
168
+	 * Test that modes are immutable
169
+	 */
170
+	public function testImmutable()
171
+	{
172
+		$this->basePathMock->shouldReceive('getPath')->andReturn(null)->once();
173
+
174
+		$mode = new Mode();
175
+
176
+		$modeNew = $mode->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
177
+
178
+		$this->assertNotSame($modeNew, $mode);
179
+	}
163 180
 }

+ 189
- 0
tests/src/App/ModuleTest.php View File

@@ -0,0 +1,189 @@
1
+<?php
2
+
3
+namespace Friendica\Test\src\App;
4
+
5
+use Friendica\App;
6
+use Friendica\Core\Config\Configuration;
7
+use Friendica\LegacyModule;
8
+use Friendica\Module\PageNotFound;
9
+use Friendica\Module\WellKnown\HostMeta;
10
+use Friendica\Test\DatabaseTest;
11
+
12
+class ModuleTest extends DatabaseTest
13
+{
14
+	private function assertModule(array $assert, App\Module $module)
15
+	{
16
+		$this->assertEquals($assert['isBackend'], $module->isBackend());
17
+		$this->assertEquals($assert['name'], $module->getName());
18
+		$this->assertEquals($assert['class'], $module->getClassName());
19
+	}
20
+
21
+	/**
22
+	 * Test the default module mode
23
+	 */
24
+	public function testDefault()
25
+	{
26
+		$module = new App\Module();
27
+
28
+		$this->assertModule([
29
+			'isBackend' => false,
30
+			'name'      => App\Module::DEFAULT,
31
+			'class'     => App\Module::DEFAULT_CLASS,
32
+		], $module);
33
+	}
34
+
35
+	public function dataModuleName()
36
+	{
37
+		return [
38
+			'default'                   => [
39
+				'assert' => [
40
+					'isBackend' => false,
41
+					'name'      => 'network',
42
+					'class'     => App\Module::DEFAULT_CLASS,
43
+				],
44
+				'args'   => new App\Arguments('network/data/in',
45
+					'network/data/in',
46
+					['network', 'data', 'in'],
47
+					3),
48
+				'server' => [],
49
+			],
50
+			'withStrikeAndPoint'        => [
51
+				'assert' => [
52
+					'isBackend' => false,
53
+					'name'      => 'with_strike_and_point',
54
+					'class'     => App\Module::DEFAULT_CLASS,
55
+				],
56
+				'args'   => new App\Arguments('with-strike.and-point/data/in',
57
+					'with-strike.and-point/data/in',
58
+					['with-strike.and-point', 'data', 'in'],
59
+					3),
60
+				'server' => [],
61
+			],
62
+			'withNothing'               => [
63
+				'assert' => [
64
+					'isBackend' => false,
65
+					'name'      => App\Module::DEFAULT,
66
+					'class'     => App\Module::DEFAULT_CLASS,
67
+				],
68
+				'args'   => new App\Arguments(),
69
+				'server' => []
70
+			],
71
+			'withIndex'                 => [
72
+				'assert' => [
73
+					'isBackend' => false,
74
+					'name'      => App\Module::DEFAULT,
75
+					'class'     => App\Module::DEFAULT_CLASS,
76
+				],
77
+				'args'   => new App\Arguments(),
78
+				'server' => ['PHP_SELF' => 'index.php']
79
+			],
80
+			'withIndexButBackendMod'    => [
81
+				'assert' => [
82
+					'isBackend' => false,
83
+					'name'      => App\Module::BACKEND_MODULES[0],
84
+					'class'     => App\Module::DEFAULT_CLASS,
85
+				],
86
+				'args'   => new App\Arguments(App\Module::BACKEND_MODULES[0] . '/data/in',
87
+					App\Module::BACKEND_MODULES[0] . '/data/in',
88
+					[App\Module::BACKEND_MODULES[0], 'data', 'in'],
89
+					3),
90
+				'server' => ['PHP_SELF' => 'index.php']
91
+			],
92
+			'withNotIndexAndBackendMod' => [
93
+				'assert' => [
94
+					'isBackend' => true,
95
+					'name'      => App\Module::BACKEND_MODULES[0],
96
+					'class'     => App\Module::DEFAULT_CLASS,
97
+				],
98
+				'args'   => new App\Arguments(App\Module::BACKEND_MODULES[0] . '/data/in',
99
+					App\Module::BACKEND_MODULES[0] . '/data/in',
100
+					[App\Module::BACKEND_MODULES[0], 'data', 'in'],
101
+					3),
102
+				'server' => ['PHP_SELF' => 'daemon.php']
103
+			],
104
+			'withFirefoxApp'            => [
105
+				'assert' => [
106
+					'isBackend' => false,
107
+					'name'      => 'login',
108
+					'class'     => App\Module::DEFAULT_CLASS,
109
+				],
110
+				'args'   => new App\Arguments('users/sign_in',
111
+					'users/sign_in',
112
+					['users', 'sign_in'],
113
+					3),
114
+				'server' => ['PHP_SELF' => 'index.php'],
115
+			],
116
+		];
117
+	}
118
+
119
+	/**
120
+	 * Test the module name and backend determination
121
+	 *
122
+	 * @dataProvider dataModuleName
123
+	 */
124
+	public function testModuleName(array $assert, App\Arguments $args, array $server)
125
+	{
126
+		$module = (new App\Module())->determineModule($args, $server);
127
+
128
+		$this->assertModule($assert, $module);
129
+	}
130
+
131
+	public function dataModuleClass()
132
+	{
133
+		return [
134
+			'default' => [
135
+				'assert'  => App\Module::DEFAULT_CLASS,
136
+				'name'    => App\Module::DEFAULT,
137
+				'command' => App\Module::DEFAULT,
138
+				'privAdd' => false,
139
+			],
140
+			'legacy'  => [
141
+				'assert'  => LegacyModule::class,
142
+				// API is one of the last modules to switch from legacy to new BaseModule
143
+				// so this should be a stable test case until we completely switch ;-)
144
+				'name'    => 'api',
145
+				'command' => 'api/test/it',
146
+				'privAdd' => false,
147
+			],
148
+			'new'     => [
149
+				'assert'  => HostMeta::class,
150
+				'not_required',
151
+				'command' => '.well-known/host-meta',
152
+				'privAdd' => false,
153
+			],
154
+			'404'     => [
155
+				'assert'  => PageNotFound::class,
156
+				'name'    => 'invalid',
157
+				'command' => 'invalid',
158
+				'privAdd' => false,
159
+			]
160
+		];
161
+	}
162
+
163
+	/**
164
+	 * Test the determination of the module class
165
+	 *
166
+	 * @dataProvider dataModuleClass
167
+	 */
168
+	public function testModuleClass($assert, string $name, string $command, bool $privAdd)
169
+	{
170
+		$config = \Mockery::mock(Configuration::class);
171
+		$config->shouldReceive('get')->with('config', 'private_addons', false)->andReturn($privAdd)->atMost()->once();
172
+
173
+		$module = (new App\Module($name))->determineClass(new App\Arguments('', $command), new App\Router(), $config);
174
+
175
+		$this->assertEquals($assert, $module->getClassName());
176
+	}
177
+
178
+	/**
179
+	 * Test that modules are immutable
180
+	 */
181
+	public function testImmutable()
182
+	{
183
+		$module = new App\Module();
184
+
185
+		$moduleNew = $module->determineModule(new App\Arguments(), []);
186
+
187
+		$this->assertNotSame($moduleNew, $module);
188
+	}
189
+}

Loading…
Cancel
Save