Passed
Push — master ( 683685...5ed673 )
by Morris
13:18 queued 11s
created
lib/private/AppFramework/Routing/RouteConfig.php 1 patch
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.
lib/private/Collaboration/Collaborators/Search.php 1 patch
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.
lib/private/L10N/Factory.php 1 patch
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.
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.
apps/settings/lib/Controller/CheckSetupController.php 1 patch
Indentation   +671 added lines, -671 removed lines patch added patch discarded remove patch
@@ -81,295 +81,295 @@  discard block
 block discarded – undo
81 81
 use Symfony\Component\EventDispatcher\GenericEvent;
82 82
 
83 83
 class CheckSetupController extends Controller {
84
-	/** @var IConfig */
85
-	private $config;
86
-	/** @var IClientService */
87
-	private $clientService;
88
-	/** @var IURLGenerator */
89
-	private $urlGenerator;
90
-	/** @var IL10N */
91
-	private $l10n;
92
-	/** @var Checker */
93
-	private $checker;
94
-	/** @var ILogger */
95
-	private $logger;
96
-	/** @var EventDispatcherInterface */
97
-	private $dispatcher;
98
-	/** @var Connection */
99
-	private $db;
100
-	/** @var ILockingProvider */
101
-	private $lockingProvider;
102
-	/** @var IDateTimeFormatter */
103
-	private $dateTimeFormatter;
104
-	/** @var MemoryInfo */
105
-	private $memoryInfo;
106
-	/** @var ISecureRandom */
107
-	private $secureRandom;
108
-	/** @var IniGetWrapper */
109
-	private $iniGetWrapper;
110
-
111
-	public function __construct($AppName,
112
-								IRequest $request,
113
-								IConfig $config,
114
-								IClientService $clientService,
115
-								IURLGenerator $urlGenerator,
116
-								IL10N $l10n,
117
-								Checker $checker,
118
-								ILogger $logger,
119
-								EventDispatcherInterface $dispatcher,
120
-								Connection $db,
121
-								ILockingProvider $lockingProvider,
122
-								IDateTimeFormatter $dateTimeFormatter,
123
-								MemoryInfo $memoryInfo,
124
-								ISecureRandom $secureRandom,
125
-								IniGetWrapper $iniGetWrapper) {
126
-		parent::__construct($AppName, $request);
127
-		$this->config = $config;
128
-		$this->clientService = $clientService;
129
-		$this->urlGenerator = $urlGenerator;
130
-		$this->l10n = $l10n;
131
-		$this->checker = $checker;
132
-		$this->logger = $logger;
133
-		$this->dispatcher = $dispatcher;
134
-		$this->db = $db;
135
-		$this->lockingProvider = $lockingProvider;
136
-		$this->dateTimeFormatter = $dateTimeFormatter;
137
-		$this->memoryInfo = $memoryInfo;
138
-		$this->secureRandom = $secureRandom;
139
-		$this->iniGetWrapper = $iniGetWrapper;
140
-	}
141
-
142
-	/**
143
-	 * Checks if the server can connect to the internet using HTTPS and HTTP
144
-	 * @return bool
145
-	 */
146
-	private function hasInternetConnectivityProblems(): bool {
147
-		if ($this->config->getSystemValue('has_internet_connection', true) === false) {
148
-			return false;
149
-		}
150
-
151
-		$siteArray = $this->config->getSystemValue('connectivity_check_domains', [
152
-			'www.nextcloud.com', 'www.startpage.com', 'www.eff.org', 'www.edri.org'
153
-		]);
154
-
155
-		foreach ($siteArray as $site) {
156
-			if ($this->isSiteReachable($site)) {
157
-				return false;
158
-			}
159
-		}
160
-		return true;
161
-	}
162
-
163
-	/**
164
-	 * Checks if the Nextcloud server can connect to a specific URL using both HTTPS and HTTP
165
-	 * @return bool
166
-	 */
167
-	private function isSiteReachable($sitename) {
168
-		$httpSiteName = 'http://' . $sitename . '/';
169
-		$httpsSiteName = 'https://' . $sitename . '/';
170
-
171
-		try {
172
-			$client = $this->clientService->newClient();
173
-			$client->get($httpSiteName);
174
-			$client->get($httpsSiteName);
175
-		} catch (\Exception $e) {
176
-			$this->logger->logException($e, ['app' => 'internet_connection_check']);
177
-			return false;
178
-		}
179
-		return true;
180
-	}
181
-
182
-	/**
183
-	 * Checks whether a local memcache is installed or not
184
-	 * @return bool
185
-	 */
186
-	private function isMemcacheConfigured() {
187
-		return $this->config->getSystemValue('memcache.local', null) !== null;
188
-	}
189
-
190
-	/**
191
-	 * Whether PHP can generate "secure" pseudorandom integers
192
-	 *
193
-	 * @return bool
194
-	 */
195
-	private function isRandomnessSecure() {
196
-		try {
197
-			$this->secureRandom->generate(1);
198
-		} catch (\Exception $ex) {
199
-			return false;
200
-		}
201
-		return true;
202
-	}
203
-
204
-	/**
205
-	 * Public for the sake of unit-testing
206
-	 *
207
-	 * @return array
208
-	 */
209
-	protected function getCurlVersion() {
210
-		return curl_version();
211
-	}
212
-
213
-	/**
214
-	 * Check if the used  SSL lib is outdated. Older OpenSSL and NSS versions do
215
-	 * have multiple bugs which likely lead to problems in combination with
216
-	 * functionality required by ownCloud such as SNI.
217
-	 *
218
-	 * @link https://github.com/owncloud/core/issues/17446#issuecomment-122877546
219
-	 * @link https://bugzilla.redhat.com/show_bug.cgi?id=1241172
220
-	 * @return string
221
-	 */
222
-	private function isUsedTlsLibOutdated() {
223
-		// Don't run check when:
224
-		// 1. Server has `has_internet_connection` set to false
225
-		// 2. AppStore AND S2S is disabled
226
-		if (!$this->config->getSystemValue('has_internet_connection', true)) {
227
-			return '';
228
-		}
229
-		if (!$this->config->getSystemValue('appstoreenabled', true)
230
-			&& $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'no'
231
-			&& $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes') === 'no') {
232
-			return '';
233
-		}
234
-
235
-		$versionString = $this->getCurlVersion();
236
-		if (isset($versionString['ssl_version'])) {
237
-			$versionString = $versionString['ssl_version'];
238
-		} else {
239
-			return '';
240
-		}
241
-
242
-		$features = $this->l10n->t('installing and updating apps via the app store or Federated Cloud Sharing');
243
-		if (!$this->config->getSystemValue('appstoreenabled', true)) {
244
-			$features = $this->l10n->t('Federated Cloud Sharing');
245
-		}
246
-
247
-		// Check if at least OpenSSL after 1.01d or 1.0.2b
248
-		if (strpos($versionString, 'OpenSSL/') === 0) {
249
-			$majorVersion = substr($versionString, 8, 5);
250
-			$patchRelease = substr($versionString, 13, 6);
251
-
252
-			if (($majorVersion === '1.0.1' && ord($patchRelease) < ord('d')) ||
253
-				($majorVersion === '1.0.2' && ord($patchRelease) < ord('b'))) {
254
-				return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['OpenSSL', $versionString, $features]);
255
-			}
256
-		}
257
-
258
-		// Check if NSS and perform heuristic check
259
-		if (strpos($versionString, 'NSS/') === 0) {
260
-			try {
261
-				$firstClient = $this->clientService->newClient();
262
-				$firstClient->get('https://nextcloud.com/');
263
-
264
-				$secondClient = $this->clientService->newClient();
265
-				$secondClient->get('https://nextcloud.com/');
266
-			} catch (ClientException $e) {
267
-				if ($e->getResponse()->getStatusCode() === 400) {
268
-					return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['NSS', $versionString, $features]);
269
-				}
270
-			} catch (\Exception $e) {
271
-				$this->logger->logException($e, ['app' => 'settings', 'level' => \OCP\ILogger::WARN]);
272
-				return $this->l10n->t('Could not determine if TLS version of cURL is outdated or not because an error happened during the HTTPS request against https://nextcloud.com. Please check the nextcloud log file for more details.');
273
-			}
274
-		}
275
-
276
-		return '';
277
-	}
278
-
279
-	/**
280
-	 * Whether the version is outdated
281
-	 *
282
-	 * @return bool
283
-	 */
284
-	protected function isPhpOutdated(): bool {
285
-		return PHP_VERSION_ID < 70300;
286
-	}
287
-
288
-	/**
289
-	 * Whether the php version is still supported (at time of release)
290
-	 * according to: https://www.php.net/supported-versions.php
291
-	 *
292
-	 * @return array
293
-	 */
294
-	private function isPhpSupported(): array {
295
-		return ['eol' => $this->isPhpOutdated(), 'version' => PHP_VERSION];
296
-	}
297
-
298
-	/**
299
-	 * Check if the reverse proxy configuration is working as expected
300
-	 *
301
-	 * @return bool
302
-	 */
303
-	private function forwardedForHeadersWorking() {
304
-		$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
305
-		$remoteAddress = $this->request->getHeader('REMOTE_ADDR');
306
-
307
-		if (empty($trustedProxies) && $this->request->getHeader('X-Forwarded-Host') !== '') {
308
-			return false;
309
-		}
310
-
311
-		if (\is_array($trustedProxies) && \in_array($remoteAddress, $trustedProxies, true)) {
312
-			return $remoteAddress !== $this->request->getRemoteAddress();
313
-		}
314
-
315
-		// either not enabled or working correctly
316
-		return true;
317
-	}
318
-
319
-	/**
320
-	 * Checks if the correct memcache module for PHP is installed. Only
321
-	 * fails if memcached is configured and the working module is not installed.
322
-	 *
323
-	 * @return bool
324
-	 */
325
-	private function isCorrectMemcachedPHPModuleInstalled() {
326
-		if ($this->config->getSystemValue('memcache.distributed', null) !== '\OC\Memcache\Memcached') {
327
-			return true;
328
-		}
329
-
330
-		// there are two different memcached modules for PHP
331
-		// we only support memcached and not memcache
332
-		// https://code.google.com/p/memcached/wiki/PHPClientComparison
333
-		return !(!extension_loaded('memcached') && extension_loaded('memcache'));
334
-	}
335
-
336
-	/**
337
-	 * Checks if set_time_limit is not disabled.
338
-	 *
339
-	 * @return bool
340
-	 */
341
-	private function isSettimelimitAvailable() {
342
-		if (function_exists('set_time_limit')
343
-			&& strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
344
-			return true;
345
-		}
346
-
347
-		return false;
348
-	}
349
-
350
-	/**
351
-	 * @return RedirectResponse
352
-	 */
353
-	public function rescanFailedIntegrityCheck() {
354
-		$this->checker->runInstanceVerification();
355
-		return new RedirectResponse(
356
-			$this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'overview'])
357
-		);
358
-	}
359
-
360
-	/**
361
-	 * @NoCSRFRequired
362
-	 * @return DataResponse
363
-	 */
364
-	public function getFailedIntegrityCheckFiles() {
365
-		if (!$this->checker->isCodeCheckEnforced()) {
366
-			return new DataDisplayResponse('Integrity checker has been disabled. Integrity cannot be verified.');
367
-		}
368
-
369
-		$completeResults = $this->checker->getResults();
370
-
371
-		if (!empty($completeResults)) {
372
-			$formattedTextResponse = 'Technical information
84
+    /** @var IConfig */
85
+    private $config;
86
+    /** @var IClientService */
87
+    private $clientService;
88
+    /** @var IURLGenerator */
89
+    private $urlGenerator;
90
+    /** @var IL10N */
91
+    private $l10n;
92
+    /** @var Checker */
93
+    private $checker;
94
+    /** @var ILogger */
95
+    private $logger;
96
+    /** @var EventDispatcherInterface */
97
+    private $dispatcher;
98
+    /** @var Connection */
99
+    private $db;
100
+    /** @var ILockingProvider */
101
+    private $lockingProvider;
102
+    /** @var IDateTimeFormatter */
103
+    private $dateTimeFormatter;
104
+    /** @var MemoryInfo */
105
+    private $memoryInfo;
106
+    /** @var ISecureRandom */
107
+    private $secureRandom;
108
+    /** @var IniGetWrapper */
109
+    private $iniGetWrapper;
110
+
111
+    public function __construct($AppName,
112
+                                IRequest $request,
113
+                                IConfig $config,
114
+                                IClientService $clientService,
115
+                                IURLGenerator $urlGenerator,
116
+                                IL10N $l10n,
117
+                                Checker $checker,
118
+                                ILogger $logger,
119
+                                EventDispatcherInterface $dispatcher,
120
+                                Connection $db,
121
+                                ILockingProvider $lockingProvider,
122
+                                IDateTimeFormatter $dateTimeFormatter,
123
+                                MemoryInfo $memoryInfo,
124
+                                ISecureRandom $secureRandom,
125
+                                IniGetWrapper $iniGetWrapper) {
126
+        parent::__construct($AppName, $request);
127
+        $this->config = $config;
128
+        $this->clientService = $clientService;
129
+        $this->urlGenerator = $urlGenerator;
130
+        $this->l10n = $l10n;
131
+        $this->checker = $checker;
132
+        $this->logger = $logger;
133
+        $this->dispatcher = $dispatcher;
134
+        $this->db = $db;
135
+        $this->lockingProvider = $lockingProvider;
136
+        $this->dateTimeFormatter = $dateTimeFormatter;
137
+        $this->memoryInfo = $memoryInfo;
138
+        $this->secureRandom = $secureRandom;
139
+        $this->iniGetWrapper = $iniGetWrapper;
140
+    }
141
+
142
+    /**
143
+     * Checks if the server can connect to the internet using HTTPS and HTTP
144
+     * @return bool
145
+     */
146
+    private function hasInternetConnectivityProblems(): bool {
147
+        if ($this->config->getSystemValue('has_internet_connection', true) === false) {
148
+            return false;
149
+        }
150
+
151
+        $siteArray = $this->config->getSystemValue('connectivity_check_domains', [
152
+            'www.nextcloud.com', 'www.startpage.com', 'www.eff.org', 'www.edri.org'
153
+        ]);
154
+
155
+        foreach ($siteArray as $site) {
156
+            if ($this->isSiteReachable($site)) {
157
+                return false;
158
+            }
159
+        }
160
+        return true;
161
+    }
162
+
163
+    /**
164
+     * Checks if the Nextcloud server can connect to a specific URL using both HTTPS and HTTP
165
+     * @return bool
166
+     */
167
+    private function isSiteReachable($sitename) {
168
+        $httpSiteName = 'http://' . $sitename . '/';
169
+        $httpsSiteName = 'https://' . $sitename . '/';
170
+
171
+        try {
172
+            $client = $this->clientService->newClient();
173
+            $client->get($httpSiteName);
174
+            $client->get($httpsSiteName);
175
+        } catch (\Exception $e) {
176
+            $this->logger->logException($e, ['app' => 'internet_connection_check']);
177
+            return false;
178
+        }
179
+        return true;
180
+    }
181
+
182
+    /**
183
+     * Checks whether a local memcache is installed or not
184
+     * @return bool
185
+     */
186
+    private function isMemcacheConfigured() {
187
+        return $this->config->getSystemValue('memcache.local', null) !== null;
188
+    }
189
+
190
+    /**
191
+     * Whether PHP can generate "secure" pseudorandom integers
192
+     *
193
+     * @return bool
194
+     */
195
+    private function isRandomnessSecure() {
196
+        try {
197
+            $this->secureRandom->generate(1);
198
+        } catch (\Exception $ex) {
199
+            return false;
200
+        }
201
+        return true;
202
+    }
203
+
204
+    /**
205
+     * Public for the sake of unit-testing
206
+     *
207
+     * @return array
208
+     */
209
+    protected function getCurlVersion() {
210
+        return curl_version();
211
+    }
212
+
213
+    /**
214
+     * Check if the used  SSL lib is outdated. Older OpenSSL and NSS versions do
215
+     * have multiple bugs which likely lead to problems in combination with
216
+     * functionality required by ownCloud such as SNI.
217
+     *
218
+     * @link https://github.com/owncloud/core/issues/17446#issuecomment-122877546
219
+     * @link https://bugzilla.redhat.com/show_bug.cgi?id=1241172
220
+     * @return string
221
+     */
222
+    private function isUsedTlsLibOutdated() {
223
+        // Don't run check when:
224
+        // 1. Server has `has_internet_connection` set to false
225
+        // 2. AppStore AND S2S is disabled
226
+        if (!$this->config->getSystemValue('has_internet_connection', true)) {
227
+            return '';
228
+        }
229
+        if (!$this->config->getSystemValue('appstoreenabled', true)
230
+            && $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'no'
231
+            && $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes') === 'no') {
232
+            return '';
233
+        }
234
+
235
+        $versionString = $this->getCurlVersion();
236
+        if (isset($versionString['ssl_version'])) {
237
+            $versionString = $versionString['ssl_version'];
238
+        } else {
239
+            return '';
240
+        }
241
+
242
+        $features = $this->l10n->t('installing and updating apps via the app store or Federated Cloud Sharing');
243
+        if (!$this->config->getSystemValue('appstoreenabled', true)) {
244
+            $features = $this->l10n->t('Federated Cloud Sharing');
245
+        }
246
+
247
+        // Check if at least OpenSSL after 1.01d or 1.0.2b
248
+        if (strpos($versionString, 'OpenSSL/') === 0) {
249
+            $majorVersion = substr($versionString, 8, 5);
250
+            $patchRelease = substr($versionString, 13, 6);
251
+
252
+            if (($majorVersion === '1.0.1' && ord($patchRelease) < ord('d')) ||
253
+                ($majorVersion === '1.0.2' && ord($patchRelease) < ord('b'))) {
254
+                return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['OpenSSL', $versionString, $features]);
255
+            }
256
+        }
257
+
258
+        // Check if NSS and perform heuristic check
259
+        if (strpos($versionString, 'NSS/') === 0) {
260
+            try {
261
+                $firstClient = $this->clientService->newClient();
262
+                $firstClient->get('https://nextcloud.com/');
263
+
264
+                $secondClient = $this->clientService->newClient();
265
+                $secondClient->get('https://nextcloud.com/');
266
+            } catch (ClientException $e) {
267
+                if ($e->getResponse()->getStatusCode() === 400) {
268
+                    return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['NSS', $versionString, $features]);
269
+                }
270
+            } catch (\Exception $e) {
271
+                $this->logger->logException($e, ['app' => 'settings', 'level' => \OCP\ILogger::WARN]);
272
+                return $this->l10n->t('Could not determine if TLS version of cURL is outdated or not because an error happened during the HTTPS request against https://nextcloud.com. Please check the nextcloud log file for more details.');
273
+            }
274
+        }
275
+
276
+        return '';
277
+    }
278
+
279
+    /**
280
+     * Whether the version is outdated
281
+     *
282
+     * @return bool
283
+     */
284
+    protected function isPhpOutdated(): bool {
285
+        return PHP_VERSION_ID < 70300;
286
+    }
287
+
288
+    /**
289
+     * Whether the php version is still supported (at time of release)
290
+     * according to: https://www.php.net/supported-versions.php
291
+     *
292
+     * @return array
293
+     */
294
+    private function isPhpSupported(): array {
295
+        return ['eol' => $this->isPhpOutdated(), 'version' => PHP_VERSION];
296
+    }
297
+
298
+    /**
299
+     * Check if the reverse proxy configuration is working as expected
300
+     *
301
+     * @return bool
302
+     */
303
+    private function forwardedForHeadersWorking() {
304
+        $trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
305
+        $remoteAddress = $this->request->getHeader('REMOTE_ADDR');
306
+
307
+        if (empty($trustedProxies) && $this->request->getHeader('X-Forwarded-Host') !== '') {
308
+            return false;
309
+        }
310
+
311
+        if (\is_array($trustedProxies) && \in_array($remoteAddress, $trustedProxies, true)) {
312
+            return $remoteAddress !== $this->request->getRemoteAddress();
313
+        }
314
+
315
+        // either not enabled or working correctly
316
+        return true;
317
+    }
318
+
319
+    /**
320
+     * Checks if the correct memcache module for PHP is installed. Only
321
+     * fails if memcached is configured and the working module is not installed.
322
+     *
323
+     * @return bool
324
+     */
325
+    private function isCorrectMemcachedPHPModuleInstalled() {
326
+        if ($this->config->getSystemValue('memcache.distributed', null) !== '\OC\Memcache\Memcached') {
327
+            return true;
328
+        }
329
+
330
+        // there are two different memcached modules for PHP
331
+        // we only support memcached and not memcache
332
+        // https://code.google.com/p/memcached/wiki/PHPClientComparison
333
+        return !(!extension_loaded('memcached') && extension_loaded('memcache'));
334
+    }
335
+
336
+    /**
337
+     * Checks if set_time_limit is not disabled.
338
+     *
339
+     * @return bool
340
+     */
341
+    private function isSettimelimitAvailable() {
342
+        if (function_exists('set_time_limit')
343
+            && strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
344
+            return true;
345
+        }
346
+
347
+        return false;
348
+    }
349
+
350
+    /**
351
+     * @return RedirectResponse
352
+     */
353
+    public function rescanFailedIntegrityCheck() {
354
+        $this->checker->runInstanceVerification();
355
+        return new RedirectResponse(
356
+            $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'overview'])
357
+        );
358
+    }
359
+
360
+    /**
361
+     * @NoCSRFRequired
362
+     * @return DataResponse
363
+     */
364
+    public function getFailedIntegrityCheckFiles() {
365
+        if (!$this->checker->isCodeCheckEnforced()) {
366
+            return new DataDisplayResponse('Integrity checker has been disabled. Integrity cannot be verified.');
367
+        }
368
+
369
+        $completeResults = $this->checker->getResults();
370
+
371
+        if (!empty($completeResults)) {
372
+            $formattedTextResponse = 'Technical information
373 373
 =====================
374 374
 The following list covers which files have failed the integrity check. Please read
375 375
 the previous linked documentation to learn more about the errors and how to fix
@@ -378,389 +378,389 @@  discard block
 block discarded – undo
378 378
 Results
379 379
 =======
380 380
 ';
381
-			foreach ($completeResults as $context => $contextResult) {
382
-				$formattedTextResponse .= "- $context\n";
383
-
384
-				foreach ($contextResult as $category => $result) {
385
-					$formattedTextResponse .= "\t- $category\n";
386
-					if ($category !== 'EXCEPTION') {
387
-						foreach ($result as $key => $results) {
388
-							$formattedTextResponse .= "\t\t- $key\n";
389
-						}
390
-					} else {
391
-						foreach ($result as $key => $results) {
392
-							$formattedTextResponse .= "\t\t- $results\n";
393
-						}
394
-					}
395
-				}
396
-			}
397
-
398
-			$formattedTextResponse .= '
381
+            foreach ($completeResults as $context => $contextResult) {
382
+                $formattedTextResponse .= "- $context\n";
383
+
384
+                foreach ($contextResult as $category => $result) {
385
+                    $formattedTextResponse .= "\t- $category\n";
386
+                    if ($category !== 'EXCEPTION') {
387
+                        foreach ($result as $key => $results) {
388
+                            $formattedTextResponse .= "\t\t- $key\n";
389
+                        }
390
+                    } else {
391
+                        foreach ($result as $key => $results) {
392
+                            $formattedTextResponse .= "\t\t- $results\n";
393
+                        }
394
+                    }
395
+                }
396
+            }
397
+
398
+            $formattedTextResponse .= '
399 399
 Raw output
400 400
 ==========
401 401
 ';
402
-			$formattedTextResponse .= print_r($completeResults, true);
403
-		} else {
404
-			$formattedTextResponse = 'No errors have been found.';
405
-		}
406
-
407
-
408
-		$response = new DataDisplayResponse(
409
-			$formattedTextResponse,
410
-			Http::STATUS_OK,
411
-			[
412
-				'Content-Type' => 'text/plain',
413
-			]
414
-		);
415
-
416
-		return $response;
417
-	}
418
-
419
-	/**
420
-	 * Checks whether a PHP opcache is properly set up
421
-	 * @return bool
422
-	 */
423
-	protected function isOpcacheProperlySetup() {
424
-		if (!$this->iniGetWrapper->getBool('opcache.enable')) {
425
-			return false;
426
-		}
427
-
428
-		if (!$this->iniGetWrapper->getBool('opcache.save_comments')) {
429
-			return false;
430
-		}
431
-
432
-		if ($this->iniGetWrapper->getNumeric('opcache.max_accelerated_files') < 10000) {
433
-			return false;
434
-		}
435
-
436
-		if ($this->iniGetWrapper->getNumeric('opcache.memory_consumption') < 128) {
437
-			return false;
438
-		}
439
-
440
-		if ($this->iniGetWrapper->getNumeric('opcache.interned_strings_buffer') < 8) {
441
-			return false;
442
-		}
443
-
444
-		return true;
445
-	}
446
-
447
-	/**
448
-	 * Check if the required FreeType functions are present
449
-	 * @return bool
450
-	 */
451
-	protected function hasFreeTypeSupport() {
452
-		return function_exists('imagettfbbox') && function_exists('imagettftext');
453
-	}
454
-
455
-	protected function hasMissingIndexes(): array {
456
-		$indexInfo = new MissingIndexInformation();
457
-		// Dispatch event so apps can also hint for pending index updates if needed
458
-		$event = new GenericEvent($indexInfo);
459
-		$this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_INDEXES_EVENT, $event);
460
-
461
-		return $indexInfo->getListOfMissingIndexes();
462
-	}
463
-
464
-	protected function hasMissingPrimaryKeys(): array {
465
-		$info = new MissingPrimaryKeyInformation();
466
-		// Dispatch event so apps can also hint for pending index updates if needed
467
-		$event = new GenericEvent($info);
468
-		$this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_PRIMARY_KEYS_EVENT, $event);
469
-
470
-		return $info->getListOfMissingPrimaryKeys();
471
-	}
472
-
473
-	protected function hasMissingColumns(): array {
474
-		$indexInfo = new MissingColumnInformation();
475
-		// Dispatch event so apps can also hint for pending index updates if needed
476
-		$event = new GenericEvent($indexInfo);
477
-		$this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_COLUMNS_EVENT, $event);
478
-
479
-		return $indexInfo->getListOfMissingColumns();
480
-	}
481
-
482
-	protected function isSqliteUsed() {
483
-		return strpos($this->config->getSystemValue('dbtype'), 'sqlite') !== false;
484
-	}
485
-
486
-	protected function isReadOnlyConfig(): bool {
487
-		return \OC_Helper::isReadOnlyConfigEnabled();
488
-	}
489
-
490
-	protected function hasValidTransactionIsolationLevel(): bool {
491
-		try {
492
-			if ($this->db->getDatabasePlatform() instanceof SqlitePlatform) {
493
-				return true;
494
-			}
495
-
496
-			return $this->db->getTransactionIsolation() === TransactionIsolationLevel::READ_COMMITTED;
497
-		} catch (Exception $e) {
498
-			// ignore
499
-		}
500
-
501
-		return true;
502
-	}
503
-
504
-	protected function hasFileinfoInstalled(): bool {
505
-		return \OC_Util::fileInfoLoaded();
506
-	}
507
-
508
-	protected function hasWorkingFileLocking(): bool {
509
-		return !($this->lockingProvider instanceof NoopLockingProvider);
510
-	}
511
-
512
-	protected function getSuggestedOverwriteCliURL(): string {
513
-		$suggestedOverwriteCliUrl = '';
514
-		if ($this->config->getSystemValue('overwrite.cli.url', '') === '') {
515
-			$suggestedOverwriteCliUrl = $this->request->getServerProtocol() . '://' . $this->request->getInsecureServerHost() . \OC::$WEBROOT;
516
-			if (!$this->config->getSystemValue('config_is_read_only', false)) {
517
-				// Set the overwrite URL when it was not set yet.
518
-				$this->config->setSystemValue('overwrite.cli.url', $suggestedOverwriteCliUrl);
519
-				$suggestedOverwriteCliUrl = '';
520
-			}
521
-		}
522
-		return $suggestedOverwriteCliUrl;
523
-	}
524
-
525
-	protected function getLastCronInfo(): array {
526
-		$lastCronRun = $this->config->getAppValue('core', 'lastcron', 0);
527
-		return [
528
-			'diffInSeconds' => time() - $lastCronRun,
529
-			'relativeTime' => $this->dateTimeFormatter->formatTimeSpan($lastCronRun),
530
-			'backgroundJobsUrl' => $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'server']) . '#backgroundjobs',
531
-		];
532
-	}
533
-
534
-	protected function getCronErrors() {
535
-		$errors = json_decode($this->config->getAppValue('core', 'cronErrors', ''), true);
536
-
537
-		if (is_array($errors)) {
538
-			return $errors;
539
-		}
540
-
541
-		return [];
542
-	}
543
-
544
-	protected function hasOpcacheLoaded(): bool {
545
-		return extension_loaded('Zend OPcache');
546
-	}
547
-
548
-	/**
549
-	 * Iterates through the configured app roots and
550
-	 * tests if the subdirectories are owned by the same user than the current user.
551
-	 *
552
-	 * @return array
553
-	 */
554
-	protected function getAppDirsWithDifferentOwner(): array {
555
-		$currentUser = posix_getuid();
556
-		$appDirsWithDifferentOwner = [[]];
557
-
558
-		foreach (OC::$APPSROOTS as $appRoot) {
559
-			if ($appRoot['writable'] === true) {
560
-				$appDirsWithDifferentOwner[] = $this->getAppDirsWithDifferentOwnerForAppRoot($currentUser, $appRoot);
561
-			}
562
-		}
563
-
564
-		$appDirsWithDifferentOwner = array_merge(...$appDirsWithDifferentOwner);
565
-		sort($appDirsWithDifferentOwner);
566
-
567
-		return $appDirsWithDifferentOwner;
568
-	}
569
-
570
-	/**
571
-	 * Tests if the directories for one apps directory are writable by the current user.
572
-	 *
573
-	 * @param int $currentUser The current user
574
-	 * @param array $appRoot The app root config
575
-	 * @return string[] The none writable directory paths inside the app root
576
-	 */
577
-	private function getAppDirsWithDifferentOwnerForAppRoot(int $currentUser, array $appRoot): array {
578
-		$appDirsWithDifferentOwner = [];
579
-		$appsPath = $appRoot['path'];
580
-		$appsDir = new DirectoryIterator($appRoot['path']);
581
-
582
-		foreach ($appsDir as $fileInfo) {
583
-			if ($fileInfo->isDir() && !$fileInfo->isDot()) {
584
-				$absAppPath = $appsPath . DIRECTORY_SEPARATOR . $fileInfo->getFilename();
585
-				$appDirUser = fileowner($absAppPath);
586
-				if ($appDirUser !== $currentUser) {
587
-					$appDirsWithDifferentOwner[] = $absAppPath;
588
-				}
589
-			}
590
-		}
591
-
592
-		return $appDirsWithDifferentOwner;
593
-	}
594
-
595
-	/**
596
-	 * Checks for potential PHP modules that would improve the instance
597
-	 *
598
-	 * @return string[] A list of PHP modules that is recommended
599
-	 */
600
-	protected function hasRecommendedPHPModules(): array {
601
-		$recommendedPHPModules = [];
602
-
603
-		if (!extension_loaded('intl')) {
604
-			$recommendedPHPModules[] = 'intl';
605
-		}
606
-
607
-		if (!extension_loaded('bcmath')) {
608
-			$recommendedPHPModules[] = 'bcmath';
609
-		}
610
-
611
-		if (!extension_loaded('gmp')) {
612
-			$recommendedPHPModules[] = 'gmp';
613
-		}
614
-
615
-		if ($this->config->getAppValue('theming', 'enabled', 'no') === 'yes') {
616
-			if (!extension_loaded('imagick')) {
617
-				$recommendedPHPModules[] = 'imagick';
618
-			}
619
-		}
620
-
621
-		return $recommendedPHPModules;
622
-	}
623
-
624
-	protected function isMysqlUsedWithoutUTF8MB4(): bool {
625
-		return ($this->config->getSystemValue('dbtype', 'sqlite') === 'mysql') && ($this->config->getSystemValue('mysql.utf8mb4', false) === false);
626
-	}
627
-
628
-	protected function hasBigIntConversionPendingColumns(): array {
629
-		// copy of ConvertFilecacheBigInt::getColumnsByTable()
630
-		$tables = [
631
-			'activity' => ['activity_id', 'object_id'],
632
-			'activity_mq' => ['mail_id'],
633
-			'authtoken' => ['id'],
634
-			'bruteforce_attempts' => ['id'],
635
-			'federated_reshares' => ['share_id'],
636
-			'filecache' => ['fileid', 'storage', 'parent', 'mimetype', 'mimepart', 'mtime', 'storage_mtime'],
637
-			'filecache_extended' => ['fileid'],
638
-			'file_locks' => ['id'],
639
-			'jobs' => ['id'],
640
-			'mimetypes' => ['id'],
641
-			'mounts' => ['id', 'storage_id', 'root_id', 'mount_id'],
642
-			'share_external' => ['id', 'parent'],
643
-			'storages' => ['numeric_id'],
644
-		];
645
-
646
-		$schema = new SchemaWrapper($this->db);
647
-		$isSqlite = $this->db->getDatabasePlatform() instanceof SqlitePlatform;
648
-		$pendingColumns = [];
649
-
650
-		foreach ($tables as $tableName => $columns) {
651
-			if (!$schema->hasTable($tableName)) {
652
-				continue;
653
-			}
654
-
655
-			$table = $schema->getTable($tableName);
656
-			foreach ($columns as $columnName) {
657
-				$column = $table->getColumn($columnName);
658
-				$isAutoIncrement = $column->getAutoincrement();
659
-				$isAutoIncrementOnSqlite = $isSqlite && $isAutoIncrement;
660
-				if ($column->getType()->getName() !== Types::BIGINT && !$isAutoIncrementOnSqlite) {
661
-					$pendingColumns[] = $tableName . '.' . $columnName;
662
-				}
663
-			}
664
-		}
665
-
666
-		return $pendingColumns;
667
-	}
668
-
669
-	protected function isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(): bool {
670
-		$objectStore = $this->config->getSystemValue('objectstore', null);
671
-		$objectStoreMultibucket = $this->config->getSystemValue('objectstore_multibucket', null);
672
-
673
-		if (!isset($objectStoreMultibucket) && !isset($objectStore)) {
674
-			return true;
675
-		}
676
-
677
-		if (isset($objectStoreMultibucket['class']) && $objectStoreMultibucket['class'] !== 'OC\\Files\\ObjectStore\\S3') {
678
-			return true;
679
-		}
680
-
681
-		if (isset($objectStore['class']) && $objectStore['class'] !== 'OC\\Files\\ObjectStore\\S3') {
682
-			return true;
683
-		}
684
-
685
-		$tempPath = sys_get_temp_dir();
686
-		if (!is_dir($tempPath)) {
687
-			$this->logger->error('Error while checking the temporary PHP path - it was not properly set to a directory. value: ' . $tempPath);
688
-			return false;
689
-		}
690
-		$freeSpaceInTemp = disk_free_space($tempPath);
691
-		if ($freeSpaceInTemp === false) {
692
-			$this->logger->error('Error while checking the available disk space of temporary PHP path - no free disk space returned. temporary path: ' . $tempPath);
693
-			return false;
694
-		}
695
-
696
-		$freeSpaceInTempInGB = $freeSpaceInTemp / 1024 / 1024 / 1024;
697
-		if ($freeSpaceInTempInGB > 50) {
698
-			return true;
699
-		}
700
-
701
-		$this->logger->warning('Checking the available space in the temporary path resulted in ' . round($freeSpaceInTempInGB, 1) . ' GB instead of the recommended 50GB. Path: ' . $tempPath);
702
-		return false;
703
-	}
704
-
705
-	protected function imageMagickLacksSVGSupport(): bool {
706
-		return extension_loaded('imagick') && count(\Imagick::queryFormats('SVG')) === 0;
707
-	}
708
-
709
-	/**
710
-	 * @return DataResponse
711
-	 */
712
-	public function check() {
713
-		$phpDefaultCharset = new PhpDefaultCharset();
714
-		$phpOutputBuffering = new PhpOutputBuffering();
715
-		$legacySSEKeyFormat = new LegacySSEKeyFormat($this->l10n, $this->config, $this->urlGenerator);
716
-		$checkUserCertificates = new CheckUserCertificates($this->l10n, $this->config, $this->urlGenerator);
717
-
718
-		return new DataResponse(
719
-			[
720
-				'isGetenvServerWorking' => !empty(getenv('PATH')),
721
-				'isReadOnlyConfig' => $this->isReadOnlyConfig(),
722
-				'hasValidTransactionIsolationLevel' => $this->hasValidTransactionIsolationLevel(),
723
-				'hasFileinfoInstalled' => $this->hasFileinfoInstalled(),
724
-				'hasWorkingFileLocking' => $this->hasWorkingFileLocking(),
725
-				'suggestedOverwriteCliURL' => $this->getSuggestedOverwriteCliURL(),
726
-				'cronInfo' => $this->getLastCronInfo(),
727
-				'cronErrors' => $this->getCronErrors(),
728
-				'serverHasInternetConnectionProblems' => $this->hasInternetConnectivityProblems(),
729
-				'isMemcacheConfigured' => $this->isMemcacheConfigured(),
730
-				'memcacheDocs' => $this->urlGenerator->linkToDocs('admin-performance'),
731
-				'isRandomnessSecure' => $this->isRandomnessSecure(),
732
-				'securityDocs' => $this->urlGenerator->linkToDocs('admin-security'),
733
-				'isUsedTlsLibOutdated' => $this->isUsedTlsLibOutdated(),
734
-				'phpSupported' => $this->isPhpSupported(),
735
-				'forwardedForHeadersWorking' => $this->forwardedForHeadersWorking(),
736
-				'reverseProxyDocs' => $this->urlGenerator->linkToDocs('admin-reverse-proxy'),
737
-				'isCorrectMemcachedPHPModuleInstalled' => $this->isCorrectMemcachedPHPModuleInstalled(),
738
-				'hasPassedCodeIntegrityCheck' => $this->checker->hasPassedCheck(),
739
-				'codeIntegrityCheckerDocumentation' => $this->urlGenerator->linkToDocs('admin-code-integrity'),
740
-				'isOpcacheProperlySetup' => $this->isOpcacheProperlySetup(),
741
-				'hasOpcacheLoaded' => $this->hasOpcacheLoaded(),
742
-				'phpOpcacheDocumentation' => $this->urlGenerator->linkToDocs('admin-php-opcache'),
743
-				'isSettimelimitAvailable' => $this->isSettimelimitAvailable(),
744
-				'hasFreeTypeSupport' => $this->hasFreeTypeSupport(),
745
-				'missingPrimaryKeys' => $this->hasMissingPrimaryKeys(),
746
-				'missingIndexes' => $this->hasMissingIndexes(),
747
-				'missingColumns' => $this->hasMissingColumns(),
748
-				'isSqliteUsed' => $this->isSqliteUsed(),
749
-				'databaseConversionDocumentation' => $this->urlGenerator->linkToDocs('admin-db-conversion'),
750
-				'isMemoryLimitSufficient' => $this->memoryInfo->isMemoryLimitSufficient(),
751
-				'appDirsWithDifferentOwner' => $this->getAppDirsWithDifferentOwner(),
752
-				'recommendedPHPModules' => $this->hasRecommendedPHPModules(),
753
-				'pendingBigIntConversionColumns' => $this->hasBigIntConversionPendingColumns(),
754
-				'isMysqlUsedWithoutUTF8MB4' => $this->isMysqlUsedWithoutUTF8MB4(),
755
-				'isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed' => $this->isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(),
756
-				'reverseProxyGeneratedURL' => $this->urlGenerator->getAbsoluteURL('index.php'),
757
-				'imageMagickLacksSVGSupport' => $this->imageMagickLacksSVGSupport(),
758
-				PhpDefaultCharset::class => ['pass' => $phpDefaultCharset->run(), 'description' => $phpDefaultCharset->description(), 'severity' => $phpDefaultCharset->severity()],
759
-				PhpOutputBuffering::class => ['pass' => $phpOutputBuffering->run(), 'description' => $phpOutputBuffering->description(), 'severity' => $phpOutputBuffering->severity()],
760
-				LegacySSEKeyFormat::class => ['pass' => $legacySSEKeyFormat->run(), 'description' => $legacySSEKeyFormat->description(), 'severity' => $legacySSEKeyFormat->severity(), 'linkToDocumentation' => $legacySSEKeyFormat->linkToDocumentation()],
761
-				CheckUserCertificates::class => ['pass' => $checkUserCertificates->run(), 'description' => $checkUserCertificates->description(), 'severity' => $checkUserCertificates->severity(), 'elements' => $checkUserCertificates->elements()],
762
-				'isDefaultPhoneRegionSet' => $this->config->getSystemValueString('default_phone_region', '') !== '',
763
-			]
764
-		);
765
-	}
402
+            $formattedTextResponse .= print_r($completeResults, true);
403
+        } else {
404
+            $formattedTextResponse = 'No errors have been found.';
405
+        }
406
+
407
+
408
+        $response = new DataDisplayResponse(
409
+            $formattedTextResponse,
410
+            Http::STATUS_OK,
411
+            [
412
+                'Content-Type' => 'text/plain',
413
+            ]
414
+        );
415
+
416
+        return $response;
417
+    }
418
+
419
+    /**
420
+     * Checks whether a PHP opcache is properly set up
421
+     * @return bool
422
+     */
423
+    protected function isOpcacheProperlySetup() {
424
+        if (!$this->iniGetWrapper->getBool('opcache.enable')) {
425
+            return false;
426
+        }
427
+
428
+        if (!$this->iniGetWrapper->getBool('opcache.save_comments')) {
429
+            return false;
430
+        }
431
+
432
+        if ($this->iniGetWrapper->getNumeric('opcache.max_accelerated_files') < 10000) {
433
+            return false;
434
+        }
435
+
436
+        if ($this->iniGetWrapper->getNumeric('opcache.memory_consumption') < 128) {
437
+            return false;
438
+        }
439
+
440
+        if ($this->iniGetWrapper->getNumeric('opcache.interned_strings_buffer') < 8) {
441
+            return false;
442
+        }
443
+
444
+        return true;
445
+    }
446
+
447
+    /**
448
+     * Check if the required FreeType functions are present
449
+     * @return bool
450
+     */
451
+    protected function hasFreeTypeSupport() {
452
+        return function_exists('imagettfbbox') && function_exists('imagettftext');
453
+    }
454
+
455
+    protected function hasMissingIndexes(): array {
456
+        $indexInfo = new MissingIndexInformation();
457
+        // Dispatch event so apps can also hint for pending index updates if needed
458
+        $event = new GenericEvent($indexInfo);
459
+        $this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_INDEXES_EVENT, $event);
460
+
461
+        return $indexInfo->getListOfMissingIndexes();
462
+    }
463
+
464
+    protected function hasMissingPrimaryKeys(): array {
465
+        $info = new MissingPrimaryKeyInformation();
466
+        // Dispatch event so apps can also hint for pending index updates if needed
467
+        $event = new GenericEvent($info);
468
+        $this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_PRIMARY_KEYS_EVENT, $event);
469
+
470
+        return $info->getListOfMissingPrimaryKeys();
471
+    }
472
+
473
+    protected function hasMissingColumns(): array {
474
+        $indexInfo = new MissingColumnInformation();
475
+        // Dispatch event so apps can also hint for pending index updates if needed
476
+        $event = new GenericEvent($indexInfo);
477
+        $this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_COLUMNS_EVENT, $event);
478
+
479
+        return $indexInfo->getListOfMissingColumns();
480
+    }
481
+
482
+    protected function isSqliteUsed() {
483
+        return strpos($this->config->getSystemValue('dbtype'), 'sqlite') !== false;
484
+    }
485
+
486
+    protected function isReadOnlyConfig(): bool {
487
+        return \OC_Helper::isReadOnlyConfigEnabled();
488
+    }
489
+
490
+    protected function hasValidTransactionIsolationLevel(): bool {
491
+        try {
492
+            if ($this->db->getDatabasePlatform() instanceof SqlitePlatform) {
493
+                return true;
494
+            }
495
+
496
+            return $this->db->getTransactionIsolation() === TransactionIsolationLevel::READ_COMMITTED;
497
+        } catch (Exception $e) {
498
+            // ignore
499
+        }
500
+
501
+        return true;
502
+    }
503
+
504
+    protected function hasFileinfoInstalled(): bool {
505
+        return \OC_Util::fileInfoLoaded();
506
+    }
507
+
508
+    protected function hasWorkingFileLocking(): bool {
509
+        return !($this->lockingProvider instanceof NoopLockingProvider);
510
+    }
511
+
512
+    protected function getSuggestedOverwriteCliURL(): string {
513
+        $suggestedOverwriteCliUrl = '';
514
+        if ($this->config->getSystemValue('overwrite.cli.url', '') === '') {
515
+            $suggestedOverwriteCliUrl = $this->request->getServerProtocol() . '://' . $this->request->getInsecureServerHost() . \OC::$WEBROOT;
516
+            if (!$this->config->getSystemValue('config_is_read_only', false)) {
517
+                // Set the overwrite URL when it was not set yet.
518
+                $this->config->setSystemValue('overwrite.cli.url', $suggestedOverwriteCliUrl);
519
+                $suggestedOverwriteCliUrl = '';
520
+            }
521
+        }
522
+        return $suggestedOverwriteCliUrl;
523
+    }
524
+
525
+    protected function getLastCronInfo(): array {
526
+        $lastCronRun = $this->config->getAppValue('core', 'lastcron', 0);
527
+        return [
528
+            'diffInSeconds' => time() - $lastCronRun,
529
+            'relativeTime' => $this->dateTimeFormatter->formatTimeSpan($lastCronRun),
530
+            'backgroundJobsUrl' => $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'server']) . '#backgroundjobs',
531
+        ];
532
+    }
533
+
534
+    protected function getCronErrors() {
535
+        $errors = json_decode($this->config->getAppValue('core', 'cronErrors', ''), true);
536
+
537
+        if (is_array($errors)) {
538
+            return $errors;
539
+        }
540
+
541
+        return [];
542
+    }
543
+
544
+    protected function hasOpcacheLoaded(): bool {
545
+        return extension_loaded('Zend OPcache');
546
+    }
547
+
548
+    /**
549
+     * Iterates through the configured app roots and
550
+     * tests if the subdirectories are owned by the same user than the current user.
551
+     *
552
+     * @return array
553
+     */
554
+    protected function getAppDirsWithDifferentOwner(): array {
555
+        $currentUser = posix_getuid();
556
+        $appDirsWithDifferentOwner = [[]];
557
+
558
+        foreach (OC::$APPSROOTS as $appRoot) {
559
+            if ($appRoot['writable'] === true) {
560
+                $appDirsWithDifferentOwner[] = $this->getAppDirsWithDifferentOwnerForAppRoot($currentUser, $appRoot);
561
+            }
562
+        }
563
+
564
+        $appDirsWithDifferentOwner = array_merge(...$appDirsWithDifferentOwner);
565
+        sort($appDirsWithDifferentOwner);
566
+
567
+        return $appDirsWithDifferentOwner;
568
+    }
569
+
570
+    /**
571
+     * Tests if the directories for one apps directory are writable by the current user.
572
+     *
573
+     * @param int $currentUser The current user
574
+     * @param array $appRoot The app root config
575
+     * @return string[] The none writable directory paths inside the app root
576
+     */
577
+    private function getAppDirsWithDifferentOwnerForAppRoot(int $currentUser, array $appRoot): array {
578
+        $appDirsWithDifferentOwner = [];
579
+        $appsPath = $appRoot['path'];
580
+        $appsDir = new DirectoryIterator($appRoot['path']);
581
+
582
+        foreach ($appsDir as $fileInfo) {
583
+            if ($fileInfo->isDir() && !$fileInfo->isDot()) {
584
+                $absAppPath = $appsPath . DIRECTORY_SEPARATOR . $fileInfo->getFilename();
585
+                $appDirUser = fileowner($absAppPath);
586
+                if ($appDirUser !== $currentUser) {
587
+                    $appDirsWithDifferentOwner[] = $absAppPath;
588
+                }
589
+            }
590
+        }
591
+
592
+        return $appDirsWithDifferentOwner;
593
+    }
594
+
595
+    /**
596
+     * Checks for potential PHP modules that would improve the instance
597
+     *
598
+     * @return string[] A list of PHP modules that is recommended
599
+     */
600
+    protected function hasRecommendedPHPModules(): array {
601
+        $recommendedPHPModules = [];
602
+
603
+        if (!extension_loaded('intl')) {
604
+            $recommendedPHPModules[] = 'intl';
605
+        }
606
+
607
+        if (!extension_loaded('bcmath')) {
608
+            $recommendedPHPModules[] = 'bcmath';
609
+        }
610
+
611
+        if (!extension_loaded('gmp')) {
612
+            $recommendedPHPModules[] = 'gmp';
613
+        }
614
+
615
+        if ($this->config->getAppValue('theming', 'enabled', 'no') === 'yes') {
616
+            if (!extension_loaded('imagick')) {
617
+                $recommendedPHPModules[] = 'imagick';
618
+            }
619
+        }
620
+
621
+        return $recommendedPHPModules;
622
+    }
623
+
624
+    protected function isMysqlUsedWithoutUTF8MB4(): bool {
625
+        return ($this->config->getSystemValue('dbtype', 'sqlite') === 'mysql') && ($this->config->getSystemValue('mysql.utf8mb4', false) === false);
626
+    }
627
+
628
+    protected function hasBigIntConversionPendingColumns(): array {
629
+        // copy of ConvertFilecacheBigInt::getColumnsByTable()
630
+        $tables = [
631
+            'activity' => ['activity_id', 'object_id'],
632
+            'activity_mq' => ['mail_id'],
633
+            'authtoken' => ['id'],
634
+            'bruteforce_attempts' => ['id'],
635
+            'federated_reshares' => ['share_id'],
636
+            'filecache' => ['fileid', 'storage', 'parent', 'mimetype', 'mimepart', 'mtime', 'storage_mtime'],
637
+            'filecache_extended' => ['fileid'],
638
+            'file_locks' => ['id'],
639
+            'jobs' => ['id'],
640
+            'mimetypes' => ['id'],
641
+            'mounts' => ['id', 'storage_id', 'root_id', 'mount_id'],
642
+            'share_external' => ['id', 'parent'],
643
+            'storages' => ['numeric_id'],
644
+        ];
645
+
646
+        $schema = new SchemaWrapper($this->db);
647
+        $isSqlite = $this->db->getDatabasePlatform() instanceof SqlitePlatform;
648
+        $pendingColumns = [];
649
+
650
+        foreach ($tables as $tableName => $columns) {
651
+            if (!$schema->hasTable($tableName)) {
652
+                continue;
653
+            }
654
+
655
+            $table = $schema->getTable($tableName);
656
+            foreach ($columns as $columnName) {
657
+                $column = $table->getColumn($columnName);
658
+                $isAutoIncrement = $column->getAutoincrement();
659
+                $isAutoIncrementOnSqlite = $isSqlite && $isAutoIncrement;
660
+                if ($column->getType()->getName() !== Types::BIGINT && !$isAutoIncrementOnSqlite) {
661
+                    $pendingColumns[] = $tableName . '.' . $columnName;
662
+                }
663
+            }
664
+        }
665
+
666
+        return $pendingColumns;
667
+    }
668
+
669
+    protected function isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(): bool {
670
+        $objectStore = $this->config->getSystemValue('objectstore', null);
671
+        $objectStoreMultibucket = $this->config->getSystemValue('objectstore_multibucket', null);
672
+
673
+        if (!isset($objectStoreMultibucket) && !isset($objectStore)) {
674
+            return true;
675
+        }
676
+
677
+        if (isset($objectStoreMultibucket['class']) && $objectStoreMultibucket['class'] !== 'OC\\Files\\ObjectStore\\S3') {
678
+            return true;
679
+        }
680
+
681
+        if (isset($objectStore['class']) && $objectStore['class'] !== 'OC\\Files\\ObjectStore\\S3') {
682
+            return true;
683
+        }
684
+
685
+        $tempPath = sys_get_temp_dir();
686
+        if (!is_dir($tempPath)) {
687
+            $this->logger->error('Error while checking the temporary PHP path - it was not properly set to a directory. value: ' . $tempPath);
688
+            return false;
689
+        }
690
+        $freeSpaceInTemp = disk_free_space($tempPath);
691
+        if ($freeSpaceInTemp === false) {
692
+            $this->logger->error('Error while checking the available disk space of temporary PHP path - no free disk space returned. temporary path: ' . $tempPath);
693
+            return false;
694
+        }
695
+
696
+        $freeSpaceInTempInGB = $freeSpaceInTemp / 1024 / 1024 / 1024;
697
+        if ($freeSpaceInTempInGB > 50) {
698
+            return true;
699
+        }
700
+
701
+        $this->logger->warning('Checking the available space in the temporary path resulted in ' . round($freeSpaceInTempInGB, 1) . ' GB instead of the recommended 50GB. Path: ' . $tempPath);
702
+        return false;
703
+    }
704
+
705
+    protected function imageMagickLacksSVGSupport(): bool {
706
+        return extension_loaded('imagick') && count(\Imagick::queryFormats('SVG')) === 0;
707
+    }
708
+
709
+    /**
710
+     * @return DataResponse
711
+     */
712
+    public function check() {
713
+        $phpDefaultCharset = new PhpDefaultCharset();
714
+        $phpOutputBuffering = new PhpOutputBuffering();
715
+        $legacySSEKeyFormat = new LegacySSEKeyFormat($this->l10n, $this->config, $this->urlGenerator);
716
+        $checkUserCertificates = new CheckUserCertificates($this->l10n, $this->config, $this->urlGenerator);
717
+
718
+        return new DataResponse(
719
+            [
720
+                'isGetenvServerWorking' => !empty(getenv('PATH')),
721
+                'isReadOnlyConfig' => $this->isReadOnlyConfig(),
722
+                'hasValidTransactionIsolationLevel' => $this->hasValidTransactionIsolationLevel(),
723
+                'hasFileinfoInstalled' => $this->hasFileinfoInstalled(),
724
+                'hasWorkingFileLocking' => $this->hasWorkingFileLocking(),
725
+                'suggestedOverwriteCliURL' => $this->getSuggestedOverwriteCliURL(),
726
+                'cronInfo' => $this->getLastCronInfo(),
727
+                'cronErrors' => $this->getCronErrors(),
728
+                'serverHasInternetConnectionProblems' => $this->hasInternetConnectivityProblems(),
729
+                'isMemcacheConfigured' => $this->isMemcacheConfigured(),
730
+                'memcacheDocs' => $this->urlGenerator->linkToDocs('admin-performance'),
731
+                'isRandomnessSecure' => $this->isRandomnessSecure(),
732
+                'securityDocs' => $this->urlGenerator->linkToDocs('admin-security'),
733
+                'isUsedTlsLibOutdated' => $this->isUsedTlsLibOutdated(),
734
+                'phpSupported' => $this->isPhpSupported(),
735
+                'forwardedForHeadersWorking' => $this->forwardedForHeadersWorking(),
736
+                'reverseProxyDocs' => $this->urlGenerator->linkToDocs('admin-reverse-proxy'),
737
+                'isCorrectMemcachedPHPModuleInstalled' => $this->isCorrectMemcachedPHPModuleInstalled(),
738
+                'hasPassedCodeIntegrityCheck' => $this->checker->hasPassedCheck(),
739
+                'codeIntegrityCheckerDocumentation' => $this->urlGenerator->linkToDocs('admin-code-integrity'),
740
+                'isOpcacheProperlySetup' => $this->isOpcacheProperlySetup(),
741
+                'hasOpcacheLoaded' => $this->hasOpcacheLoaded(),
742
+                'phpOpcacheDocumentation' => $this->urlGenerator->linkToDocs('admin-php-opcache'),
743
+                'isSettimelimitAvailable' => $this->isSettimelimitAvailable(),
744
+                'hasFreeTypeSupport' => $this->hasFreeTypeSupport(),
745
+                'missingPrimaryKeys' => $this->hasMissingPrimaryKeys(),
746
+                'missingIndexes' => $this->hasMissingIndexes(),
747
+                'missingColumns' => $this->hasMissingColumns(),
748
+                'isSqliteUsed' => $this->isSqliteUsed(),
749
+                'databaseConversionDocumentation' => $this->urlGenerator->linkToDocs('admin-db-conversion'),
750
+                'isMemoryLimitSufficient' => $this->memoryInfo->isMemoryLimitSufficient(),
751
+                'appDirsWithDifferentOwner' => $this->getAppDirsWithDifferentOwner(),
752
+                'recommendedPHPModules' => $this->hasRecommendedPHPModules(),
753
+                'pendingBigIntConversionColumns' => $this->hasBigIntConversionPendingColumns(),
754
+                'isMysqlUsedWithoutUTF8MB4' => $this->isMysqlUsedWithoutUTF8MB4(),
755
+                'isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed' => $this->isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(),
756
+                'reverseProxyGeneratedURL' => $this->urlGenerator->getAbsoluteURL('index.php'),
757
+                'imageMagickLacksSVGSupport' => $this->imageMagickLacksSVGSupport(),
758
+                PhpDefaultCharset::class => ['pass' => $phpDefaultCharset->run(), 'description' => $phpDefaultCharset->description(), 'severity' => $phpDefaultCharset->severity()],
759
+                PhpOutputBuffering::class => ['pass' => $phpOutputBuffering->run(), 'description' => $phpOutputBuffering->description(), 'severity' => $phpOutputBuffering->severity()],
760
+                LegacySSEKeyFormat::class => ['pass' => $legacySSEKeyFormat->run(), 'description' => $legacySSEKeyFormat->description(), 'severity' => $legacySSEKeyFormat->severity(), 'linkToDocumentation' => $legacySSEKeyFormat->linkToDocumentation()],
761
+                CheckUserCertificates::class => ['pass' => $checkUserCertificates->run(), 'description' => $checkUserCertificates->description(), 'severity' => $checkUserCertificates->severity(), 'elements' => $checkUserCertificates->elements()],
762
+                'isDefaultPhoneRegionSet' => $this->config->getSystemValueString('default_phone_region', '') !== '',
763
+            ]
764
+        );
765
+    }
766 766
 }
Please login to merge, or discard this patch.