Passed
Push — master ( 683685...5ed673 )
by Morris
13:18 queued 11s
created
lib/private/AppFramework/Http/Dispatcher.php 2 patches
Indentation   +189 added lines, -189 removed lines patch added patch discarded remove patch
@@ -49,193 +49,193 @@
 block discarded – undo
49 49
  */
50 50
 class Dispatcher {
51 51
 
52
-	/** @var MiddlewareDispatcher */
53
-	private $middlewareDispatcher;
54
-
55
-	/** @var Http */
56
-	private $protocol;
57
-
58
-	/** @var ControllerMethodReflector */
59
-	private $reflector;
60
-
61
-	/** @var IRequest */
62
-	private $request;
63
-
64
-	/** @var IConfig */
65
-	private $config;
66
-
67
-	/** @var ConnectionAdapter */
68
-	private $connection;
69
-
70
-	/** @var LoggerInterface */
71
-	private $logger;
72
-
73
-	/**
74
-	 * @param Http $protocol the http protocol with contains all status headers
75
-	 * @param MiddlewareDispatcher $middlewareDispatcher the dispatcher which
76
-	 * runs the middleware
77
-	 * @param ControllerMethodReflector $reflector the reflector that is used to inject
78
-	 * the arguments for the controller
79
-	 * @param IRequest $request the incoming request
80
-	 * @param IConfig $config
81
-	 * @param ConnectionAdapter $connection
82
-	 * @param LoggerInterface $logger
83
-	 */
84
-	public function __construct(Http $protocol,
85
-								MiddlewareDispatcher $middlewareDispatcher,
86
-								ControllerMethodReflector $reflector,
87
-								IRequest $request,
88
-								IConfig $config,
89
-								ConnectionAdapter $connection,
90
-								LoggerInterface $logger) {
91
-		$this->protocol = $protocol;
92
-		$this->middlewareDispatcher = $middlewareDispatcher;
93
-		$this->reflector = $reflector;
94
-		$this->request = $request;
95
-		$this->config = $config;
96
-		$this->connection = $connection;
97
-		$this->logger = $logger;
98
-	}
99
-
100
-
101
-	/**
102
-	 * Handles a request and calls the dispatcher on the controller
103
-	 * @param Controller $controller the controller which will be called
104
-	 * @param string $methodName the method name which will be called on
105
-	 * the controller
106
-	 * @return array $array[0] contains a string with the http main header,
107
-	 * $array[1] contains headers in the form: $key => value, $array[2] contains
108
-	 * the response output
109
-	 * @throws \Exception
110
-	 */
111
-	public function dispatch(Controller $controller, string $methodName): array {
112
-		$out = [null, [], null];
113
-
114
-		try {
115
-			// prefill reflector with everything thats needed for the
116
-			// middlewares
117
-			$this->reflector->reflect($controller, $methodName);
118
-
119
-			$this->middlewareDispatcher->beforeController($controller,
120
-				$methodName);
121
-
122
-			$databaseStatsBefore = [];
123
-			if ($this->config->getSystemValueBool('debug', false)) {
124
-				$databaseStatsBefore = $this->connection->getInner()->getStats();
125
-			}
126
-
127
-			$response = $this->executeController($controller, $methodName);
128
-
129
-			if (!empty($databaseStatsBefore)) {
130
-				$databaseStatsAfter = $this->connection->getInner()->getStats();
131
-				$numBuilt = $databaseStatsAfter['built'] - $databaseStatsBefore['built'];
132
-				$numExecuted = $databaseStatsAfter['executed'] - $databaseStatsBefore['executed'];
133
-
134
-				if ($numBuilt > 50) {
135
-					$this->logger->debug('Controller {class}::{method} created {count} QueryBuilder objects, please check if they are created inside a loop by accident.' , [
136
-						'class' => get_class($controller),
137
-						'method' => $methodName,
138
-						'count' => $numBuilt,
139
-					]);
140
-				}
141
-
142
-				if ($numExecuted > 100) {
143
-					$this->logger->warning('Controller {class}::{method} executed {count} queries.' , [
144
-						'class' => get_class($controller),
145
-						'method' => $methodName,
146
-						'count' => $numExecuted,
147
-					]);
148
-				}
149
-			}
150
-
151
-			// if an exception appears, the middleware checks if it can handle the
152
-			// exception and creates a response. If no response is created, it is
153
-			// assumed that theres no middleware who can handle it and the error is
154
-			// thrown again
155
-		} catch (\Exception $exception) {
156
-			$response = $this->middlewareDispatcher->afterException(
157
-				$controller, $methodName, $exception);
158
-		} catch (\Throwable $throwable) {
159
-			$exception = new \Exception($throwable->getMessage(), $throwable->getCode(), $throwable);
160
-			$response = $this->middlewareDispatcher->afterException(
161
-			$controller, $methodName, $exception);
162
-		}
163
-
164
-		$response = $this->middlewareDispatcher->afterController(
165
-			$controller, $methodName, $response);
166
-
167
-		// depending on the cache object the headers need to be changed
168
-		$out[0] = $this->protocol->getStatusHeader($response->getStatus());
169
-		$out[1] = array_merge($response->getHeaders());
170
-		$out[2] = $response->getCookies();
171
-		$out[3] = $this->middlewareDispatcher->beforeOutput(
172
-			$controller, $methodName, $response->render()
173
-		);
174
-		$out[4] = $response;
175
-
176
-		return $out;
177
-	}
178
-
179
-
180
-	/**
181
-	 * Uses the reflected parameters, types and request parameters to execute
182
-	 * the controller
183
-	 * @param Controller $controller the controller to be executed
184
-	 * @param string $methodName the method on the controller that should be executed
185
-	 * @return Response
186
-	 */
187
-	private function executeController(Controller $controller, string $methodName): Response {
188
-		$arguments = [];
189
-
190
-		// valid types that will be casted
191
-		$types = ['int', 'integer', 'bool', 'boolean', 'float'];
192
-
193
-		foreach ($this->reflector->getParameters() as $param => $default) {
194
-
195
-			// try to get the parameter from the request object and cast
196
-			// it to the type annotated in the @param annotation
197
-			$value = $this->request->getParam($param, $default);
198
-			$type = $this->reflector->getType($param);
199
-
200
-			// if this is submitted using GET or a POST form, 'false' should be
201
-			// converted to false
202
-			if (($type === 'bool' || $type === 'boolean') &&
203
-				$value === 'false' &&
204
-				(
205
-					$this->request->method === 'GET' ||
206
-					strpos($this->request->getHeader('Content-Type'),
207
-						'application/x-www-form-urlencoded') !== false
208
-				)
209
-			) {
210
-				$value = false;
211
-			} elseif ($value !== null && \in_array($type, $types, true)) {
212
-				settype($value, $type);
213
-			}
214
-
215
-			$arguments[] = $value;
216
-		}
217
-
218
-		$response = \call_user_func_array([$controller, $methodName], $arguments);
219
-
220
-		// format response
221
-		if ($response instanceof DataResponse || !($response instanceof Response)) {
222
-
223
-			// get format from the url format or request format parameter
224
-			$format = $this->request->getParam('format');
225
-
226
-			// if none is given try the first Accept header
227
-			if ($format === null) {
228
-				$headers = $this->request->getHeader('Accept');
229
-				$format = $controller->getResponderByHTTPHeader($headers, null);
230
-			}
231
-
232
-			if ($format !== null) {
233
-				$response = $controller->buildResponse($response, $format);
234
-			} else {
235
-				$response = $controller->buildResponse($response);
236
-			}
237
-		}
238
-
239
-		return $response;
240
-	}
52
+    /** @var MiddlewareDispatcher */
53
+    private $middlewareDispatcher;
54
+
55
+    /** @var Http */
56
+    private $protocol;
57
+
58
+    /** @var ControllerMethodReflector */
59
+    private $reflector;
60
+
61
+    /** @var IRequest */
62
+    private $request;
63
+
64
+    /** @var IConfig */
65
+    private $config;
66
+
67
+    /** @var ConnectionAdapter */
68
+    private $connection;
69
+
70
+    /** @var LoggerInterface */
71
+    private $logger;
72
+
73
+    /**
74
+     * @param Http $protocol the http protocol with contains all status headers
75
+     * @param MiddlewareDispatcher $middlewareDispatcher the dispatcher which
76
+     * runs the middleware
77
+     * @param ControllerMethodReflector $reflector the reflector that is used to inject
78
+     * the arguments for the controller
79
+     * @param IRequest $request the incoming request
80
+     * @param IConfig $config
81
+     * @param ConnectionAdapter $connection
82
+     * @param LoggerInterface $logger
83
+     */
84
+    public function __construct(Http $protocol,
85
+                                MiddlewareDispatcher $middlewareDispatcher,
86
+                                ControllerMethodReflector $reflector,
87
+                                IRequest $request,
88
+                                IConfig $config,
89
+                                ConnectionAdapter $connection,
90
+                                LoggerInterface $logger) {
91
+        $this->protocol = $protocol;
92
+        $this->middlewareDispatcher = $middlewareDispatcher;
93
+        $this->reflector = $reflector;
94
+        $this->request = $request;
95
+        $this->config = $config;
96
+        $this->connection = $connection;
97
+        $this->logger = $logger;
98
+    }
99
+
100
+
101
+    /**
102
+     * Handles a request and calls the dispatcher on the controller
103
+     * @param Controller $controller the controller which will be called
104
+     * @param string $methodName the method name which will be called on
105
+     * the controller
106
+     * @return array $array[0] contains a string with the http main header,
107
+     * $array[1] contains headers in the form: $key => value, $array[2] contains
108
+     * the response output
109
+     * @throws \Exception
110
+     */
111
+    public function dispatch(Controller $controller, string $methodName): array {
112
+        $out = [null, [], null];
113
+
114
+        try {
115
+            // prefill reflector with everything thats needed for the
116
+            // middlewares
117
+            $this->reflector->reflect($controller, $methodName);
118
+
119
+            $this->middlewareDispatcher->beforeController($controller,
120
+                $methodName);
121
+
122
+            $databaseStatsBefore = [];
123
+            if ($this->config->getSystemValueBool('debug', false)) {
124
+                $databaseStatsBefore = $this->connection->getInner()->getStats();
125
+            }
126
+
127
+            $response = $this->executeController($controller, $methodName);
128
+
129
+            if (!empty($databaseStatsBefore)) {
130
+                $databaseStatsAfter = $this->connection->getInner()->getStats();
131
+                $numBuilt = $databaseStatsAfter['built'] - $databaseStatsBefore['built'];
132
+                $numExecuted = $databaseStatsAfter['executed'] - $databaseStatsBefore['executed'];
133
+
134
+                if ($numBuilt > 50) {
135
+                    $this->logger->debug('Controller {class}::{method} created {count} QueryBuilder objects, please check if they are created inside a loop by accident.' , [
136
+                        'class' => get_class($controller),
137
+                        'method' => $methodName,
138
+                        'count' => $numBuilt,
139
+                    ]);
140
+                }
141
+
142
+                if ($numExecuted > 100) {
143
+                    $this->logger->warning('Controller {class}::{method} executed {count} queries.' , [
144
+                        'class' => get_class($controller),
145
+                        'method' => $methodName,
146
+                        'count' => $numExecuted,
147
+                    ]);
148
+                }
149
+            }
150
+
151
+            // if an exception appears, the middleware checks if it can handle the
152
+            // exception and creates a response. If no response is created, it is
153
+            // assumed that theres no middleware who can handle it and the error is
154
+            // thrown again
155
+        } catch (\Exception $exception) {
156
+            $response = $this->middlewareDispatcher->afterException(
157
+                $controller, $methodName, $exception);
158
+        } catch (\Throwable $throwable) {
159
+            $exception = new \Exception($throwable->getMessage(), $throwable->getCode(), $throwable);
160
+            $response = $this->middlewareDispatcher->afterException(
161
+            $controller, $methodName, $exception);
162
+        }
163
+
164
+        $response = $this->middlewareDispatcher->afterController(
165
+            $controller, $methodName, $response);
166
+
167
+        // depending on the cache object the headers need to be changed
168
+        $out[0] = $this->protocol->getStatusHeader($response->getStatus());
169
+        $out[1] = array_merge($response->getHeaders());
170
+        $out[2] = $response->getCookies();
171
+        $out[3] = $this->middlewareDispatcher->beforeOutput(
172
+            $controller, $methodName, $response->render()
173
+        );
174
+        $out[4] = $response;
175
+
176
+        return $out;
177
+    }
178
+
179
+
180
+    /**
181
+     * Uses the reflected parameters, types and request parameters to execute
182
+     * the controller
183
+     * @param Controller $controller the controller to be executed
184
+     * @param string $methodName the method on the controller that should be executed
185
+     * @return Response
186
+     */
187
+    private function executeController(Controller $controller, string $methodName): Response {
188
+        $arguments = [];
189
+
190
+        // valid types that will be casted
191
+        $types = ['int', 'integer', 'bool', 'boolean', 'float'];
192
+
193
+        foreach ($this->reflector->getParameters() as $param => $default) {
194
+
195
+            // try to get the parameter from the request object and cast
196
+            // it to the type annotated in the @param annotation
197
+            $value = $this->request->getParam($param, $default);
198
+            $type = $this->reflector->getType($param);
199
+
200
+            // if this is submitted using GET or a POST form, 'false' should be
201
+            // converted to false
202
+            if (($type === 'bool' || $type === 'boolean') &&
203
+                $value === 'false' &&
204
+                (
205
+                    $this->request->method === 'GET' ||
206
+                    strpos($this->request->getHeader('Content-Type'),
207
+                        'application/x-www-form-urlencoded') !== false
208
+                )
209
+            ) {
210
+                $value = false;
211
+            } elseif ($value !== null && \in_array($type, $types, true)) {
212
+                settype($value, $type);
213
+            }
214
+
215
+            $arguments[] = $value;
216
+        }
217
+
218
+        $response = \call_user_func_array([$controller, $methodName], $arguments);
219
+
220
+        // format response
221
+        if ($response instanceof DataResponse || !($response instanceof Response)) {
222
+
223
+            // get format from the url format or request format parameter
224
+            $format = $this->request->getParam('format');
225
+
226
+            // if none is given try the first Accept header
227
+            if ($format === null) {
228
+                $headers = $this->request->getHeader('Accept');
229
+                $format = $controller->getResponderByHTTPHeader($headers, null);
230
+            }
231
+
232
+            if ($format !== null) {
233
+                $response = $controller->buildResponse($response, $format);
234
+            } else {
235
+                $response = $controller->buildResponse($response);
236
+            }
237
+        }
238
+
239
+        return $response;
240
+    }
241 241
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -132,7 +132,7 @@  discard block
 block discarded – undo
132 132
 				$numExecuted = $databaseStatsAfter['executed'] - $databaseStatsBefore['executed'];
133 133
 
134 134
 				if ($numBuilt > 50) {
135
-					$this->logger->debug('Controller {class}::{method} created {count} QueryBuilder objects, please check if they are created inside a loop by accident.' , [
135
+					$this->logger->debug('Controller {class}::{method} created {count} QueryBuilder objects, please check if they are created inside a loop by accident.', [
136 136
 						'class' => get_class($controller),
137 137
 						'method' => $methodName,
138 138
 						'count' => $numBuilt,
@@ -140,7 +140,7 @@  discard block
 block discarded – undo
140 140
 				}
141 141
 
142 142
 				if ($numExecuted > 100) {
143
-					$this->logger->warning('Controller {class}::{method} executed {count} queries.' , [
143
+					$this->logger->warning('Controller {class}::{method} executed {count} queries.', [
144 144
 						'class' => get_class($controller),
145 145
 						'method' => $methodName,
146 146
 						'count' => $numExecuted,
Please login to merge, or discard this patch.
lib/private/AppFramework/Routing/RouteConfig.php 2 patches
Indentation   +252 added lines, -252 removed lines patch added patch discarded remove patch
@@ -39,258 +39,258 @@
 block discarded – undo
39 39
  * @package OC\AppFramework\routing
40 40
  */
41 41
 class RouteConfig {
42
-	/** @var DIContainer */
43
-	private $container;
44
-
45
-	/** @var Router */
46
-	private $router;
47
-
48
-	/** @var array */
49
-	private $routes;
50
-
51
-	/** @var string */
52
-	private $appName;
53
-
54
-	/** @var string[] */
55
-	private $controllerNameCache = [];
56
-
57
-	protected $rootUrlApps = [
58
-		'cloud_federation_api',
59
-		'core',
60
-		'files_sharing',
61
-		'files',
62
-		'settings',
63
-		'spreed',
64
-	];
65
-
66
-	/**
67
-	 * @param \OC\AppFramework\DependencyInjection\DIContainer $container
68
-	 * @param \OC\Route\Router $router
69
-	 * @param array $routes
70
-	 * @internal param $appName
71
-	 */
72
-	public function __construct(DIContainer $container, Router $router, $routes) {
73
-		$this->routes = $routes;
74
-		$this->container = $container;
75
-		$this->router = $router;
76
-		$this->appName = $container['AppName'];
77
-	}
78
-
79
-	/**
80
-	 * The routes and resource will be registered to the \OCP\Route\IRouter
81
-	 */
82
-	public function register() {
83
-
84
-		// parse simple
85
-		$this->processIndexRoutes($this->routes);
86
-
87
-		// parse resources
88
-		$this->processIndexResources($this->routes);
89
-
90
-		/*
42
+    /** @var DIContainer */
43
+    private $container;
44
+
45
+    /** @var Router */
46
+    private $router;
47
+
48
+    /** @var array */
49
+    private $routes;
50
+
51
+    /** @var string */
52
+    private $appName;
53
+
54
+    /** @var string[] */
55
+    private $controllerNameCache = [];
56
+
57
+    protected $rootUrlApps = [
58
+        'cloud_federation_api',
59
+        'core',
60
+        'files_sharing',
61
+        'files',
62
+        'settings',
63
+        'spreed',
64
+    ];
65
+
66
+    /**
67
+     * @param \OC\AppFramework\DependencyInjection\DIContainer $container
68
+     * @param \OC\Route\Router $router
69
+     * @param array $routes
70
+     * @internal param $appName
71
+     */
72
+    public function __construct(DIContainer $container, Router $router, $routes) {
73
+        $this->routes = $routes;
74
+        $this->container = $container;
75
+        $this->router = $router;
76
+        $this->appName = $container['AppName'];
77
+    }
78
+
79
+    /**
80
+     * The routes and resource will be registered to the \OCP\Route\IRouter
81
+     */
82
+    public function register() {
83
+
84
+        // parse simple
85
+        $this->processIndexRoutes($this->routes);
86
+
87
+        // parse resources
88
+        $this->processIndexResources($this->routes);
89
+
90
+        /*
91 91
 		 * OCS routes go into a different collection
92 92
 		 */
93
-		$oldCollection = $this->router->getCurrentCollection();
94
-		$this->router->useCollection($oldCollection . '.ocs');
95
-
96
-		// parse ocs simple routes
97
-		$this->processOCS($this->routes);
98
-
99
-		// parse ocs simple routes
100
-		$this->processOCSResources($this->routes);
101
-
102
-		$this->router->useCollection($oldCollection);
103
-	}
104
-
105
-	private function processOCS(array $routes): void {
106
-		$ocsRoutes = $routes['ocs'] ?? [];
107
-		foreach ($ocsRoutes as $ocsRoute) {
108
-			$this->processRoute($ocsRoute, 'ocs.');
109
-		}
110
-	}
111
-
112
-	/**
113
-	 * Creates one route base on the give configuration
114
-	 * @param array $routes
115
-	 * @throws \UnexpectedValueException
116
-	 */
117
-	private function processIndexRoutes(array $routes): void {
118
-		$simpleRoutes = $routes['routes'] ?? [];
119
-		foreach ($simpleRoutes as $simpleRoute) {
120
-			$this->processRoute($simpleRoute);
121
-		}
122
-	}
123
-
124
-	protected function processRoute(array $route, string $routeNamePrefix = ''): void {
125
-		$name = $route['name'];
126
-		$postfix = $route['postfix'] ?? '';
127
-		$root = $this->buildRootPrefix($route, $routeNamePrefix);
128
-
129
-		$url = $root . '/' . ltrim($route['url'], '/');
130
-		$verb = strtoupper($route['verb'] ?? 'GET');
131
-
132
-		$split = explode('#', $name, 2);
133
-		if (count($split) !== 2) {
134
-			throw new \UnexpectedValueException('Invalid route name');
135
-		}
136
-		list($controller, $action) = $split;
137
-
138
-		$controllerName = $this->buildControllerName($controller);
139
-		$actionName = $this->buildActionName($action);
140
-
141
-		$routeName = $routeNamePrefix . $this->appName . '.' . $controller . '.' . $action . $postfix;
142
-
143
-		$router = $this->router->create($routeName, $url)
144
-			->method($verb);
145
-
146
-		// optionally register requirements for route. This is used to
147
-		// tell the route parser how url parameters should be matched
148
-		if (array_key_exists('requirements', $route)) {
149
-			$router->requirements($route['requirements']);
150
-		}
151
-
152
-		// optionally register defaults for route. This is used to
153
-		// tell the route parser how url parameters should be default valued
154
-		$defaults = [];
155
-		if (array_key_exists('defaults', $route)) {
156
-			$defaults = $route['defaults'];
157
-		}
158
-
159
-		$defaults['caller'] = [$this->appName, $controllerName, $actionName];
160
-		$router->defaults($defaults);
161
-	}
162
-
163
-	/**
164
-	 * For a given name and url restful OCS routes are created:
165
-	 *  - index
166
-	 *  - show
167
-	 *  - create
168
-	 *  - update
169
-	 *  - destroy
170
-	 *
171
-	 * @param array $routes
172
-	 */
173
-	private function processOCSResources(array $routes): void {
174
-		$this->processResources($routes['ocs-resources'] ?? [], 'ocs.');
175
-	}
176
-
177
-	/**
178
-	 * For a given name and url restful routes are created:
179
-	 *  - index
180
-	 *  - show
181
-	 *  - create
182
-	 *  - update
183
-	 *  - destroy
184
-	 *
185
-	 * @param array $routes
186
-	 */
187
-	private function processIndexResources(array $routes): void {
188
-		$this->processResources($routes['resources'] ?? []);
189
-	}
190
-
191
-	/**
192
-	 * For a given name and url restful routes are created:
193
-	 *  - index
194
-	 *  - show
195
-	 *  - create
196
-	 *  - update
197
-	 *  - destroy
198
-	 *
199
-	 * @param array $resources
200
-	 * @param string $routeNamePrefix
201
-	 */
202
-	protected function processResources(array $resources, string $routeNamePrefix = ''): void {
203
-		// declaration of all restful actions
204
-		$actions = [
205
-			['name' => 'index', 'verb' => 'GET', 'on-collection' => true],
206
-			['name' => 'show', 'verb' => 'GET'],
207
-			['name' => 'create', 'verb' => 'POST', 'on-collection' => true],
208
-			['name' => 'update', 'verb' => 'PUT'],
209
-			['name' => 'destroy', 'verb' => 'DELETE'],
210
-		];
211
-
212
-		foreach ($resources as $resource => $config) {
213
-			$root = $this->buildRootPrefix($config, $routeNamePrefix);
214
-
215
-			// the url parameter used as id to the resource
216
-			foreach ($actions as $action) {
217
-				$url = $root . '/' . ltrim($config['url'], '/');
218
-				$method = $action['name'];
219
-
220
-				$verb = strtoupper($action['verb'] ?? 'GET');
221
-				$collectionAction = $action['on-collection'] ?? false;
222
-				if (!$collectionAction) {
223
-					$url .= '/{id}';
224
-				}
225
-				if (isset($action['url-postfix'])) {
226
-					$url .= '/' . $action['url-postfix'];
227
-				}
228
-
229
-				$controller = $resource;
230
-
231
-				$controllerName = $this->buildControllerName($controller);
232
-				$actionName = $this->buildActionName($method);
233
-
234
-				$routeName = $routeNamePrefix . $this->appName . '.' . strtolower($resource) . '.' . $method;
235
-
236
-				$route = $this->router->create($routeName, $url)
237
-					->method($verb);
238
-
239
-				$route->defaults(['caller' => [$this->appName, $controllerName, $actionName]]);
240
-			}
241
-		}
242
-	}
243
-
244
-	private function buildRootPrefix(array $route, string $routeNamePrefix): string {
245
-		$defaultRoot = $this->appName === 'core' ? '' : '/apps/' . $this->appName;
246
-		$root = $route['root'] ?? $defaultRoot;
247
-
248
-		if ($routeNamePrefix !== '') {
249
-			// In OCS all apps are whitelisted
250
-			return $root;
251
-		}
252
-
253
-		if (!\in_array($this->appName, $this->rootUrlApps, true)) {
254
-			// Only allow root URLS for some apps
255
-			return  $defaultRoot;
256
-		}
257
-
258
-		return $root;
259
-	}
260
-
261
-	/**
262
-	 * Based on a given route name the controller name is generated
263
-	 * @param string $controller
264
-	 * @return string
265
-	 */
266
-	private function buildControllerName(string $controller): string {
267
-		if (!isset($this->controllerNameCache[$controller])) {
268
-			$this->controllerNameCache[$controller] = $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller';
269
-		}
270
-		return $this->controllerNameCache[$controller];
271
-	}
272
-
273
-	/**
274
-	 * Based on the action part of the route name the controller method name is generated
275
-	 * @param string $action
276
-	 * @return string
277
-	 */
278
-	private function buildActionName(string $action): string {
279
-		return $this->underScoreToCamelCase($action);
280
-	}
281
-
282
-	/**
283
-	 * Underscored strings are converted to camel case strings
284
-	 * @param string $str
285
-	 * @return string
286
-	 */
287
-	private function underScoreToCamelCase(string $str): string {
288
-		$pattern = '/_[a-z]?/';
289
-		return preg_replace_callback(
290
-			$pattern,
291
-			function ($matches) {
292
-				return strtoupper(ltrim($matches[0], '_'));
293
-			},
294
-			$str);
295
-	}
93
+        $oldCollection = $this->router->getCurrentCollection();
94
+        $this->router->useCollection($oldCollection . '.ocs');
95
+
96
+        // parse ocs simple routes
97
+        $this->processOCS($this->routes);
98
+
99
+        // parse ocs simple routes
100
+        $this->processOCSResources($this->routes);
101
+
102
+        $this->router->useCollection($oldCollection);
103
+    }
104
+
105
+    private function processOCS(array $routes): void {
106
+        $ocsRoutes = $routes['ocs'] ?? [];
107
+        foreach ($ocsRoutes as $ocsRoute) {
108
+            $this->processRoute($ocsRoute, 'ocs.');
109
+        }
110
+    }
111
+
112
+    /**
113
+     * Creates one route base on the give configuration
114
+     * @param array $routes
115
+     * @throws \UnexpectedValueException
116
+     */
117
+    private function processIndexRoutes(array $routes): void {
118
+        $simpleRoutes = $routes['routes'] ?? [];
119
+        foreach ($simpleRoutes as $simpleRoute) {
120
+            $this->processRoute($simpleRoute);
121
+        }
122
+    }
123
+
124
+    protected function processRoute(array $route, string $routeNamePrefix = ''): void {
125
+        $name = $route['name'];
126
+        $postfix = $route['postfix'] ?? '';
127
+        $root = $this->buildRootPrefix($route, $routeNamePrefix);
128
+
129
+        $url = $root . '/' . ltrim($route['url'], '/');
130
+        $verb = strtoupper($route['verb'] ?? 'GET');
131
+
132
+        $split = explode('#', $name, 2);
133
+        if (count($split) !== 2) {
134
+            throw new \UnexpectedValueException('Invalid route name');
135
+        }
136
+        list($controller, $action) = $split;
137
+
138
+        $controllerName = $this->buildControllerName($controller);
139
+        $actionName = $this->buildActionName($action);
140
+
141
+        $routeName = $routeNamePrefix . $this->appName . '.' . $controller . '.' . $action . $postfix;
142
+
143
+        $router = $this->router->create($routeName, $url)
144
+            ->method($verb);
145
+
146
+        // optionally register requirements for route. This is used to
147
+        // tell the route parser how url parameters should be matched
148
+        if (array_key_exists('requirements', $route)) {
149
+            $router->requirements($route['requirements']);
150
+        }
151
+
152
+        // optionally register defaults for route. This is used to
153
+        // tell the route parser how url parameters should be default valued
154
+        $defaults = [];
155
+        if (array_key_exists('defaults', $route)) {
156
+            $defaults = $route['defaults'];
157
+        }
158
+
159
+        $defaults['caller'] = [$this->appName, $controllerName, $actionName];
160
+        $router->defaults($defaults);
161
+    }
162
+
163
+    /**
164
+     * For a given name and url restful OCS routes are created:
165
+     *  - index
166
+     *  - show
167
+     *  - create
168
+     *  - update
169
+     *  - destroy
170
+     *
171
+     * @param array $routes
172
+     */
173
+    private function processOCSResources(array $routes): void {
174
+        $this->processResources($routes['ocs-resources'] ?? [], 'ocs.');
175
+    }
176
+
177
+    /**
178
+     * For a given name and url restful routes are created:
179
+     *  - index
180
+     *  - show
181
+     *  - create
182
+     *  - update
183
+     *  - destroy
184
+     *
185
+     * @param array $routes
186
+     */
187
+    private function processIndexResources(array $routes): void {
188
+        $this->processResources($routes['resources'] ?? []);
189
+    }
190
+
191
+    /**
192
+     * For a given name and url restful routes are created:
193
+     *  - index
194
+     *  - show
195
+     *  - create
196
+     *  - update
197
+     *  - destroy
198
+     *
199
+     * @param array $resources
200
+     * @param string $routeNamePrefix
201
+     */
202
+    protected function processResources(array $resources, string $routeNamePrefix = ''): void {
203
+        // declaration of all restful actions
204
+        $actions = [
205
+            ['name' => 'index', 'verb' => 'GET', 'on-collection' => true],
206
+            ['name' => 'show', 'verb' => 'GET'],
207
+            ['name' => 'create', 'verb' => 'POST', 'on-collection' => true],
208
+            ['name' => 'update', 'verb' => 'PUT'],
209
+            ['name' => 'destroy', 'verb' => 'DELETE'],
210
+        ];
211
+
212
+        foreach ($resources as $resource => $config) {
213
+            $root = $this->buildRootPrefix($config, $routeNamePrefix);
214
+
215
+            // the url parameter used as id to the resource
216
+            foreach ($actions as $action) {
217
+                $url = $root . '/' . ltrim($config['url'], '/');
218
+                $method = $action['name'];
219
+
220
+                $verb = strtoupper($action['verb'] ?? 'GET');
221
+                $collectionAction = $action['on-collection'] ?? false;
222
+                if (!$collectionAction) {
223
+                    $url .= '/{id}';
224
+                }
225
+                if (isset($action['url-postfix'])) {
226
+                    $url .= '/' . $action['url-postfix'];
227
+                }
228
+
229
+                $controller = $resource;
230
+
231
+                $controllerName = $this->buildControllerName($controller);
232
+                $actionName = $this->buildActionName($method);
233
+
234
+                $routeName = $routeNamePrefix . $this->appName . '.' . strtolower($resource) . '.' . $method;
235
+
236
+                $route = $this->router->create($routeName, $url)
237
+                    ->method($verb);
238
+
239
+                $route->defaults(['caller' => [$this->appName, $controllerName, $actionName]]);
240
+            }
241
+        }
242
+    }
243
+
244
+    private function buildRootPrefix(array $route, string $routeNamePrefix): string {
245
+        $defaultRoot = $this->appName === 'core' ? '' : '/apps/' . $this->appName;
246
+        $root = $route['root'] ?? $defaultRoot;
247
+
248
+        if ($routeNamePrefix !== '') {
249
+            // In OCS all apps are whitelisted
250
+            return $root;
251
+        }
252
+
253
+        if (!\in_array($this->appName, $this->rootUrlApps, true)) {
254
+            // Only allow root URLS for some apps
255
+            return  $defaultRoot;
256
+        }
257
+
258
+        return $root;
259
+    }
260
+
261
+    /**
262
+     * Based on a given route name the controller name is generated
263
+     * @param string $controller
264
+     * @return string
265
+     */
266
+    private function buildControllerName(string $controller): string {
267
+        if (!isset($this->controllerNameCache[$controller])) {
268
+            $this->controllerNameCache[$controller] = $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller';
269
+        }
270
+        return $this->controllerNameCache[$controller];
271
+    }
272
+
273
+    /**
274
+     * Based on the action part of the route name the controller method name is generated
275
+     * @param string $action
276
+     * @return string
277
+     */
278
+    private function buildActionName(string $action): string {
279
+        return $this->underScoreToCamelCase($action);
280
+    }
281
+
282
+    /**
283
+     * Underscored strings are converted to camel case strings
284
+     * @param string $str
285
+     * @return string
286
+     */
287
+    private function underScoreToCamelCase(string $str): string {
288
+        $pattern = '/_[a-z]?/';
289
+        return preg_replace_callback(
290
+            $pattern,
291
+            function ($matches) {
292
+                return strtoupper(ltrim($matches[0], '_'));
293
+            },
294
+            $str);
295
+    }
296 296
 }
Please login to merge, or discard this patch.
Spacing   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -91,7 +91,7 @@  discard block
 block discarded – undo
91 91
 		 * OCS routes go into a different collection
92 92
 		 */
93 93
 		$oldCollection = $this->router->getCurrentCollection();
94
-		$this->router->useCollection($oldCollection . '.ocs');
94
+		$this->router->useCollection($oldCollection.'.ocs');
95 95
 
96 96
 		// parse ocs simple routes
97 97
 		$this->processOCS($this->routes);
@@ -126,7 +126,7 @@  discard block
 block discarded – undo
126 126
 		$postfix = $route['postfix'] ?? '';
127 127
 		$root = $this->buildRootPrefix($route, $routeNamePrefix);
128 128
 
129
-		$url = $root . '/' . ltrim($route['url'], '/');
129
+		$url = $root.'/'.ltrim($route['url'], '/');
130 130
 		$verb = strtoupper($route['verb'] ?? 'GET');
131 131
 
132 132
 		$split = explode('#', $name, 2);
@@ -138,7 +138,7 @@  discard block
 block discarded – undo
138 138
 		$controllerName = $this->buildControllerName($controller);
139 139
 		$actionName = $this->buildActionName($action);
140 140
 
141
-		$routeName = $routeNamePrefix . $this->appName . '.' . $controller . '.' . $action . $postfix;
141
+		$routeName = $routeNamePrefix.$this->appName.'.'.$controller.'.'.$action.$postfix;
142 142
 
143 143
 		$router = $this->router->create($routeName, $url)
144 144
 			->method($verb);
@@ -214,7 +214,7 @@  discard block
 block discarded – undo
214 214
 
215 215
 			// the url parameter used as id to the resource
216 216
 			foreach ($actions as $action) {
217
-				$url = $root . '/' . ltrim($config['url'], '/');
217
+				$url = $root.'/'.ltrim($config['url'], '/');
218 218
 				$method = $action['name'];
219 219
 
220 220
 				$verb = strtoupper($action['verb'] ?? 'GET');
@@ -223,7 +223,7 @@  discard block
 block discarded – undo
223 223
 					$url .= '/{id}';
224 224
 				}
225 225
 				if (isset($action['url-postfix'])) {
226
-					$url .= '/' . $action['url-postfix'];
226
+					$url .= '/'.$action['url-postfix'];
227 227
 				}
228 228
 
229 229
 				$controller = $resource;
@@ -231,7 +231,7 @@  discard block
 block discarded – undo
231 231
 				$controllerName = $this->buildControllerName($controller);
232 232
 				$actionName = $this->buildActionName($method);
233 233
 
234
-				$routeName = $routeNamePrefix . $this->appName . '.' . strtolower($resource) . '.' . $method;
234
+				$routeName = $routeNamePrefix.$this->appName.'.'.strtolower($resource).'.'.$method;
235 235
 
236 236
 				$route = $this->router->create($routeName, $url)
237 237
 					->method($verb);
@@ -242,7 +242,7 @@  discard block
 block discarded – undo
242 242
 	}
243 243
 
244 244
 	private function buildRootPrefix(array $route, string $routeNamePrefix): string {
245
-		$defaultRoot = $this->appName === 'core' ? '' : '/apps/' . $this->appName;
245
+		$defaultRoot = $this->appName === 'core' ? '' : '/apps/'.$this->appName;
246 246
 		$root = $route['root'] ?? $defaultRoot;
247 247
 
248 248
 		if ($routeNamePrefix !== '') {
@@ -265,7 +265,7 @@  discard block
 block discarded – undo
265 265
 	 */
266 266
 	private function buildControllerName(string $controller): string {
267 267
 		if (!isset($this->controllerNameCache[$controller])) {
268
-			$this->controllerNameCache[$controller] = $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller';
268
+			$this->controllerNameCache[$controller] = $this->underScoreToCamelCase(ucfirst($controller)).'Controller';
269 269
 		}
270 270
 		return $this->controllerNameCache[$controller];
271 271
 	}
@@ -288,7 +288,7 @@  discard block
 block discarded – undo
288 288
 		$pattern = '/_[a-z]?/';
289 289
 		return preg_replace_callback(
290 290
 			$pattern,
291
-			function ($matches) {
291
+			function($matches) {
292 292
 				return strtoupper(ltrim($matches[0], '_'));
293 293
 			},
294 294
 			$str);
Please login to merge, or discard this patch.
lib/private/Collaboration/Collaborators/Search.php 2 patches
Indentation   +63 added lines, -63 removed lines patch added patch discarded remove patch
@@ -35,79 +35,79 @@
 block discarded – undo
35 35
 use OCP\Share;
36 36
 
37 37
 class Search implements ISearch {
38
-	/** @var IContainer */
39
-	private $c;
38
+    /** @var IContainer */
39
+    private $c;
40 40
 
41
-	protected $pluginList = [];
41
+    protected $pluginList = [];
42 42
 
43
-	public function __construct(IContainer $c) {
44
-		$this->c = $c;
45
-	}
43
+    public function __construct(IContainer $c) {
44
+        $this->c = $c;
45
+    }
46 46
 
47
-	/**
48
-	 * @param string $search
49
-	 * @param array $shareTypes
50
-	 * @param bool $lookup
51
-	 * @param int|null $limit
52
-	 * @param int|null $offset
53
-	 * @return array
54
-	 * @throws \OCP\AppFramework\QueryException
55
-	 */
56
-	public function search($search, array $shareTypes, $lookup, $limit, $offset) {
57
-		$hasMoreResults = false;
47
+    /**
48
+     * @param string $search
49
+     * @param array $shareTypes
50
+     * @param bool $lookup
51
+     * @param int|null $limit
52
+     * @param int|null $offset
53
+     * @return array
54
+     * @throws \OCP\AppFramework\QueryException
55
+     */
56
+    public function search($search, array $shareTypes, $lookup, $limit, $offset) {
57
+        $hasMoreResults = false;
58 58
 
59
-		// Trim leading and trailing whitespace characters, e.g. when query is copy-pasted
60
-		$search = trim($search);
59
+        // Trim leading and trailing whitespace characters, e.g. when query is copy-pasted
60
+        $search = trim($search);
61 61
 
62
-		/** @var ISearchResult $searchResult */
63
-		$searchResult = $this->c->resolve(SearchResult::class);
62
+        /** @var ISearchResult $searchResult */
63
+        $searchResult = $this->c->resolve(SearchResult::class);
64 64
 
65
-		foreach ($shareTypes as $type) {
66
-			if (!isset($this->pluginList[$type])) {
67
-				continue;
68
-			}
69
-			foreach ($this->pluginList[$type] as $plugin) {
70
-				/** @var ISearchPlugin $searchPlugin */
71
-				$searchPlugin = $this->c->resolve($plugin);
72
-				$hasMoreResults = $searchPlugin->search($search, $limit, $offset, $searchResult) || $hasMoreResults;
73
-			}
74
-		}
65
+        foreach ($shareTypes as $type) {
66
+            if (!isset($this->pluginList[$type])) {
67
+                continue;
68
+            }
69
+            foreach ($this->pluginList[$type] as $plugin) {
70
+                /** @var ISearchPlugin $searchPlugin */
71
+                $searchPlugin = $this->c->resolve($plugin);
72
+                $hasMoreResults = $searchPlugin->search($search, $limit, $offset, $searchResult) || $hasMoreResults;
73
+            }
74
+        }
75 75
 
76
-		// Get from lookup server, not a separate share type
77
-		if ($lookup) {
78
-			$searchPlugin = $this->c->resolve(LookupPlugin::class);
79
-			$hasMoreResults = $searchPlugin->search($search, $limit, $offset, $searchResult) || $hasMoreResults;
80
-		}
76
+        // Get from lookup server, not a separate share type
77
+        if ($lookup) {
78
+            $searchPlugin = $this->c->resolve(LookupPlugin::class);
79
+            $hasMoreResults = $searchPlugin->search($search, $limit, $offset, $searchResult) || $hasMoreResults;
80
+        }
81 81
 
82
-		// sanitizing, could go into the plugins as well
82
+        // sanitizing, could go into the plugins as well
83 83
 
84
-		// if we have a exact match, either for the federated cloud id or for the
85
-		// email address we only return the exact match. It is highly unlikely
86
-		// that the exact same email address and federated cloud id exists
87
-		$emailType = new SearchResultType('emails');
88
-		$remoteType = new SearchResultType('remotes');
89
-		if ($searchResult->hasExactIdMatch($emailType) && !$searchResult->hasExactIdMatch($remoteType)) {
90
-			$searchResult->unsetResult($remoteType);
91
-		} elseif (!$searchResult->hasExactIdMatch($emailType) && $searchResult->hasExactIdMatch($remoteType)) {
92
-			$searchResult->unsetResult($emailType);
93
-		}
84
+        // if we have a exact match, either for the federated cloud id or for the
85
+        // email address we only return the exact match. It is highly unlikely
86
+        // that the exact same email address and federated cloud id exists
87
+        $emailType = new SearchResultType('emails');
88
+        $remoteType = new SearchResultType('remotes');
89
+        if ($searchResult->hasExactIdMatch($emailType) && !$searchResult->hasExactIdMatch($remoteType)) {
90
+            $searchResult->unsetResult($remoteType);
91
+        } elseif (!$searchResult->hasExactIdMatch($emailType) && $searchResult->hasExactIdMatch($remoteType)) {
92
+            $searchResult->unsetResult($emailType);
93
+        }
94 94
 
95
-		// if we have an exact local user match with an email-a-like query,
96
-		// there is no need to show the remote and email matches.
97
-		$userType = new SearchResultType('users');
98
-		if (strpos($search, '@') !== false && $searchResult->hasExactIdMatch($userType)) {
99
-			$searchResult->unsetResult($remoteType);
100
-			$searchResult->unsetResult($emailType);
101
-		}
95
+        // if we have an exact local user match with an email-a-like query,
96
+        // there is no need to show the remote and email matches.
97
+        $userType = new SearchResultType('users');
98
+        if (strpos($search, '@') !== false && $searchResult->hasExactIdMatch($userType)) {
99
+            $searchResult->unsetResult($remoteType);
100
+            $searchResult->unsetResult($emailType);
101
+        }
102 102
 
103
-		return [$searchResult->asArray(), $hasMoreResults];
104
-	}
103
+        return [$searchResult->asArray(), $hasMoreResults];
104
+    }
105 105
 
106
-	public function registerPlugin(array $pluginInfo) {
107
-		$shareType = constant(Share::class . '::' . $pluginInfo['shareType']);
108
-		if ($shareType === null) {
109
-			throw new \InvalidArgumentException('Provided ShareType is invalid');
110
-		}
111
-		$this->pluginList[$shareType][] = $pluginInfo['class'];
112
-	}
106
+    public function registerPlugin(array $pluginInfo) {
107
+        $shareType = constant(Share::class . '::' . $pluginInfo['shareType']);
108
+        if ($shareType === null) {
109
+            throw new \InvalidArgumentException('Provided ShareType is invalid');
110
+        }
111
+        $this->pluginList[$shareType][] = $pluginInfo['class'];
112
+    }
113 113
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -104,7 +104,7 @@
 block discarded – undo
104 104
 	}
105 105
 
106 106
 	public function registerPlugin(array $pluginInfo) {
107
-		$shareType = constant(Share::class . '::' . $pluginInfo['shareType']);
107
+		$shareType = constant(Share::class.'::'.$pluginInfo['shareType']);
108 108
 		if ($shareType === null) {
109 109
 			throw new \InvalidArgumentException('Provided ShareType is invalid');
110 110
 		}
Please login to merge, or discard this patch.
lib/private/L10N/Factory.php 2 patches
Indentation   +636 added lines, -636 removed lines patch added patch discarded remove patch
@@ -49,640 +49,640 @@
 block discarded – undo
49 49
  */
50 50
 class Factory implements IFactory {
51 51
 
52
-	/** @var string */
53
-	protected $requestLanguage = '';
54
-
55
-	/**
56
-	 * cached instances
57
-	 * @var array Structure: Lang => App => \OCP\IL10N
58
-	 */
59
-	protected $instances = [];
60
-
61
-	/**
62
-	 * @var array Structure: App => string[]
63
-	 */
64
-	protected $availableLanguages = [];
65
-
66
-	/**
67
-	 * @var array
68
-	 */
69
-	protected $localeCache = [];
70
-
71
-	/**
72
-	 * @var array
73
-	 */
74
-	protected $availableLocales = [];
75
-
76
-	/**
77
-	 * @var array Structure: string => callable
78
-	 */
79
-	protected $pluralFunctions = [];
80
-
81
-	public const COMMON_LANGUAGE_CODES = [
82
-		'en', 'es', 'fr', 'de', 'de_DE', 'ja', 'ar', 'ru', 'nl', 'it',
83
-		'pt_BR', 'pt_PT', 'da', 'fi_FI', 'nb_NO', 'sv', 'tr', 'zh_CN', 'ko'
84
-	];
85
-
86
-	/** @var IConfig */
87
-	protected $config;
88
-
89
-	/** @var IRequest */
90
-	protected $request;
91
-
92
-	/** @var IUserSession */
93
-	protected $userSession;
94
-
95
-	/** @var string */
96
-	protected $serverRoot;
97
-
98
-	/**
99
-	 * @param IConfig $config
100
-	 * @param IRequest $request
101
-	 * @param IUserSession $userSession
102
-	 * @param string $serverRoot
103
-	 */
104
-	public function __construct(IConfig $config,
105
-								IRequest $request,
106
-								IUserSession $userSession,
107
-								$serverRoot) {
108
-		$this->config = $config;
109
-		$this->request = $request;
110
-		$this->userSession = $userSession;
111
-		$this->serverRoot = $serverRoot;
112
-	}
113
-
114
-	/**
115
-	 * Get a language instance
116
-	 *
117
-	 * @param string $app
118
-	 * @param string|null $lang
119
-	 * @param string|null $locale
120
-	 * @return \OCP\IL10N
121
-	 */
122
-	public function get($app, $lang = null, $locale = null) {
123
-		return new LazyL10N(function () use ($app, $lang, $locale) {
124
-			$app = \OC_App::cleanAppId($app);
125
-			if ($lang !== null) {
126
-				$lang = str_replace(['\0', '/', '\\', '..'], '', $lang);
127
-			}
128
-
129
-			$forceLang = $this->config->getSystemValue('force_language', false);
130
-			if (is_string($forceLang)) {
131
-				$lang = $forceLang;
132
-			}
133
-
134
-			$forceLocale = $this->config->getSystemValue('force_locale', false);
135
-			if (is_string($forceLocale)) {
136
-				$locale = $forceLocale;
137
-			}
138
-
139
-			if ($lang === null || !$this->languageExists($app, $lang)) {
140
-				$lang = $this->findLanguage($app);
141
-			}
142
-
143
-			if ($locale === null || !$this->localeExists($locale)) {
144
-				$locale = $this->findLocale($lang);
145
-			}
146
-
147
-			if (!isset($this->instances[$lang][$app])) {
148
-				$this->instances[$lang][$app] = new L10N(
149
-					$this, $app, $lang, $locale,
150
-					$this->getL10nFilesForApp($app, $lang)
151
-				);
152
-			}
153
-
154
-			return $this->instances[$lang][$app];
155
-		});
156
-	}
157
-
158
-	/**
159
-	 * Find the best language
160
-	 *
161
-	 * @param string|null $app App id or null for core
162
-	 * @return string language If nothing works it returns 'en'
163
-	 */
164
-	public function findLanguage($app = null) {
165
-		$forceLang = $this->config->getSystemValue('force_language', false);
166
-		if (is_string($forceLang)) {
167
-			$this->requestLanguage = $forceLang;
168
-		}
169
-
170
-		if ($this->requestLanguage !== '' && $this->languageExists($app, $this->requestLanguage)) {
171
-			return $this->requestLanguage;
172
-		}
173
-
174
-		/**
175
-		 * At this point Nextcloud might not yet be installed and thus the lookup
176
-		 * in the preferences table might fail. For this reason we need to check
177
-		 * whether the instance has already been installed
178
-		 *
179
-		 * @link https://github.com/owncloud/core/issues/21955
180
-		 */
181
-		if ($this->config->getSystemValue('installed', false)) {
182
-			$userId = !is_null($this->userSession->getUser()) ? $this->userSession->getUser()->getUID() :  null;
183
-			if (!is_null($userId)) {
184
-				$userLang = $this->config->getUserValue($userId, 'core', 'lang', null);
185
-			} else {
186
-				$userLang = null;
187
-			}
188
-		} else {
189
-			$userId = null;
190
-			$userLang = null;
191
-		}
192
-
193
-		if ($userLang) {
194
-			$this->requestLanguage = $userLang;
195
-			if ($this->languageExists($app, $userLang)) {
196
-				return $userLang;
197
-			}
198
-		}
199
-
200
-		try {
201
-			// Try to get the language from the Request
202
-			$lang = $this->getLanguageFromRequest($app);
203
-			if ($userId !== null && $app === null && !$userLang) {
204
-				$this->config->setUserValue($userId, 'core', 'lang', $lang);
205
-			}
206
-			return $lang;
207
-		} catch (LanguageNotFoundException $e) {
208
-			// Finding language from request failed fall back to default language
209
-			$defaultLanguage = $this->config->getSystemValue('default_language', false);
210
-			if ($defaultLanguage !== false && $this->languageExists($app, $defaultLanguage)) {
211
-				return $defaultLanguage;
212
-			}
213
-		}
214
-
215
-		// We could not find any language so fall back to english
216
-		return 'en';
217
-	}
218
-
219
-	/**
220
-	 * find the best locale
221
-	 *
222
-	 * @param string $lang
223
-	 * @return null|string
224
-	 */
225
-	public function findLocale($lang = null) {
226
-		$forceLocale = $this->config->getSystemValue('force_locale', false);
227
-		if (is_string($forceLocale) && $this->localeExists($forceLocale)) {
228
-			return $forceLocale;
229
-		}
230
-
231
-		if ($this->config->getSystemValue('installed', false)) {
232
-			$userId = null !== $this->userSession->getUser() ? $this->userSession->getUser()->getUID() :  null;
233
-			$userLocale = null;
234
-			if (null !== $userId) {
235
-				$userLocale = $this->config->getUserValue($userId, 'core', 'locale', null);
236
-			}
237
-		} else {
238
-			$userId = null;
239
-			$userLocale = null;
240
-		}
241
-
242
-		if ($userLocale && $this->localeExists($userLocale)) {
243
-			return $userLocale;
244
-		}
245
-
246
-		// Default : use system default locale
247
-		$defaultLocale = $this->config->getSystemValue('default_locale', false);
248
-		if ($defaultLocale !== false && $this->localeExists($defaultLocale)) {
249
-			return $defaultLocale;
250
-		}
251
-
252
-		// If no user locale set, use lang as locale
253
-		if (null !== $lang && $this->localeExists($lang)) {
254
-			return $lang;
255
-		}
256
-
257
-		// At last, return USA
258
-		return 'en_US';
259
-	}
260
-
261
-	/**
262
-	 * find the matching lang from the locale
263
-	 *
264
-	 * @param string $app
265
-	 * @param string $locale
266
-	 * @return null|string
267
-	 */
268
-	public function findLanguageFromLocale(string $app = 'core', string $locale = null) {
269
-		if ($this->languageExists($app, $locale)) {
270
-			return $locale;
271
-		}
272
-
273
-		// Try to split e.g: fr_FR => fr
274
-		$locale = explode('_', $locale)[0];
275
-		if ($this->languageExists($app, $locale)) {
276
-			return $locale;
277
-		}
278
-	}
279
-
280
-	/**
281
-	 * Find all available languages for an app
282
-	 *
283
-	 * @param string|null $app App id or null for core
284
-	 * @return array an array of available languages
285
-	 */
286
-	public function findAvailableLanguages($app = null) {
287
-		$key = $app;
288
-		if ($key === null) {
289
-			$key = 'null';
290
-		}
291
-
292
-		// also works with null as key
293
-		if (!empty($this->availableLanguages[$key])) {
294
-			return $this->availableLanguages[$key];
295
-		}
296
-
297
-		$available = ['en']; //english is always available
298
-		$dir = $this->findL10nDir($app);
299
-		if (is_dir($dir)) {
300
-			$files = scandir($dir);
301
-			if ($files !== false) {
302
-				foreach ($files as $file) {
303
-					if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') {
304
-						$available[] = substr($file, 0, -5);
305
-					}
306
-				}
307
-			}
308
-		}
309
-
310
-		// merge with translations from theme
311
-		$theme = $this->config->getSystemValue('theme');
312
-		if (!empty($theme)) {
313
-			$themeDir = $this->serverRoot . '/themes/' . $theme . substr($dir, strlen($this->serverRoot));
314
-
315
-			if (is_dir($themeDir)) {
316
-				$files = scandir($themeDir);
317
-				if ($files !== false) {
318
-					foreach ($files as $file) {
319
-						if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') {
320
-							$available[] = substr($file, 0, -5);
321
-						}
322
-					}
323
-				}
324
-			}
325
-		}
326
-
327
-		$this->availableLanguages[$key] = $available;
328
-		return $available;
329
-	}
330
-
331
-	/**
332
-	 * @return array|mixed
333
-	 */
334
-	public function findAvailableLocales() {
335
-		if (!empty($this->availableLocales)) {
336
-			return $this->availableLocales;
337
-		}
338
-
339
-		$localeData = file_get_contents(\OC::$SERVERROOT . '/resources/locales.json');
340
-		$this->availableLocales = \json_decode($localeData, true);
341
-
342
-		return $this->availableLocales;
343
-	}
344
-
345
-	/**
346
-	 * @param string|null $app App id or null for core
347
-	 * @param string $lang
348
-	 * @return bool
349
-	 */
350
-	public function languageExists($app, $lang) {
351
-		if ($lang === 'en') {//english is always available
352
-			return true;
353
-		}
354
-
355
-		$languages = $this->findAvailableLanguages($app);
356
-		return array_search($lang, $languages) !== false;
357
-	}
358
-
359
-	public function getLanguageIterator(IUser $user = null): ILanguageIterator {
360
-		$user = $user ?? $this->userSession->getUser();
361
-		if ($user === null) {
362
-			throw new \RuntimeException('Failed to get an IUser instance');
363
-		}
364
-		return new LanguageIterator($user, $this->config);
365
-	}
366
-
367
-	/**
368
-	 * Return the language to use when sending something to a user
369
-	 *
370
-	 * @param IUser|null $user
371
-	 * @return string
372
-	 * @since 20.0.0
373
-	 */
374
-	public function getUserLanguage(IUser $user = null): string {
375
-		$language = $this->config->getSystemValue('force_language', false);
376
-		if ($language !== false) {
377
-			return $language;
378
-		}
379
-
380
-		if ($user instanceof IUser) {
381
-			$language = $this->config->getUserValue($user->getUID(), 'core', 'lang', null);
382
-			if ($language !== null) {
383
-				return $language;
384
-			}
385
-		}
386
-
387
-		return $this->config->getSystemValue('default_language', 'en');
388
-	}
389
-
390
-	/**
391
-	 * @param string $locale
392
-	 * @return bool
393
-	 */
394
-	public function localeExists($locale) {
395
-		if ($locale === 'en') { //english is always available
396
-			return true;
397
-		}
398
-
399
-		if ($this->localeCache === []) {
400
-			$locales = $this->findAvailableLocales();
401
-			foreach ($locales as $l) {
402
-				$this->localeCache[$l['code']] = true;
403
-			}
404
-		}
405
-
406
-		return isset($this->localeCache[$locale]);
407
-	}
408
-
409
-	/**
410
-	 * @param string|null $app
411
-	 * @return string
412
-	 * @throws LanguageNotFoundException
413
-	 */
414
-	private function getLanguageFromRequest($app) {
415
-		$header = $this->request->getHeader('ACCEPT_LANGUAGE');
416
-		if ($header !== '') {
417
-			$available = $this->findAvailableLanguages($app);
418
-
419
-			// E.g. make sure that 'de' is before 'de_DE'.
420
-			sort($available);
421
-
422
-			$preferences = preg_split('/,\s*/', strtolower($header));
423
-			foreach ($preferences as $preference) {
424
-				list($preferred_language) = explode(';', $preference);
425
-				$preferred_language = str_replace('-', '_', $preferred_language);
426
-
427
-				foreach ($available as $available_language) {
428
-					if ($preferred_language === strtolower($available_language)) {
429
-						return $this->respectDefaultLanguage($app, $available_language);
430
-					}
431
-				}
432
-
433
-				// Fallback from de_De to de
434
-				foreach ($available as $available_language) {
435
-					if (substr($preferred_language, 0, 2) === $available_language) {
436
-						return $available_language;
437
-					}
438
-				}
439
-			}
440
-		}
441
-
442
-		throw new LanguageNotFoundException();
443
-	}
444
-
445
-	/**
446
-	 * if default language is set to de_DE (formal German) this should be
447
-	 * preferred to 'de' (non-formal German) if possible
448
-	 *
449
-	 * @param string|null $app
450
-	 * @param string $lang
451
-	 * @return string
452
-	 */
453
-	protected function respectDefaultLanguage($app, $lang) {
454
-		$result = $lang;
455
-		$defaultLanguage = $this->config->getSystemValue('default_language', false);
456
-
457
-		// use formal version of german ("Sie" instead of "Du") if the default
458
-		// language is set to 'de_DE' if possible
459
-		if (is_string($defaultLanguage) &&
460
-			strtolower($lang) === 'de' &&
461
-			strtolower($defaultLanguage) === 'de_de' &&
462
-			$this->languageExists($app, 'de_DE')
463
-		) {
464
-			$result = 'de_DE';
465
-		}
466
-
467
-		return $result;
468
-	}
469
-
470
-	/**
471
-	 * Checks if $sub is a subdirectory of $parent
472
-	 *
473
-	 * @param string $sub
474
-	 * @param string $parent
475
-	 * @return bool
476
-	 */
477
-	private function isSubDirectory($sub, $parent) {
478
-		// Check whether $sub contains no ".."
479
-		if (strpos($sub, '..') !== false) {
480
-			return false;
481
-		}
482
-
483
-		// Check whether $sub is a subdirectory of $parent
484
-		if (strpos($sub, $parent) === 0) {
485
-			return true;
486
-		}
487
-
488
-		return false;
489
-	}
490
-
491
-	/**
492
-	 * Get a list of language files that should be loaded
493
-	 *
494
-	 * @param string $app
495
-	 * @param string $lang
496
-	 * @return string[]
497
-	 */
498
-	// FIXME This method is only public, until OC_L10N does not need it anymore,
499
-	// FIXME This is also the reason, why it is not in the public interface
500
-	public function getL10nFilesForApp($app, $lang) {
501
-		$languageFiles = [];
502
-
503
-		$i18nDir = $this->findL10nDir($app);
504
-		$transFile = strip_tags($i18nDir) . strip_tags($lang) . '.json';
505
-
506
-		if (($this->isSubDirectory($transFile, $this->serverRoot . '/core/l10n/')
507
-				|| $this->isSubDirectory($transFile, $this->serverRoot . '/lib/l10n/')
508
-				|| $this->isSubDirectory($transFile, \OC_App::getAppPath($app) . '/l10n/')
509
-			)
510
-			&& file_exists($transFile)) {
511
-			// load the translations file
512
-			$languageFiles[] = $transFile;
513
-		}
514
-
515
-		// merge with translations from theme
516
-		$theme = $this->config->getSystemValue('theme');
517
-		if (!empty($theme)) {
518
-			$transFile = $this->serverRoot . '/themes/' . $theme . substr($transFile, strlen($this->serverRoot));
519
-			if (file_exists($transFile)) {
520
-				$languageFiles[] = $transFile;
521
-			}
522
-		}
523
-
524
-		return $languageFiles;
525
-	}
526
-
527
-	/**
528
-	 * find the l10n directory
529
-	 *
530
-	 * @param string $app App id or empty string for core
531
-	 * @return string directory
532
-	 */
533
-	protected function findL10nDir($app = null) {
534
-		if (in_array($app, ['core', 'lib'])) {
535
-			if (file_exists($this->serverRoot . '/' . $app . '/l10n/')) {
536
-				return $this->serverRoot . '/' . $app . '/l10n/';
537
-			}
538
-		} elseif ($app && \OC_App::getAppPath($app) !== false) {
539
-			// Check if the app is in the app folder
540
-			return \OC_App::getAppPath($app) . '/l10n/';
541
-		}
542
-		return $this->serverRoot . '/core/l10n/';
543
-	}
544
-
545
-
546
-	/**
547
-	 * Creates a function from the plural string
548
-	 *
549
-	 * Parts of the code is copied from Habari:
550
-	 * https://github.com/habari/system/blob/master/classes/locale.php
551
-	 * @param string $string
552
-	 * @return string
553
-	 */
554
-	public function createPluralFunction($string) {
555
-		if (isset($this->pluralFunctions[$string])) {
556
-			return $this->pluralFunctions[$string];
557
-		}
558
-
559
-		if (preg_match('/^\s*nplurals\s*=\s*(\d+)\s*;\s*plural=(.*)$/u', $string, $matches)) {
560
-			// sanitize
561
-			$nplurals = preg_replace('/[^0-9]/', '', $matches[1]);
562
-			$plural = preg_replace('#[^n0-9:\(\)\?\|\&=!<>+*/\%-]#', '', $matches[2]);
563
-
564
-			$body = str_replace(
565
-				[ 'plural', 'n', '$n$plurals', ],
566
-				[ '$plural', '$n', '$nplurals', ],
567
-				'nplurals='. $nplurals . '; plural=' . $plural
568
-			);
569
-
570
-			// add parents
571
-			// important since PHP's ternary evaluates from left to right
572
-			$body .= ';';
573
-			$res = '';
574
-			$p = 0;
575
-			$length = strlen($body);
576
-			for ($i = 0; $i < $length; $i++) {
577
-				$ch = $body[$i];
578
-				switch ($ch) {
579
-					case '?':
580
-						$res .= ' ? (';
581
-						$p++;
582
-						break;
583
-					case ':':
584
-						$res .= ') : (';
585
-						break;
586
-					case ';':
587
-						$res .= str_repeat(')', $p) . ';';
588
-						$p = 0;
589
-						break;
590
-					default:
591
-						$res .= $ch;
592
-				}
593
-			}
594
-
595
-			$body = $res . 'return ($plural>=$nplurals?$nplurals-1:$plural);';
596
-			$function = create_function('$n', $body);
597
-			$this->pluralFunctions[$string] = $function;
598
-			return $function;
599
-		} else {
600
-			// default: one plural form for all cases but n==1 (english)
601
-			$function = create_function(
602
-				'$n',
603
-				'$nplurals=2;$plural=($n==1?0:1);return ($plural>=$nplurals?$nplurals-1:$plural);'
604
-			);
605
-			$this->pluralFunctions[$string] = $function;
606
-			return $function;
607
-		}
608
-	}
609
-
610
-	/**
611
-	 * returns the common language and other languages in an
612
-	 * associative array
613
-	 *
614
-	 * @return array
615
-	 */
616
-	public function getLanguages() {
617
-		$forceLanguage = $this->config->getSystemValue('force_language', false);
618
-		if ($forceLanguage !== false) {
619
-			$l = $this->get('lib', $forceLanguage);
620
-			$potentialName = $l->t('__language_name__');
621
-
622
-			return [
623
-				'commonlanguages' => [[
624
-					'code' => $forceLanguage,
625
-					'name' => $potentialName,
626
-				]],
627
-				'languages' => [],
628
-			];
629
-		}
630
-
631
-		$languageCodes = $this->findAvailableLanguages();
632
-
633
-		$commonLanguages = [];
634
-		$languages = [];
635
-
636
-		foreach ($languageCodes as $lang) {
637
-			$l = $this->get('lib', $lang);
638
-			// TRANSLATORS this is the language name for the language switcher in the personal settings and should be the localized version
639
-			$potentialName = $l->t('__language_name__');
640
-			if ($l->getLanguageCode() === $lang && $potentialName[0] !== '_') {//first check if the language name is in the translation file
641
-				$ln = [
642
-					'code' => $lang,
643
-					'name' => $potentialName
644
-				];
645
-			} elseif ($lang === 'en') {
646
-				$ln = [
647
-					'code' => $lang,
648
-					'name' => 'English (US)'
649
-				];
650
-			} else {//fallback to language code
651
-				$ln = [
652
-					'code' => $lang,
653
-					'name' => $lang
654
-				];
655
-			}
656
-
657
-			// put appropriate languages into appropriate arrays, to print them sorted
658
-			// common languages -> divider -> other languages
659
-			if (in_array($lang, self::COMMON_LANGUAGE_CODES)) {
660
-				$commonLanguages[array_search($lang, self::COMMON_LANGUAGE_CODES)] = $ln;
661
-			} else {
662
-				$languages[] = $ln;
663
-			}
664
-		}
665
-
666
-		ksort($commonLanguages);
667
-
668
-		// sort now by displayed language not the iso-code
669
-		usort($languages, function ($a, $b) {
670
-			if ($a['code'] === $a['name'] && $b['code'] !== $b['name']) {
671
-				// If a doesn't have a name, but b does, list b before a
672
-				return 1;
673
-			}
674
-			if ($a['code'] !== $a['name'] && $b['code'] === $b['name']) {
675
-				// If a does have a name, but b doesn't, list a before b
676
-				return -1;
677
-			}
678
-			// Otherwise compare the names
679
-			return strcmp($a['name'], $b['name']);
680
-		});
681
-
682
-		return [
683
-			// reset indexes
684
-			'commonlanguages' => array_values($commonLanguages),
685
-			'languages' => $languages
686
-		];
687
-	}
52
+    /** @var string */
53
+    protected $requestLanguage = '';
54
+
55
+    /**
56
+     * cached instances
57
+     * @var array Structure: Lang => App => \OCP\IL10N
58
+     */
59
+    protected $instances = [];
60
+
61
+    /**
62
+     * @var array Structure: App => string[]
63
+     */
64
+    protected $availableLanguages = [];
65
+
66
+    /**
67
+     * @var array
68
+     */
69
+    protected $localeCache = [];
70
+
71
+    /**
72
+     * @var array
73
+     */
74
+    protected $availableLocales = [];
75
+
76
+    /**
77
+     * @var array Structure: string => callable
78
+     */
79
+    protected $pluralFunctions = [];
80
+
81
+    public const COMMON_LANGUAGE_CODES = [
82
+        'en', 'es', 'fr', 'de', 'de_DE', 'ja', 'ar', 'ru', 'nl', 'it',
83
+        'pt_BR', 'pt_PT', 'da', 'fi_FI', 'nb_NO', 'sv', 'tr', 'zh_CN', 'ko'
84
+    ];
85
+
86
+    /** @var IConfig */
87
+    protected $config;
88
+
89
+    /** @var IRequest */
90
+    protected $request;
91
+
92
+    /** @var IUserSession */
93
+    protected $userSession;
94
+
95
+    /** @var string */
96
+    protected $serverRoot;
97
+
98
+    /**
99
+     * @param IConfig $config
100
+     * @param IRequest $request
101
+     * @param IUserSession $userSession
102
+     * @param string $serverRoot
103
+     */
104
+    public function __construct(IConfig $config,
105
+                                IRequest $request,
106
+                                IUserSession $userSession,
107
+                                $serverRoot) {
108
+        $this->config = $config;
109
+        $this->request = $request;
110
+        $this->userSession = $userSession;
111
+        $this->serverRoot = $serverRoot;
112
+    }
113
+
114
+    /**
115
+     * Get a language instance
116
+     *
117
+     * @param string $app
118
+     * @param string|null $lang
119
+     * @param string|null $locale
120
+     * @return \OCP\IL10N
121
+     */
122
+    public function get($app, $lang = null, $locale = null) {
123
+        return new LazyL10N(function () use ($app, $lang, $locale) {
124
+            $app = \OC_App::cleanAppId($app);
125
+            if ($lang !== null) {
126
+                $lang = str_replace(['\0', '/', '\\', '..'], '', $lang);
127
+            }
128
+
129
+            $forceLang = $this->config->getSystemValue('force_language', false);
130
+            if (is_string($forceLang)) {
131
+                $lang = $forceLang;
132
+            }
133
+
134
+            $forceLocale = $this->config->getSystemValue('force_locale', false);
135
+            if (is_string($forceLocale)) {
136
+                $locale = $forceLocale;
137
+            }
138
+
139
+            if ($lang === null || !$this->languageExists($app, $lang)) {
140
+                $lang = $this->findLanguage($app);
141
+            }
142
+
143
+            if ($locale === null || !$this->localeExists($locale)) {
144
+                $locale = $this->findLocale($lang);
145
+            }
146
+
147
+            if (!isset($this->instances[$lang][$app])) {
148
+                $this->instances[$lang][$app] = new L10N(
149
+                    $this, $app, $lang, $locale,
150
+                    $this->getL10nFilesForApp($app, $lang)
151
+                );
152
+            }
153
+
154
+            return $this->instances[$lang][$app];
155
+        });
156
+    }
157
+
158
+    /**
159
+     * Find the best language
160
+     *
161
+     * @param string|null $app App id or null for core
162
+     * @return string language If nothing works it returns 'en'
163
+     */
164
+    public function findLanguage($app = null) {
165
+        $forceLang = $this->config->getSystemValue('force_language', false);
166
+        if (is_string($forceLang)) {
167
+            $this->requestLanguage = $forceLang;
168
+        }
169
+
170
+        if ($this->requestLanguage !== '' && $this->languageExists($app, $this->requestLanguage)) {
171
+            return $this->requestLanguage;
172
+        }
173
+
174
+        /**
175
+         * At this point Nextcloud might not yet be installed and thus the lookup
176
+         * in the preferences table might fail. For this reason we need to check
177
+         * whether the instance has already been installed
178
+         *
179
+         * @link https://github.com/owncloud/core/issues/21955
180
+         */
181
+        if ($this->config->getSystemValue('installed', false)) {
182
+            $userId = !is_null($this->userSession->getUser()) ? $this->userSession->getUser()->getUID() :  null;
183
+            if (!is_null($userId)) {
184
+                $userLang = $this->config->getUserValue($userId, 'core', 'lang', null);
185
+            } else {
186
+                $userLang = null;
187
+            }
188
+        } else {
189
+            $userId = null;
190
+            $userLang = null;
191
+        }
192
+
193
+        if ($userLang) {
194
+            $this->requestLanguage = $userLang;
195
+            if ($this->languageExists($app, $userLang)) {
196
+                return $userLang;
197
+            }
198
+        }
199
+
200
+        try {
201
+            // Try to get the language from the Request
202
+            $lang = $this->getLanguageFromRequest($app);
203
+            if ($userId !== null && $app === null && !$userLang) {
204
+                $this->config->setUserValue($userId, 'core', 'lang', $lang);
205
+            }
206
+            return $lang;
207
+        } catch (LanguageNotFoundException $e) {
208
+            // Finding language from request failed fall back to default language
209
+            $defaultLanguage = $this->config->getSystemValue('default_language', false);
210
+            if ($defaultLanguage !== false && $this->languageExists($app, $defaultLanguage)) {
211
+                return $defaultLanguage;
212
+            }
213
+        }
214
+
215
+        // We could not find any language so fall back to english
216
+        return 'en';
217
+    }
218
+
219
+    /**
220
+     * find the best locale
221
+     *
222
+     * @param string $lang
223
+     * @return null|string
224
+     */
225
+    public function findLocale($lang = null) {
226
+        $forceLocale = $this->config->getSystemValue('force_locale', false);
227
+        if (is_string($forceLocale) && $this->localeExists($forceLocale)) {
228
+            return $forceLocale;
229
+        }
230
+
231
+        if ($this->config->getSystemValue('installed', false)) {
232
+            $userId = null !== $this->userSession->getUser() ? $this->userSession->getUser()->getUID() :  null;
233
+            $userLocale = null;
234
+            if (null !== $userId) {
235
+                $userLocale = $this->config->getUserValue($userId, 'core', 'locale', null);
236
+            }
237
+        } else {
238
+            $userId = null;
239
+            $userLocale = null;
240
+        }
241
+
242
+        if ($userLocale && $this->localeExists($userLocale)) {
243
+            return $userLocale;
244
+        }
245
+
246
+        // Default : use system default locale
247
+        $defaultLocale = $this->config->getSystemValue('default_locale', false);
248
+        if ($defaultLocale !== false && $this->localeExists($defaultLocale)) {
249
+            return $defaultLocale;
250
+        }
251
+
252
+        // If no user locale set, use lang as locale
253
+        if (null !== $lang && $this->localeExists($lang)) {
254
+            return $lang;
255
+        }
256
+
257
+        // At last, return USA
258
+        return 'en_US';
259
+    }
260
+
261
+    /**
262
+     * find the matching lang from the locale
263
+     *
264
+     * @param string $app
265
+     * @param string $locale
266
+     * @return null|string
267
+     */
268
+    public function findLanguageFromLocale(string $app = 'core', string $locale = null) {
269
+        if ($this->languageExists($app, $locale)) {
270
+            return $locale;
271
+        }
272
+
273
+        // Try to split e.g: fr_FR => fr
274
+        $locale = explode('_', $locale)[0];
275
+        if ($this->languageExists($app, $locale)) {
276
+            return $locale;
277
+        }
278
+    }
279
+
280
+    /**
281
+     * Find all available languages for an app
282
+     *
283
+     * @param string|null $app App id or null for core
284
+     * @return array an array of available languages
285
+     */
286
+    public function findAvailableLanguages($app = null) {
287
+        $key = $app;
288
+        if ($key === null) {
289
+            $key = 'null';
290
+        }
291
+
292
+        // also works with null as key
293
+        if (!empty($this->availableLanguages[$key])) {
294
+            return $this->availableLanguages[$key];
295
+        }
296
+
297
+        $available = ['en']; //english is always available
298
+        $dir = $this->findL10nDir($app);
299
+        if (is_dir($dir)) {
300
+            $files = scandir($dir);
301
+            if ($files !== false) {
302
+                foreach ($files as $file) {
303
+                    if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') {
304
+                        $available[] = substr($file, 0, -5);
305
+                    }
306
+                }
307
+            }
308
+        }
309
+
310
+        // merge with translations from theme
311
+        $theme = $this->config->getSystemValue('theme');
312
+        if (!empty($theme)) {
313
+            $themeDir = $this->serverRoot . '/themes/' . $theme . substr($dir, strlen($this->serverRoot));
314
+
315
+            if (is_dir($themeDir)) {
316
+                $files = scandir($themeDir);
317
+                if ($files !== false) {
318
+                    foreach ($files as $file) {
319
+                        if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') {
320
+                            $available[] = substr($file, 0, -5);
321
+                        }
322
+                    }
323
+                }
324
+            }
325
+        }
326
+
327
+        $this->availableLanguages[$key] = $available;
328
+        return $available;
329
+    }
330
+
331
+    /**
332
+     * @return array|mixed
333
+     */
334
+    public function findAvailableLocales() {
335
+        if (!empty($this->availableLocales)) {
336
+            return $this->availableLocales;
337
+        }
338
+
339
+        $localeData = file_get_contents(\OC::$SERVERROOT . '/resources/locales.json');
340
+        $this->availableLocales = \json_decode($localeData, true);
341
+
342
+        return $this->availableLocales;
343
+    }
344
+
345
+    /**
346
+     * @param string|null $app App id or null for core
347
+     * @param string $lang
348
+     * @return bool
349
+     */
350
+    public function languageExists($app, $lang) {
351
+        if ($lang === 'en') {//english is always available
352
+            return true;
353
+        }
354
+
355
+        $languages = $this->findAvailableLanguages($app);
356
+        return array_search($lang, $languages) !== false;
357
+    }
358
+
359
+    public function getLanguageIterator(IUser $user = null): ILanguageIterator {
360
+        $user = $user ?? $this->userSession->getUser();
361
+        if ($user === null) {
362
+            throw new \RuntimeException('Failed to get an IUser instance');
363
+        }
364
+        return new LanguageIterator($user, $this->config);
365
+    }
366
+
367
+    /**
368
+     * Return the language to use when sending something to a user
369
+     *
370
+     * @param IUser|null $user
371
+     * @return string
372
+     * @since 20.0.0
373
+     */
374
+    public function getUserLanguage(IUser $user = null): string {
375
+        $language = $this->config->getSystemValue('force_language', false);
376
+        if ($language !== false) {
377
+            return $language;
378
+        }
379
+
380
+        if ($user instanceof IUser) {
381
+            $language = $this->config->getUserValue($user->getUID(), 'core', 'lang', null);
382
+            if ($language !== null) {
383
+                return $language;
384
+            }
385
+        }
386
+
387
+        return $this->config->getSystemValue('default_language', 'en');
388
+    }
389
+
390
+    /**
391
+     * @param string $locale
392
+     * @return bool
393
+     */
394
+    public function localeExists($locale) {
395
+        if ($locale === 'en') { //english is always available
396
+            return true;
397
+        }
398
+
399
+        if ($this->localeCache === []) {
400
+            $locales = $this->findAvailableLocales();
401
+            foreach ($locales as $l) {
402
+                $this->localeCache[$l['code']] = true;
403
+            }
404
+        }
405
+
406
+        return isset($this->localeCache[$locale]);
407
+    }
408
+
409
+    /**
410
+     * @param string|null $app
411
+     * @return string
412
+     * @throws LanguageNotFoundException
413
+     */
414
+    private function getLanguageFromRequest($app) {
415
+        $header = $this->request->getHeader('ACCEPT_LANGUAGE');
416
+        if ($header !== '') {
417
+            $available = $this->findAvailableLanguages($app);
418
+
419
+            // E.g. make sure that 'de' is before 'de_DE'.
420
+            sort($available);
421
+
422
+            $preferences = preg_split('/,\s*/', strtolower($header));
423
+            foreach ($preferences as $preference) {
424
+                list($preferred_language) = explode(';', $preference);
425
+                $preferred_language = str_replace('-', '_', $preferred_language);
426
+
427
+                foreach ($available as $available_language) {
428
+                    if ($preferred_language === strtolower($available_language)) {
429
+                        return $this->respectDefaultLanguage($app, $available_language);
430
+                    }
431
+                }
432
+
433
+                // Fallback from de_De to de
434
+                foreach ($available as $available_language) {
435
+                    if (substr($preferred_language, 0, 2) === $available_language) {
436
+                        return $available_language;
437
+                    }
438
+                }
439
+            }
440
+        }
441
+
442
+        throw new LanguageNotFoundException();
443
+    }
444
+
445
+    /**
446
+     * if default language is set to de_DE (formal German) this should be
447
+     * preferred to 'de' (non-formal German) if possible
448
+     *
449
+     * @param string|null $app
450
+     * @param string $lang
451
+     * @return string
452
+     */
453
+    protected function respectDefaultLanguage($app, $lang) {
454
+        $result = $lang;
455
+        $defaultLanguage = $this->config->getSystemValue('default_language', false);
456
+
457
+        // use formal version of german ("Sie" instead of "Du") if the default
458
+        // language is set to 'de_DE' if possible
459
+        if (is_string($defaultLanguage) &&
460
+            strtolower($lang) === 'de' &&
461
+            strtolower($defaultLanguage) === 'de_de' &&
462
+            $this->languageExists($app, 'de_DE')
463
+        ) {
464
+            $result = 'de_DE';
465
+        }
466
+
467
+        return $result;
468
+    }
469
+
470
+    /**
471
+     * Checks if $sub is a subdirectory of $parent
472
+     *
473
+     * @param string $sub
474
+     * @param string $parent
475
+     * @return bool
476
+     */
477
+    private function isSubDirectory($sub, $parent) {
478
+        // Check whether $sub contains no ".."
479
+        if (strpos($sub, '..') !== false) {
480
+            return false;
481
+        }
482
+
483
+        // Check whether $sub is a subdirectory of $parent
484
+        if (strpos($sub, $parent) === 0) {
485
+            return true;
486
+        }
487
+
488
+        return false;
489
+    }
490
+
491
+    /**
492
+     * Get a list of language files that should be loaded
493
+     *
494
+     * @param string $app
495
+     * @param string $lang
496
+     * @return string[]
497
+     */
498
+    // FIXME This method is only public, until OC_L10N does not need it anymore,
499
+    // FIXME This is also the reason, why it is not in the public interface
500
+    public function getL10nFilesForApp($app, $lang) {
501
+        $languageFiles = [];
502
+
503
+        $i18nDir = $this->findL10nDir($app);
504
+        $transFile = strip_tags($i18nDir) . strip_tags($lang) . '.json';
505
+
506
+        if (($this->isSubDirectory($transFile, $this->serverRoot . '/core/l10n/')
507
+                || $this->isSubDirectory($transFile, $this->serverRoot . '/lib/l10n/')
508
+                || $this->isSubDirectory($transFile, \OC_App::getAppPath($app) . '/l10n/')
509
+            )
510
+            && file_exists($transFile)) {
511
+            // load the translations file
512
+            $languageFiles[] = $transFile;
513
+        }
514
+
515
+        // merge with translations from theme
516
+        $theme = $this->config->getSystemValue('theme');
517
+        if (!empty($theme)) {
518
+            $transFile = $this->serverRoot . '/themes/' . $theme . substr($transFile, strlen($this->serverRoot));
519
+            if (file_exists($transFile)) {
520
+                $languageFiles[] = $transFile;
521
+            }
522
+        }
523
+
524
+        return $languageFiles;
525
+    }
526
+
527
+    /**
528
+     * find the l10n directory
529
+     *
530
+     * @param string $app App id or empty string for core
531
+     * @return string directory
532
+     */
533
+    protected function findL10nDir($app = null) {
534
+        if (in_array($app, ['core', 'lib'])) {
535
+            if (file_exists($this->serverRoot . '/' . $app . '/l10n/')) {
536
+                return $this->serverRoot . '/' . $app . '/l10n/';
537
+            }
538
+        } elseif ($app && \OC_App::getAppPath($app) !== false) {
539
+            // Check if the app is in the app folder
540
+            return \OC_App::getAppPath($app) . '/l10n/';
541
+        }
542
+        return $this->serverRoot . '/core/l10n/';
543
+    }
544
+
545
+
546
+    /**
547
+     * Creates a function from the plural string
548
+     *
549
+     * Parts of the code is copied from Habari:
550
+     * https://github.com/habari/system/blob/master/classes/locale.php
551
+     * @param string $string
552
+     * @return string
553
+     */
554
+    public function createPluralFunction($string) {
555
+        if (isset($this->pluralFunctions[$string])) {
556
+            return $this->pluralFunctions[$string];
557
+        }
558
+
559
+        if (preg_match('/^\s*nplurals\s*=\s*(\d+)\s*;\s*plural=(.*)$/u', $string, $matches)) {
560
+            // sanitize
561
+            $nplurals = preg_replace('/[^0-9]/', '', $matches[1]);
562
+            $plural = preg_replace('#[^n0-9:\(\)\?\|\&=!<>+*/\%-]#', '', $matches[2]);
563
+
564
+            $body = str_replace(
565
+                [ 'plural', 'n', '$n$plurals', ],
566
+                [ '$plural', '$n', '$nplurals', ],
567
+                'nplurals='. $nplurals . '; plural=' . $plural
568
+            );
569
+
570
+            // add parents
571
+            // important since PHP's ternary evaluates from left to right
572
+            $body .= ';';
573
+            $res = '';
574
+            $p = 0;
575
+            $length = strlen($body);
576
+            for ($i = 0; $i < $length; $i++) {
577
+                $ch = $body[$i];
578
+                switch ($ch) {
579
+                    case '?':
580
+                        $res .= ' ? (';
581
+                        $p++;
582
+                        break;
583
+                    case ':':
584
+                        $res .= ') : (';
585
+                        break;
586
+                    case ';':
587
+                        $res .= str_repeat(')', $p) . ';';
588
+                        $p = 0;
589
+                        break;
590
+                    default:
591
+                        $res .= $ch;
592
+                }
593
+            }
594
+
595
+            $body = $res . 'return ($plural>=$nplurals?$nplurals-1:$plural);';
596
+            $function = create_function('$n', $body);
597
+            $this->pluralFunctions[$string] = $function;
598
+            return $function;
599
+        } else {
600
+            // default: one plural form for all cases but n==1 (english)
601
+            $function = create_function(
602
+                '$n',
603
+                '$nplurals=2;$plural=($n==1?0:1);return ($plural>=$nplurals?$nplurals-1:$plural);'
604
+            );
605
+            $this->pluralFunctions[$string] = $function;
606
+            return $function;
607
+        }
608
+    }
609
+
610
+    /**
611
+     * returns the common language and other languages in an
612
+     * associative array
613
+     *
614
+     * @return array
615
+     */
616
+    public function getLanguages() {
617
+        $forceLanguage = $this->config->getSystemValue('force_language', false);
618
+        if ($forceLanguage !== false) {
619
+            $l = $this->get('lib', $forceLanguage);
620
+            $potentialName = $l->t('__language_name__');
621
+
622
+            return [
623
+                'commonlanguages' => [[
624
+                    'code' => $forceLanguage,
625
+                    'name' => $potentialName,
626
+                ]],
627
+                'languages' => [],
628
+            ];
629
+        }
630
+
631
+        $languageCodes = $this->findAvailableLanguages();
632
+
633
+        $commonLanguages = [];
634
+        $languages = [];
635
+
636
+        foreach ($languageCodes as $lang) {
637
+            $l = $this->get('lib', $lang);
638
+            // TRANSLATORS this is the language name for the language switcher in the personal settings and should be the localized version
639
+            $potentialName = $l->t('__language_name__');
640
+            if ($l->getLanguageCode() === $lang && $potentialName[0] !== '_') {//first check if the language name is in the translation file
641
+                $ln = [
642
+                    'code' => $lang,
643
+                    'name' => $potentialName
644
+                ];
645
+            } elseif ($lang === 'en') {
646
+                $ln = [
647
+                    'code' => $lang,
648
+                    'name' => 'English (US)'
649
+                ];
650
+            } else {//fallback to language code
651
+                $ln = [
652
+                    'code' => $lang,
653
+                    'name' => $lang
654
+                ];
655
+            }
656
+
657
+            // put appropriate languages into appropriate arrays, to print them sorted
658
+            // common languages -> divider -> other languages
659
+            if (in_array($lang, self::COMMON_LANGUAGE_CODES)) {
660
+                $commonLanguages[array_search($lang, self::COMMON_LANGUAGE_CODES)] = $ln;
661
+            } else {
662
+                $languages[] = $ln;
663
+            }
664
+        }
665
+
666
+        ksort($commonLanguages);
667
+
668
+        // sort now by displayed language not the iso-code
669
+        usort($languages, function ($a, $b) {
670
+            if ($a['code'] === $a['name'] && $b['code'] !== $b['name']) {
671
+                // If a doesn't have a name, but b does, list b before a
672
+                return 1;
673
+            }
674
+            if ($a['code'] !== $a['name'] && $b['code'] === $b['name']) {
675
+                // If a does have a name, but b doesn't, list a before b
676
+                return -1;
677
+            }
678
+            // Otherwise compare the names
679
+            return strcmp($a['name'], $b['name']);
680
+        });
681
+
682
+        return [
683
+            // reset indexes
684
+            'commonlanguages' => array_values($commonLanguages),
685
+            'languages' => $languages
686
+        ];
687
+    }
688 688
 }
Please login to merge, or discard this patch.
Spacing   +20 added lines, -20 removed lines patch added patch discarded remove patch
@@ -120,7 +120,7 @@  discard block
 block discarded – undo
120 120
 	 * @return \OCP\IL10N
121 121
 	 */
122 122
 	public function get($app, $lang = null, $locale = null) {
123
-		return new LazyL10N(function () use ($app, $lang, $locale) {
123
+		return new LazyL10N(function() use ($app, $lang, $locale) {
124 124
 			$app = \OC_App::cleanAppId($app);
125 125
 			if ($lang !== null) {
126 126
 				$lang = str_replace(['\0', '/', '\\', '..'], '', $lang);
@@ -179,7 +179,7 @@  discard block
 block discarded – undo
179 179
 		 * @link https://github.com/owncloud/core/issues/21955
180 180
 		 */
181 181
 		if ($this->config->getSystemValue('installed', false)) {
182
-			$userId = !is_null($this->userSession->getUser()) ? $this->userSession->getUser()->getUID() :  null;
182
+			$userId = !is_null($this->userSession->getUser()) ? $this->userSession->getUser()->getUID() : null;
183 183
 			if (!is_null($userId)) {
184 184
 				$userLang = $this->config->getUserValue($userId, 'core', 'lang', null);
185 185
 			} else {
@@ -229,7 +229,7 @@  discard block
 block discarded – undo
229 229
 		}
230 230
 
231 231
 		if ($this->config->getSystemValue('installed', false)) {
232
-			$userId = null !== $this->userSession->getUser() ? $this->userSession->getUser()->getUID() :  null;
232
+			$userId = null !== $this->userSession->getUser() ? $this->userSession->getUser()->getUID() : null;
233 233
 			$userLocale = null;
234 234
 			if (null !== $userId) {
235 235
 				$userLocale = $this->config->getUserValue($userId, 'core', 'locale', null);
@@ -310,7 +310,7 @@  discard block
 block discarded – undo
310 310
 		// merge with translations from theme
311 311
 		$theme = $this->config->getSystemValue('theme');
312 312
 		if (!empty($theme)) {
313
-			$themeDir = $this->serverRoot . '/themes/' . $theme . substr($dir, strlen($this->serverRoot));
313
+			$themeDir = $this->serverRoot.'/themes/'.$theme.substr($dir, strlen($this->serverRoot));
314 314
 
315 315
 			if (is_dir($themeDir)) {
316 316
 				$files = scandir($themeDir);
@@ -336,7 +336,7 @@  discard block
 block discarded – undo
336 336
 			return $this->availableLocales;
337 337
 		}
338 338
 
339
-		$localeData = file_get_contents(\OC::$SERVERROOT . '/resources/locales.json');
339
+		$localeData = file_get_contents(\OC::$SERVERROOT.'/resources/locales.json');
340 340
 		$this->availableLocales = \json_decode($localeData, true);
341 341
 
342 342
 		return $this->availableLocales;
@@ -501,11 +501,11 @@  discard block
 block discarded – undo
501 501
 		$languageFiles = [];
502 502
 
503 503
 		$i18nDir = $this->findL10nDir($app);
504
-		$transFile = strip_tags($i18nDir) . strip_tags($lang) . '.json';
504
+		$transFile = strip_tags($i18nDir).strip_tags($lang).'.json';
505 505
 
506
-		if (($this->isSubDirectory($transFile, $this->serverRoot . '/core/l10n/')
507
-				|| $this->isSubDirectory($transFile, $this->serverRoot . '/lib/l10n/')
508
-				|| $this->isSubDirectory($transFile, \OC_App::getAppPath($app) . '/l10n/')
506
+		if (($this->isSubDirectory($transFile, $this->serverRoot.'/core/l10n/')
507
+				|| $this->isSubDirectory($transFile, $this->serverRoot.'/lib/l10n/')
508
+				|| $this->isSubDirectory($transFile, \OC_App::getAppPath($app).'/l10n/')
509 509
 			)
510 510
 			&& file_exists($transFile)) {
511 511
 			// load the translations file
@@ -515,7 +515,7 @@  discard block
 block discarded – undo
515 515
 		// merge with translations from theme
516 516
 		$theme = $this->config->getSystemValue('theme');
517 517
 		if (!empty($theme)) {
518
-			$transFile = $this->serverRoot . '/themes/' . $theme . substr($transFile, strlen($this->serverRoot));
518
+			$transFile = $this->serverRoot.'/themes/'.$theme.substr($transFile, strlen($this->serverRoot));
519 519
 			if (file_exists($transFile)) {
520 520
 				$languageFiles[] = $transFile;
521 521
 			}
@@ -532,14 +532,14 @@  discard block
 block discarded – undo
532 532
 	 */
533 533
 	protected function findL10nDir($app = null) {
534 534
 		if (in_array($app, ['core', 'lib'])) {
535
-			if (file_exists($this->serverRoot . '/' . $app . '/l10n/')) {
536
-				return $this->serverRoot . '/' . $app . '/l10n/';
535
+			if (file_exists($this->serverRoot.'/'.$app.'/l10n/')) {
536
+				return $this->serverRoot.'/'.$app.'/l10n/';
537 537
 			}
538 538
 		} elseif ($app && \OC_App::getAppPath($app) !== false) {
539 539
 			// Check if the app is in the app folder
540
-			return \OC_App::getAppPath($app) . '/l10n/';
540
+			return \OC_App::getAppPath($app).'/l10n/';
541 541
 		}
542
-		return $this->serverRoot . '/core/l10n/';
542
+		return $this->serverRoot.'/core/l10n/';
543 543
 	}
544 544
 
545 545
 
@@ -562,9 +562,9 @@  discard block
 block discarded – undo
562 562
 			$plural = preg_replace('#[^n0-9:\(\)\?\|\&=!<>+*/\%-]#', '', $matches[2]);
563 563
 
564 564
 			$body = str_replace(
565
-				[ 'plural', 'n', '$n$plurals', ],
566
-				[ '$plural', '$n', '$nplurals', ],
567
-				'nplurals='. $nplurals . '; plural=' . $plural
565
+				['plural', 'n', '$n$plurals', ],
566
+				['$plural', '$n', '$nplurals', ],
567
+				'nplurals='.$nplurals.'; plural='.$plural
568 568
 			);
569 569
 
570 570
 			// add parents
@@ -584,7 +584,7 @@  discard block
 block discarded – undo
584 584
 						$res .= ') : (';
585 585
 						break;
586 586
 					case ';':
587
-						$res .= str_repeat(')', $p) . ';';
587
+						$res .= str_repeat(')', $p).';';
588 588
 						$p = 0;
589 589
 						break;
590 590
 					default:
@@ -592,7 +592,7 @@  discard block
 block discarded – undo
592 592
 				}
593 593
 			}
594 594
 
595
-			$body = $res . 'return ($plural>=$nplurals?$nplurals-1:$plural);';
595
+			$body = $res.'return ($plural>=$nplurals?$nplurals-1:$plural);';
596 596
 			$function = create_function('$n', $body);
597 597
 			$this->pluralFunctions[$string] = $function;
598 598
 			return $function;
@@ -666,7 +666,7 @@  discard block
 block discarded – undo
666 666
 		ksort($commonLanguages);
667 667
 
668 668
 		// sort now by displayed language not the iso-code
669
-		usort($languages, function ($a, $b) {
669
+		usort($languages, function($a, $b) {
670 670
 			if ($a['code'] === $a['name'] && $b['code'] !== $b['name']) {
671 671
 				// If a doesn't have a name, but b does, list b before a
672 672
 				return 1;
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Encoding.php 1 patch
Indentation   +502 added lines, -502 removed lines patch added patch discarded remove patch
@@ -40,506 +40,506 @@
 block discarded – undo
40 40
  */
41 41
 class Encoding extends Wrapper {
42 42
 
43
-	/**
44
-	 * @var ICache
45
-	 */
46
-	private $namesCache;
47
-
48
-	/**
49
-	 * @param array $parameters
50
-	 */
51
-	public function __construct($parameters) {
52
-		$this->storage = $parameters['storage'];
53
-		$this->namesCache = new CappedMemoryCache();
54
-	}
55
-
56
-	/**
57
-	 * Returns whether the given string is only made of ASCII characters
58
-	 *
59
-	 * @param string $str string
60
-	 *
61
-	 * @return bool true if the string is all ASCII, false otherwise
62
-	 */
63
-	private function isAscii($str) {
64
-		return !preg_match('/[\\x80-\\xff]+/', $str);
65
-	}
66
-
67
-	/**
68
-	 * Checks whether the given path exists in NFC or NFD form after checking
69
-	 * each form for each path section and returns the correct form.
70
-	 * If no existing path found, returns the path as it was given.
71
-	 *
72
-	 * @param string $fullPath path to check
73
-	 *
74
-	 * @return string original or converted path
75
-	 */
76
-	private function findPathToUse($fullPath) {
77
-		$cachedPath = $this->namesCache[$fullPath];
78
-		if ($cachedPath !== null) {
79
-			return $cachedPath;
80
-		}
81
-
82
-		$sections = explode('/', $fullPath);
83
-		$path = '';
84
-		foreach ($sections as $section) {
85
-			$convertedPath = $this->findPathToUseLastSection($path, $section);
86
-			if ($convertedPath === null) {
87
-				// no point in continuing if the section was not found, use original path
88
-				return $fullPath;
89
-			}
90
-			$path = $convertedPath . '/';
91
-		}
92
-		$path = rtrim($path, '/');
93
-		return $path;
94
-	}
95
-
96
-	/**
97
-	 * Checks whether the last path section of the given path exists in NFC or NFD form
98
-	 * and returns the correct form. If no existing path found, returns null.
99
-	 *
100
-	 * @param string $basePath base path to check
101
-	 * @param string $lastSection last section of the path to check for NFD/NFC variations
102
-	 *
103
-	 * @return string|null original or converted path, or null if none of the forms was found
104
-	 */
105
-	private function findPathToUseLastSection($basePath, $lastSection) {
106
-		$fullPath = $basePath . $lastSection;
107
-		if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) {
108
-			$this->namesCache[$fullPath] = $fullPath;
109
-			return $fullPath;
110
-		}
111
-
112
-		// swap encoding
113
-		if (\Normalizer::isNormalized($lastSection, \Normalizer::FORM_C)) {
114
-			$otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_D);
115
-		} else {
116
-			$otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C);
117
-		}
118
-		$otherFullPath = $basePath . $otherFormPath;
119
-		if ($this->storage->file_exists($otherFullPath)) {
120
-			$this->namesCache[$fullPath] = $otherFullPath;
121
-			return $otherFullPath;
122
-		}
123
-
124
-		// return original path, file did not exist at all
125
-		$this->namesCache[$fullPath] = $fullPath;
126
-		return null;
127
-	}
128
-
129
-	/**
130
-	 * see https://www.php.net/manual/en/function.mkdir.php
131
-	 *
132
-	 * @param string $path
133
-	 * @return bool
134
-	 */
135
-	public function mkdir($path) {
136
-		// note: no conversion here, method should not be called with non-NFC names!
137
-		$result = $this->storage->mkdir($path);
138
-		if ($result) {
139
-			$this->namesCache[$path] = $path;
140
-		}
141
-		return $result;
142
-	}
143
-
144
-	/**
145
-	 * see https://www.php.net/manual/en/function.rmdir.php
146
-	 *
147
-	 * @param string $path
148
-	 * @return bool
149
-	 */
150
-	public function rmdir($path) {
151
-		$result = $this->storage->rmdir($this->findPathToUse($path));
152
-		if ($result) {
153
-			unset($this->namesCache[$path]);
154
-		}
155
-		return $result;
156
-	}
157
-
158
-	/**
159
-	 * see https://www.php.net/manual/en/function.opendir.php
160
-	 *
161
-	 * @param string $path
162
-	 * @return resource|bool
163
-	 */
164
-	public function opendir($path) {
165
-		return $this->storage->opendir($this->findPathToUse($path));
166
-	}
167
-
168
-	/**
169
-	 * see https://www.php.net/manual/en/function.is_dir.php
170
-	 *
171
-	 * @param string $path
172
-	 * @return bool
173
-	 */
174
-	public function is_dir($path) {
175
-		return $this->storage->is_dir($this->findPathToUse($path));
176
-	}
177
-
178
-	/**
179
-	 * see https://www.php.net/manual/en/function.is_file.php
180
-	 *
181
-	 * @param string $path
182
-	 * @return bool
183
-	 */
184
-	public function is_file($path) {
185
-		return $this->storage->is_file($this->findPathToUse($path));
186
-	}
187
-
188
-	/**
189
-	 * see https://www.php.net/manual/en/function.stat.php
190
-	 * only the following keys are required in the result: size and mtime
191
-	 *
192
-	 * @param string $path
193
-	 * @return array|bool
194
-	 */
195
-	public function stat($path) {
196
-		return $this->storage->stat($this->findPathToUse($path));
197
-	}
198
-
199
-	/**
200
-	 * see https://www.php.net/manual/en/function.filetype.php
201
-	 *
202
-	 * @param string $path
203
-	 * @return string|bool
204
-	 */
205
-	public function filetype($path) {
206
-		return $this->storage->filetype($this->findPathToUse($path));
207
-	}
208
-
209
-	/**
210
-	 * see https://www.php.net/manual/en/function.filesize.php
211
-	 * The result for filesize when called on a folder is required to be 0
212
-	 *
213
-	 * @param string $path
214
-	 * @return int|bool
215
-	 */
216
-	public function filesize($path) {
217
-		return $this->storage->filesize($this->findPathToUse($path));
218
-	}
219
-
220
-	/**
221
-	 * check if a file can be created in $path
222
-	 *
223
-	 * @param string $path
224
-	 * @return bool
225
-	 */
226
-	public function isCreatable($path) {
227
-		return $this->storage->isCreatable($this->findPathToUse($path));
228
-	}
229
-
230
-	/**
231
-	 * check if a file can be read
232
-	 *
233
-	 * @param string $path
234
-	 * @return bool
235
-	 */
236
-	public function isReadable($path) {
237
-		return $this->storage->isReadable($this->findPathToUse($path));
238
-	}
239
-
240
-	/**
241
-	 * check if a file can be written to
242
-	 *
243
-	 * @param string $path
244
-	 * @return bool
245
-	 */
246
-	public function isUpdatable($path) {
247
-		return $this->storage->isUpdatable($this->findPathToUse($path));
248
-	}
249
-
250
-	/**
251
-	 * check if a file can be deleted
252
-	 *
253
-	 * @param string $path
254
-	 * @return bool
255
-	 */
256
-	public function isDeletable($path) {
257
-		return $this->storage->isDeletable($this->findPathToUse($path));
258
-	}
259
-
260
-	/**
261
-	 * check if a file can be shared
262
-	 *
263
-	 * @param string $path
264
-	 * @return bool
265
-	 */
266
-	public function isSharable($path) {
267
-		return $this->storage->isSharable($this->findPathToUse($path));
268
-	}
269
-
270
-	/**
271
-	 * get the full permissions of a path.
272
-	 * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
273
-	 *
274
-	 * @param string $path
275
-	 * @return int
276
-	 */
277
-	public function getPermissions($path) {
278
-		return $this->storage->getPermissions($this->findPathToUse($path));
279
-	}
280
-
281
-	/**
282
-	 * see https://www.php.net/manual/en/function.file_exists.php
283
-	 *
284
-	 * @param string $path
285
-	 * @return bool
286
-	 */
287
-	public function file_exists($path) {
288
-		return $this->storage->file_exists($this->findPathToUse($path));
289
-	}
290
-
291
-	/**
292
-	 * see https://www.php.net/manual/en/function.filemtime.php
293
-	 *
294
-	 * @param string $path
295
-	 * @return int|bool
296
-	 */
297
-	public function filemtime($path) {
298
-		return $this->storage->filemtime($this->findPathToUse($path));
299
-	}
300
-
301
-	/**
302
-	 * see https://www.php.net/manual/en/function.file_get_contents.php
303
-	 *
304
-	 * @param string $path
305
-	 * @return string|bool
306
-	 */
307
-	public function file_get_contents($path) {
308
-		return $this->storage->file_get_contents($this->findPathToUse($path));
309
-	}
310
-
311
-	/**
312
-	 * see https://www.php.net/manual/en/function.file_put_contents.php
313
-	 *
314
-	 * @param string $path
315
-	 * @param mixed $data
316
-	 * @return int|false
317
-	 */
318
-	public function file_put_contents($path, $data) {
319
-		return $this->storage->file_put_contents($this->findPathToUse($path), $data);
320
-	}
321
-
322
-	/**
323
-	 * see https://www.php.net/manual/en/function.unlink.php
324
-	 *
325
-	 * @param string $path
326
-	 * @return bool
327
-	 */
328
-	public function unlink($path) {
329
-		$result = $this->storage->unlink($this->findPathToUse($path));
330
-		if ($result) {
331
-			unset($this->namesCache[$path]);
332
-		}
333
-		return $result;
334
-	}
335
-
336
-	/**
337
-	 * see https://www.php.net/manual/en/function.rename.php
338
-	 *
339
-	 * @param string $path1
340
-	 * @param string $path2
341
-	 * @return bool
342
-	 */
343
-	public function rename($path1, $path2) {
344
-		// second name always NFC
345
-		return $this->storage->rename($this->findPathToUse($path1), $this->findPathToUse($path2));
346
-	}
347
-
348
-	/**
349
-	 * see https://www.php.net/manual/en/function.copy.php
350
-	 *
351
-	 * @param string $path1
352
-	 * @param string $path2
353
-	 * @return bool
354
-	 */
355
-	public function copy($path1, $path2) {
356
-		return $this->storage->copy($this->findPathToUse($path1), $this->findPathToUse($path2));
357
-	}
358
-
359
-	/**
360
-	 * see https://www.php.net/manual/en/function.fopen.php
361
-	 *
362
-	 * @param string $path
363
-	 * @param string $mode
364
-	 * @return resource|bool
365
-	 */
366
-	public function fopen($path, $mode) {
367
-		$result = $this->storage->fopen($this->findPathToUse($path), $mode);
368
-		if ($result && $mode !== 'r' && $mode !== 'rb') {
369
-			unset($this->namesCache[$path]);
370
-		}
371
-		return $result;
372
-	}
373
-
374
-	/**
375
-	 * get the mimetype for a file or folder
376
-	 * The mimetype for a folder is required to be "httpd/unix-directory"
377
-	 *
378
-	 * @param string $path
379
-	 * @return string|bool
380
-	 */
381
-	public function getMimeType($path) {
382
-		return $this->storage->getMimeType($this->findPathToUse($path));
383
-	}
384
-
385
-	/**
386
-	 * see https://www.php.net/manual/en/function.hash.php
387
-	 *
388
-	 * @param string $type
389
-	 * @param string $path
390
-	 * @param bool $raw
391
-	 * @return string|bool
392
-	 */
393
-	public function hash($type, $path, $raw = false) {
394
-		return $this->storage->hash($type, $this->findPathToUse($path), $raw);
395
-	}
396
-
397
-	/**
398
-	 * see https://www.php.net/manual/en/function.free_space.php
399
-	 *
400
-	 * @param string $path
401
-	 * @return int|bool
402
-	 */
403
-	public function free_space($path) {
404
-		return $this->storage->free_space($this->findPathToUse($path));
405
-	}
406
-
407
-	/**
408
-	 * search for occurrences of $query in file names
409
-	 *
410
-	 * @param string $query
411
-	 * @return array|bool
412
-	 */
413
-	public function search($query) {
414
-		return $this->storage->search($query);
415
-	}
416
-
417
-	/**
418
-	 * see https://www.php.net/manual/en/function.touch.php
419
-	 * If the backend does not support the operation, false should be returned
420
-	 *
421
-	 * @param string $path
422
-	 * @param int $mtime
423
-	 * @return bool
424
-	 */
425
-	public function touch($path, $mtime = null) {
426
-		return $this->storage->touch($this->findPathToUse($path), $mtime);
427
-	}
428
-
429
-	/**
430
-	 * get the path to a local version of the file.
431
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
432
-	 *
433
-	 * @param string $path
434
-	 * @return string|bool
435
-	 */
436
-	public function getLocalFile($path) {
437
-		return $this->storage->getLocalFile($this->findPathToUse($path));
438
-	}
439
-
440
-	/**
441
-	 * check if a file or folder has been updated since $time
442
-	 *
443
-	 * @param string $path
444
-	 * @param int $time
445
-	 * @return bool
446
-	 *
447
-	 * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
448
-	 * returning true for other changes in the folder is optional
449
-	 */
450
-	public function hasUpdated($path, $time) {
451
-		return $this->storage->hasUpdated($this->findPathToUse($path), $time);
452
-	}
453
-
454
-	/**
455
-	 * get a cache instance for the storage
456
-	 *
457
-	 * @param string $path
458
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
459
-	 * @return \OC\Files\Cache\Cache
460
-	 */
461
-	public function getCache($path = '', $storage = null) {
462
-		if (!$storage) {
463
-			$storage = $this;
464
-		}
465
-		return $this->storage->getCache($this->findPathToUse($path), $storage);
466
-	}
467
-
468
-	/**
469
-	 * get a scanner instance for the storage
470
-	 *
471
-	 * @param string $path
472
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
473
-	 * @return \OC\Files\Cache\Scanner
474
-	 */
475
-	public function getScanner($path = '', $storage = null) {
476
-		if (!$storage) {
477
-			$storage = $this;
478
-		}
479
-		return $this->storage->getScanner($this->findPathToUse($path), $storage);
480
-	}
481
-
482
-	/**
483
-	 * get the ETag for a file or folder
484
-	 *
485
-	 * @param string $path
486
-	 * @return string|bool
487
-	 */
488
-	public function getETag($path) {
489
-		return $this->storage->getETag($this->findPathToUse($path));
490
-	}
491
-
492
-	/**
493
-	 * @param IStorage $sourceStorage
494
-	 * @param string $sourceInternalPath
495
-	 * @param string $targetInternalPath
496
-	 * @return bool
497
-	 */
498
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
499
-		if ($sourceStorage === $this) {
500
-			return $this->copy($sourceInternalPath, $this->findPathToUse($targetInternalPath));
501
-		}
502
-
503
-		$result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
504
-		if ($result) {
505
-			unset($this->namesCache[$targetInternalPath]);
506
-		}
507
-		return $result;
508
-	}
509
-
510
-	/**
511
-	 * @param IStorage $sourceStorage
512
-	 * @param string $sourceInternalPath
513
-	 * @param string $targetInternalPath
514
-	 * @return bool
515
-	 */
516
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
517
-		if ($sourceStorage === $this) {
518
-			$result = $this->rename($sourceInternalPath, $this->findPathToUse($targetInternalPath));
519
-			if ($result) {
520
-				unset($this->namesCache[$sourceInternalPath]);
521
-				unset($this->namesCache[$targetInternalPath]);
522
-			}
523
-			return $result;
524
-		}
525
-
526
-		$result = $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
527
-		if ($result) {
528
-			unset($this->namesCache[$sourceInternalPath]);
529
-			unset($this->namesCache[$targetInternalPath]);
530
-		}
531
-		return $result;
532
-	}
533
-
534
-	/**
535
-	 * @param string $path
536
-	 * @return array
537
-	 */
538
-	public function getMetaData($path) {
539
-		return $this->storage->getMetaData($this->findPathToUse($path));
540
-	}
541
-
542
-	public function getDirectoryContent($directory): \Traversable {
543
-		return $this->storage->getDirectoryContent($this->findPathToUse($directory));
544
-	}
43
+    /**
44
+     * @var ICache
45
+     */
46
+    private $namesCache;
47
+
48
+    /**
49
+     * @param array $parameters
50
+     */
51
+    public function __construct($parameters) {
52
+        $this->storage = $parameters['storage'];
53
+        $this->namesCache = new CappedMemoryCache();
54
+    }
55
+
56
+    /**
57
+     * Returns whether the given string is only made of ASCII characters
58
+     *
59
+     * @param string $str string
60
+     *
61
+     * @return bool true if the string is all ASCII, false otherwise
62
+     */
63
+    private function isAscii($str) {
64
+        return !preg_match('/[\\x80-\\xff]+/', $str);
65
+    }
66
+
67
+    /**
68
+     * Checks whether the given path exists in NFC or NFD form after checking
69
+     * each form for each path section and returns the correct form.
70
+     * If no existing path found, returns the path as it was given.
71
+     *
72
+     * @param string $fullPath path to check
73
+     *
74
+     * @return string original or converted path
75
+     */
76
+    private function findPathToUse($fullPath) {
77
+        $cachedPath = $this->namesCache[$fullPath];
78
+        if ($cachedPath !== null) {
79
+            return $cachedPath;
80
+        }
81
+
82
+        $sections = explode('/', $fullPath);
83
+        $path = '';
84
+        foreach ($sections as $section) {
85
+            $convertedPath = $this->findPathToUseLastSection($path, $section);
86
+            if ($convertedPath === null) {
87
+                // no point in continuing if the section was not found, use original path
88
+                return $fullPath;
89
+            }
90
+            $path = $convertedPath . '/';
91
+        }
92
+        $path = rtrim($path, '/');
93
+        return $path;
94
+    }
95
+
96
+    /**
97
+     * Checks whether the last path section of the given path exists in NFC or NFD form
98
+     * and returns the correct form. If no existing path found, returns null.
99
+     *
100
+     * @param string $basePath base path to check
101
+     * @param string $lastSection last section of the path to check for NFD/NFC variations
102
+     *
103
+     * @return string|null original or converted path, or null if none of the forms was found
104
+     */
105
+    private function findPathToUseLastSection($basePath, $lastSection) {
106
+        $fullPath = $basePath . $lastSection;
107
+        if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) {
108
+            $this->namesCache[$fullPath] = $fullPath;
109
+            return $fullPath;
110
+        }
111
+
112
+        // swap encoding
113
+        if (\Normalizer::isNormalized($lastSection, \Normalizer::FORM_C)) {
114
+            $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_D);
115
+        } else {
116
+            $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C);
117
+        }
118
+        $otherFullPath = $basePath . $otherFormPath;
119
+        if ($this->storage->file_exists($otherFullPath)) {
120
+            $this->namesCache[$fullPath] = $otherFullPath;
121
+            return $otherFullPath;
122
+        }
123
+
124
+        // return original path, file did not exist at all
125
+        $this->namesCache[$fullPath] = $fullPath;
126
+        return null;
127
+    }
128
+
129
+    /**
130
+     * see https://www.php.net/manual/en/function.mkdir.php
131
+     *
132
+     * @param string $path
133
+     * @return bool
134
+     */
135
+    public function mkdir($path) {
136
+        // note: no conversion here, method should not be called with non-NFC names!
137
+        $result = $this->storage->mkdir($path);
138
+        if ($result) {
139
+            $this->namesCache[$path] = $path;
140
+        }
141
+        return $result;
142
+    }
143
+
144
+    /**
145
+     * see https://www.php.net/manual/en/function.rmdir.php
146
+     *
147
+     * @param string $path
148
+     * @return bool
149
+     */
150
+    public function rmdir($path) {
151
+        $result = $this->storage->rmdir($this->findPathToUse($path));
152
+        if ($result) {
153
+            unset($this->namesCache[$path]);
154
+        }
155
+        return $result;
156
+    }
157
+
158
+    /**
159
+     * see https://www.php.net/manual/en/function.opendir.php
160
+     *
161
+     * @param string $path
162
+     * @return resource|bool
163
+     */
164
+    public function opendir($path) {
165
+        return $this->storage->opendir($this->findPathToUse($path));
166
+    }
167
+
168
+    /**
169
+     * see https://www.php.net/manual/en/function.is_dir.php
170
+     *
171
+     * @param string $path
172
+     * @return bool
173
+     */
174
+    public function is_dir($path) {
175
+        return $this->storage->is_dir($this->findPathToUse($path));
176
+    }
177
+
178
+    /**
179
+     * see https://www.php.net/manual/en/function.is_file.php
180
+     *
181
+     * @param string $path
182
+     * @return bool
183
+     */
184
+    public function is_file($path) {
185
+        return $this->storage->is_file($this->findPathToUse($path));
186
+    }
187
+
188
+    /**
189
+     * see https://www.php.net/manual/en/function.stat.php
190
+     * only the following keys are required in the result: size and mtime
191
+     *
192
+     * @param string $path
193
+     * @return array|bool
194
+     */
195
+    public function stat($path) {
196
+        return $this->storage->stat($this->findPathToUse($path));
197
+    }
198
+
199
+    /**
200
+     * see https://www.php.net/manual/en/function.filetype.php
201
+     *
202
+     * @param string $path
203
+     * @return string|bool
204
+     */
205
+    public function filetype($path) {
206
+        return $this->storage->filetype($this->findPathToUse($path));
207
+    }
208
+
209
+    /**
210
+     * see https://www.php.net/manual/en/function.filesize.php
211
+     * The result for filesize when called on a folder is required to be 0
212
+     *
213
+     * @param string $path
214
+     * @return int|bool
215
+     */
216
+    public function filesize($path) {
217
+        return $this->storage->filesize($this->findPathToUse($path));
218
+    }
219
+
220
+    /**
221
+     * check if a file can be created in $path
222
+     *
223
+     * @param string $path
224
+     * @return bool
225
+     */
226
+    public function isCreatable($path) {
227
+        return $this->storage->isCreatable($this->findPathToUse($path));
228
+    }
229
+
230
+    /**
231
+     * check if a file can be read
232
+     *
233
+     * @param string $path
234
+     * @return bool
235
+     */
236
+    public function isReadable($path) {
237
+        return $this->storage->isReadable($this->findPathToUse($path));
238
+    }
239
+
240
+    /**
241
+     * check if a file can be written to
242
+     *
243
+     * @param string $path
244
+     * @return bool
245
+     */
246
+    public function isUpdatable($path) {
247
+        return $this->storage->isUpdatable($this->findPathToUse($path));
248
+    }
249
+
250
+    /**
251
+     * check if a file can be deleted
252
+     *
253
+     * @param string $path
254
+     * @return bool
255
+     */
256
+    public function isDeletable($path) {
257
+        return $this->storage->isDeletable($this->findPathToUse($path));
258
+    }
259
+
260
+    /**
261
+     * check if a file can be shared
262
+     *
263
+     * @param string $path
264
+     * @return bool
265
+     */
266
+    public function isSharable($path) {
267
+        return $this->storage->isSharable($this->findPathToUse($path));
268
+    }
269
+
270
+    /**
271
+     * get the full permissions of a path.
272
+     * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
273
+     *
274
+     * @param string $path
275
+     * @return int
276
+     */
277
+    public function getPermissions($path) {
278
+        return $this->storage->getPermissions($this->findPathToUse($path));
279
+    }
280
+
281
+    /**
282
+     * see https://www.php.net/manual/en/function.file_exists.php
283
+     *
284
+     * @param string $path
285
+     * @return bool
286
+     */
287
+    public function file_exists($path) {
288
+        return $this->storage->file_exists($this->findPathToUse($path));
289
+    }
290
+
291
+    /**
292
+     * see https://www.php.net/manual/en/function.filemtime.php
293
+     *
294
+     * @param string $path
295
+     * @return int|bool
296
+     */
297
+    public function filemtime($path) {
298
+        return $this->storage->filemtime($this->findPathToUse($path));
299
+    }
300
+
301
+    /**
302
+     * see https://www.php.net/manual/en/function.file_get_contents.php
303
+     *
304
+     * @param string $path
305
+     * @return string|bool
306
+     */
307
+    public function file_get_contents($path) {
308
+        return $this->storage->file_get_contents($this->findPathToUse($path));
309
+    }
310
+
311
+    /**
312
+     * see https://www.php.net/manual/en/function.file_put_contents.php
313
+     *
314
+     * @param string $path
315
+     * @param mixed $data
316
+     * @return int|false
317
+     */
318
+    public function file_put_contents($path, $data) {
319
+        return $this->storage->file_put_contents($this->findPathToUse($path), $data);
320
+    }
321
+
322
+    /**
323
+     * see https://www.php.net/manual/en/function.unlink.php
324
+     *
325
+     * @param string $path
326
+     * @return bool
327
+     */
328
+    public function unlink($path) {
329
+        $result = $this->storage->unlink($this->findPathToUse($path));
330
+        if ($result) {
331
+            unset($this->namesCache[$path]);
332
+        }
333
+        return $result;
334
+    }
335
+
336
+    /**
337
+     * see https://www.php.net/manual/en/function.rename.php
338
+     *
339
+     * @param string $path1
340
+     * @param string $path2
341
+     * @return bool
342
+     */
343
+    public function rename($path1, $path2) {
344
+        // second name always NFC
345
+        return $this->storage->rename($this->findPathToUse($path1), $this->findPathToUse($path2));
346
+    }
347
+
348
+    /**
349
+     * see https://www.php.net/manual/en/function.copy.php
350
+     *
351
+     * @param string $path1
352
+     * @param string $path2
353
+     * @return bool
354
+     */
355
+    public function copy($path1, $path2) {
356
+        return $this->storage->copy($this->findPathToUse($path1), $this->findPathToUse($path2));
357
+    }
358
+
359
+    /**
360
+     * see https://www.php.net/manual/en/function.fopen.php
361
+     *
362
+     * @param string $path
363
+     * @param string $mode
364
+     * @return resource|bool
365
+     */
366
+    public function fopen($path, $mode) {
367
+        $result = $this->storage->fopen($this->findPathToUse($path), $mode);
368
+        if ($result && $mode !== 'r' && $mode !== 'rb') {
369
+            unset($this->namesCache[$path]);
370
+        }
371
+        return $result;
372
+    }
373
+
374
+    /**
375
+     * get the mimetype for a file or folder
376
+     * The mimetype for a folder is required to be "httpd/unix-directory"
377
+     *
378
+     * @param string $path
379
+     * @return string|bool
380
+     */
381
+    public function getMimeType($path) {
382
+        return $this->storage->getMimeType($this->findPathToUse($path));
383
+    }
384
+
385
+    /**
386
+     * see https://www.php.net/manual/en/function.hash.php
387
+     *
388
+     * @param string $type
389
+     * @param string $path
390
+     * @param bool $raw
391
+     * @return string|bool
392
+     */
393
+    public function hash($type, $path, $raw = false) {
394
+        return $this->storage->hash($type, $this->findPathToUse($path), $raw);
395
+    }
396
+
397
+    /**
398
+     * see https://www.php.net/manual/en/function.free_space.php
399
+     *
400
+     * @param string $path
401
+     * @return int|bool
402
+     */
403
+    public function free_space($path) {
404
+        return $this->storage->free_space($this->findPathToUse($path));
405
+    }
406
+
407
+    /**
408
+     * search for occurrences of $query in file names
409
+     *
410
+     * @param string $query
411
+     * @return array|bool
412
+     */
413
+    public function search($query) {
414
+        return $this->storage->search($query);
415
+    }
416
+
417
+    /**
418
+     * see https://www.php.net/manual/en/function.touch.php
419
+     * If the backend does not support the operation, false should be returned
420
+     *
421
+     * @param string $path
422
+     * @param int $mtime
423
+     * @return bool
424
+     */
425
+    public function touch($path, $mtime = null) {
426
+        return $this->storage->touch($this->findPathToUse($path), $mtime);
427
+    }
428
+
429
+    /**
430
+     * get the path to a local version of the file.
431
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
432
+     *
433
+     * @param string $path
434
+     * @return string|bool
435
+     */
436
+    public function getLocalFile($path) {
437
+        return $this->storage->getLocalFile($this->findPathToUse($path));
438
+    }
439
+
440
+    /**
441
+     * check if a file or folder has been updated since $time
442
+     *
443
+     * @param string $path
444
+     * @param int $time
445
+     * @return bool
446
+     *
447
+     * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
448
+     * returning true for other changes in the folder is optional
449
+     */
450
+    public function hasUpdated($path, $time) {
451
+        return $this->storage->hasUpdated($this->findPathToUse($path), $time);
452
+    }
453
+
454
+    /**
455
+     * get a cache instance for the storage
456
+     *
457
+     * @param string $path
458
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
459
+     * @return \OC\Files\Cache\Cache
460
+     */
461
+    public function getCache($path = '', $storage = null) {
462
+        if (!$storage) {
463
+            $storage = $this;
464
+        }
465
+        return $this->storage->getCache($this->findPathToUse($path), $storage);
466
+    }
467
+
468
+    /**
469
+     * get a scanner instance for the storage
470
+     *
471
+     * @param string $path
472
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
473
+     * @return \OC\Files\Cache\Scanner
474
+     */
475
+    public function getScanner($path = '', $storage = null) {
476
+        if (!$storage) {
477
+            $storage = $this;
478
+        }
479
+        return $this->storage->getScanner($this->findPathToUse($path), $storage);
480
+    }
481
+
482
+    /**
483
+     * get the ETag for a file or folder
484
+     *
485
+     * @param string $path
486
+     * @return string|bool
487
+     */
488
+    public function getETag($path) {
489
+        return $this->storage->getETag($this->findPathToUse($path));
490
+    }
491
+
492
+    /**
493
+     * @param IStorage $sourceStorage
494
+     * @param string $sourceInternalPath
495
+     * @param string $targetInternalPath
496
+     * @return bool
497
+     */
498
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
499
+        if ($sourceStorage === $this) {
500
+            return $this->copy($sourceInternalPath, $this->findPathToUse($targetInternalPath));
501
+        }
502
+
503
+        $result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
504
+        if ($result) {
505
+            unset($this->namesCache[$targetInternalPath]);
506
+        }
507
+        return $result;
508
+    }
509
+
510
+    /**
511
+     * @param IStorage $sourceStorage
512
+     * @param string $sourceInternalPath
513
+     * @param string $targetInternalPath
514
+     * @return bool
515
+     */
516
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
517
+        if ($sourceStorage === $this) {
518
+            $result = $this->rename($sourceInternalPath, $this->findPathToUse($targetInternalPath));
519
+            if ($result) {
520
+                unset($this->namesCache[$sourceInternalPath]);
521
+                unset($this->namesCache[$targetInternalPath]);
522
+            }
523
+            return $result;
524
+        }
525
+
526
+        $result = $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
527
+        if ($result) {
528
+            unset($this->namesCache[$sourceInternalPath]);
529
+            unset($this->namesCache[$targetInternalPath]);
530
+        }
531
+        return $result;
532
+    }
533
+
534
+    /**
535
+     * @param string $path
536
+     * @return array
537
+     */
538
+    public function getMetaData($path) {
539
+        return $this->storage->getMetaData($this->findPathToUse($path));
540
+    }
541
+
542
+    public function getDirectoryContent($directory): \Traversable {
543
+        return $this->storage->getDirectoryContent($this->findPathToUse($directory));
544
+    }
545 545
 }
Please login to merge, or discard this patch.
lib/private/Files/FileInfo.php 1 patch
Indentation   +376 added lines, -376 removed lines patch added patch discarded remove patch
@@ -38,380 +38,380 @@
 block discarded – undo
38 38
 use OCP\IUser;
39 39
 
40 40
 class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
41
-	/**
42
-	 * @var array $data
43
-	 */
44
-	private $data;
45
-
46
-	/**
47
-	 * @var string $path
48
-	 */
49
-	private $path;
50
-
51
-	/**
52
-	 * @var \OC\Files\Storage\Storage $storage
53
-	 */
54
-	private $storage;
55
-
56
-	/**
57
-	 * @var string $internalPath
58
-	 */
59
-	private $internalPath;
60
-
61
-	/**
62
-	 * @var \OCP\Files\Mount\IMountPoint
63
-	 */
64
-	private $mount;
65
-
66
-	/**
67
-	 * @var IUser
68
-	 */
69
-	private $owner;
70
-
71
-	/**
72
-	 * @var string[]
73
-	 */
74
-	private $childEtags = [];
75
-
76
-	/**
77
-	 * @var IMountPoint[]
78
-	 */
79
-	private $subMounts = [];
80
-
81
-	private $subMountsUsed = false;
82
-
83
-	/**
84
-	 * The size of the file/folder without any sub mount
85
-	 *
86
-	 * @var int
87
-	 */
88
-	private $rawSize = 0;
89
-
90
-	/**
91
-	 * @param string|boolean $path
92
-	 * @param Storage\Storage $storage
93
-	 * @param string $internalPath
94
-	 * @param array|ICacheEntry $data
95
-	 * @param \OCP\Files\Mount\IMountPoint $mount
96
-	 * @param \OCP\IUser|null $owner
97
-	 */
98
-	public function __construct($path, $storage, $internalPath, $data, $mount, $owner = null) {
99
-		$this->path = $path;
100
-		$this->storage = $storage;
101
-		$this->internalPath = $internalPath;
102
-		$this->data = $data;
103
-		$this->mount = $mount;
104
-		$this->owner = $owner;
105
-		$this->rawSize = $this->data['size'] ?? 0;
106
-	}
107
-
108
-	public function offsetSet($offset, $value) {
109
-		$this->data[$offset] = $value;
110
-	}
111
-
112
-	public function offsetExists($offset) {
113
-		return isset($this->data[$offset]);
114
-	}
115
-
116
-	public function offsetUnset($offset) {
117
-		unset($this->data[$offset]);
118
-	}
119
-
120
-	public function offsetGet($offset) {
121
-		if ($offset === 'type') {
122
-			return $this->getType();
123
-		} elseif ($offset === 'etag') {
124
-			return $this->getEtag();
125
-		} elseif ($offset === 'size') {
126
-			return $this->getSize();
127
-		} elseif ($offset === 'mtime') {
128
-			return $this->getMTime();
129
-		} elseif ($offset === 'permissions') {
130
-			return $this->getPermissions();
131
-		} elseif (isset($this->data[$offset])) {
132
-			return $this->data[$offset];
133
-		} else {
134
-			return null;
135
-		}
136
-	}
137
-
138
-	/**
139
-	 * @return string
140
-	 */
141
-	public function getPath() {
142
-		return $this->path;
143
-	}
144
-
145
-	/**
146
-	 * @return \OCP\Files\Storage
147
-	 */
148
-	public function getStorage() {
149
-		return $this->storage;
150
-	}
151
-
152
-	/**
153
-	 * @return string
154
-	 */
155
-	public function getInternalPath() {
156
-		return $this->internalPath;
157
-	}
158
-
159
-	/**
160
-	 * Get FileInfo ID or null in case of part file
161
-	 *
162
-	 * @return int|null
163
-	 */
164
-	public function getId() {
165
-		return isset($this->data['fileid']) ? (int)  $this->data['fileid'] : null;
166
-	}
167
-
168
-	/**
169
-	 * @return string
170
-	 */
171
-	public function getMimetype() {
172
-		return $this->data['mimetype'];
173
-	}
174
-
175
-	/**
176
-	 * @return string
177
-	 */
178
-	public function getMimePart() {
179
-		return $this->data['mimepart'];
180
-	}
181
-
182
-	/**
183
-	 * @return string
184
-	 */
185
-	public function getName() {
186
-		return isset($this->data['name']) ? $this->data['name'] : basename($this->getPath());
187
-	}
188
-
189
-	/**
190
-	 * @return string
191
-	 */
192
-	public function getEtag() {
193
-		$this->updateEntryfromSubMounts();
194
-		if (count($this->childEtags) > 0) {
195
-			$combinedEtag = $this->data['etag'] . '::' . implode('::', $this->childEtags);
196
-			return md5($combinedEtag);
197
-		} else {
198
-			return $this->data['etag'];
199
-		}
200
-	}
201
-
202
-	/**
203
-	 * @return int
204
-	 */
205
-	public function getSize($includeMounts = true) {
206
-		if ($includeMounts) {
207
-			$this->updateEntryfromSubMounts();
208
-			return isset($this->data['size']) ? 0 + $this->data['size'] : 0;
209
-		} else {
210
-			return $this->rawSize;
211
-		}
212
-	}
213
-
214
-	/**
215
-	 * @return int
216
-	 */
217
-	public function getMTime() {
218
-		$this->updateEntryfromSubMounts();
219
-		return (int) $this->data['mtime'];
220
-	}
221
-
222
-	/**
223
-	 * @return bool
224
-	 */
225
-	public function isEncrypted() {
226
-		return $this->data['encrypted'];
227
-	}
228
-
229
-	/**
230
-	 * Return the currently version used for the HMAC in the encryption app
231
-	 *
232
-	 * @return int
233
-	 */
234
-	public function getEncryptedVersion() {
235
-		return isset($this->data['encryptedVersion']) ? (int) $this->data['encryptedVersion'] : 1;
236
-	}
237
-
238
-	/**
239
-	 * @return int
240
-	 */
241
-	public function getPermissions() {
242
-		$perms = (int) $this->data['permissions'];
243
-		if (\OCP\Util::isSharingDisabledForUser() || ($this->isShared() && !\OC\Share\Share::isResharingAllowed())) {
244
-			$perms = $perms & ~\OCP\Constants::PERMISSION_SHARE;
245
-		}
246
-		return $perms;
247
-	}
248
-
249
-	/**
250
-	 * @return string \OCP\Files\FileInfo::TYPE_FILE|\OCP\Files\FileInfo::TYPE_FOLDER
251
-	 */
252
-	public function getType() {
253
-		if (!isset($this->data['type'])) {
254
-			$this->data['type'] = ($this->getMimetype() === 'httpd/unix-directory') ? self::TYPE_FOLDER : self::TYPE_FILE;
255
-		}
256
-		return $this->data['type'];
257
-	}
258
-
259
-	public function getData() {
260
-		return $this->data;
261
-	}
262
-
263
-	/**
264
-	 * @param int $permissions
265
-	 * @return bool
266
-	 */
267
-	protected function checkPermissions($permissions) {
268
-		return ($this->getPermissions() & $permissions) === $permissions;
269
-	}
270
-
271
-	/**
272
-	 * @return bool
273
-	 */
274
-	public function isReadable() {
275
-		return $this->checkPermissions(\OCP\Constants::PERMISSION_READ);
276
-	}
277
-
278
-	/**
279
-	 * @return bool
280
-	 */
281
-	public function isUpdateable() {
282
-		return $this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE);
283
-	}
284
-
285
-	/**
286
-	 * Check whether new files or folders can be created inside this folder
287
-	 *
288
-	 * @return bool
289
-	 */
290
-	public function isCreatable() {
291
-		return $this->checkPermissions(\OCP\Constants::PERMISSION_CREATE);
292
-	}
293
-
294
-	/**
295
-	 * @return bool
296
-	 */
297
-	public function isDeletable() {
298
-		return $this->checkPermissions(\OCP\Constants::PERMISSION_DELETE);
299
-	}
300
-
301
-	/**
302
-	 * @return bool
303
-	 */
304
-	public function isShareable() {
305
-		return $this->checkPermissions(\OCP\Constants::PERMISSION_SHARE);
306
-	}
307
-
308
-	/**
309
-	 * Check if a file or folder is shared
310
-	 *
311
-	 * @return bool
312
-	 */
313
-	public function isShared() {
314
-		$sid = $this->getStorage()->getId();
315
-		if (!is_null($sid)) {
316
-			$sid = explode(':', $sid);
317
-			return ($sid[0] === 'shared');
318
-		}
319
-
320
-		return false;
321
-	}
322
-
323
-	public function isMounted() {
324
-		$storage = $this->getStorage();
325
-		if ($storage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
326
-			return false;
327
-		}
328
-		$sid = $storage->getId();
329
-		if (!is_null($sid)) {
330
-			$sid = explode(':', $sid);
331
-			return ($sid[0] !== 'home' and $sid[0] !== 'shared');
332
-		}
333
-
334
-		return false;
335
-	}
336
-
337
-	/**
338
-	 * Get the mountpoint the file belongs to
339
-	 *
340
-	 * @return \OCP\Files\Mount\IMountPoint
341
-	 */
342
-	public function getMountPoint() {
343
-		return $this->mount;
344
-	}
345
-
346
-	/**
347
-	 * Get the owner of the file
348
-	 *
349
-	 * @return \OCP\IUser
350
-	 */
351
-	public function getOwner() {
352
-		return $this->owner;
353
-	}
354
-
355
-	/**
356
-	 * @param IMountPoint[] $mounts
357
-	 */
358
-	public function setSubMounts(array $mounts) {
359
-		$this->subMounts = $mounts;
360
-	}
361
-
362
-	private function updateEntryfromSubMounts() {
363
-		if ($this->subMountsUsed) {
364
-			return;
365
-		}
366
-		$this->subMountsUsed = true;
367
-		foreach ($this->subMounts as $mount) {
368
-			$subStorage = $mount->getStorage();
369
-			if ($subStorage) {
370
-				$subCache = $subStorage->getCache('');
371
-				$rootEntry = $subCache->get('');
372
-				$this->addSubEntry($rootEntry, $mount->getMountPoint());
373
-			}
374
-		}
375
-	}
376
-
377
-	/**
378
-	 * Add a cache entry which is the child of this folder
379
-	 *
380
-	 * Sets the size, etag and size to for cross-storage childs
381
-	 *
382
-	 * @param array|ICacheEntry $data cache entry for the child
383
-	 * @param string $entryPath full path of the child entry
384
-	 */
385
-	public function addSubEntry($data, $entryPath) {
386
-		$this->data['size'] += isset($data['size']) ? $data['size'] : 0;
387
-		if (isset($data['mtime'])) {
388
-			$this->data['mtime'] = max($this->data['mtime'], $data['mtime']);
389
-		}
390
-		if (isset($data['etag'])) {
391
-			// prefix the etag with the relative path of the subentry to propagate etag on mount moves
392
-			$relativeEntryPath = substr($entryPath, strlen($this->getPath()));
393
-			// attach the permissions to propagate etag on permision changes of submounts
394
-			$permissions = isset($data['permissions']) ? $data['permissions'] : 0;
395
-			$this->childEtags[] = $relativeEntryPath . '/' . $data['etag'] . $permissions;
396
-		}
397
-	}
398
-
399
-	/**
400
-	 * @inheritdoc
401
-	 */
402
-	public function getChecksum() {
403
-		return $this->data['checksum'];
404
-	}
405
-
406
-	public function getExtension(): string {
407
-		return pathinfo($this->getName(), PATHINFO_EXTENSION);
408
-	}
409
-
410
-	public function getCreationTime(): int {
411
-		return (int) $this->data['creation_time'];
412
-	}
413
-
414
-	public function getUploadTime(): int {
415
-		return (int) $this->data['upload_time'];
416
-	}
41
+    /**
42
+     * @var array $data
43
+     */
44
+    private $data;
45
+
46
+    /**
47
+     * @var string $path
48
+     */
49
+    private $path;
50
+
51
+    /**
52
+     * @var \OC\Files\Storage\Storage $storage
53
+     */
54
+    private $storage;
55
+
56
+    /**
57
+     * @var string $internalPath
58
+     */
59
+    private $internalPath;
60
+
61
+    /**
62
+     * @var \OCP\Files\Mount\IMountPoint
63
+     */
64
+    private $mount;
65
+
66
+    /**
67
+     * @var IUser
68
+     */
69
+    private $owner;
70
+
71
+    /**
72
+     * @var string[]
73
+     */
74
+    private $childEtags = [];
75
+
76
+    /**
77
+     * @var IMountPoint[]
78
+     */
79
+    private $subMounts = [];
80
+
81
+    private $subMountsUsed = false;
82
+
83
+    /**
84
+     * The size of the file/folder without any sub mount
85
+     *
86
+     * @var int
87
+     */
88
+    private $rawSize = 0;
89
+
90
+    /**
91
+     * @param string|boolean $path
92
+     * @param Storage\Storage $storage
93
+     * @param string $internalPath
94
+     * @param array|ICacheEntry $data
95
+     * @param \OCP\Files\Mount\IMountPoint $mount
96
+     * @param \OCP\IUser|null $owner
97
+     */
98
+    public function __construct($path, $storage, $internalPath, $data, $mount, $owner = null) {
99
+        $this->path = $path;
100
+        $this->storage = $storage;
101
+        $this->internalPath = $internalPath;
102
+        $this->data = $data;
103
+        $this->mount = $mount;
104
+        $this->owner = $owner;
105
+        $this->rawSize = $this->data['size'] ?? 0;
106
+    }
107
+
108
+    public function offsetSet($offset, $value) {
109
+        $this->data[$offset] = $value;
110
+    }
111
+
112
+    public function offsetExists($offset) {
113
+        return isset($this->data[$offset]);
114
+    }
115
+
116
+    public function offsetUnset($offset) {
117
+        unset($this->data[$offset]);
118
+    }
119
+
120
+    public function offsetGet($offset) {
121
+        if ($offset === 'type') {
122
+            return $this->getType();
123
+        } elseif ($offset === 'etag') {
124
+            return $this->getEtag();
125
+        } elseif ($offset === 'size') {
126
+            return $this->getSize();
127
+        } elseif ($offset === 'mtime') {
128
+            return $this->getMTime();
129
+        } elseif ($offset === 'permissions') {
130
+            return $this->getPermissions();
131
+        } elseif (isset($this->data[$offset])) {
132
+            return $this->data[$offset];
133
+        } else {
134
+            return null;
135
+        }
136
+    }
137
+
138
+    /**
139
+     * @return string
140
+     */
141
+    public function getPath() {
142
+        return $this->path;
143
+    }
144
+
145
+    /**
146
+     * @return \OCP\Files\Storage
147
+     */
148
+    public function getStorage() {
149
+        return $this->storage;
150
+    }
151
+
152
+    /**
153
+     * @return string
154
+     */
155
+    public function getInternalPath() {
156
+        return $this->internalPath;
157
+    }
158
+
159
+    /**
160
+     * Get FileInfo ID or null in case of part file
161
+     *
162
+     * @return int|null
163
+     */
164
+    public function getId() {
165
+        return isset($this->data['fileid']) ? (int)  $this->data['fileid'] : null;
166
+    }
167
+
168
+    /**
169
+     * @return string
170
+     */
171
+    public function getMimetype() {
172
+        return $this->data['mimetype'];
173
+    }
174
+
175
+    /**
176
+     * @return string
177
+     */
178
+    public function getMimePart() {
179
+        return $this->data['mimepart'];
180
+    }
181
+
182
+    /**
183
+     * @return string
184
+     */
185
+    public function getName() {
186
+        return isset($this->data['name']) ? $this->data['name'] : basename($this->getPath());
187
+    }
188
+
189
+    /**
190
+     * @return string
191
+     */
192
+    public function getEtag() {
193
+        $this->updateEntryfromSubMounts();
194
+        if (count($this->childEtags) > 0) {
195
+            $combinedEtag = $this->data['etag'] . '::' . implode('::', $this->childEtags);
196
+            return md5($combinedEtag);
197
+        } else {
198
+            return $this->data['etag'];
199
+        }
200
+    }
201
+
202
+    /**
203
+     * @return int
204
+     */
205
+    public function getSize($includeMounts = true) {
206
+        if ($includeMounts) {
207
+            $this->updateEntryfromSubMounts();
208
+            return isset($this->data['size']) ? 0 + $this->data['size'] : 0;
209
+        } else {
210
+            return $this->rawSize;
211
+        }
212
+    }
213
+
214
+    /**
215
+     * @return int
216
+     */
217
+    public function getMTime() {
218
+        $this->updateEntryfromSubMounts();
219
+        return (int) $this->data['mtime'];
220
+    }
221
+
222
+    /**
223
+     * @return bool
224
+     */
225
+    public function isEncrypted() {
226
+        return $this->data['encrypted'];
227
+    }
228
+
229
+    /**
230
+     * Return the currently version used for the HMAC in the encryption app
231
+     *
232
+     * @return int
233
+     */
234
+    public function getEncryptedVersion() {
235
+        return isset($this->data['encryptedVersion']) ? (int) $this->data['encryptedVersion'] : 1;
236
+    }
237
+
238
+    /**
239
+     * @return int
240
+     */
241
+    public function getPermissions() {
242
+        $perms = (int) $this->data['permissions'];
243
+        if (\OCP\Util::isSharingDisabledForUser() || ($this->isShared() && !\OC\Share\Share::isResharingAllowed())) {
244
+            $perms = $perms & ~\OCP\Constants::PERMISSION_SHARE;
245
+        }
246
+        return $perms;
247
+    }
248
+
249
+    /**
250
+     * @return string \OCP\Files\FileInfo::TYPE_FILE|\OCP\Files\FileInfo::TYPE_FOLDER
251
+     */
252
+    public function getType() {
253
+        if (!isset($this->data['type'])) {
254
+            $this->data['type'] = ($this->getMimetype() === 'httpd/unix-directory') ? self::TYPE_FOLDER : self::TYPE_FILE;
255
+        }
256
+        return $this->data['type'];
257
+    }
258
+
259
+    public function getData() {
260
+        return $this->data;
261
+    }
262
+
263
+    /**
264
+     * @param int $permissions
265
+     * @return bool
266
+     */
267
+    protected function checkPermissions($permissions) {
268
+        return ($this->getPermissions() & $permissions) === $permissions;
269
+    }
270
+
271
+    /**
272
+     * @return bool
273
+     */
274
+    public function isReadable() {
275
+        return $this->checkPermissions(\OCP\Constants::PERMISSION_READ);
276
+    }
277
+
278
+    /**
279
+     * @return bool
280
+     */
281
+    public function isUpdateable() {
282
+        return $this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE);
283
+    }
284
+
285
+    /**
286
+     * Check whether new files or folders can be created inside this folder
287
+     *
288
+     * @return bool
289
+     */
290
+    public function isCreatable() {
291
+        return $this->checkPermissions(\OCP\Constants::PERMISSION_CREATE);
292
+    }
293
+
294
+    /**
295
+     * @return bool
296
+     */
297
+    public function isDeletable() {
298
+        return $this->checkPermissions(\OCP\Constants::PERMISSION_DELETE);
299
+    }
300
+
301
+    /**
302
+     * @return bool
303
+     */
304
+    public function isShareable() {
305
+        return $this->checkPermissions(\OCP\Constants::PERMISSION_SHARE);
306
+    }
307
+
308
+    /**
309
+     * Check if a file or folder is shared
310
+     *
311
+     * @return bool
312
+     */
313
+    public function isShared() {
314
+        $sid = $this->getStorage()->getId();
315
+        if (!is_null($sid)) {
316
+            $sid = explode(':', $sid);
317
+            return ($sid[0] === 'shared');
318
+        }
319
+
320
+        return false;
321
+    }
322
+
323
+    public function isMounted() {
324
+        $storage = $this->getStorage();
325
+        if ($storage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
326
+            return false;
327
+        }
328
+        $sid = $storage->getId();
329
+        if (!is_null($sid)) {
330
+            $sid = explode(':', $sid);
331
+            return ($sid[0] !== 'home' and $sid[0] !== 'shared');
332
+        }
333
+
334
+        return false;
335
+    }
336
+
337
+    /**
338
+     * Get the mountpoint the file belongs to
339
+     *
340
+     * @return \OCP\Files\Mount\IMountPoint
341
+     */
342
+    public function getMountPoint() {
343
+        return $this->mount;
344
+    }
345
+
346
+    /**
347
+     * Get the owner of the file
348
+     *
349
+     * @return \OCP\IUser
350
+     */
351
+    public function getOwner() {
352
+        return $this->owner;
353
+    }
354
+
355
+    /**
356
+     * @param IMountPoint[] $mounts
357
+     */
358
+    public function setSubMounts(array $mounts) {
359
+        $this->subMounts = $mounts;
360
+    }
361
+
362
+    private function updateEntryfromSubMounts() {
363
+        if ($this->subMountsUsed) {
364
+            return;
365
+        }
366
+        $this->subMountsUsed = true;
367
+        foreach ($this->subMounts as $mount) {
368
+            $subStorage = $mount->getStorage();
369
+            if ($subStorage) {
370
+                $subCache = $subStorage->getCache('');
371
+                $rootEntry = $subCache->get('');
372
+                $this->addSubEntry($rootEntry, $mount->getMountPoint());
373
+            }
374
+        }
375
+    }
376
+
377
+    /**
378
+     * Add a cache entry which is the child of this folder
379
+     *
380
+     * Sets the size, etag and size to for cross-storage childs
381
+     *
382
+     * @param array|ICacheEntry $data cache entry for the child
383
+     * @param string $entryPath full path of the child entry
384
+     */
385
+    public function addSubEntry($data, $entryPath) {
386
+        $this->data['size'] += isset($data['size']) ? $data['size'] : 0;
387
+        if (isset($data['mtime'])) {
388
+            $this->data['mtime'] = max($this->data['mtime'], $data['mtime']);
389
+        }
390
+        if (isset($data['etag'])) {
391
+            // prefix the etag with the relative path of the subentry to propagate etag on mount moves
392
+            $relativeEntryPath = substr($entryPath, strlen($this->getPath()));
393
+            // attach the permissions to propagate etag on permision changes of submounts
394
+            $permissions = isset($data['permissions']) ? $data['permissions'] : 0;
395
+            $this->childEtags[] = $relativeEntryPath . '/' . $data['etag'] . $permissions;
396
+        }
397
+    }
398
+
399
+    /**
400
+     * @inheritdoc
401
+     */
402
+    public function getChecksum() {
403
+        return $this->data['checksum'];
404
+    }
405
+
406
+    public function getExtension(): string {
407
+        return pathinfo($this->getName(), PATHINFO_EXTENSION);
408
+    }
409
+
410
+    public function getCreationTime(): int {
411
+        return (int) $this->data['creation_time'];
412
+    }
413
+
414
+    public function getUploadTime(): int {
415
+        return (int) $this->data['upload_time'];
416
+    }
417 417
 }
Please login to merge, or discard this patch.
lib/private/Notification/Notification.php 1 patch
Indentation   +543 added lines, -543 removed lines patch added patch discarded remove patch
@@ -34,547 +34,547 @@
 block discarded – undo
34 34
 
35 35
 class Notification implements INotification {
36 36
 
37
-	/** @var IValidator */
38
-	protected $richValidator;
39
-
40
-	/** @var string */
41
-	protected $app;
42
-
43
-	/** @var string */
44
-	protected $user;
45
-
46
-	/** @var \DateTime */
47
-	protected $dateTime;
48
-
49
-	/** @var string */
50
-	protected $objectType;
51
-
52
-	/** @var string */
53
-	protected $objectId;
54
-
55
-	/** @var string */
56
-	protected $subject;
57
-
58
-	/** @var array */
59
-	protected $subjectParameters;
60
-
61
-	/** @var string */
62
-	protected $subjectParsed;
63
-
64
-	/** @var string */
65
-	protected $subjectRich;
66
-
67
-	/** @var array */
68
-	protected $subjectRichParameters;
69
-
70
-	/** @var string */
71
-	protected $message;
72
-
73
-	/** @var array */
74
-	protected $messageParameters;
75
-
76
-	/** @var string */
77
-	protected $messageParsed;
78
-
79
-	/** @var string */
80
-	protected $messageRich;
81
-
82
-	/** @var array */
83
-	protected $messageRichParameters;
84
-
85
-	/** @var string */
86
-	protected $link;
87
-
88
-	/** @var string */
89
-	protected $icon;
90
-
91
-	/** @var array */
92
-	protected $actions;
93
-
94
-	/** @var array */
95
-	protected $actionsParsed;
96
-
97
-	/** @var bool */
98
-	protected $hasPrimaryAction;
99
-
100
-	/** @var bool */
101
-	protected $hasPrimaryParsedAction;
102
-
103
-	public function __construct(IValidator $richValidator) {
104
-		$this->richValidator = $richValidator;
105
-		$this->app = '';
106
-		$this->user = '';
107
-		$this->dateTime = new \DateTime();
108
-		$this->dateTime->setTimestamp(0);
109
-		$this->objectType = '';
110
-		$this->objectId = '';
111
-		$this->subject = '';
112
-		$this->subjectParameters = [];
113
-		$this->subjectParsed = '';
114
-		$this->subjectRich = '';
115
-		$this->subjectRichParameters = [];
116
-		$this->message = '';
117
-		$this->messageParameters = [];
118
-		$this->messageParsed = '';
119
-		$this->messageRich = '';
120
-		$this->messageRichParameters = [];
121
-		$this->link = '';
122
-		$this->icon = '';
123
-		$this->actions = [];
124
-		$this->actionsParsed = [];
125
-	}
126
-
127
-	/**
128
-	 * @param string $app
129
-	 * @return $this
130
-	 * @throws \InvalidArgumentException if the app id is invalid
131
-	 * @since 8.2.0
132
-	 */
133
-	public function setApp(string $app): INotification {
134
-		if ($app === '' || isset($app[32])) {
135
-			throw new \InvalidArgumentException('The given app name is invalid');
136
-		}
137
-		$this->app = $app;
138
-		return $this;
139
-	}
140
-
141
-	/**
142
-	 * @return string
143
-	 * @since 8.2.0
144
-	 */
145
-	public function getApp(): string {
146
-		return $this->app;
147
-	}
148
-
149
-	/**
150
-	 * @param string $user
151
-	 * @return $this
152
-	 * @throws \InvalidArgumentException if the user id is invalid
153
-	 * @since 8.2.0
154
-	 */
155
-	public function setUser(string $user): INotification {
156
-		if ($user === '' || isset($user[64])) {
157
-			throw new \InvalidArgumentException('The given user id is invalid');
158
-		}
159
-		$this->user = $user;
160
-		return $this;
161
-	}
162
-
163
-	/**
164
-	 * @return string
165
-	 * @since 8.2.0
166
-	 */
167
-	public function getUser(): string {
168
-		return $this->user;
169
-	}
170
-
171
-	/**
172
-	 * @param \DateTime $dateTime
173
-	 * @return $this
174
-	 * @throws \InvalidArgumentException if the $dateTime is invalid
175
-	 * @since 9.0.0
176
-	 */
177
-	public function setDateTime(\DateTime $dateTime): INotification {
178
-		if ($dateTime->getTimestamp() === 0) {
179
-			throw new \InvalidArgumentException('The given date time is invalid');
180
-		}
181
-		$this->dateTime = $dateTime;
182
-		return $this;
183
-	}
184
-
185
-	/**
186
-	 * @return \DateTime
187
-	 * @since 9.0.0
188
-	 */
189
-	public function getDateTime(): \DateTime {
190
-		return $this->dateTime;
191
-	}
192
-
193
-	/**
194
-	 * @param string $type
195
-	 * @param string $id
196
-	 * @return $this
197
-	 * @throws \InvalidArgumentException if the object type or id is invalid
198
-	 * @since 8.2.0 - 9.0.0: Type of $id changed to string
199
-	 */
200
-	public function setObject(string $type, string $id): INotification {
201
-		if ($type === '' || isset($type[64])) {
202
-			throw new \InvalidArgumentException('The given object type is invalid');
203
-		}
204
-		$this->objectType = $type;
205
-
206
-		if ($id === '' || isset($id[64])) {
207
-			throw new \InvalidArgumentException('The given object id is invalid');
208
-		}
209
-		$this->objectId = $id;
210
-		return $this;
211
-	}
212
-
213
-	/**
214
-	 * @return string
215
-	 * @since 8.2.0
216
-	 */
217
-	public function getObjectType(): string {
218
-		return $this->objectType;
219
-	}
220
-
221
-	/**
222
-	 * @return string
223
-	 * @since 8.2.0 - 9.0.0: Return type changed to string
224
-	 */
225
-	public function getObjectId(): string {
226
-		return $this->objectId;
227
-	}
228
-
229
-	/**
230
-	 * @param string $subject
231
-	 * @param array $parameters
232
-	 * @return $this
233
-	 * @throws \InvalidArgumentException if the subject or parameters are invalid
234
-	 * @since 8.2.0
235
-	 */
236
-	public function setSubject(string $subject, array $parameters = []): INotification {
237
-		if ($subject === '' || isset($subject[64])) {
238
-			throw new \InvalidArgumentException('The given subject is invalid');
239
-		}
240
-
241
-		$this->subject = $subject;
242
-		$this->subjectParameters = $parameters;
243
-
244
-		return $this;
245
-	}
246
-
247
-	/**
248
-	 * @return string
249
-	 * @since 8.2.0
250
-	 */
251
-	public function getSubject(): string {
252
-		return $this->subject;
253
-	}
254
-
255
-	/**
256
-	 * @return array
257
-	 * @since 8.2.0
258
-	 */
259
-	public function getSubjectParameters(): array {
260
-		return $this->subjectParameters;
261
-	}
262
-
263
-	/**
264
-	 * @param string $subject
265
-	 * @return $this
266
-	 * @throws \InvalidArgumentException if the subject is invalid
267
-	 * @since 8.2.0
268
-	 */
269
-	public function setParsedSubject(string $subject): INotification {
270
-		if ($subject === '') {
271
-			throw new \InvalidArgumentException('The given parsed subject is invalid');
272
-		}
273
-		$this->subjectParsed = $subject;
274
-		return $this;
275
-	}
276
-
277
-	/**
278
-	 * @return string
279
-	 * @since 8.2.0
280
-	 */
281
-	public function getParsedSubject(): string {
282
-		return $this->subjectParsed;
283
-	}
284
-
285
-	/**
286
-	 * @param string $subject
287
-	 * @param array $parameters
288
-	 * @return $this
289
-	 * @throws \InvalidArgumentException if the subject or parameters are invalid
290
-	 * @since 11.0.0
291
-	 */
292
-	public function setRichSubject(string $subject, array $parameters = []): INotification {
293
-		if ($subject === '') {
294
-			throw new \InvalidArgumentException('The given parsed subject is invalid');
295
-		}
296
-
297
-		$this->subjectRich = $subject;
298
-		$this->subjectRichParameters = $parameters;
299
-
300
-		return $this;
301
-	}
302
-
303
-	/**
304
-	 * @return string
305
-	 * @since 11.0.0
306
-	 */
307
-	public function getRichSubject(): string {
308
-		return $this->subjectRich;
309
-	}
310
-
311
-	/**
312
-	 * @return array[]
313
-	 * @since 11.0.0
314
-	 */
315
-	public function getRichSubjectParameters(): array {
316
-		return $this->subjectRichParameters;
317
-	}
318
-
319
-	/**
320
-	 * @param string $message
321
-	 * @param array $parameters
322
-	 * @return $this
323
-	 * @throws \InvalidArgumentException if the message or parameters are invalid
324
-	 * @since 8.2.0
325
-	 */
326
-	public function setMessage(string $message, array $parameters = []): INotification {
327
-		if ($message === '' || isset($message[64])) {
328
-			throw new \InvalidArgumentException('The given message is invalid');
329
-		}
330
-
331
-		$this->message = $message;
332
-		$this->messageParameters = $parameters;
333
-
334
-		return $this;
335
-	}
336
-
337
-	/**
338
-	 * @return string
339
-	 * @since 8.2.0
340
-	 */
341
-	public function getMessage(): string {
342
-		return $this->message;
343
-	}
344
-
345
-	/**
346
-	 * @return array
347
-	 * @since 8.2.0
348
-	 */
349
-	public function getMessageParameters(): array {
350
-		return $this->messageParameters;
351
-	}
352
-
353
-	/**
354
-	 * @param string $message
355
-	 * @return $this
356
-	 * @throws \InvalidArgumentException if the message is invalid
357
-	 * @since 8.2.0
358
-	 */
359
-	public function setParsedMessage(string $message): INotification {
360
-		if ($message === '') {
361
-			throw new \InvalidArgumentException('The given parsed message is invalid');
362
-		}
363
-		$this->messageParsed = $message;
364
-		return $this;
365
-	}
366
-
367
-	/**
368
-	 * @return string
369
-	 * @since 8.2.0
370
-	 */
371
-	public function getParsedMessage(): string {
372
-		return $this->messageParsed;
373
-	}
374
-
375
-	/**
376
-	 * @param string $message
377
-	 * @param array $parameters
378
-	 * @return $this
379
-	 * @throws \InvalidArgumentException if the message or parameters are invalid
380
-	 * @since 11.0.0
381
-	 */
382
-	public function setRichMessage(string $message, array $parameters = []): INotification {
383
-		if ($message === '') {
384
-			throw new \InvalidArgumentException('The given parsed message is invalid');
385
-		}
386
-
387
-		$this->messageRich = $message;
388
-		$this->messageRichParameters = $parameters;
389
-
390
-		return $this;
391
-	}
392
-
393
-	/**
394
-	 * @return string
395
-	 * @since 11.0.0
396
-	 */
397
-	public function getRichMessage(): string {
398
-		return $this->messageRich;
399
-	}
400
-
401
-	/**
402
-	 * @return array[]
403
-	 * @since 11.0.0
404
-	 */
405
-	public function getRichMessageParameters(): array {
406
-		return $this->messageRichParameters;
407
-	}
408
-
409
-	/**
410
-	 * @param string $link
411
-	 * @return $this
412
-	 * @throws \InvalidArgumentException if the link is invalid
413
-	 * @since 8.2.0
414
-	 */
415
-	public function setLink(string $link): INotification {
416
-		if ($link === '' || isset($link[4000])) {
417
-			throw new \InvalidArgumentException('The given link is invalid');
418
-		}
419
-		$this->link = $link;
420
-		return $this;
421
-	}
422
-
423
-	/**
424
-	 * @return string
425
-	 * @since 8.2.0
426
-	 */
427
-	public function getLink(): string {
428
-		return $this->link;
429
-	}
430
-
431
-	/**
432
-	 * @param string $icon
433
-	 * @return $this
434
-	 * @throws \InvalidArgumentException if the icon is invalid
435
-	 * @since 11.0.0
436
-	 */
437
-	public function setIcon(string $icon): INotification {
438
-		if ($icon === '' || isset($icon[4000])) {
439
-			throw new \InvalidArgumentException('The given icon is invalid');
440
-		}
441
-		$this->icon = $icon;
442
-		return $this;
443
-	}
444
-
445
-	/**
446
-	 * @return string
447
-	 * @since 11.0.0
448
-	 */
449
-	public function getIcon(): string {
450
-		return $this->icon;
451
-	}
452
-
453
-	/**
454
-	 * @return IAction
455
-	 * @since 8.2.0
456
-	 */
457
-	public function createAction(): IAction {
458
-		return new Action();
459
-	}
460
-
461
-	/**
462
-	 * @param IAction $action
463
-	 * @return $this
464
-	 * @throws \InvalidArgumentException if the action is invalid
465
-	 * @since 8.2.0
466
-	 */
467
-	public function addAction(IAction $action): INotification {
468
-		if (!$action->isValid()) {
469
-			throw new \InvalidArgumentException('The given action is invalid');
470
-		}
471
-
472
-		if ($action->isPrimary()) {
473
-			if ($this->hasPrimaryAction) {
474
-				throw new \InvalidArgumentException('The notification already has a primary action');
475
-			}
476
-
477
-			$this->hasPrimaryAction = true;
478
-		}
479
-
480
-		$this->actions[] = $action;
481
-		return $this;
482
-	}
483
-
484
-	/**
485
-	 * @return IAction[]
486
-	 * @since 8.2.0
487
-	 */
488
-	public function getActions(): array {
489
-		return $this->actions;
490
-	}
491
-
492
-	/**
493
-	 * @param IAction $action
494
-	 * @return $this
495
-	 * @throws \InvalidArgumentException if the action is invalid
496
-	 * @since 8.2.0
497
-	 */
498
-	public function addParsedAction(IAction $action): INotification {
499
-		if (!$action->isValidParsed()) {
500
-			throw new \InvalidArgumentException('The given parsed action is invalid');
501
-		}
502
-
503
-		if ($action->isPrimary()) {
504
-			if ($this->hasPrimaryParsedAction) {
505
-				throw new \InvalidArgumentException('The notification already has a primary action');
506
-			}
507
-
508
-			$this->hasPrimaryParsedAction = true;
509
-
510
-			// Make sure the primary action is always the first one
511
-			array_unshift($this->actionsParsed, $action);
512
-		} else {
513
-			$this->actionsParsed[] = $action;
514
-		}
515
-
516
-		return $this;
517
-	}
518
-
519
-	/**
520
-	 * @return IAction[]
521
-	 * @since 8.2.0
522
-	 */
523
-	public function getParsedActions(): array {
524
-		return $this->actionsParsed;
525
-	}
526
-
527
-	/**
528
-	 * @return bool
529
-	 * @since 8.2.0
530
-	 */
531
-	public function isValid(): bool {
532
-		return
533
-			$this->isValidCommon()
534
-			&&
535
-			$this->getSubject() !== ''
536
-		;
537
-	}
538
-
539
-	/**
540
-	 * @return bool
541
-	 * @since 8.2.0
542
-	 */
543
-	public function isValidParsed(): bool {
544
-		if ($this->getRichSubject() !== '' || !empty($this->getRichSubjectParameters())) {
545
-			try {
546
-				$this->richValidator->validate($this->getRichSubject(), $this->getRichSubjectParameters());
547
-			} catch (InvalidObjectExeption $e) {
548
-				return false;
549
-			}
550
-		}
551
-
552
-		if ($this->getRichMessage() !== '' || !empty($this->getRichMessageParameters())) {
553
-			try {
554
-				$this->richValidator->validate($this->getRichMessage(), $this->getRichMessageParameters());
555
-			} catch (InvalidObjectExeption $e) {
556
-				return false;
557
-			}
558
-		}
559
-
560
-		return
561
-			$this->isValidCommon()
562
-			&&
563
-			$this->getParsedSubject() !== ''
564
-		;
565
-	}
566
-
567
-	protected function isValidCommon(): bool {
568
-		return
569
-			$this->getApp() !== ''
570
-			&&
571
-			$this->getUser() !== ''
572
-			&&
573
-			$this->getDateTime()->getTimestamp() !== 0
574
-			&&
575
-			$this->getObjectType() !== ''
576
-			&&
577
-			$this->getObjectId() !== ''
578
-		;
579
-	}
37
+    /** @var IValidator */
38
+    protected $richValidator;
39
+
40
+    /** @var string */
41
+    protected $app;
42
+
43
+    /** @var string */
44
+    protected $user;
45
+
46
+    /** @var \DateTime */
47
+    protected $dateTime;
48
+
49
+    /** @var string */
50
+    protected $objectType;
51
+
52
+    /** @var string */
53
+    protected $objectId;
54
+
55
+    /** @var string */
56
+    protected $subject;
57
+
58
+    /** @var array */
59
+    protected $subjectParameters;
60
+
61
+    /** @var string */
62
+    protected $subjectParsed;
63
+
64
+    /** @var string */
65
+    protected $subjectRich;
66
+
67
+    /** @var array */
68
+    protected $subjectRichParameters;
69
+
70
+    /** @var string */
71
+    protected $message;
72
+
73
+    /** @var array */
74
+    protected $messageParameters;
75
+
76
+    /** @var string */
77
+    protected $messageParsed;
78
+
79
+    /** @var string */
80
+    protected $messageRich;
81
+
82
+    /** @var array */
83
+    protected $messageRichParameters;
84
+
85
+    /** @var string */
86
+    protected $link;
87
+
88
+    /** @var string */
89
+    protected $icon;
90
+
91
+    /** @var array */
92
+    protected $actions;
93
+
94
+    /** @var array */
95
+    protected $actionsParsed;
96
+
97
+    /** @var bool */
98
+    protected $hasPrimaryAction;
99
+
100
+    /** @var bool */
101
+    protected $hasPrimaryParsedAction;
102
+
103
+    public function __construct(IValidator $richValidator) {
104
+        $this->richValidator = $richValidator;
105
+        $this->app = '';
106
+        $this->user = '';
107
+        $this->dateTime = new \DateTime();
108
+        $this->dateTime->setTimestamp(0);
109
+        $this->objectType = '';
110
+        $this->objectId = '';
111
+        $this->subject = '';
112
+        $this->subjectParameters = [];
113
+        $this->subjectParsed = '';
114
+        $this->subjectRich = '';
115
+        $this->subjectRichParameters = [];
116
+        $this->message = '';
117
+        $this->messageParameters = [];
118
+        $this->messageParsed = '';
119
+        $this->messageRich = '';
120
+        $this->messageRichParameters = [];
121
+        $this->link = '';
122
+        $this->icon = '';
123
+        $this->actions = [];
124
+        $this->actionsParsed = [];
125
+    }
126
+
127
+    /**
128
+     * @param string $app
129
+     * @return $this
130
+     * @throws \InvalidArgumentException if the app id is invalid
131
+     * @since 8.2.0
132
+     */
133
+    public function setApp(string $app): INotification {
134
+        if ($app === '' || isset($app[32])) {
135
+            throw new \InvalidArgumentException('The given app name is invalid');
136
+        }
137
+        $this->app = $app;
138
+        return $this;
139
+    }
140
+
141
+    /**
142
+     * @return string
143
+     * @since 8.2.0
144
+     */
145
+    public function getApp(): string {
146
+        return $this->app;
147
+    }
148
+
149
+    /**
150
+     * @param string $user
151
+     * @return $this
152
+     * @throws \InvalidArgumentException if the user id is invalid
153
+     * @since 8.2.0
154
+     */
155
+    public function setUser(string $user): INotification {
156
+        if ($user === '' || isset($user[64])) {
157
+            throw new \InvalidArgumentException('The given user id is invalid');
158
+        }
159
+        $this->user = $user;
160
+        return $this;
161
+    }
162
+
163
+    /**
164
+     * @return string
165
+     * @since 8.2.0
166
+     */
167
+    public function getUser(): string {
168
+        return $this->user;
169
+    }
170
+
171
+    /**
172
+     * @param \DateTime $dateTime
173
+     * @return $this
174
+     * @throws \InvalidArgumentException if the $dateTime is invalid
175
+     * @since 9.0.0
176
+     */
177
+    public function setDateTime(\DateTime $dateTime): INotification {
178
+        if ($dateTime->getTimestamp() === 0) {
179
+            throw new \InvalidArgumentException('The given date time is invalid');
180
+        }
181
+        $this->dateTime = $dateTime;
182
+        return $this;
183
+    }
184
+
185
+    /**
186
+     * @return \DateTime
187
+     * @since 9.0.0
188
+     */
189
+    public function getDateTime(): \DateTime {
190
+        return $this->dateTime;
191
+    }
192
+
193
+    /**
194
+     * @param string $type
195
+     * @param string $id
196
+     * @return $this
197
+     * @throws \InvalidArgumentException if the object type or id is invalid
198
+     * @since 8.2.0 - 9.0.0: Type of $id changed to string
199
+     */
200
+    public function setObject(string $type, string $id): INotification {
201
+        if ($type === '' || isset($type[64])) {
202
+            throw new \InvalidArgumentException('The given object type is invalid');
203
+        }
204
+        $this->objectType = $type;
205
+
206
+        if ($id === '' || isset($id[64])) {
207
+            throw new \InvalidArgumentException('The given object id is invalid');
208
+        }
209
+        $this->objectId = $id;
210
+        return $this;
211
+    }
212
+
213
+    /**
214
+     * @return string
215
+     * @since 8.2.0
216
+     */
217
+    public function getObjectType(): string {
218
+        return $this->objectType;
219
+    }
220
+
221
+    /**
222
+     * @return string
223
+     * @since 8.2.0 - 9.0.0: Return type changed to string
224
+     */
225
+    public function getObjectId(): string {
226
+        return $this->objectId;
227
+    }
228
+
229
+    /**
230
+     * @param string $subject
231
+     * @param array $parameters
232
+     * @return $this
233
+     * @throws \InvalidArgumentException if the subject or parameters are invalid
234
+     * @since 8.2.0
235
+     */
236
+    public function setSubject(string $subject, array $parameters = []): INotification {
237
+        if ($subject === '' || isset($subject[64])) {
238
+            throw new \InvalidArgumentException('The given subject is invalid');
239
+        }
240
+
241
+        $this->subject = $subject;
242
+        $this->subjectParameters = $parameters;
243
+
244
+        return $this;
245
+    }
246
+
247
+    /**
248
+     * @return string
249
+     * @since 8.2.0
250
+     */
251
+    public function getSubject(): string {
252
+        return $this->subject;
253
+    }
254
+
255
+    /**
256
+     * @return array
257
+     * @since 8.2.0
258
+     */
259
+    public function getSubjectParameters(): array {
260
+        return $this->subjectParameters;
261
+    }
262
+
263
+    /**
264
+     * @param string $subject
265
+     * @return $this
266
+     * @throws \InvalidArgumentException if the subject is invalid
267
+     * @since 8.2.0
268
+     */
269
+    public function setParsedSubject(string $subject): INotification {
270
+        if ($subject === '') {
271
+            throw new \InvalidArgumentException('The given parsed subject is invalid');
272
+        }
273
+        $this->subjectParsed = $subject;
274
+        return $this;
275
+    }
276
+
277
+    /**
278
+     * @return string
279
+     * @since 8.2.0
280
+     */
281
+    public function getParsedSubject(): string {
282
+        return $this->subjectParsed;
283
+    }
284
+
285
+    /**
286
+     * @param string $subject
287
+     * @param array $parameters
288
+     * @return $this
289
+     * @throws \InvalidArgumentException if the subject or parameters are invalid
290
+     * @since 11.0.0
291
+     */
292
+    public function setRichSubject(string $subject, array $parameters = []): INotification {
293
+        if ($subject === '') {
294
+            throw new \InvalidArgumentException('The given parsed subject is invalid');
295
+        }
296
+
297
+        $this->subjectRich = $subject;
298
+        $this->subjectRichParameters = $parameters;
299
+
300
+        return $this;
301
+    }
302
+
303
+    /**
304
+     * @return string
305
+     * @since 11.0.0
306
+     */
307
+    public function getRichSubject(): string {
308
+        return $this->subjectRich;
309
+    }
310
+
311
+    /**
312
+     * @return array[]
313
+     * @since 11.0.0
314
+     */
315
+    public function getRichSubjectParameters(): array {
316
+        return $this->subjectRichParameters;
317
+    }
318
+
319
+    /**
320
+     * @param string $message
321
+     * @param array $parameters
322
+     * @return $this
323
+     * @throws \InvalidArgumentException if the message or parameters are invalid
324
+     * @since 8.2.0
325
+     */
326
+    public function setMessage(string $message, array $parameters = []): INotification {
327
+        if ($message === '' || isset($message[64])) {
328
+            throw new \InvalidArgumentException('The given message is invalid');
329
+        }
330
+
331
+        $this->message = $message;
332
+        $this->messageParameters = $parameters;
333
+
334
+        return $this;
335
+    }
336
+
337
+    /**
338
+     * @return string
339
+     * @since 8.2.0
340
+     */
341
+    public function getMessage(): string {
342
+        return $this->message;
343
+    }
344
+
345
+    /**
346
+     * @return array
347
+     * @since 8.2.0
348
+     */
349
+    public function getMessageParameters(): array {
350
+        return $this->messageParameters;
351
+    }
352
+
353
+    /**
354
+     * @param string $message
355
+     * @return $this
356
+     * @throws \InvalidArgumentException if the message is invalid
357
+     * @since 8.2.0
358
+     */
359
+    public function setParsedMessage(string $message): INotification {
360
+        if ($message === '') {
361
+            throw new \InvalidArgumentException('The given parsed message is invalid');
362
+        }
363
+        $this->messageParsed = $message;
364
+        return $this;
365
+    }
366
+
367
+    /**
368
+     * @return string
369
+     * @since 8.2.0
370
+     */
371
+    public function getParsedMessage(): string {
372
+        return $this->messageParsed;
373
+    }
374
+
375
+    /**
376
+     * @param string $message
377
+     * @param array $parameters
378
+     * @return $this
379
+     * @throws \InvalidArgumentException if the message or parameters are invalid
380
+     * @since 11.0.0
381
+     */
382
+    public function setRichMessage(string $message, array $parameters = []): INotification {
383
+        if ($message === '') {
384
+            throw new \InvalidArgumentException('The given parsed message is invalid');
385
+        }
386
+
387
+        $this->messageRich = $message;
388
+        $this->messageRichParameters = $parameters;
389
+
390
+        return $this;
391
+    }
392
+
393
+    /**
394
+     * @return string
395
+     * @since 11.0.0
396
+     */
397
+    public function getRichMessage(): string {
398
+        return $this->messageRich;
399
+    }
400
+
401
+    /**
402
+     * @return array[]
403
+     * @since 11.0.0
404
+     */
405
+    public function getRichMessageParameters(): array {
406
+        return $this->messageRichParameters;
407
+    }
408
+
409
+    /**
410
+     * @param string $link
411
+     * @return $this
412
+     * @throws \InvalidArgumentException if the link is invalid
413
+     * @since 8.2.0
414
+     */
415
+    public function setLink(string $link): INotification {
416
+        if ($link === '' || isset($link[4000])) {
417
+            throw new \InvalidArgumentException('The given link is invalid');
418
+        }
419
+        $this->link = $link;
420
+        return $this;
421
+    }
422
+
423
+    /**
424
+     * @return string
425
+     * @since 8.2.0
426
+     */
427
+    public function getLink(): string {
428
+        return $this->link;
429
+    }
430
+
431
+    /**
432
+     * @param string $icon
433
+     * @return $this
434
+     * @throws \InvalidArgumentException if the icon is invalid
435
+     * @since 11.0.0
436
+     */
437
+    public function setIcon(string $icon): INotification {
438
+        if ($icon === '' || isset($icon[4000])) {
439
+            throw new \InvalidArgumentException('The given icon is invalid');
440
+        }
441
+        $this->icon = $icon;
442
+        return $this;
443
+    }
444
+
445
+    /**
446
+     * @return string
447
+     * @since 11.0.0
448
+     */
449
+    public function getIcon(): string {
450
+        return $this->icon;
451
+    }
452
+
453
+    /**
454
+     * @return IAction
455
+     * @since 8.2.0
456
+     */
457
+    public function createAction(): IAction {
458
+        return new Action();
459
+    }
460
+
461
+    /**
462
+     * @param IAction $action
463
+     * @return $this
464
+     * @throws \InvalidArgumentException if the action is invalid
465
+     * @since 8.2.0
466
+     */
467
+    public function addAction(IAction $action): INotification {
468
+        if (!$action->isValid()) {
469
+            throw new \InvalidArgumentException('The given action is invalid');
470
+        }
471
+
472
+        if ($action->isPrimary()) {
473
+            if ($this->hasPrimaryAction) {
474
+                throw new \InvalidArgumentException('The notification already has a primary action');
475
+            }
476
+
477
+            $this->hasPrimaryAction = true;
478
+        }
479
+
480
+        $this->actions[] = $action;
481
+        return $this;
482
+    }
483
+
484
+    /**
485
+     * @return IAction[]
486
+     * @since 8.2.0
487
+     */
488
+    public function getActions(): array {
489
+        return $this->actions;
490
+    }
491
+
492
+    /**
493
+     * @param IAction $action
494
+     * @return $this
495
+     * @throws \InvalidArgumentException if the action is invalid
496
+     * @since 8.2.0
497
+     */
498
+    public function addParsedAction(IAction $action): INotification {
499
+        if (!$action->isValidParsed()) {
500
+            throw new \InvalidArgumentException('The given parsed action is invalid');
501
+        }
502
+
503
+        if ($action->isPrimary()) {
504
+            if ($this->hasPrimaryParsedAction) {
505
+                throw new \InvalidArgumentException('The notification already has a primary action');
506
+            }
507
+
508
+            $this->hasPrimaryParsedAction = true;
509
+
510
+            // Make sure the primary action is always the first one
511
+            array_unshift($this->actionsParsed, $action);
512
+        } else {
513
+            $this->actionsParsed[] = $action;
514
+        }
515
+
516
+        return $this;
517
+    }
518
+
519
+    /**
520
+     * @return IAction[]
521
+     * @since 8.2.0
522
+     */
523
+    public function getParsedActions(): array {
524
+        return $this->actionsParsed;
525
+    }
526
+
527
+    /**
528
+     * @return bool
529
+     * @since 8.2.0
530
+     */
531
+    public function isValid(): bool {
532
+        return
533
+            $this->isValidCommon()
534
+            &&
535
+            $this->getSubject() !== ''
536
+        ;
537
+    }
538
+
539
+    /**
540
+     * @return bool
541
+     * @since 8.2.0
542
+     */
543
+    public function isValidParsed(): bool {
544
+        if ($this->getRichSubject() !== '' || !empty($this->getRichSubjectParameters())) {
545
+            try {
546
+                $this->richValidator->validate($this->getRichSubject(), $this->getRichSubjectParameters());
547
+            } catch (InvalidObjectExeption $e) {
548
+                return false;
549
+            }
550
+        }
551
+
552
+        if ($this->getRichMessage() !== '' || !empty($this->getRichMessageParameters())) {
553
+            try {
554
+                $this->richValidator->validate($this->getRichMessage(), $this->getRichMessageParameters());
555
+            } catch (InvalidObjectExeption $e) {
556
+                return false;
557
+            }
558
+        }
559
+
560
+        return
561
+            $this->isValidCommon()
562
+            &&
563
+            $this->getParsedSubject() !== ''
564
+        ;
565
+    }
566
+
567
+    protected function isValidCommon(): bool {
568
+        return
569
+            $this->getApp() !== ''
570
+            &&
571
+            $this->getUser() !== ''
572
+            &&
573
+            $this->getDateTime()->getTimestamp() !== 0
574
+            &&
575
+            $this->getObjectType() !== ''
576
+            &&
577
+            $this->getObjectId() !== ''
578
+        ;
579
+    }
580 580
 }
Please login to merge, or discard this patch.
apps/workflowengine/lib/Check/RequestUserAgent.php 1 patch
Indentation   +51 added lines, -51 removed lines patch added patch discarded remove patch
@@ -26,60 +26,60 @@
 block discarded – undo
26 26
 
27 27
 class RequestUserAgent extends AbstractStringCheck {
28 28
 
29
-	/** @var IRequest */
30
-	protected $request;
29
+    /** @var IRequest */
30
+    protected $request;
31 31
 
32
-	/**
33
-	 * @param IL10N $l
34
-	 * @param IRequest $request
35
-	 */
36
-	public function __construct(IL10N $l, IRequest $request) {
37
-		parent::__construct($l);
38
-		$this->request = $request;
39
-	}
32
+    /**
33
+     * @param IL10N $l
34
+     * @param IRequest $request
35
+     */
36
+    public function __construct(IL10N $l, IRequest $request) {
37
+        parent::__construct($l);
38
+        $this->request = $request;
39
+    }
40 40
 
41
-	/**
42
-	 * @param string $operator
43
-	 * @param string $value
44
-	 * @return bool
45
-	 */
46
-	public function executeCheck($operator, $value) {
47
-		$actualValue = $this->getActualValue();
48
-		if (in_array($operator, ['is', '!is'], true)) {
49
-			switch ($value) {
50
-				case 'android':
51
-					$operator = $operator === 'is' ? 'matches' : '!matches';
52
-					$value = IRequest::USER_AGENT_CLIENT_ANDROID;
53
-					break;
54
-				case 'ios':
55
-					$operator = $operator === 'is' ? 'matches' : '!matches';
56
-					$value = IRequest::USER_AGENT_CLIENT_IOS;
57
-					break;
58
-				case 'desktop':
59
-					$operator = $operator === 'is' ? 'matches' : '!matches';
60
-					$value = IRequest::USER_AGENT_CLIENT_DESKTOP;
61
-					break;
62
-				case 'mail':
63
-					if ($operator === 'is') {
64
-						return $this->executeStringCheck('matches', IRequest::USER_AGENT_OUTLOOK_ADDON, $actualValue)
65
-							|| $this->executeStringCheck('matches', IRequest::USER_AGENT_THUNDERBIRD_ADDON, $actualValue);
66
-					}
41
+    /**
42
+     * @param string $operator
43
+     * @param string $value
44
+     * @return bool
45
+     */
46
+    public function executeCheck($operator, $value) {
47
+        $actualValue = $this->getActualValue();
48
+        if (in_array($operator, ['is', '!is'], true)) {
49
+            switch ($value) {
50
+                case 'android':
51
+                    $operator = $operator === 'is' ? 'matches' : '!matches';
52
+                    $value = IRequest::USER_AGENT_CLIENT_ANDROID;
53
+                    break;
54
+                case 'ios':
55
+                    $operator = $operator === 'is' ? 'matches' : '!matches';
56
+                    $value = IRequest::USER_AGENT_CLIENT_IOS;
57
+                    break;
58
+                case 'desktop':
59
+                    $operator = $operator === 'is' ? 'matches' : '!matches';
60
+                    $value = IRequest::USER_AGENT_CLIENT_DESKTOP;
61
+                    break;
62
+                case 'mail':
63
+                    if ($operator === 'is') {
64
+                        return $this->executeStringCheck('matches', IRequest::USER_AGENT_OUTLOOK_ADDON, $actualValue)
65
+                            || $this->executeStringCheck('matches', IRequest::USER_AGENT_THUNDERBIRD_ADDON, $actualValue);
66
+                    }
67 67
 
68
-					return $this->executeStringCheck('!matches', IRequest::USER_AGENT_OUTLOOK_ADDON, $actualValue)
69
-						&& $this->executeStringCheck('!matches', IRequest::USER_AGENT_THUNDERBIRD_ADDON, $actualValue);
70
-			}
71
-		}
72
-		return $this->executeStringCheck($operator, $value, $actualValue);
73
-	}
68
+                    return $this->executeStringCheck('!matches', IRequest::USER_AGENT_OUTLOOK_ADDON, $actualValue)
69
+                        && $this->executeStringCheck('!matches', IRequest::USER_AGENT_THUNDERBIRD_ADDON, $actualValue);
70
+            }
71
+        }
72
+        return $this->executeStringCheck($operator, $value, $actualValue);
73
+    }
74 74
 
75
-	/**
76
-	 * @return string
77
-	 */
78
-	protected function getActualValue() {
79
-		return $this->request->getHeader('User-Agent');
80
-	}
75
+    /**
76
+     * @return string
77
+     */
78
+    protected function getActualValue() {
79
+        return $this->request->getHeader('User-Agent');
80
+    }
81 81
 
82
-	public function isAvailableForScope(int $scope): bool {
83
-		return true;
84
-	}
82
+    public function isAvailableForScope(int $scope): bool {
83
+        return true;
84
+    }
85 85
 }
Please login to merge, or discard this patch.
apps/comments/lib/Activity/Provider.php 1 patch
Indentation   +240 added lines, -240 removed lines patch added patch discarded remove patch
@@ -37,244 +37,244 @@
 block discarded – undo
37 37
 
38 38
 class Provider implements IProvider {
39 39
 
40
-	/** @var IFactory */
41
-	protected $languageFactory;
42
-
43
-	/** @var IL10N */
44
-	protected $l;
45
-
46
-	/** @var IURLGenerator */
47
-	protected $url;
48
-
49
-	/** @var ICommentsManager */
50
-	protected $commentsManager;
51
-
52
-	/** @var IUserManager */
53
-	protected $userManager;
54
-
55
-	/** @var IManager */
56
-	protected $activityManager;
57
-
58
-	/** @var string[] */
59
-	protected $displayNames = [];
60
-
61
-	/**
62
-	 * @param IFactory $languageFactory
63
-	 * @param IURLGenerator $url
64
-	 * @param ICommentsManager $commentsManager
65
-	 * @param IUserManager $userManager
66
-	 * @param IManager $activityManager
67
-	 */
68
-	public function __construct(IFactory $languageFactory, IURLGenerator $url, ICommentsManager $commentsManager, IUserManager $userManager, IManager $activityManager) {
69
-		$this->languageFactory = $languageFactory;
70
-		$this->url = $url;
71
-		$this->commentsManager = $commentsManager;
72
-		$this->userManager = $userManager;
73
-		$this->activityManager = $activityManager;
74
-	}
75
-
76
-	/**
77
-	 * @param string $language
78
-	 * @param IEvent $event
79
-	 * @param IEvent|null $previousEvent
80
-	 * @return IEvent
81
-	 * @throws \InvalidArgumentException
82
-	 * @since 11.0.0
83
-	 */
84
-	public function parse($language, IEvent $event, IEvent $previousEvent = null) {
85
-		if ($event->getApp() !== 'comments') {
86
-			throw new \InvalidArgumentException();
87
-		}
88
-
89
-		$this->l = $this->languageFactory->get('comments', $language);
90
-
91
-		if ($event->getSubject() === 'add_comment_subject') {
92
-			$this->parseMessage($event);
93
-			if ($this->activityManager->getRequirePNG()) {
94
-				$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/comment.png')));
95
-			} else {
96
-				$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/comment.svg')));
97
-			}
98
-
99
-			if ($this->activityManager->isFormattingFilteredObject()) {
100
-				try {
101
-					return $this->parseShortVersion($event);
102
-				} catch (\InvalidArgumentException $e) {
103
-					// Ignore and simply use the long version...
104
-				}
105
-			}
106
-
107
-			return $this->parseLongVersion($event);
108
-		} else {
109
-			throw new \InvalidArgumentException();
110
-		}
111
-	}
112
-
113
-	/**
114
-	 * @param IEvent $event
115
-	 * @return IEvent
116
-	 * @throws \InvalidArgumentException
117
-	 */
118
-	protected function parseShortVersion(IEvent $event) {
119
-		$subjectParameters = $this->getSubjectParameters($event);
120
-
121
-		if ($event->getSubject() === 'add_comment_subject') {
122
-			if ($subjectParameters['actor'] === $this->activityManager->getCurrentUserId()) {
123
-				$event->setParsedSubject($this->l->t('You commented'))
124
-					->setRichSubject($this->l->t('You commented'), []);
125
-			} else {
126
-				$author = $this->generateUserParameter($subjectParameters['actor']);
127
-				$event->setParsedSubject($this->l->t('%1$s commented', [$author['name']]))
128
-					->setRichSubject($this->l->t('{author} commented'), [
129
-						'author' => $author,
130
-					]);
131
-			}
132
-		} else {
133
-			throw new \InvalidArgumentException();
134
-		}
135
-
136
-		return $event;
137
-	}
138
-
139
-	/**
140
-	 * @param IEvent $event
141
-	 * @return IEvent
142
-	 * @throws \InvalidArgumentException
143
-	 */
144
-	protected function parseLongVersion(IEvent $event) {
145
-		$subjectParameters = $this->getSubjectParameters($event);
146
-
147
-		if ($event->getSubject() === 'add_comment_subject') {
148
-			if ($subjectParameters['actor'] === $this->activityManager->getCurrentUserId()) {
149
-				$event->setParsedSubject($this->l->t('You commented on %1$s', [
150
-					$subjectParameters['filePath'],
151
-				]))
152
-					->setRichSubject($this->l->t('You commented on {file}'), [
153
-						'file' => $this->generateFileParameter($subjectParameters['fileId'], $subjectParameters['filePath']),
154
-					]);
155
-			} else {
156
-				$author = $this->generateUserParameter($subjectParameters['actor']);
157
-				$event->setParsedSubject($this->l->t('%1$s commented on %2$s', [
158
-					$author['name'],
159
-					$subjectParameters['filePath'],
160
-				]))
161
-					->setRichSubject($this->l->t('{author} commented on {file}'), [
162
-						'author' => $author,
163
-						'file' => $this->generateFileParameter($subjectParameters['fileId'], $subjectParameters['filePath']),
164
-					]);
165
-			}
166
-		} else {
167
-			throw new \InvalidArgumentException();
168
-		}
169
-
170
-		return $event;
171
-	}
172
-
173
-	protected function getSubjectParameters(IEvent $event) {
174
-		$subjectParameters = $event->getSubjectParameters();
175
-		if (isset($subjectParameters['fileId'])) {
176
-			return $subjectParameters;
177
-		}
178
-
179
-		// Fix subjects from 12.0.3 and older
180
-		//
181
-		// Do NOT Remove unless necessary
182
-		// Removing this will break parsing of activities that were created on
183
-		// Nextcloud 12, so we should keep this as long as it's acceptable.
184
-		// Otherwise if people upgrade over multiple releases in a short period,
185
-		// they will get the dead entries in their stream.
186
-		return [
187
-			'actor' => $subjectParameters[0],
188
-			'fileId' => $event->getObjectId(),
189
-			'filePath' => trim($subjectParameters[1], '/'),
190
-		];
191
-	}
192
-
193
-	/**
194
-	 * @param IEvent $event
195
-	 */
196
-	protected function parseMessage(IEvent $event) {
197
-		$messageParameters = $event->getMessageParameters();
198
-		if (empty($messageParameters)) {
199
-			// Email
200
-			return;
201
-		}
202
-
203
-		$commentId = isset($messageParameters['commentId']) ? $messageParameters['commentId'] : $messageParameters[0];
204
-
205
-		try {
206
-			$comment = $this->commentsManager->get((string) $commentId);
207
-			$message = $comment->getMessage();
208
-
209
-			$mentionCount = 1;
210
-			$mentions = [];
211
-			foreach ($comment->getMentions() as $mention) {
212
-				if ($mention['type'] !== 'user') {
213
-					continue;
214
-				}
215
-
216
-				$pattern = '/(^|\s)(' . '@' . $mention['id'] . ')(\b)/';
217
-				if (strpos($mention['id'], ' ') !== false) {
218
-					$pattern = '/(^|\s)(' . '@"' . $mention['id'] . '"' . ')(\b)?/';
219
-				}
220
-
221
-				$message = preg_replace(
222
-					$pattern,
223
-					//'${1}' . $this->regexSafeUser($mention['id'], $displayName) . '${3}',
224
-					'${1}' . '{mention' . $mentionCount . '}' . '${3}',
225
-					$message
226
-				);
227
-				$mentions['mention' . $mentionCount] = $this->generateUserParameter($mention['id']);
228
-				$mentionCount++;
229
-			}
230
-
231
-			$event->setParsedMessage($comment->getMessage())
232
-				->setRichMessage($message, $mentions);
233
-		} catch (NotFoundException $e) {
234
-		}
235
-	}
236
-
237
-	/**
238
-	 * @param int $id
239
-	 * @param string $path
240
-	 * @return array
241
-	 */
242
-	protected function generateFileParameter($id, $path) {
243
-		return [
244
-			'type' => 'file',
245
-			'id' => $id,
246
-			'name' => basename($path),
247
-			'path' => $path,
248
-			'link' => $this->url->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $id]),
249
-		];
250
-	}
251
-
252
-	/**
253
-	 * @param string $uid
254
-	 * @return array
255
-	 */
256
-	protected function generateUserParameter($uid) {
257
-		if (!isset($this->displayNames[$uid])) {
258
-			$this->displayNames[$uid] = $this->getDisplayName($uid);
259
-		}
260
-
261
-		return [
262
-			'type' => 'user',
263
-			'id' => $uid,
264
-			'name' => $this->displayNames[$uid],
265
-		];
266
-	}
267
-
268
-	/**
269
-	 * @param string $uid
270
-	 * @return string
271
-	 */
272
-	protected function getDisplayName($uid) {
273
-		$user = $this->userManager->get($uid);
274
-		if ($user instanceof IUser) {
275
-			return $user->getDisplayName();
276
-		} else {
277
-			return $uid;
278
-		}
279
-	}
40
+    /** @var IFactory */
41
+    protected $languageFactory;
42
+
43
+    /** @var IL10N */
44
+    protected $l;
45
+
46
+    /** @var IURLGenerator */
47
+    protected $url;
48
+
49
+    /** @var ICommentsManager */
50
+    protected $commentsManager;
51
+
52
+    /** @var IUserManager */
53
+    protected $userManager;
54
+
55
+    /** @var IManager */
56
+    protected $activityManager;
57
+
58
+    /** @var string[] */
59
+    protected $displayNames = [];
60
+
61
+    /**
62
+     * @param IFactory $languageFactory
63
+     * @param IURLGenerator $url
64
+     * @param ICommentsManager $commentsManager
65
+     * @param IUserManager $userManager
66
+     * @param IManager $activityManager
67
+     */
68
+    public function __construct(IFactory $languageFactory, IURLGenerator $url, ICommentsManager $commentsManager, IUserManager $userManager, IManager $activityManager) {
69
+        $this->languageFactory = $languageFactory;
70
+        $this->url = $url;
71
+        $this->commentsManager = $commentsManager;
72
+        $this->userManager = $userManager;
73
+        $this->activityManager = $activityManager;
74
+    }
75
+
76
+    /**
77
+     * @param string $language
78
+     * @param IEvent $event
79
+     * @param IEvent|null $previousEvent
80
+     * @return IEvent
81
+     * @throws \InvalidArgumentException
82
+     * @since 11.0.0
83
+     */
84
+    public function parse($language, IEvent $event, IEvent $previousEvent = null) {
85
+        if ($event->getApp() !== 'comments') {
86
+            throw new \InvalidArgumentException();
87
+        }
88
+
89
+        $this->l = $this->languageFactory->get('comments', $language);
90
+
91
+        if ($event->getSubject() === 'add_comment_subject') {
92
+            $this->parseMessage($event);
93
+            if ($this->activityManager->getRequirePNG()) {
94
+                $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/comment.png')));
95
+            } else {
96
+                $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/comment.svg')));
97
+            }
98
+
99
+            if ($this->activityManager->isFormattingFilteredObject()) {
100
+                try {
101
+                    return $this->parseShortVersion($event);
102
+                } catch (\InvalidArgumentException $e) {
103
+                    // Ignore and simply use the long version...
104
+                }
105
+            }
106
+
107
+            return $this->parseLongVersion($event);
108
+        } else {
109
+            throw new \InvalidArgumentException();
110
+        }
111
+    }
112
+
113
+    /**
114
+     * @param IEvent $event
115
+     * @return IEvent
116
+     * @throws \InvalidArgumentException
117
+     */
118
+    protected function parseShortVersion(IEvent $event) {
119
+        $subjectParameters = $this->getSubjectParameters($event);
120
+
121
+        if ($event->getSubject() === 'add_comment_subject') {
122
+            if ($subjectParameters['actor'] === $this->activityManager->getCurrentUserId()) {
123
+                $event->setParsedSubject($this->l->t('You commented'))
124
+                    ->setRichSubject($this->l->t('You commented'), []);
125
+            } else {
126
+                $author = $this->generateUserParameter($subjectParameters['actor']);
127
+                $event->setParsedSubject($this->l->t('%1$s commented', [$author['name']]))
128
+                    ->setRichSubject($this->l->t('{author} commented'), [
129
+                        'author' => $author,
130
+                    ]);
131
+            }
132
+        } else {
133
+            throw new \InvalidArgumentException();
134
+        }
135
+
136
+        return $event;
137
+    }
138
+
139
+    /**
140
+     * @param IEvent $event
141
+     * @return IEvent
142
+     * @throws \InvalidArgumentException
143
+     */
144
+    protected function parseLongVersion(IEvent $event) {
145
+        $subjectParameters = $this->getSubjectParameters($event);
146
+
147
+        if ($event->getSubject() === 'add_comment_subject') {
148
+            if ($subjectParameters['actor'] === $this->activityManager->getCurrentUserId()) {
149
+                $event->setParsedSubject($this->l->t('You commented on %1$s', [
150
+                    $subjectParameters['filePath'],
151
+                ]))
152
+                    ->setRichSubject($this->l->t('You commented on {file}'), [
153
+                        'file' => $this->generateFileParameter($subjectParameters['fileId'], $subjectParameters['filePath']),
154
+                    ]);
155
+            } else {
156
+                $author = $this->generateUserParameter($subjectParameters['actor']);
157
+                $event->setParsedSubject($this->l->t('%1$s commented on %2$s', [
158
+                    $author['name'],
159
+                    $subjectParameters['filePath'],
160
+                ]))
161
+                    ->setRichSubject($this->l->t('{author} commented on {file}'), [
162
+                        'author' => $author,
163
+                        'file' => $this->generateFileParameter($subjectParameters['fileId'], $subjectParameters['filePath']),
164
+                    ]);
165
+            }
166
+        } else {
167
+            throw new \InvalidArgumentException();
168
+        }
169
+
170
+        return $event;
171
+    }
172
+
173
+    protected function getSubjectParameters(IEvent $event) {
174
+        $subjectParameters = $event->getSubjectParameters();
175
+        if (isset($subjectParameters['fileId'])) {
176
+            return $subjectParameters;
177
+        }
178
+
179
+        // Fix subjects from 12.0.3 and older
180
+        //
181
+        // Do NOT Remove unless necessary
182
+        // Removing this will break parsing of activities that were created on
183
+        // Nextcloud 12, so we should keep this as long as it's acceptable.
184
+        // Otherwise if people upgrade over multiple releases in a short period,
185
+        // they will get the dead entries in their stream.
186
+        return [
187
+            'actor' => $subjectParameters[0],
188
+            'fileId' => $event->getObjectId(),
189
+            'filePath' => trim($subjectParameters[1], '/'),
190
+        ];
191
+    }
192
+
193
+    /**
194
+     * @param IEvent $event
195
+     */
196
+    protected function parseMessage(IEvent $event) {
197
+        $messageParameters = $event->getMessageParameters();
198
+        if (empty($messageParameters)) {
199
+            // Email
200
+            return;
201
+        }
202
+
203
+        $commentId = isset($messageParameters['commentId']) ? $messageParameters['commentId'] : $messageParameters[0];
204
+
205
+        try {
206
+            $comment = $this->commentsManager->get((string) $commentId);
207
+            $message = $comment->getMessage();
208
+
209
+            $mentionCount = 1;
210
+            $mentions = [];
211
+            foreach ($comment->getMentions() as $mention) {
212
+                if ($mention['type'] !== 'user') {
213
+                    continue;
214
+                }
215
+
216
+                $pattern = '/(^|\s)(' . '@' . $mention['id'] . ')(\b)/';
217
+                if (strpos($mention['id'], ' ') !== false) {
218
+                    $pattern = '/(^|\s)(' . '@"' . $mention['id'] . '"' . ')(\b)?/';
219
+                }
220
+
221
+                $message = preg_replace(
222
+                    $pattern,
223
+                    //'${1}' . $this->regexSafeUser($mention['id'], $displayName) . '${3}',
224
+                    '${1}' . '{mention' . $mentionCount . '}' . '${3}',
225
+                    $message
226
+                );
227
+                $mentions['mention' . $mentionCount] = $this->generateUserParameter($mention['id']);
228
+                $mentionCount++;
229
+            }
230
+
231
+            $event->setParsedMessage($comment->getMessage())
232
+                ->setRichMessage($message, $mentions);
233
+        } catch (NotFoundException $e) {
234
+        }
235
+    }
236
+
237
+    /**
238
+     * @param int $id
239
+     * @param string $path
240
+     * @return array
241
+     */
242
+    protected function generateFileParameter($id, $path) {
243
+        return [
244
+            'type' => 'file',
245
+            'id' => $id,
246
+            'name' => basename($path),
247
+            'path' => $path,
248
+            'link' => $this->url->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $id]),
249
+        ];
250
+    }
251
+
252
+    /**
253
+     * @param string $uid
254
+     * @return array
255
+     */
256
+    protected function generateUserParameter($uid) {
257
+        if (!isset($this->displayNames[$uid])) {
258
+            $this->displayNames[$uid] = $this->getDisplayName($uid);
259
+        }
260
+
261
+        return [
262
+            'type' => 'user',
263
+            'id' => $uid,
264
+            'name' => $this->displayNames[$uid],
265
+        ];
266
+    }
267
+
268
+    /**
269
+     * @param string $uid
270
+     * @return string
271
+     */
272
+    protected function getDisplayName($uid) {
273
+        $user = $this->userManager->get($uid);
274
+        if ($user instanceof IUser) {
275
+            return $user->getDisplayName();
276
+        } else {
277
+            return $uid;
278
+        }
279
+    }
280 280
 }
Please login to merge, or discard this patch.