Passed
Push — master ( f51c75...7e2828 )
by Morris
11:24 queued 11s
created

App::part()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 4
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2016, ownCloud, Inc.
7
 *
8
 * @author Bernhard Posselt <[email protected]>
9
 * @author Christoph Wurst <[email protected]>
10
 * @author Joas Schilling <[email protected]>
11
 * @author Lukas Reschke <[email protected]>
12
 * @author Morris Jobke <[email protected]>
13
 * @author Roeland Jago Douma <[email protected]>
14
 * @author Thomas Müller <[email protected]>
15
 *
16
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program. If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
32
namespace OC\AppFramework;
33
34
use OC\AppFramework\DependencyInjection\DIContainer;
35
use OC\AppFramework\Http\Dispatcher;
36
use OC\HintException;
37
use OCP\AppFramework\Http;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, OC\AppFramework\Http. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
38
use OCP\AppFramework\Http\ICallbackResponse;
39
use OCP\AppFramework\Http\IOutput;
40
use OCP\AppFramework\QueryException;
41
use OCP\IRequest;
42
43
/**
44
 * Entry point for every request in your app. You can consider this as your
45
 * public static void main() method
46
 *
47
 * Handles all the dependency injection, controllers and output flow
48
 */
49
class App {
50
51
	/** @var string[] */
52
	private static $nameSpaceCache = [];
53
54
	/**
55
	 * Turns an app id into a namespace by either reading the appinfo.xml's
56
	 * namespace tag or uppercasing the appid's first letter
57
	 * @param string $appId the app id
58
	 * @param string $topNamespace the namespace which should be prepended to
59
	 * the transformed app id, defaults to OCA\
60
	 * @return string the starting namespace for the app
61
	 */
62
	public static function buildAppNamespace(string $appId, string $topNamespace='OCA\\'): string {
63
		// Hit the cache!
64
		if (isset(self::$nameSpaceCache[$appId])) {
65
			return $topNamespace . self::$nameSpaceCache[$appId];
66
		}
67
68
		$appInfo = \OC_App::getAppInfo($appId);
69
		if (isset($appInfo['namespace'])) {
70
			self::$nameSpaceCache[$appId] = trim($appInfo['namespace']);
71
		} else {
72
			if ($appId !== 'spreed') {
73
				// if the tag is not found, fall back to uppercasing the first letter
74
				self::$nameSpaceCache[$appId] = ucfirst($appId);
75
			} else {
76
				// For the Talk app (appid spreed) the above fallback doesn't work.
77
				// This leads to a problem when trying to install it freshly,
78
				// because the apps namespace is already registered before the
79
				// app is downloaded from the appstore, because of the hackish
80
				// global route index.php/call/{token} which is registered via
81
				// the core/routes.php so it does not have the app namespace.
82
				// @ref https://github.com/nextcloud/server/pull/19433
83
				self::$nameSpaceCache[$appId] = 'Talk';
84
			}
85
		}
86
87
		return $topNamespace . self::$nameSpaceCache[$appId];
88
	}
89
90
	public static function getAppIdForClass(string $className, string $topNamespace='OCA\\'): ?string {
91
		if (strpos($className, $topNamespace) !== 0) {
92
			return null;
93
		}
94
95
		foreach (self::$nameSpaceCache as $appId => $namespace) {
96
			if (strpos($className, $topNamespace . $namespace . '\\') === 0) {
97
				return $appId;
98
			}
99
		}
100
101
		return null;
102
	}
103
104
105
	/**
106
	 * Shortcut for calling a controller method and printing the result
107
	 * @param string $controllerName the name of the controller under which it is
108
	 *                               stored in the DI container
109
	 * @param string $methodName the method that you want to call
110
	 * @param DIContainer $container an instance of a pimple container.
111
	 * @param array $urlParams list of URL parameters (optional)
112
	 * @throws HintException
113
	 */
114
	public static function main(string $controllerName, string $methodName, DIContainer $container, array $urlParams = null) {
115
		if (!is_null($urlParams)) {
116
			$container->query(IRequest::class)->setUrlParameters($urlParams);
117
		} elseif (isset($container['urlParams']) && !is_null($container['urlParams'])) {
118
			$container->query(IRequest::class)->setUrlParameters($container['urlParams']);
119
		}
120
		$appName = $container['AppName'];
121
122
		// first try $controllerName then go for \OCA\AppName\Controller\$controllerName
123
		try {
124
			$controller = $container->query($controllerName);
125
		} catch (QueryException $e) {
126
			if (strpos($controllerName, '\\Controller\\') !== false) {
127
				// This is from a global registered app route that is not enabled.
128
				[/*OC(A)*/, $app, /* Controller/Name*/] = explode('\\', $controllerName, 3);
129
				throw new HintException('App ' . strtolower($app) . ' is not enabled');
130
			}
131
132
			if ($appName === 'core') {
133
				$appNameSpace = 'OC\\Core';
134
			} else {
135
				$appNameSpace = self::buildAppNamespace($appName);
136
			}
137
			$controllerName = $appNameSpace . '\\Controller\\' . $controllerName;
138
			$controller = $container->query($controllerName);
139
		}
140
141
		// initialize the dispatcher and run all the middleware before the controller
142
		/** @var Dispatcher $dispatcher */
143
		$dispatcher = $container['Dispatcher'];
144
145
		list(
146
			$httpHeaders,
147
			$responseHeaders,
148
			$responseCookies,
149
			$output,
150
			$response
151
		) = $dispatcher->dispatch($controller, $methodName);
152
153
		$io = $container[IOutput::class];
154
155
		if (!is_null($httpHeaders)) {
0 ignored issues
show
introduced by
The condition is_null($httpHeaders) is always false.
Loading history...
156
			$io->setHeader($httpHeaders);
157
		}
158
159
		foreach ($responseHeaders as $name => $value) {
160
			$io->setHeader($name . ': ' . $value);
161
		}
162
163
		foreach ($responseCookies as $name => $value) {
164
			$expireDate = null;
165
			if ($value['expireDate'] instanceof \DateTime) {
166
				$expireDate = $value['expireDate']->getTimestamp();
167
			}
168
			$sameSite = $value['sameSite'] ?? 'Lax';
169
170
			$io->setCookie(
171
				$name,
172
				$value['value'],
173
				$expireDate,
174
				$container->getServer()->getWebRoot(),
175
				null,
176
				$container->getServer()->getRequest()->getServerProtocol() === 'https',
177
				true,
178
				$sameSite
179
			);
180
		}
181
182
		/*
183
		 * Status 204 does not have a body and no Content Length
184
		 * Status 304 does not have a body and does not need a Content Length
185
		 * https://tools.ietf.org/html/rfc7230#section-3.3
186
		 * https://tools.ietf.org/html/rfc7230#section-3.3.2
187
		 */
188
		$emptyResponse = false;
189
		if (preg_match('/^HTTP\/\d\.\d (\d{3}) .*$/', $httpHeaders, $matches)) {
190
			$status = (int)$matches[1];
191
			if ($status === Http::STATUS_NO_CONTENT || $status === Http::STATUS_NOT_MODIFIED) {
192
				$emptyResponse = true;
193
			}
194
		}
195
196
		if (!$emptyResponse) {
197
			if ($response instanceof ICallbackResponse) {
198
				$response->callback($io);
199
			} elseif (!is_null($output)) {
0 ignored issues
show
introduced by
The condition is_null($output) is always false.
Loading history...
200
				$io->setHeader('Content-Length: ' . strlen($output));
201
				$io->setOutput($output);
202
			}
203
		}
204
	}
205
206
	/**
207
	 * Shortcut for calling a controller method and printing the result.
208
	 * Similar to App:main except that no headers will be sent.
209
	 * This should be used for example when registering sections via
210
	 * \OC\AppFramework\Core\API::registerAdmin()
211
	 *
212
	 * @param string $controllerName the name of the controller under which it is
213
	 *                               stored in the DI container
214
	 * @param string $methodName the method that you want to call
215
	 * @param array $urlParams an array with variables extracted from the routes
216
	 * @param DIContainer $container an instance of a pimple container.
217
	 */
218
	public static function part(string $controllerName, string $methodName, array $urlParams,
219
								DIContainer $container) {
220
		$container['urlParams'] = $urlParams;
221
		$controller = $container[$controllerName];
222
223
		$dispatcher = $container['Dispatcher'];
224
225
		list(, , $output) =  $dispatcher->dispatch($controller, $methodName);
226
		return $output;
227
	}
228
}
229