Passed
Push — master ( 62403d...0c3e2f )
by Joas
14:50 queued 14s
created
lib/private/DB/MDB2SchemaReader.php 1 patch
Indentation   +292 added lines, -292 removed lines patch added patch discarded remove patch
@@ -36,313 +36,313 @@
 block discarded – undo
36 36
 
37 37
 class MDB2SchemaReader {
38 38
 
39
-	/**
40
-	 * @var string $DBTABLEPREFIX
41
-	 */
42
-	protected $DBTABLEPREFIX;
39
+    /**
40
+     * @var string $DBTABLEPREFIX
41
+     */
42
+    protected $DBTABLEPREFIX;
43 43
 
44
-	/**
45
-	 * @var \Doctrine\DBAL\Platforms\AbstractPlatform $platform
46
-	 */
47
-	protected $platform;
44
+    /**
45
+     * @var \Doctrine\DBAL\Platforms\AbstractPlatform $platform
46
+     */
47
+    protected $platform;
48 48
 
49
-	/** @var IConfig */
50
-	protected $config;
49
+    /** @var IConfig */
50
+    protected $config;
51 51
 
52
-	/**
53
-	 * @param \OCP\IConfig $config
54
-	 * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
55
-	 */
56
-	public function __construct(IConfig $config, AbstractPlatform $platform) {
57
-		$this->platform = $platform;
58
-		$this->config = $config;
59
-		$this->DBTABLEPREFIX = $config->getSystemValue('dbtableprefix', 'oc_');
60
-	}
52
+    /**
53
+     * @param \OCP\IConfig $config
54
+     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
55
+     */
56
+    public function __construct(IConfig $config, AbstractPlatform $platform) {
57
+        $this->platform = $platform;
58
+        $this->config = $config;
59
+        $this->DBTABLEPREFIX = $config->getSystemValue('dbtableprefix', 'oc_');
60
+    }
61 61
 
62
-	/**
63
-	 * @param string $file
64
-	 * @param Schema $schema
65
-	 * @return Schema
66
-	 * @throws \DomainException
67
-	 */
68
-	public function loadSchemaFromFile($file, Schema $schema) {
69
-		$loadEntities = libxml_disable_entity_loader(false);
70
-		$xml = simplexml_load_file($file);
71
-		libxml_disable_entity_loader($loadEntities);
72
-		foreach ($xml->children() as $child) {
73
-			/**
74
-			 * @var \SimpleXMLElement $child
75
-			 */
76
-			switch ($child->getName()) {
77
-				case 'name':
78
-				case 'create':
79
-				case 'overwrite':
80
-				case 'charset':
81
-					break;
82
-				case 'table':
83
-					$this->loadTable($schema, $child);
84
-					break;
85
-				default:
86
-					throw new \DomainException('Unknown element: ' . $child->getName());
62
+    /**
63
+     * @param string $file
64
+     * @param Schema $schema
65
+     * @return Schema
66
+     * @throws \DomainException
67
+     */
68
+    public function loadSchemaFromFile($file, Schema $schema) {
69
+        $loadEntities = libxml_disable_entity_loader(false);
70
+        $xml = simplexml_load_file($file);
71
+        libxml_disable_entity_loader($loadEntities);
72
+        foreach ($xml->children() as $child) {
73
+            /**
74
+             * @var \SimpleXMLElement $child
75
+             */
76
+            switch ($child->getName()) {
77
+                case 'name':
78
+                case 'create':
79
+                case 'overwrite':
80
+                case 'charset':
81
+                    break;
82
+                case 'table':
83
+                    $this->loadTable($schema, $child);
84
+                    break;
85
+                default:
86
+                    throw new \DomainException('Unknown element: ' . $child->getName());
87 87
 
88
-			}
89
-		}
90
-		return $schema;
91
-	}
88
+            }
89
+        }
90
+        return $schema;
91
+    }
92 92
 
93
-	/**
94
-	 * @param \Doctrine\DBAL\Schema\Schema $schema
95
-	 * @param \SimpleXMLElement $xml
96
-	 * @throws \DomainException
97
-	 */
98
-	private function loadTable($schema, $xml) {
99
-		$table = null;
100
-		foreach ($xml->children() as $child) {
101
-			/**
102
-			 * @var \SimpleXMLElement $child
103
-			 */
104
-			switch ($child->getName()) {
105
-				case 'name':
106
-					$name = (string)$child;
107
-					$name = str_replace('*dbprefix*', $this->DBTABLEPREFIX, $name);
108
-					$name = $this->platform->quoteIdentifier($name);
109
-					$table = $schema->createTable($name);
110
-					break;
111
-				case 'create':
112
-				case 'overwrite':
113
-				case 'charset':
114
-					break;
115
-				case 'declaration':
116
-					if (is_null($table)) {
117
-						throw new \DomainException('Table declaration before table name');
118
-					}
119
-					$this->loadDeclaration($table, $child);
120
-					break;
121
-				default:
122
-					throw new \DomainException('Unknown element: ' . $child->getName());
93
+    /**
94
+     * @param \Doctrine\DBAL\Schema\Schema $schema
95
+     * @param \SimpleXMLElement $xml
96
+     * @throws \DomainException
97
+     */
98
+    private function loadTable($schema, $xml) {
99
+        $table = null;
100
+        foreach ($xml->children() as $child) {
101
+            /**
102
+             * @var \SimpleXMLElement $child
103
+             */
104
+            switch ($child->getName()) {
105
+                case 'name':
106
+                    $name = (string)$child;
107
+                    $name = str_replace('*dbprefix*', $this->DBTABLEPREFIX, $name);
108
+                    $name = $this->platform->quoteIdentifier($name);
109
+                    $table = $schema->createTable($name);
110
+                    break;
111
+                case 'create':
112
+                case 'overwrite':
113
+                case 'charset':
114
+                    break;
115
+                case 'declaration':
116
+                    if (is_null($table)) {
117
+                        throw new \DomainException('Table declaration before table name');
118
+                    }
119
+                    $this->loadDeclaration($table, $child);
120
+                    break;
121
+                default:
122
+                    throw new \DomainException('Unknown element: ' . $child->getName());
123 123
 
124
-			}
125
-		}
126
-	}
124
+            }
125
+        }
126
+    }
127 127
 
128
-	/**
129
-	 * @param \Doctrine\DBAL\Schema\Table $table
130
-	 * @param \SimpleXMLElement $xml
131
-	 * @throws \DomainException
132
-	 */
133
-	private function loadDeclaration($table, $xml) {
134
-		foreach ($xml->children() as $child) {
135
-			/**
136
-			 * @var \SimpleXMLElement $child
137
-			 */
138
-			switch ($child->getName()) {
139
-				case 'field':
140
-					$this->loadField($table, $child);
141
-					break;
142
-				case 'index':
143
-					$this->loadIndex($table, $child);
144
-					break;
145
-				default:
146
-					throw new \DomainException('Unknown element: ' . $child->getName());
128
+    /**
129
+     * @param \Doctrine\DBAL\Schema\Table $table
130
+     * @param \SimpleXMLElement $xml
131
+     * @throws \DomainException
132
+     */
133
+    private function loadDeclaration($table, $xml) {
134
+        foreach ($xml->children() as $child) {
135
+            /**
136
+             * @var \SimpleXMLElement $child
137
+             */
138
+            switch ($child->getName()) {
139
+                case 'field':
140
+                    $this->loadField($table, $child);
141
+                    break;
142
+                case 'index':
143
+                    $this->loadIndex($table, $child);
144
+                    break;
145
+                default:
146
+                    throw new \DomainException('Unknown element: ' . $child->getName());
147 147
 
148
-			}
149
-		}
150
-	}
148
+            }
149
+        }
150
+    }
151 151
 
152
-	/**
153
-	 * @param \Doctrine\DBAL\Schema\Table $table
154
-	 * @param \SimpleXMLElement $xml
155
-	 * @throws \DomainException
156
-	 */
157
-	private function loadField($table, $xml) {
158
-		$options = [ 'notnull' => false ];
159
-		foreach ($xml->children() as $child) {
160
-			/**
161
-			 * @var \SimpleXMLElement $child
162
-			 */
163
-			switch ($child->getName()) {
164
-				case 'name':
165
-					$name = (string)$child;
166
-					$name = $this->platform->quoteIdentifier($name);
167
-					break;
168
-				case 'type':
169
-					$type = (string)$child;
170
-					switch ($type) {
171
-						case 'text':
172
-							$type = 'string';
173
-							break;
174
-						case 'clob':
175
-							$type = 'text';
176
-							break;
177
-						case 'timestamp':
178
-							$type = 'datetime';
179
-							break;
180
-						case 'numeric':
181
-							$type = 'decimal';
182
-							break;
183
-					}
184
-					break;
185
-				case 'length':
186
-					$length = (string)$child;
187
-					$options['length'] = $length;
188
-					break;
189
-				case 'unsigned':
190
-					$unsigned = $this->asBool($child);
191
-					$options['unsigned'] = $unsigned;
192
-					break;
193
-				case 'notnull':
194
-					$notnull = $this->asBool($child);
195
-					$options['notnull'] = $notnull;
196
-					break;
197
-				case 'autoincrement':
198
-					$autoincrement = $this->asBool($child);
199
-					$options['autoincrement'] = $autoincrement;
200
-					break;
201
-				case 'default':
202
-					$default = (string)$child;
203
-					$options['default'] = $default;
204
-					break;
205
-				case 'comments':
206
-					$comment = (string)$child;
207
-					$options['comment'] = $comment;
208
-					break;
209
-				case 'primary':
210
-					$primary = $this->asBool($child);
211
-					$options['primary'] = $primary;
212
-					break;
213
-				case 'precision':
214
-					$precision = (string)$child;
215
-					$options['precision'] = $precision;
216
-					break;
217
-				case 'scale':
218
-					$scale = (string)$child;
219
-					$options['scale'] = $scale;
220
-					break;
221
-				default:
222
-					throw new \DomainException('Unknown element: ' . $child->getName());
152
+    /**
153
+     * @param \Doctrine\DBAL\Schema\Table $table
154
+     * @param \SimpleXMLElement $xml
155
+     * @throws \DomainException
156
+     */
157
+    private function loadField($table, $xml) {
158
+        $options = [ 'notnull' => false ];
159
+        foreach ($xml->children() as $child) {
160
+            /**
161
+             * @var \SimpleXMLElement $child
162
+             */
163
+            switch ($child->getName()) {
164
+                case 'name':
165
+                    $name = (string)$child;
166
+                    $name = $this->platform->quoteIdentifier($name);
167
+                    break;
168
+                case 'type':
169
+                    $type = (string)$child;
170
+                    switch ($type) {
171
+                        case 'text':
172
+                            $type = 'string';
173
+                            break;
174
+                        case 'clob':
175
+                            $type = 'text';
176
+                            break;
177
+                        case 'timestamp':
178
+                            $type = 'datetime';
179
+                            break;
180
+                        case 'numeric':
181
+                            $type = 'decimal';
182
+                            break;
183
+                    }
184
+                    break;
185
+                case 'length':
186
+                    $length = (string)$child;
187
+                    $options['length'] = $length;
188
+                    break;
189
+                case 'unsigned':
190
+                    $unsigned = $this->asBool($child);
191
+                    $options['unsigned'] = $unsigned;
192
+                    break;
193
+                case 'notnull':
194
+                    $notnull = $this->asBool($child);
195
+                    $options['notnull'] = $notnull;
196
+                    break;
197
+                case 'autoincrement':
198
+                    $autoincrement = $this->asBool($child);
199
+                    $options['autoincrement'] = $autoincrement;
200
+                    break;
201
+                case 'default':
202
+                    $default = (string)$child;
203
+                    $options['default'] = $default;
204
+                    break;
205
+                case 'comments':
206
+                    $comment = (string)$child;
207
+                    $options['comment'] = $comment;
208
+                    break;
209
+                case 'primary':
210
+                    $primary = $this->asBool($child);
211
+                    $options['primary'] = $primary;
212
+                    break;
213
+                case 'precision':
214
+                    $precision = (string)$child;
215
+                    $options['precision'] = $precision;
216
+                    break;
217
+                case 'scale':
218
+                    $scale = (string)$child;
219
+                    $options['scale'] = $scale;
220
+                    break;
221
+                default:
222
+                    throw new \DomainException('Unknown element: ' . $child->getName());
223 223
 
224
-			}
225
-		}
226
-		if (isset($name) && isset($type)) {
227
-			if (isset($options['default']) && empty($options['default'])) {
228
-				if (empty($options['notnull']) || !$options['notnull']) {
229
-					unset($options['default']);
230
-					$options['notnull'] = false;
231
-				} else {
232
-					$options['default'] = '';
233
-				}
234
-				if ($type == 'integer' || $type == 'decimal') {
235
-					$options['default'] = 0;
236
-				} elseif ($type == 'boolean') {
237
-					$options['default'] = false;
238
-				}
239
-				if (!empty($options['autoincrement']) && $options['autoincrement']) {
240
-					unset($options['default']);
241
-				}
242
-			}
243
-			if ($type === 'integer' && isset($options['default'])) {
244
-				$options['default'] = (int)$options['default'];
245
-			}
246
-			if ($type === 'integer' && isset($options['length'])) {
247
-				$length = $options['length'];
248
-				if ($length < 4) {
249
-					$type = 'smallint';
250
-				} else if ($length > 4) {
251
-					$type = 'bigint';
252
-				}
253
-			}
254
-			if ($type === 'boolean' && isset($options['default'])) {
255
-				$options['default'] = $this->asBool($options['default']);
256
-			}
257
-			if (!empty($options['autoincrement'])
258
-				&& !empty($options['notnull'])
259
-			) {
260
-				$options['primary'] = true;
261
-			}
224
+            }
225
+        }
226
+        if (isset($name) && isset($type)) {
227
+            if (isset($options['default']) && empty($options['default'])) {
228
+                if (empty($options['notnull']) || !$options['notnull']) {
229
+                    unset($options['default']);
230
+                    $options['notnull'] = false;
231
+                } else {
232
+                    $options['default'] = '';
233
+                }
234
+                if ($type == 'integer' || $type == 'decimal') {
235
+                    $options['default'] = 0;
236
+                } elseif ($type == 'boolean') {
237
+                    $options['default'] = false;
238
+                }
239
+                if (!empty($options['autoincrement']) && $options['autoincrement']) {
240
+                    unset($options['default']);
241
+                }
242
+            }
243
+            if ($type === 'integer' && isset($options['default'])) {
244
+                $options['default'] = (int)$options['default'];
245
+            }
246
+            if ($type === 'integer' && isset($options['length'])) {
247
+                $length = $options['length'];
248
+                if ($length < 4) {
249
+                    $type = 'smallint';
250
+                } else if ($length > 4) {
251
+                    $type = 'bigint';
252
+                }
253
+            }
254
+            if ($type === 'boolean' && isset($options['default'])) {
255
+                $options['default'] = $this->asBool($options['default']);
256
+            }
257
+            if (!empty($options['autoincrement'])
258
+                && !empty($options['notnull'])
259
+            ) {
260
+                $options['primary'] = true;
261
+            }
262 262
 
263
-			$table->addColumn($name, $type, $options);
264
-			if (!empty($options['primary']) && $options['primary']) {
265
-				$table->setPrimaryKey([$name]);
266
-			}
267
-		}
268
-	}
263
+            $table->addColumn($name, $type, $options);
264
+            if (!empty($options['primary']) && $options['primary']) {
265
+                $table->setPrimaryKey([$name]);
266
+            }
267
+        }
268
+    }
269 269
 
270
-	/**
271
-	 * @param \Doctrine\DBAL\Schema\Table $table
272
-	 * @param \SimpleXMLElement $xml
273
-	 * @throws \DomainException
274
-	 */
275
-	private function loadIndex($table, $xml) {
276
-		$name = null;
277
-		$fields = [];
278
-		foreach ($xml->children() as $child) {
279
-			/**
280
-			 * @var \SimpleXMLElement $child
281
-			 */
282
-			switch ($child->getName()) {
283
-				case 'name':
284
-					$name = (string)$child;
285
-					break;
286
-				case 'primary':
287
-					$primary = $this->asBool($child);
288
-					break;
289
-				case 'unique':
290
-					$unique = $this->asBool($child);
291
-					break;
292
-				case 'field':
293
-					foreach ($child->children() as $field) {
294
-						/**
295
-						 * @var \SimpleXMLElement $field
296
-						 */
297
-						switch ($field->getName()) {
298
-							case 'name':
299
-								$field_name = (string)$field;
300
-								$field_name = $this->platform->quoteIdentifier($field_name);
301
-								$fields[] = $field_name;
302
-								break;
303
-							case 'sorting':
304
-								break;
305
-							default:
306
-								throw new \DomainException('Unknown element: ' . $field->getName());
270
+    /**
271
+     * @param \Doctrine\DBAL\Schema\Table $table
272
+     * @param \SimpleXMLElement $xml
273
+     * @throws \DomainException
274
+     */
275
+    private function loadIndex($table, $xml) {
276
+        $name = null;
277
+        $fields = [];
278
+        foreach ($xml->children() as $child) {
279
+            /**
280
+             * @var \SimpleXMLElement $child
281
+             */
282
+            switch ($child->getName()) {
283
+                case 'name':
284
+                    $name = (string)$child;
285
+                    break;
286
+                case 'primary':
287
+                    $primary = $this->asBool($child);
288
+                    break;
289
+                case 'unique':
290
+                    $unique = $this->asBool($child);
291
+                    break;
292
+                case 'field':
293
+                    foreach ($child->children() as $field) {
294
+                        /**
295
+                         * @var \SimpleXMLElement $field
296
+                         */
297
+                        switch ($field->getName()) {
298
+                            case 'name':
299
+                                $field_name = (string)$field;
300
+                                $field_name = $this->platform->quoteIdentifier($field_name);
301
+                                $fields[] = $field_name;
302
+                                break;
303
+                            case 'sorting':
304
+                                break;
305
+                            default:
306
+                                throw new \DomainException('Unknown element: ' . $field->getName());
307 307
 
308
-						}
309
-					}
310
-					break;
311
-				default:
312
-					throw new \DomainException('Unknown element: ' . $child->getName());
308
+                        }
309
+                    }
310
+                    break;
311
+                default:
312
+                    throw new \DomainException('Unknown element: ' . $child->getName());
313 313
 
314
-			}
315
-		}
316
-		if (!empty($fields)) {
317
-			if (isset($primary) && $primary) {
318
-				if ($table->hasPrimaryKey()) {
319
-					return;
320
-				}
321
-				$table->setPrimaryKey($fields, $name);
322
-			} else {
323
-				if (isset($unique) && $unique) {
324
-					$table->addUniqueIndex($fields, $name);
325
-				} else {
326
-					$table->addIndex($fields, $name);
327
-				}
328
-			}
329
-		} else {
330
-			throw new \DomainException('Empty index definition: ' . $name . ' options:' . print_r($fields, true));
331
-		}
332
-	}
314
+            }
315
+        }
316
+        if (!empty($fields)) {
317
+            if (isset($primary) && $primary) {
318
+                if ($table->hasPrimaryKey()) {
319
+                    return;
320
+                }
321
+                $table->setPrimaryKey($fields, $name);
322
+            } else {
323
+                if (isset($unique) && $unique) {
324
+                    $table->addUniqueIndex($fields, $name);
325
+                } else {
326
+                    $table->addIndex($fields, $name);
327
+                }
328
+            }
329
+        } else {
330
+            throw new \DomainException('Empty index definition: ' . $name . ' options:' . print_r($fields, true));
331
+        }
332
+    }
333 333
 
334
-	/**
335
-	 * @param \SimpleXMLElement|string $xml
336
-	 * @return bool
337
-	 */
338
-	private function asBool($xml) {
339
-		$result = (string)$xml;
340
-		if ($result == 'true') {
341
-			$result = true;
342
-		} elseif ($result == 'false') {
343
-			$result = false;
344
-		}
345
-		return (bool)$result;
346
-	}
334
+    /**
335
+     * @param \SimpleXMLElement|string $xml
336
+     * @return bool
337
+     */
338
+    private function asBool($xml) {
339
+        $result = (string)$xml;
340
+        if ($result == 'true') {
341
+            $result = true;
342
+        } elseif ($result == 'false') {
343
+            $result = false;
344
+        }
345
+        return (bool)$result;
346
+    }
347 347
 
348 348
 }
Please login to merge, or discard this patch.
lib/private/DB/QueryBuilder/CompositeExpression.php 1 patch
Indentation   +57 added lines, -57 removed lines patch added patch discarded remove patch
@@ -25,69 +25,69 @@
 block discarded – undo
25 25
 use OCP\DB\QueryBuilder\ICompositeExpression;
26 26
 
27 27
 class CompositeExpression implements ICompositeExpression, \Countable {
28
-	/** @var \Doctrine\DBAL\Query\Expression\CompositeExpression */
29
-	protected $compositeExpression;
28
+    /** @var \Doctrine\DBAL\Query\Expression\CompositeExpression */
29
+    protected $compositeExpression;
30 30
 
31
-	/**
32
-	 * Constructor.
33
-	 *
34
-	 * @param \Doctrine\DBAL\Query\Expression\CompositeExpression $compositeExpression
35
-	 */
36
-	public function __construct(\Doctrine\DBAL\Query\Expression\CompositeExpression $compositeExpression) {
37
-		$this->compositeExpression = $compositeExpression;
38
-	}
31
+    /**
32
+     * Constructor.
33
+     *
34
+     * @param \Doctrine\DBAL\Query\Expression\CompositeExpression $compositeExpression
35
+     */
36
+    public function __construct(\Doctrine\DBAL\Query\Expression\CompositeExpression $compositeExpression) {
37
+        $this->compositeExpression = $compositeExpression;
38
+    }
39 39
 
40
-	/**
41
-	 * Adds multiple parts to composite expression.
42
-	 *
43
-	 * @param array $parts
44
-	 *
45
-	 * @return \OCP\DB\QueryBuilder\ICompositeExpression
46
-	 */
47
-	public function addMultiple(array $parts = []) {
48
-		$this->compositeExpression->addMultiple($parts);
40
+    /**
41
+     * Adds multiple parts to composite expression.
42
+     *
43
+     * @param array $parts
44
+     *
45
+     * @return \OCP\DB\QueryBuilder\ICompositeExpression
46
+     */
47
+    public function addMultiple(array $parts = []) {
48
+        $this->compositeExpression->addMultiple($parts);
49 49
 
50
-		return $this;
51
-	}
50
+        return $this;
51
+    }
52 52
 
53
-	/**
54
-	 * Adds an expression to composite expression.
55
-	 *
56
-	 * @param mixed $part
57
-	 *
58
-	 * @return \OCP\DB\QueryBuilder\ICompositeExpression
59
-	 */
60
-	public function add($part) {
61
-		$this->compositeExpression->add($part);
53
+    /**
54
+     * Adds an expression to composite expression.
55
+     *
56
+     * @param mixed $part
57
+     *
58
+     * @return \OCP\DB\QueryBuilder\ICompositeExpression
59
+     */
60
+    public function add($part) {
61
+        $this->compositeExpression->add($part);
62 62
 
63
-		return $this;
64
-	}
63
+        return $this;
64
+    }
65 65
 
66
-	/**
67
-	 * Retrieves the amount of expressions on composite expression.
68
-	 *
69
-	 * @return integer
70
-	 */
71
-	public function count() {
72
-		return $this->compositeExpression->count();
73
-	}
66
+    /**
67
+     * Retrieves the amount of expressions on composite expression.
68
+     *
69
+     * @return integer
70
+     */
71
+    public function count() {
72
+        return $this->compositeExpression->count();
73
+    }
74 74
 
75
-	/**
76
-	 * Returns the type of this composite expression (AND/OR).
77
-	 *
78
-	 * @return string
79
-	 */
80
-	public function getType() {
81
-		return $this->compositeExpression->getType();
82
-	}
75
+    /**
76
+     * Returns the type of this composite expression (AND/OR).
77
+     *
78
+     * @return string
79
+     */
80
+    public function getType() {
81
+        return $this->compositeExpression->getType();
82
+    }
83 83
 
84
-	/**
85
-	 * Retrieves the string representation of this composite expression.
86
-	 *
87
-	 * @return string
88
-	 */
89
-	public function __toString()
90
-	{
91
-		return (string) $this->compositeExpression;
92
-	}
84
+    /**
85
+     * Retrieves the string representation of this composite expression.
86
+     *
87
+     * @return string
88
+     */
89
+    public function __toString()
90
+    {
91
+        return (string) $this->compositeExpression;
92
+    }
93 93
 }
Please login to merge, or discard this patch.
lib/private/DB/QueryBuilder/QueryBuilder.php 1 patch
Indentation   +1173 added lines, -1173 removed lines patch added patch discarded remove patch
@@ -48,1177 +48,1177 @@
 block discarded – undo
48 48
 
49 49
 class QueryBuilder implements IQueryBuilder {
50 50
 
51
-	/** @var \OCP\IDBConnection */
52
-	private $connection;
53
-
54
-	/** @var SystemConfig */
55
-	private $systemConfig;
56
-
57
-	/** @var ILogger */
58
-	private $logger;
59
-
60
-	/** @var \Doctrine\DBAL\Query\QueryBuilder */
61
-	private $queryBuilder;
62
-
63
-	/** @var QuoteHelper */
64
-	private $helper;
65
-
66
-	/** @var bool */
67
-	private $automaticTablePrefix = true;
68
-
69
-	/** @var string */
70
-	protected $lastInsertedTable;
71
-
72
-	/**
73
-	 * Initializes a new QueryBuilder.
74
-	 *
75
-	 * @param IDBConnection $connection
76
-	 * @param SystemConfig $systemConfig
77
-	 * @param ILogger $logger
78
-	 */
79
-	public function __construct(IDBConnection $connection, SystemConfig $systemConfig, ILogger $logger) {
80
-		$this->connection = $connection;
81
-		$this->systemConfig = $systemConfig;
82
-		$this->logger = $logger;
83
-		$this->queryBuilder = new \Doctrine\DBAL\Query\QueryBuilder($this->connection);
84
-		$this->helper = new QuoteHelper();
85
-	}
86
-
87
-	/**
88
-	 * Enable/disable automatic prefixing of table names with the oc_ prefix
89
-	 *
90
-	 * @param bool $enabled If set to true table names will be prefixed with the
91
-	 * owncloud database prefix automatically.
92
-	 * @since 8.2.0
93
-	 */
94
-	public function automaticTablePrefix($enabled) {
95
-		$this->automaticTablePrefix = (bool) $enabled;
96
-	}
97
-
98
-	/**
99
-	 * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
100
-	 * This producer method is intended for convenient inline usage. Example:
101
-	 *
102
-	 * <code>
103
-	 *     $qb = $conn->getQueryBuilder()
104
-	 *         ->select('u')
105
-	 *         ->from('users', 'u')
106
-	 *         ->where($qb->expr()->eq('u.id', 1));
107
-	 * </code>
108
-	 *
109
-	 * For more complex expression construction, consider storing the expression
110
-	 * builder object in a local variable.
111
-	 *
112
-	 * @return \OCP\DB\QueryBuilder\IExpressionBuilder
113
-	 */
114
-	public function expr() {
115
-		if ($this->connection instanceof OracleConnection) {
116
-			return new OCIExpressionBuilder($this->connection, $this);
117
-		} else if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) {
118
-			return new PgSqlExpressionBuilder($this->connection, $this);
119
-		} else if ($this->connection->getDatabasePlatform() instanceof MySqlPlatform) {
120
-			return new MySqlExpressionBuilder($this->connection, $this);
121
-		} else if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) {
122
-			return new SqliteExpressionBuilder($this->connection, $this);
123
-		} else {
124
-			return new ExpressionBuilder($this->connection, $this);
125
-		}
126
-	}
127
-
128
-	/**
129
-	 * Gets an FunctionBuilder used for object-oriented construction of query functions.
130
-	 * This producer method is intended for convenient inline usage. Example:
131
-	 *
132
-	 * <code>
133
-	 *     $qb = $conn->getQueryBuilder()
134
-	 *         ->select('u')
135
-	 *         ->from('users', 'u')
136
-	 *         ->where($qb->fun()->md5('u.id'));
137
-	 * </code>
138
-	 *
139
-	 * For more complex function construction, consider storing the function
140
-	 * builder object in a local variable.
141
-	 *
142
-	 * @return \OCP\DB\QueryBuilder\IFunctionBuilder
143
-	 */
144
-	public function func() {
145
-		if ($this->connection instanceof OracleConnection) {
146
-			return new OCIFunctionBuilder($this->helper);
147
-		} else if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) {
148
-			return new SqliteFunctionBuilder($this->helper);
149
-		} else if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) {
150
-			return new PgSqlFunctionBuilder($this->helper);
151
-		} else {
152
-			return new FunctionBuilder($this->helper);
153
-		}
154
-	}
155
-
156
-	/**
157
-	 * Gets the type of the currently built query.
158
-	 *
159
-	 * @return integer
160
-	 */
161
-	public function getType() {
162
-		return $this->queryBuilder->getType();
163
-	}
164
-
165
-	/**
166
-	 * Gets the associated DBAL Connection for this query builder.
167
-	 *
168
-	 * @return \OCP\IDBConnection
169
-	 */
170
-	public function getConnection() {
171
-		return $this->connection;
172
-	}
173
-
174
-	/**
175
-	 * Gets the state of this query builder instance.
176
-	 *
177
-	 * @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
178
-	 */
179
-	public function getState() {
180
-		return $this->queryBuilder->getState();
181
-	}
182
-
183
-	/**
184
-	 * Executes this query using the bound parameters and their types.
185
-	 *
186
-	 * Uses {@see Connection::executeQuery} for select statements and {@see Connection::executeUpdate}
187
-	 * for insert, update and delete statements.
188
-	 *
189
-	 * @return \Doctrine\DBAL\Driver\Statement|int
190
-	 */
191
-	public function execute() {
192
-		if ($this->systemConfig->getValue('log_query', false)) {
193
-			$params = [];
194
-			foreach ($this->getParameters() as $placeholder => $value) {
195
-				if (is_array($value)) {
196
-					$params[] = $placeholder . ' => (\'' . implode('\', \'', $value) . '\')';
197
-				} else {
198
-					$params[] = $placeholder . ' => \'' . $value . '\'';
199
-				}
200
-			}
201
-			if (empty($params)) {
202
-				$this->logger->debug('DB QueryBuilder: \'{query}\'', [
203
-					'query' => $this->getSQL(),
204
-					'app' => 'core',
205
-				]);
206
-			} else {
207
-				$this->logger->debug('DB QueryBuilder: \'{query}\' with parameters: {params}', [
208
-					'query' => $this->getSQL(),
209
-					'params' => implode(', ', $params),
210
-					'app' => 'core',
211
-				]);
212
-			}
213
-		}
214
-
215
-		return $this->queryBuilder->execute();
216
-	}
217
-
218
-	/**
219
-	 * Gets the complete SQL string formed by the current specifications of this QueryBuilder.
220
-	 *
221
-	 * <code>
222
-	 *     $qb = $conn->getQueryBuilder()
223
-	 *         ->select('u')
224
-	 *         ->from('User', 'u')
225
-	 *     echo $qb->getSQL(); // SELECT u FROM User u
226
-	 * </code>
227
-	 *
228
-	 * @return string The SQL query string.
229
-	 */
230
-	public function getSQL() {
231
-		return $this->queryBuilder->getSQL();
232
-	}
233
-
234
-	/**
235
-	 * Sets a query parameter for the query being constructed.
236
-	 *
237
-	 * <code>
238
-	 *     $qb = $conn->getQueryBuilder()
239
-	 *         ->select('u')
240
-	 *         ->from('users', 'u')
241
-	 *         ->where('u.id = :user_id')
242
-	 *         ->setParameter(':user_id', 1);
243
-	 * </code>
244
-	 *
245
-	 * @param string|integer $key The parameter position or name.
246
-	 * @param mixed $value The parameter value.
247
-	 * @param string|null|int $type One of the IQueryBuilder::PARAM_* constants.
248
-	 *
249
-	 * @return $this This QueryBuilder instance.
250
-	 */
251
-	public function setParameter($key, $value, $type = null) {
252
-		$this->queryBuilder->setParameter($key, $value, $type);
253
-
254
-		return $this;
255
-	}
256
-
257
-	/**
258
-	 * Sets a collection of query parameters for the query being constructed.
259
-	 *
260
-	 * <code>
261
-	 *     $qb = $conn->getQueryBuilder()
262
-	 *         ->select('u')
263
-	 *         ->from('users', 'u')
264
-	 *         ->where('u.id = :user_id1 OR u.id = :user_id2')
265
-	 *         ->setParameters(array(
266
-	 *             ':user_id1' => 1,
267
-	 *             ':user_id2' => 2
268
-	 *         ));
269
-	 * </code>
270
-	 *
271
-	 * @param array $params The query parameters to set.
272
-	 * @param array $types The query parameters types to set.
273
-	 *
274
-	 * @return $this This QueryBuilder instance.
275
-	 */
276
-	public function setParameters(array $params, array $types = []) {
277
-		$this->queryBuilder->setParameters($params, $types);
278
-
279
-		return $this;
280
-	}
281
-
282
-	/**
283
-	 * Gets all defined query parameters for the query being constructed indexed by parameter index or name.
284
-	 *
285
-	 * @return array The currently defined query parameters indexed by parameter index or name.
286
-	 */
287
-	public function getParameters() {
288
-		return $this->queryBuilder->getParameters();
289
-	}
290
-
291
-	/**
292
-	 * Gets a (previously set) query parameter of the query being constructed.
293
-	 *
294
-	 * @param mixed $key The key (index or name) of the bound parameter.
295
-	 *
296
-	 * @return mixed The value of the bound parameter.
297
-	 */
298
-	public function getParameter($key) {
299
-		return $this->queryBuilder->getParameter($key);
300
-	}
301
-
302
-	/**
303
-	 * Gets all defined query parameter types for the query being constructed indexed by parameter index or name.
304
-	 *
305
-	 * @return array The currently defined query parameter types indexed by parameter index or name.
306
-	 */
307
-	public function getParameterTypes() {
308
-		return $this->queryBuilder->getParameterTypes();
309
-	}
310
-
311
-	/**
312
-	 * Gets a (previously set) query parameter type of the query being constructed.
313
-	 *
314
-	 * @param mixed $key The key (index or name) of the bound parameter type.
315
-	 *
316
-	 * @return mixed The value of the bound parameter type.
317
-	 */
318
-	public function getParameterType($key) {
319
-		return $this->queryBuilder->getParameterType($key);
320
-	}
321
-
322
-	/**
323
-	 * Sets the position of the first result to retrieve (the "offset").
324
-	 *
325
-	 * @param integer $firstResult The first result to return.
326
-	 *
327
-	 * @return $this This QueryBuilder instance.
328
-	 */
329
-	public function setFirstResult($firstResult) {
330
-		$this->queryBuilder->setFirstResult($firstResult);
331
-
332
-		return $this;
333
-	}
334
-
335
-	/**
336
-	 * Gets the position of the first result the query object was set to retrieve (the "offset").
337
-	 * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
338
-	 *
339
-	 * @return integer The position of the first result.
340
-	 */
341
-	public function getFirstResult() {
342
-		return $this->queryBuilder->getFirstResult();
343
-	}
344
-
345
-	/**
346
-	 * Sets the maximum number of results to retrieve (the "limit").
347
-	 *
348
-	 * NOTE: Setting max results to "0" will cause mixed behaviour. While most
349
-	 * of the databases will just return an empty result set, Oracle will return
350
-	 * all entries.
351
-	 *
352
-	 * @param integer $maxResults The maximum number of results to retrieve.
353
-	 *
354
-	 * @return $this This QueryBuilder instance.
355
-	 */
356
-	public function setMaxResults($maxResults) {
357
-		$this->queryBuilder->setMaxResults($maxResults);
358
-
359
-		return $this;
360
-	}
361
-
362
-	/**
363
-	 * Gets the maximum number of results the query object was set to retrieve (the "limit").
364
-	 * Returns NULL if {@link setMaxResults} was not applied to this query builder.
365
-	 *
366
-	 * @return integer The maximum number of results.
367
-	 */
368
-	public function getMaxResults() {
369
-		return $this->queryBuilder->getMaxResults();
370
-	}
371
-
372
-	/**
373
-	 * Specifies an item that is to be returned in the query result.
374
-	 * Replaces any previously specified selections, if any.
375
-	 *
376
-	 * <code>
377
-	 *     $qb = $conn->getQueryBuilder()
378
-	 *         ->select('u.id', 'p.id')
379
-	 *         ->from('users', 'u')
380
-	 *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
381
-	 * </code>
382
-	 *
383
-	 * @param mixed ...$selects The selection expressions.
384
-	 *
385
-	 * '@return $this This QueryBuilder instance.
386
-	 */
387
-	public function select(...$selects) {
388
-		if (count($selects) === 1 && is_array($selects[0])) {
389
-			$selects = $selects[0];
390
-		}
391
-
392
-		$this->queryBuilder->select(
393
-			$this->helper->quoteColumnNames($selects)
394
-		);
395
-
396
-		return $this;
397
-	}
398
-
399
-	/**
400
-	 * Specifies an item that is to be returned with a different name in the query result.
401
-	 *
402
-	 * <code>
403
-	 *     $qb = $conn->getQueryBuilder()
404
-	 *         ->selectAlias('u.id', 'user_id')
405
-	 *         ->from('users', 'u')
406
-	 *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
407
-	 * </code>
408
-	 *
409
-	 * @param mixed $select The selection expressions.
410
-	 * @param string $alias The column alias used in the constructed query.
411
-	 *
412
-	 * @return $this This QueryBuilder instance.
413
-	 */
414
-	public function selectAlias($select, $alias) {
415
-
416
-		$this->queryBuilder->addSelect(
417
-			$this->helper->quoteColumnName($select) . ' AS ' . $this->helper->quoteColumnName($alias)
418
-		);
419
-
420
-		return $this;
421
-	}
422
-
423
-	/**
424
-	 * Specifies an item that is to be returned uniquely in the query result.
425
-	 *
426
-	 * <code>
427
-	 *     $qb = $conn->getQueryBuilder()
428
-	 *         ->selectDistinct('type')
429
-	 *         ->from('users');
430
-	 * </code>
431
-	 *
432
-	 * @param mixed $select The selection expressions.
433
-	 *
434
-	 * @return $this This QueryBuilder instance.
435
-	 */
436
-	public function selectDistinct($select) {
437
-
438
-		$this->queryBuilder->addSelect(
439
-			'DISTINCT ' . $this->helper->quoteColumnName($select)
440
-		);
441
-
442
-		return $this;
443
-	}
444
-
445
-	/**
446
-	 * Adds an item that is to be returned in the query result.
447
-	 *
448
-	 * <code>
449
-	 *     $qb = $conn->getQueryBuilder()
450
-	 *         ->select('u.id')
451
-	 *         ->addSelect('p.id')
452
-	 *         ->from('users', 'u')
453
-	 *         ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id');
454
-	 * </code>
455
-	 *
456
-	 * @param mixed ...$selects The selection expression.
457
-	 *
458
-	 * @return $this This QueryBuilder instance.
459
-	 */
460
-	public function addSelect(...$selects) {
461
-		if (count($selects) === 1 && is_array($selects[0])) {
462
-			$selects = $selects[0];
463
-		}
464
-
465
-		$this->queryBuilder->addSelect(
466
-			$this->helper->quoteColumnNames($selects)
467
-		);
468
-
469
-		return $this;
470
-	}
471
-
472
-	/**
473
-	 * Turns the query being built into a bulk delete query that ranges over
474
-	 * a certain table.
475
-	 *
476
-	 * <code>
477
-	 *     $qb = $conn->getQueryBuilder()
478
-	 *         ->delete('users', 'u')
479
-	 *         ->where('u.id = :user_id');
480
-	 *         ->setParameter(':user_id', 1);
481
-	 * </code>
482
-	 *
483
-	 * @param string $delete The table whose rows are subject to the deletion.
484
-	 * @param string $alias The table alias used in the constructed query.
485
-	 *
486
-	 * @return $this This QueryBuilder instance.
487
-	 */
488
-	public function delete($delete = null, $alias = null) {
489
-		$this->queryBuilder->delete(
490
-			$this->getTableName($delete),
491
-			$alias
492
-		);
493
-
494
-		return $this;
495
-	}
496
-
497
-	/**
498
-	 * Turns the query being built into a bulk update query that ranges over
499
-	 * a certain table
500
-	 *
501
-	 * <code>
502
-	 *     $qb = $conn->getQueryBuilder()
503
-	 *         ->update('users', 'u')
504
-	 *         ->set('u.password', md5('password'))
505
-	 *         ->where('u.id = ?');
506
-	 * </code>
507
-	 *
508
-	 * @param string $update The table whose rows are subject to the update.
509
-	 * @param string $alias The table alias used in the constructed query.
510
-	 *
511
-	 * @return $this This QueryBuilder instance.
512
-	 */
513
-	public function update($update = null, $alias = null) {
514
-		$this->queryBuilder->update(
515
-			$this->getTableName($update),
516
-			$alias
517
-		);
518
-
519
-		return $this;
520
-	}
521
-
522
-	/**
523
-	 * Turns the query being built into an insert query that inserts into
524
-	 * a certain table
525
-	 *
526
-	 * <code>
527
-	 *     $qb = $conn->getQueryBuilder()
528
-	 *         ->insert('users')
529
-	 *         ->values(
530
-	 *             array(
531
-	 *                 'name' => '?',
532
-	 *                 'password' => '?'
533
-	 *             )
534
-	 *         );
535
-	 * </code>
536
-	 *
537
-	 * @param string $insert The table into which the rows should be inserted.
538
-	 *
539
-	 * @return $this This QueryBuilder instance.
540
-	 */
541
-	public function insert($insert = null) {
542
-		$this->queryBuilder->insert(
543
-			$this->getTableName($insert)
544
-		);
545
-
546
-		$this->lastInsertedTable = $insert;
547
-
548
-		return $this;
549
-	}
550
-
551
-	/**
552
-	 * Creates and adds a query root corresponding to the table identified by the
553
-	 * given alias, forming a cartesian product with any existing query roots.
554
-	 *
555
-	 * <code>
556
-	 *     $qb = $conn->getQueryBuilder()
557
-	 *         ->select('u.id')
558
-	 *         ->from('users', 'u')
559
-	 * </code>
560
-	 *
561
-	 * @param string $from The table.
562
-	 * @param string|null $alias The alias of the table.
563
-	 *
564
-	 * @return $this This QueryBuilder instance.
565
-	 */
566
-	public function from($from, $alias = null) {
567
-		$this->queryBuilder->from(
568
-			$this->getTableName($from),
569
-			$this->quoteAlias($alias)
570
-		);
571
-
572
-		return $this;
573
-	}
574
-
575
-	/**
576
-	 * Creates and adds a join to the query.
577
-	 *
578
-	 * <code>
579
-	 *     $qb = $conn->getQueryBuilder()
580
-	 *         ->select('u.name')
581
-	 *         ->from('users', 'u')
582
-	 *         ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1');
583
-	 * </code>
584
-	 *
585
-	 * @param string $fromAlias The alias that points to a from clause.
586
-	 * @param string $join The table name to join.
587
-	 * @param string $alias The alias of the join table.
588
-	 * @param string $condition The condition for the join.
589
-	 *
590
-	 * @return $this This QueryBuilder instance.
591
-	 */
592
-	public function join($fromAlias, $join, $alias, $condition = null) {
593
-		$this->queryBuilder->join(
594
-			$this->quoteAlias($fromAlias),
595
-			$this->getTableName($join),
596
-			$this->quoteAlias($alias),
597
-			$condition
598
-		);
599
-
600
-		return $this;
601
-	}
602
-
603
-	/**
604
-	 * Creates and adds a join to the query.
605
-	 *
606
-	 * <code>
607
-	 *     $qb = $conn->getQueryBuilder()
608
-	 *         ->select('u.name')
609
-	 *         ->from('users', 'u')
610
-	 *         ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
611
-	 * </code>
612
-	 *
613
-	 * @param string $fromAlias The alias that points to a from clause.
614
-	 * @param string $join The table name to join.
615
-	 * @param string $alias The alias of the join table.
616
-	 * @param string $condition The condition for the join.
617
-	 *
618
-	 * @return $this This QueryBuilder instance.
619
-	 */
620
-	public function innerJoin($fromAlias, $join, $alias, $condition = null) {
621
-		$this->queryBuilder->innerJoin(
622
-			$this->quoteAlias($fromAlias),
623
-			$this->getTableName($join),
624
-			$this->quoteAlias($alias),
625
-			$condition
626
-		);
627
-
628
-		return $this;
629
-	}
630
-
631
-	/**
632
-	 * Creates and adds a left join to the query.
633
-	 *
634
-	 * <code>
635
-	 *     $qb = $conn->getQueryBuilder()
636
-	 *         ->select('u.name')
637
-	 *         ->from('users', 'u')
638
-	 *         ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
639
-	 * </code>
640
-	 *
641
-	 * @param string $fromAlias The alias that points to a from clause.
642
-	 * @param string $join The table name to join.
643
-	 * @param string $alias The alias of the join table.
644
-	 * @param string $condition The condition for the join.
645
-	 *
646
-	 * @return $this This QueryBuilder instance.
647
-	 */
648
-	public function leftJoin($fromAlias, $join, $alias, $condition = null) {
649
-		$this->queryBuilder->leftJoin(
650
-			$this->quoteAlias($fromAlias),
651
-			$this->getTableName($join),
652
-			$this->quoteAlias($alias),
653
-			$condition
654
-		);
655
-
656
-		return $this;
657
-	}
658
-
659
-	/**
660
-	 * Creates and adds a right join to the query.
661
-	 *
662
-	 * <code>
663
-	 *     $qb = $conn->getQueryBuilder()
664
-	 *         ->select('u.name')
665
-	 *         ->from('users', 'u')
666
-	 *         ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
667
-	 * </code>
668
-	 *
669
-	 * @param string $fromAlias The alias that points to a from clause.
670
-	 * @param string $join The table name to join.
671
-	 * @param string $alias The alias of the join table.
672
-	 * @param string $condition The condition for the join.
673
-	 *
674
-	 * @return $this This QueryBuilder instance.
675
-	 */
676
-	public function rightJoin($fromAlias, $join, $alias, $condition = null) {
677
-		$this->queryBuilder->rightJoin(
678
-			$this->quoteAlias($fromAlias),
679
-			$this->getTableName($join),
680
-			$this->quoteAlias($alias),
681
-			$condition
682
-		);
683
-
684
-		return $this;
685
-	}
686
-
687
-	/**
688
-	 * Sets a new value for a column in a bulk update query.
689
-	 *
690
-	 * <code>
691
-	 *     $qb = $conn->getQueryBuilder()
692
-	 *         ->update('users', 'u')
693
-	 *         ->set('u.password', md5('password'))
694
-	 *         ->where('u.id = ?');
695
-	 * </code>
696
-	 *
697
-	 * @param string $key The column to set.
698
-	 * @param string $value The value, expression, placeholder, etc.
699
-	 *
700
-	 * @return $this This QueryBuilder instance.
701
-	 */
702
-	public function set($key, $value) {
703
-		$this->queryBuilder->set(
704
-			$this->helper->quoteColumnName($key),
705
-			$this->helper->quoteColumnName($value)
706
-		);
707
-
708
-		return $this;
709
-	}
710
-
711
-	/**
712
-	 * Specifies one or more restrictions to the query result.
713
-	 * Replaces any previously specified restrictions, if any.
714
-	 *
715
-	 * <code>
716
-	 *     $qb = $conn->getQueryBuilder()
717
-	 *         ->select('u.name')
718
-	 *         ->from('users', 'u')
719
-	 *         ->where('u.id = ?');
720
-	 *
721
-	 *     // You can optionally programatically build and/or expressions
722
-	 *     $qb = $conn->getQueryBuilder();
723
-	 *
724
-	 *     $or = $qb->expr()->orx();
725
-	 *     $or->add($qb->expr()->eq('u.id', 1));
726
-	 *     $or->add($qb->expr()->eq('u.id', 2));
727
-	 *
728
-	 *     $qb->update('users', 'u')
729
-	 *         ->set('u.password', md5('password'))
730
-	 *         ->where($or);
731
-	 * </code>
732
-	 *
733
-	 * @param mixed ...$predicates The restriction predicates.
734
-	 *
735
-	 * @return $this This QueryBuilder instance.
736
-	 */
737
-	public function where(...$predicates) {
738
-		call_user_func_array(
739
-			[$this->queryBuilder, 'where'],
740
-			$predicates
741
-		);
742
-
743
-		return $this;
744
-	}
745
-
746
-	/**
747
-	 * Adds one or more restrictions to the query results, forming a logical
748
-	 * conjunction with any previously specified restrictions.
749
-	 *
750
-	 * <code>
751
-	 *     $qb = $conn->getQueryBuilder()
752
-	 *         ->select('u')
753
-	 *         ->from('users', 'u')
754
-	 *         ->where('u.username LIKE ?')
755
-	 *         ->andWhere('u.is_active = 1');
756
-	 * </code>
757
-	 *
758
-	 * @param mixed ...$where The query restrictions.
759
-	 *
760
-	 * @return $this This QueryBuilder instance.
761
-	 *
762
-	 * @see where()
763
-	 */
764
-	public function andWhere(...$where) {
765
-		call_user_func_array(
766
-			[$this->queryBuilder, 'andWhere'],
767
-			$where
768
-		);
769
-
770
-		return $this;
771
-	}
772
-
773
-	/**
774
-	 * Adds one or more restrictions to the query results, forming a logical
775
-	 * disjunction with any previously specified restrictions.
776
-	 *
777
-	 * <code>
778
-	 *     $qb = $conn->getQueryBuilder()
779
-	 *         ->select('u.name')
780
-	 *         ->from('users', 'u')
781
-	 *         ->where('u.id = 1')
782
-	 *         ->orWhere('u.id = 2');
783
-	 * </code>
784
-	 *
785
-	 * @param mixed ...$where The WHERE statement.
786
-	 *
787
-	 * @return $this This QueryBuilder instance.
788
-	 *
789
-	 * @see where()
790
-	 */
791
-	public function orWhere(...$where) {
792
-		call_user_func_array(
793
-			[$this->queryBuilder, 'orWhere'],
794
-			$where
795
-		);
796
-
797
-		return $this;
798
-	}
799
-
800
-	/**
801
-	 * Specifies a grouping over the results of the query.
802
-	 * Replaces any previously specified groupings, if any.
803
-	 *
804
-	 * <code>
805
-	 *     $qb = $conn->getQueryBuilder()
806
-	 *         ->select('u.name')
807
-	 *         ->from('users', 'u')
808
-	 *         ->groupBy('u.id');
809
-	 * </code>
810
-	 *
811
-	 * @param mixed ...$groupBys The grouping expression.
812
-	 *
813
-	 * @return $this This QueryBuilder instance.
814
-	 */
815
-	public function groupBy(...$groupBys) {
816
-		if (count($groupBys) === 1 && is_array($groupBys[0])) {
817
-			$groupBys = $groupBys[0];
818
-		}
819
-
820
-		call_user_func_array(
821
-			[$this->queryBuilder, 'groupBy'],
822
-			$this->helper->quoteColumnNames($groupBys)
823
-		);
824
-
825
-		return $this;
826
-	}
827
-
828
-	/**
829
-	 * Adds a grouping expression to the query.
830
-	 *
831
-	 * <code>
832
-	 *     $qb = $conn->getQueryBuilder()
833
-	 *         ->select('u.name')
834
-	 *         ->from('users', 'u')
835
-	 *         ->groupBy('u.lastLogin');
836
-	 *         ->addGroupBy('u.createdAt')
837
-	 * </code>
838
-	 *
839
-	 * @param mixed ...$groupBy The grouping expression.
840
-	 *
841
-	 * @return $this This QueryBuilder instance.
842
-	 */
843
-	public function addGroupBy(...$groupBys) {
844
-		if (count($groupBys) === 1 && is_array($groupBys[0])) {
845
-			$$groupBys = $groupBys[0];
846
-		}
847
-
848
-		call_user_func_array(
849
-			[$this->queryBuilder, 'addGroupBy'],
850
-			$this->helper->quoteColumnNames($groupBys)
851
-		);
852
-
853
-		return $this;
854
-	}
855
-
856
-	/**
857
-	 * Sets a value for a column in an insert query.
858
-	 *
859
-	 * <code>
860
-	 *     $qb = $conn->getQueryBuilder()
861
-	 *         ->insert('users')
862
-	 *         ->values(
863
-	 *             array(
864
-	 *                 'name' => '?'
865
-	 *             )
866
-	 *         )
867
-	 *         ->setValue('password', '?');
868
-	 * </code>
869
-	 *
870
-	 * @param string $column The column into which the value should be inserted.
871
-	 * @param string $value The value that should be inserted into the column.
872
-	 *
873
-	 * @return $this This QueryBuilder instance.
874
-	 */
875
-	public function setValue($column, $value) {
876
-		$this->queryBuilder->setValue(
877
-			$this->helper->quoteColumnName($column),
878
-			$value
879
-		);
880
-
881
-		return $this;
882
-	}
883
-
884
-	/**
885
-	 * Specifies values for an insert query indexed by column names.
886
-	 * Replaces any previous values, if any.
887
-	 *
888
-	 * <code>
889
-	 *     $qb = $conn->getQueryBuilder()
890
-	 *         ->insert('users')
891
-	 *         ->values(
892
-	 *             array(
893
-	 *                 'name' => '?',
894
-	 *                 'password' => '?'
895
-	 *             )
896
-	 *         );
897
-	 * </code>
898
-	 *
899
-	 * @param array $values The values to specify for the insert query indexed by column names.
900
-	 *
901
-	 * @return $this This QueryBuilder instance.
902
-	 */
903
-	public function values(array $values) {
904
-		$quotedValues = [];
905
-		foreach ($values as $key => $value) {
906
-			$quotedValues[$this->helper->quoteColumnName($key)] = $value;
907
-		}
908
-
909
-		$this->queryBuilder->values($quotedValues);
910
-
911
-		return $this;
912
-	}
913
-
914
-	/**
915
-	 * Specifies a restriction over the groups of the query.
916
-	 * Replaces any previous having restrictions, if any.
917
-	 *
918
-	 * @param mixed ...$having The restriction over the groups.
919
-	 *
920
-	 * @return $this This QueryBuilder instance.
921
-	 */
922
-	public function having(...$having) {
923
-		call_user_func_array(
924
-			[$this->queryBuilder, 'having'],
925
-			$having
926
-		);
927
-
928
-		return $this;
929
-	}
930
-
931
-	/**
932
-	 * Adds a restriction over the groups of the query, forming a logical
933
-	 * conjunction with any existing having restrictions.
934
-	 *
935
-	 * @param mixed ...$having The restriction to append.
936
-	 *
937
-	 * @return $this This QueryBuilder instance.
938
-	 */
939
-	public function andHaving(...$having) {
940
-		call_user_func_array(
941
-			[$this->queryBuilder, 'andHaving'],
942
-			$having
943
-		);
944
-
945
-		return $this;
946
-	}
947
-
948
-	/**
949
-	 * Adds a restriction over the groups of the query, forming a logical
950
-	 * disjunction with any existing having restrictions.
951
-	 *
952
-	 * @param mixed ...$having The restriction to add.
953
-	 *
954
-	 * @return $this This QueryBuilder instance.
955
-	 */
956
-	public function orHaving(...$having) {
957
-		call_user_func_array(
958
-			[$this->queryBuilder, 'orHaving'],
959
-			$having
960
-		);
961
-
962
-		return $this;
963
-	}
964
-
965
-	/**
966
-	 * Specifies an ordering for the query results.
967
-	 * Replaces any previously specified orderings, if any.
968
-	 *
969
-	 * @param string $sort The ordering expression.
970
-	 * @param string $order The ordering direction.
971
-	 *
972
-	 * @return $this This QueryBuilder instance.
973
-	 */
974
-	public function orderBy($sort, $order = null) {
975
-		$this->queryBuilder->orderBy(
976
-			$this->helper->quoteColumnName($sort),
977
-			$order
978
-		);
979
-
980
-		return $this;
981
-	}
982
-
983
-	/**
984
-	 * Adds an ordering to the query results.
985
-	 *
986
-	 * @param string $sort The ordering expression.
987
-	 * @param string $order The ordering direction.
988
-	 *
989
-	 * @return $this This QueryBuilder instance.
990
-	 */
991
-	public function addOrderBy($sort, $order = null) {
992
-		$this->queryBuilder->addOrderBy(
993
-			$this->helper->quoteColumnName($sort),
994
-			$order
995
-		);
996
-
997
-		return $this;
998
-	}
999
-
1000
-	/**
1001
-	 * Gets a query part by its name.
1002
-	 *
1003
-	 * @param string $queryPartName
1004
-	 *
1005
-	 * @return mixed
1006
-	 */
1007
-	public function getQueryPart($queryPartName) {
1008
-		return $this->queryBuilder->getQueryPart($queryPartName);
1009
-	}
1010
-
1011
-	/**
1012
-	 * Gets all query parts.
1013
-	 *
1014
-	 * @return array
1015
-	 */
1016
-	public function getQueryParts() {
1017
-		return $this->queryBuilder->getQueryParts();
1018
-	}
1019
-
1020
-	/**
1021
-	 * Resets SQL parts.
1022
-	 *
1023
-	 * @param array|null $queryPartNames
1024
-	 *
1025
-	 * @return $this This QueryBuilder instance.
1026
-	 */
1027
-	public function resetQueryParts($queryPartNames = null) {
1028
-		$this->queryBuilder->resetQueryParts($queryPartNames);
1029
-
1030
-		return $this;
1031
-	}
1032
-
1033
-	/**
1034
-	 * Resets a single SQL part.
1035
-	 *
1036
-	 * @param string $queryPartName
1037
-	 *
1038
-	 * @return $this This QueryBuilder instance.
1039
-	 */
1040
-	public function resetQueryPart($queryPartName) {
1041
-		$this->queryBuilder->resetQueryPart($queryPartName);
1042
-
1043
-		return $this;
1044
-	}
1045
-
1046
-	/**
1047
-	 * Creates a new named parameter and bind the value $value to it.
1048
-	 *
1049
-	 * This method provides a shortcut for PDOStatement::bindValue
1050
-	 * when using prepared statements.
1051
-	 *
1052
-	 * The parameter $value specifies the value that you want to bind. If
1053
-	 * $placeholder is not provided bindValue() will automatically create a
1054
-	 * placeholder for you. An automatic placeholder will be of the name
1055
-	 * ':dcValue1', ':dcValue2' etc.
1056
-	 *
1057
-	 * For more information see {@link http://php.net/pdostatement-bindparam}
1058
-	 *
1059
-	 * Example:
1060
-	 * <code>
1061
-	 * $value = 2;
1062
-	 * $q->eq( 'id', $q->bindValue( $value ) );
1063
-	 * $stmt = $q->executeQuery(); // executed with 'id = 2'
1064
-	 * </code>
1065
-	 *
1066
-	 * @license New BSD License
1067
-	 * @link http://www.zetacomponents.org
1068
-	 *
1069
-	 * @param mixed $value
1070
-	 * @param mixed $type
1071
-	 * @param string $placeHolder The name to bind with. The string must start with a colon ':'.
1072
-	 *
1073
-	 * @return IParameter the placeholder name used.
1074
-	 */
1075
-	public function createNamedParameter($value, $type = IQueryBuilder::PARAM_STR, $placeHolder = null) {
1076
-		return new Parameter($this->queryBuilder->createNamedParameter($value, $type, $placeHolder));
1077
-	}
1078
-
1079
-	/**
1080
-	 * Creates a new positional parameter and bind the given value to it.
1081
-	 *
1082
-	 * Attention: If you are using positional parameters with the query builder you have
1083
-	 * to be very careful to bind all parameters in the order they appear in the SQL
1084
-	 * statement , otherwise they get bound in the wrong order which can lead to serious
1085
-	 * bugs in your code.
1086
-	 *
1087
-	 * Example:
1088
-	 * <code>
1089
-	 *  $qb = $conn->getQueryBuilder();
1090
-	 *  $qb->select('u.*')
1091
-	 *     ->from('users', 'u')
1092
-	 *     ->where('u.username = ' . $qb->createPositionalParameter('Foo', IQueryBuilder::PARAM_STR))
1093
-	 *     ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', IQueryBuilder::PARAM_STR))
1094
-	 * </code>
1095
-	 *
1096
-	 * @param mixed $value
1097
-	 * @param integer $type
1098
-	 *
1099
-	 * @return IParameter
1100
-	 */
1101
-	public function createPositionalParameter($value, $type = IQueryBuilder::PARAM_STR) {
1102
-		return new Parameter($this->queryBuilder->createPositionalParameter($value, $type));
1103
-	}
1104
-
1105
-	/**
1106
-	 * Creates a new parameter
1107
-	 *
1108
-	 * Example:
1109
-	 * <code>
1110
-	 *  $qb = $conn->getQueryBuilder();
1111
-	 *  $qb->select('u.*')
1112
-	 *     ->from('users', 'u')
1113
-	 *     ->where('u.username = ' . $qb->createParameter('name'))
1114
-	 *     ->setParameter('name', 'Bar', IQueryBuilder::PARAM_STR))
1115
-	 * </code>
1116
-	 *
1117
-	 * @param string $name
1118
-	 *
1119
-	 * @return IParameter
1120
-	 */
1121
-	public function createParameter($name) {
1122
-		return new Parameter(':' . $name);
1123
-	}
1124
-
1125
-	/**
1126
-	 * Creates a new function
1127
-	 *
1128
-	 * Attention: Column names inside the call have to be quoted before hand
1129
-	 *
1130
-	 * Example:
1131
-	 * <code>
1132
-	 *  $qb = $conn->getQueryBuilder();
1133
-	 *  $qb->select($qb->createFunction('COUNT(*)'))
1134
-	 *     ->from('users', 'u')
1135
-	 *  echo $qb->getSQL(); // SELECT COUNT(*) FROM `users` u
1136
-	 * </code>
1137
-	 * <code>
1138
-	 *  $qb = $conn->getQueryBuilder();
1139
-	 *  $qb->select($qb->createFunction('COUNT(`column`)'))
1140
-	 *     ->from('users', 'u')
1141
-	 *  echo $qb->getSQL(); // SELECT COUNT(`column`) FROM `users` u
1142
-	 * </code>
1143
-	 *
1144
-	 * @param string $call
1145
-	 *
1146
-	 * @return IQueryFunction
1147
-	 */
1148
-	public function createFunction($call) {
1149
-		return new QueryFunction($call);
1150
-	}
1151
-
1152
-	/**
1153
-	 * Used to get the id of the last inserted element
1154
-	 * @return int
1155
-	 * @throws \BadMethodCallException When being called before an insert query has been run.
1156
-	 */
1157
-	public function getLastInsertId() {
1158
-		if ($this->getType() === \Doctrine\DBAL\Query\QueryBuilder::INSERT && $this->lastInsertedTable) {
1159
-			// lastInsertId() needs the prefix but no quotes
1160
-			$table = $this->prefixTableName($this->lastInsertedTable);
1161
-			return (int) $this->connection->lastInsertId($table);
1162
-		}
1163
-
1164
-		throw new \BadMethodCallException('Invalid call to getLastInsertId without using insert() before.');
1165
-	}
1166
-
1167
-	/**
1168
-	 * Returns the table name quoted and with database prefix as needed by the implementation
1169
-	 *
1170
-	 * @param string $table
1171
-	 * @return string
1172
-	 */
1173
-	public function getTableName($table) {
1174
-		if ($table instanceof IQueryFunction) {
1175
-			return (string) $table;
1176
-		}
1177
-
1178
-		$table = $this->prefixTableName($table);
1179
-		return $this->helper->quoteColumnName($table);
1180
-	}
1181
-
1182
-	/**
1183
-	 * Returns the table name with database prefix as needed by the implementation
1184
-	 *
1185
-	 * @param string $table
1186
-	 * @return string
1187
-	 */
1188
-	protected function prefixTableName($table) {
1189
-		if ($this->automaticTablePrefix === false || strpos($table, '*PREFIX*') === 0) {
1190
-			return $table;
1191
-		}
1192
-
1193
-		return '*PREFIX*' . $table;
1194
-	}
1195
-
1196
-	/**
1197
-	 * Returns the column name quoted and with table alias prefix as needed by the implementation
1198
-	 *
1199
-	 * @param string $column
1200
-	 * @param string $tableAlias
1201
-	 * @return string
1202
-	 */
1203
-	public function getColumnName($column, $tableAlias = '') {
1204
-		if ($tableAlias !== '') {
1205
-			$tableAlias .= '.';
1206
-		}
1207
-
1208
-		return $this->helper->quoteColumnName($tableAlias . $column);
1209
-	}
1210
-
1211
-	/**
1212
-	 * Returns the column name quoted and with table alias prefix as needed by the implementation
1213
-	 *
1214
-	 * @param string $alias
1215
-	 * @return string
1216
-	 */
1217
-	public function quoteAlias($alias) {
1218
-		if ($alias === '' || $alias === null) {
1219
-			return $alias;
1220
-		}
1221
-
1222
-		return $this->helper->quoteColumnName($alias);
1223
-	}
51
+    /** @var \OCP\IDBConnection */
52
+    private $connection;
53
+
54
+    /** @var SystemConfig */
55
+    private $systemConfig;
56
+
57
+    /** @var ILogger */
58
+    private $logger;
59
+
60
+    /** @var \Doctrine\DBAL\Query\QueryBuilder */
61
+    private $queryBuilder;
62
+
63
+    /** @var QuoteHelper */
64
+    private $helper;
65
+
66
+    /** @var bool */
67
+    private $automaticTablePrefix = true;
68
+
69
+    /** @var string */
70
+    protected $lastInsertedTable;
71
+
72
+    /**
73
+     * Initializes a new QueryBuilder.
74
+     *
75
+     * @param IDBConnection $connection
76
+     * @param SystemConfig $systemConfig
77
+     * @param ILogger $logger
78
+     */
79
+    public function __construct(IDBConnection $connection, SystemConfig $systemConfig, ILogger $logger) {
80
+        $this->connection = $connection;
81
+        $this->systemConfig = $systemConfig;
82
+        $this->logger = $logger;
83
+        $this->queryBuilder = new \Doctrine\DBAL\Query\QueryBuilder($this->connection);
84
+        $this->helper = new QuoteHelper();
85
+    }
86
+
87
+    /**
88
+     * Enable/disable automatic prefixing of table names with the oc_ prefix
89
+     *
90
+     * @param bool $enabled If set to true table names will be prefixed with the
91
+     * owncloud database prefix automatically.
92
+     * @since 8.2.0
93
+     */
94
+    public function automaticTablePrefix($enabled) {
95
+        $this->automaticTablePrefix = (bool) $enabled;
96
+    }
97
+
98
+    /**
99
+     * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
100
+     * This producer method is intended for convenient inline usage. Example:
101
+     *
102
+     * <code>
103
+     *     $qb = $conn->getQueryBuilder()
104
+     *         ->select('u')
105
+     *         ->from('users', 'u')
106
+     *         ->where($qb->expr()->eq('u.id', 1));
107
+     * </code>
108
+     *
109
+     * For more complex expression construction, consider storing the expression
110
+     * builder object in a local variable.
111
+     *
112
+     * @return \OCP\DB\QueryBuilder\IExpressionBuilder
113
+     */
114
+    public function expr() {
115
+        if ($this->connection instanceof OracleConnection) {
116
+            return new OCIExpressionBuilder($this->connection, $this);
117
+        } else if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) {
118
+            return new PgSqlExpressionBuilder($this->connection, $this);
119
+        } else if ($this->connection->getDatabasePlatform() instanceof MySqlPlatform) {
120
+            return new MySqlExpressionBuilder($this->connection, $this);
121
+        } else if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) {
122
+            return new SqliteExpressionBuilder($this->connection, $this);
123
+        } else {
124
+            return new ExpressionBuilder($this->connection, $this);
125
+        }
126
+    }
127
+
128
+    /**
129
+     * Gets an FunctionBuilder used for object-oriented construction of query functions.
130
+     * This producer method is intended for convenient inline usage. Example:
131
+     *
132
+     * <code>
133
+     *     $qb = $conn->getQueryBuilder()
134
+     *         ->select('u')
135
+     *         ->from('users', 'u')
136
+     *         ->where($qb->fun()->md5('u.id'));
137
+     * </code>
138
+     *
139
+     * For more complex function construction, consider storing the function
140
+     * builder object in a local variable.
141
+     *
142
+     * @return \OCP\DB\QueryBuilder\IFunctionBuilder
143
+     */
144
+    public function func() {
145
+        if ($this->connection instanceof OracleConnection) {
146
+            return new OCIFunctionBuilder($this->helper);
147
+        } else if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) {
148
+            return new SqliteFunctionBuilder($this->helper);
149
+        } else if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) {
150
+            return new PgSqlFunctionBuilder($this->helper);
151
+        } else {
152
+            return new FunctionBuilder($this->helper);
153
+        }
154
+    }
155
+
156
+    /**
157
+     * Gets the type of the currently built query.
158
+     *
159
+     * @return integer
160
+     */
161
+    public function getType() {
162
+        return $this->queryBuilder->getType();
163
+    }
164
+
165
+    /**
166
+     * Gets the associated DBAL Connection for this query builder.
167
+     *
168
+     * @return \OCP\IDBConnection
169
+     */
170
+    public function getConnection() {
171
+        return $this->connection;
172
+    }
173
+
174
+    /**
175
+     * Gets the state of this query builder instance.
176
+     *
177
+     * @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
178
+     */
179
+    public function getState() {
180
+        return $this->queryBuilder->getState();
181
+    }
182
+
183
+    /**
184
+     * Executes this query using the bound parameters and their types.
185
+     *
186
+     * Uses {@see Connection::executeQuery} for select statements and {@see Connection::executeUpdate}
187
+     * for insert, update and delete statements.
188
+     *
189
+     * @return \Doctrine\DBAL\Driver\Statement|int
190
+     */
191
+    public function execute() {
192
+        if ($this->systemConfig->getValue('log_query', false)) {
193
+            $params = [];
194
+            foreach ($this->getParameters() as $placeholder => $value) {
195
+                if (is_array($value)) {
196
+                    $params[] = $placeholder . ' => (\'' . implode('\', \'', $value) . '\')';
197
+                } else {
198
+                    $params[] = $placeholder . ' => \'' . $value . '\'';
199
+                }
200
+            }
201
+            if (empty($params)) {
202
+                $this->logger->debug('DB QueryBuilder: \'{query}\'', [
203
+                    'query' => $this->getSQL(),
204
+                    'app' => 'core',
205
+                ]);
206
+            } else {
207
+                $this->logger->debug('DB QueryBuilder: \'{query}\' with parameters: {params}', [
208
+                    'query' => $this->getSQL(),
209
+                    'params' => implode(', ', $params),
210
+                    'app' => 'core',
211
+                ]);
212
+            }
213
+        }
214
+
215
+        return $this->queryBuilder->execute();
216
+    }
217
+
218
+    /**
219
+     * Gets the complete SQL string formed by the current specifications of this QueryBuilder.
220
+     *
221
+     * <code>
222
+     *     $qb = $conn->getQueryBuilder()
223
+     *         ->select('u')
224
+     *         ->from('User', 'u')
225
+     *     echo $qb->getSQL(); // SELECT u FROM User u
226
+     * </code>
227
+     *
228
+     * @return string The SQL query string.
229
+     */
230
+    public function getSQL() {
231
+        return $this->queryBuilder->getSQL();
232
+    }
233
+
234
+    /**
235
+     * Sets a query parameter for the query being constructed.
236
+     *
237
+     * <code>
238
+     *     $qb = $conn->getQueryBuilder()
239
+     *         ->select('u')
240
+     *         ->from('users', 'u')
241
+     *         ->where('u.id = :user_id')
242
+     *         ->setParameter(':user_id', 1);
243
+     * </code>
244
+     *
245
+     * @param string|integer $key The parameter position or name.
246
+     * @param mixed $value The parameter value.
247
+     * @param string|null|int $type One of the IQueryBuilder::PARAM_* constants.
248
+     *
249
+     * @return $this This QueryBuilder instance.
250
+     */
251
+    public function setParameter($key, $value, $type = null) {
252
+        $this->queryBuilder->setParameter($key, $value, $type);
253
+
254
+        return $this;
255
+    }
256
+
257
+    /**
258
+     * Sets a collection of query parameters for the query being constructed.
259
+     *
260
+     * <code>
261
+     *     $qb = $conn->getQueryBuilder()
262
+     *         ->select('u')
263
+     *         ->from('users', 'u')
264
+     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
265
+     *         ->setParameters(array(
266
+     *             ':user_id1' => 1,
267
+     *             ':user_id2' => 2
268
+     *         ));
269
+     * </code>
270
+     *
271
+     * @param array $params The query parameters to set.
272
+     * @param array $types The query parameters types to set.
273
+     *
274
+     * @return $this This QueryBuilder instance.
275
+     */
276
+    public function setParameters(array $params, array $types = []) {
277
+        $this->queryBuilder->setParameters($params, $types);
278
+
279
+        return $this;
280
+    }
281
+
282
+    /**
283
+     * Gets all defined query parameters for the query being constructed indexed by parameter index or name.
284
+     *
285
+     * @return array The currently defined query parameters indexed by parameter index or name.
286
+     */
287
+    public function getParameters() {
288
+        return $this->queryBuilder->getParameters();
289
+    }
290
+
291
+    /**
292
+     * Gets a (previously set) query parameter of the query being constructed.
293
+     *
294
+     * @param mixed $key The key (index or name) of the bound parameter.
295
+     *
296
+     * @return mixed The value of the bound parameter.
297
+     */
298
+    public function getParameter($key) {
299
+        return $this->queryBuilder->getParameter($key);
300
+    }
301
+
302
+    /**
303
+     * Gets all defined query parameter types for the query being constructed indexed by parameter index or name.
304
+     *
305
+     * @return array The currently defined query parameter types indexed by parameter index or name.
306
+     */
307
+    public function getParameterTypes() {
308
+        return $this->queryBuilder->getParameterTypes();
309
+    }
310
+
311
+    /**
312
+     * Gets a (previously set) query parameter type of the query being constructed.
313
+     *
314
+     * @param mixed $key The key (index or name) of the bound parameter type.
315
+     *
316
+     * @return mixed The value of the bound parameter type.
317
+     */
318
+    public function getParameterType($key) {
319
+        return $this->queryBuilder->getParameterType($key);
320
+    }
321
+
322
+    /**
323
+     * Sets the position of the first result to retrieve (the "offset").
324
+     *
325
+     * @param integer $firstResult The first result to return.
326
+     *
327
+     * @return $this This QueryBuilder instance.
328
+     */
329
+    public function setFirstResult($firstResult) {
330
+        $this->queryBuilder->setFirstResult($firstResult);
331
+
332
+        return $this;
333
+    }
334
+
335
+    /**
336
+     * Gets the position of the first result the query object was set to retrieve (the "offset").
337
+     * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
338
+     *
339
+     * @return integer The position of the first result.
340
+     */
341
+    public function getFirstResult() {
342
+        return $this->queryBuilder->getFirstResult();
343
+    }
344
+
345
+    /**
346
+     * Sets the maximum number of results to retrieve (the "limit").
347
+     *
348
+     * NOTE: Setting max results to "0" will cause mixed behaviour. While most
349
+     * of the databases will just return an empty result set, Oracle will return
350
+     * all entries.
351
+     *
352
+     * @param integer $maxResults The maximum number of results to retrieve.
353
+     *
354
+     * @return $this This QueryBuilder instance.
355
+     */
356
+    public function setMaxResults($maxResults) {
357
+        $this->queryBuilder->setMaxResults($maxResults);
358
+
359
+        return $this;
360
+    }
361
+
362
+    /**
363
+     * Gets the maximum number of results the query object was set to retrieve (the "limit").
364
+     * Returns NULL if {@link setMaxResults} was not applied to this query builder.
365
+     *
366
+     * @return integer The maximum number of results.
367
+     */
368
+    public function getMaxResults() {
369
+        return $this->queryBuilder->getMaxResults();
370
+    }
371
+
372
+    /**
373
+     * Specifies an item that is to be returned in the query result.
374
+     * Replaces any previously specified selections, if any.
375
+     *
376
+     * <code>
377
+     *     $qb = $conn->getQueryBuilder()
378
+     *         ->select('u.id', 'p.id')
379
+     *         ->from('users', 'u')
380
+     *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
381
+     * </code>
382
+     *
383
+     * @param mixed ...$selects The selection expressions.
384
+     *
385
+     * '@return $this This QueryBuilder instance.
386
+     */
387
+    public function select(...$selects) {
388
+        if (count($selects) === 1 && is_array($selects[0])) {
389
+            $selects = $selects[0];
390
+        }
391
+
392
+        $this->queryBuilder->select(
393
+            $this->helper->quoteColumnNames($selects)
394
+        );
395
+
396
+        return $this;
397
+    }
398
+
399
+    /**
400
+     * Specifies an item that is to be returned with a different name in the query result.
401
+     *
402
+     * <code>
403
+     *     $qb = $conn->getQueryBuilder()
404
+     *         ->selectAlias('u.id', 'user_id')
405
+     *         ->from('users', 'u')
406
+     *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
407
+     * </code>
408
+     *
409
+     * @param mixed $select The selection expressions.
410
+     * @param string $alias The column alias used in the constructed query.
411
+     *
412
+     * @return $this This QueryBuilder instance.
413
+     */
414
+    public function selectAlias($select, $alias) {
415
+
416
+        $this->queryBuilder->addSelect(
417
+            $this->helper->quoteColumnName($select) . ' AS ' . $this->helper->quoteColumnName($alias)
418
+        );
419
+
420
+        return $this;
421
+    }
422
+
423
+    /**
424
+     * Specifies an item that is to be returned uniquely in the query result.
425
+     *
426
+     * <code>
427
+     *     $qb = $conn->getQueryBuilder()
428
+     *         ->selectDistinct('type')
429
+     *         ->from('users');
430
+     * </code>
431
+     *
432
+     * @param mixed $select The selection expressions.
433
+     *
434
+     * @return $this This QueryBuilder instance.
435
+     */
436
+    public function selectDistinct($select) {
437
+
438
+        $this->queryBuilder->addSelect(
439
+            'DISTINCT ' . $this->helper->quoteColumnName($select)
440
+        );
441
+
442
+        return $this;
443
+    }
444
+
445
+    /**
446
+     * Adds an item that is to be returned in the query result.
447
+     *
448
+     * <code>
449
+     *     $qb = $conn->getQueryBuilder()
450
+     *         ->select('u.id')
451
+     *         ->addSelect('p.id')
452
+     *         ->from('users', 'u')
453
+     *         ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id');
454
+     * </code>
455
+     *
456
+     * @param mixed ...$selects The selection expression.
457
+     *
458
+     * @return $this This QueryBuilder instance.
459
+     */
460
+    public function addSelect(...$selects) {
461
+        if (count($selects) === 1 && is_array($selects[0])) {
462
+            $selects = $selects[0];
463
+        }
464
+
465
+        $this->queryBuilder->addSelect(
466
+            $this->helper->quoteColumnNames($selects)
467
+        );
468
+
469
+        return $this;
470
+    }
471
+
472
+    /**
473
+     * Turns the query being built into a bulk delete query that ranges over
474
+     * a certain table.
475
+     *
476
+     * <code>
477
+     *     $qb = $conn->getQueryBuilder()
478
+     *         ->delete('users', 'u')
479
+     *         ->where('u.id = :user_id');
480
+     *         ->setParameter(':user_id', 1);
481
+     * </code>
482
+     *
483
+     * @param string $delete The table whose rows are subject to the deletion.
484
+     * @param string $alias The table alias used in the constructed query.
485
+     *
486
+     * @return $this This QueryBuilder instance.
487
+     */
488
+    public function delete($delete = null, $alias = null) {
489
+        $this->queryBuilder->delete(
490
+            $this->getTableName($delete),
491
+            $alias
492
+        );
493
+
494
+        return $this;
495
+    }
496
+
497
+    /**
498
+     * Turns the query being built into a bulk update query that ranges over
499
+     * a certain table
500
+     *
501
+     * <code>
502
+     *     $qb = $conn->getQueryBuilder()
503
+     *         ->update('users', 'u')
504
+     *         ->set('u.password', md5('password'))
505
+     *         ->where('u.id = ?');
506
+     * </code>
507
+     *
508
+     * @param string $update The table whose rows are subject to the update.
509
+     * @param string $alias The table alias used in the constructed query.
510
+     *
511
+     * @return $this This QueryBuilder instance.
512
+     */
513
+    public function update($update = null, $alias = null) {
514
+        $this->queryBuilder->update(
515
+            $this->getTableName($update),
516
+            $alias
517
+        );
518
+
519
+        return $this;
520
+    }
521
+
522
+    /**
523
+     * Turns the query being built into an insert query that inserts into
524
+     * a certain table
525
+     *
526
+     * <code>
527
+     *     $qb = $conn->getQueryBuilder()
528
+     *         ->insert('users')
529
+     *         ->values(
530
+     *             array(
531
+     *                 'name' => '?',
532
+     *                 'password' => '?'
533
+     *             )
534
+     *         );
535
+     * </code>
536
+     *
537
+     * @param string $insert The table into which the rows should be inserted.
538
+     *
539
+     * @return $this This QueryBuilder instance.
540
+     */
541
+    public function insert($insert = null) {
542
+        $this->queryBuilder->insert(
543
+            $this->getTableName($insert)
544
+        );
545
+
546
+        $this->lastInsertedTable = $insert;
547
+
548
+        return $this;
549
+    }
550
+
551
+    /**
552
+     * Creates and adds a query root corresponding to the table identified by the
553
+     * given alias, forming a cartesian product with any existing query roots.
554
+     *
555
+     * <code>
556
+     *     $qb = $conn->getQueryBuilder()
557
+     *         ->select('u.id')
558
+     *         ->from('users', 'u')
559
+     * </code>
560
+     *
561
+     * @param string $from The table.
562
+     * @param string|null $alias The alias of the table.
563
+     *
564
+     * @return $this This QueryBuilder instance.
565
+     */
566
+    public function from($from, $alias = null) {
567
+        $this->queryBuilder->from(
568
+            $this->getTableName($from),
569
+            $this->quoteAlias($alias)
570
+        );
571
+
572
+        return $this;
573
+    }
574
+
575
+    /**
576
+     * Creates and adds a join to the query.
577
+     *
578
+     * <code>
579
+     *     $qb = $conn->getQueryBuilder()
580
+     *         ->select('u.name')
581
+     *         ->from('users', 'u')
582
+     *         ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1');
583
+     * </code>
584
+     *
585
+     * @param string $fromAlias The alias that points to a from clause.
586
+     * @param string $join The table name to join.
587
+     * @param string $alias The alias of the join table.
588
+     * @param string $condition The condition for the join.
589
+     *
590
+     * @return $this This QueryBuilder instance.
591
+     */
592
+    public function join($fromAlias, $join, $alias, $condition = null) {
593
+        $this->queryBuilder->join(
594
+            $this->quoteAlias($fromAlias),
595
+            $this->getTableName($join),
596
+            $this->quoteAlias($alias),
597
+            $condition
598
+        );
599
+
600
+        return $this;
601
+    }
602
+
603
+    /**
604
+     * Creates and adds a join to the query.
605
+     *
606
+     * <code>
607
+     *     $qb = $conn->getQueryBuilder()
608
+     *         ->select('u.name')
609
+     *         ->from('users', 'u')
610
+     *         ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
611
+     * </code>
612
+     *
613
+     * @param string $fromAlias The alias that points to a from clause.
614
+     * @param string $join The table name to join.
615
+     * @param string $alias The alias of the join table.
616
+     * @param string $condition The condition for the join.
617
+     *
618
+     * @return $this This QueryBuilder instance.
619
+     */
620
+    public function innerJoin($fromAlias, $join, $alias, $condition = null) {
621
+        $this->queryBuilder->innerJoin(
622
+            $this->quoteAlias($fromAlias),
623
+            $this->getTableName($join),
624
+            $this->quoteAlias($alias),
625
+            $condition
626
+        );
627
+
628
+        return $this;
629
+    }
630
+
631
+    /**
632
+     * Creates and adds a left join to the query.
633
+     *
634
+     * <code>
635
+     *     $qb = $conn->getQueryBuilder()
636
+     *         ->select('u.name')
637
+     *         ->from('users', 'u')
638
+     *         ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
639
+     * </code>
640
+     *
641
+     * @param string $fromAlias The alias that points to a from clause.
642
+     * @param string $join The table name to join.
643
+     * @param string $alias The alias of the join table.
644
+     * @param string $condition The condition for the join.
645
+     *
646
+     * @return $this This QueryBuilder instance.
647
+     */
648
+    public function leftJoin($fromAlias, $join, $alias, $condition = null) {
649
+        $this->queryBuilder->leftJoin(
650
+            $this->quoteAlias($fromAlias),
651
+            $this->getTableName($join),
652
+            $this->quoteAlias($alias),
653
+            $condition
654
+        );
655
+
656
+        return $this;
657
+    }
658
+
659
+    /**
660
+     * Creates and adds a right join to the query.
661
+     *
662
+     * <code>
663
+     *     $qb = $conn->getQueryBuilder()
664
+     *         ->select('u.name')
665
+     *         ->from('users', 'u')
666
+     *         ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
667
+     * </code>
668
+     *
669
+     * @param string $fromAlias The alias that points to a from clause.
670
+     * @param string $join The table name to join.
671
+     * @param string $alias The alias of the join table.
672
+     * @param string $condition The condition for the join.
673
+     *
674
+     * @return $this This QueryBuilder instance.
675
+     */
676
+    public function rightJoin($fromAlias, $join, $alias, $condition = null) {
677
+        $this->queryBuilder->rightJoin(
678
+            $this->quoteAlias($fromAlias),
679
+            $this->getTableName($join),
680
+            $this->quoteAlias($alias),
681
+            $condition
682
+        );
683
+
684
+        return $this;
685
+    }
686
+
687
+    /**
688
+     * Sets a new value for a column in a bulk update query.
689
+     *
690
+     * <code>
691
+     *     $qb = $conn->getQueryBuilder()
692
+     *         ->update('users', 'u')
693
+     *         ->set('u.password', md5('password'))
694
+     *         ->where('u.id = ?');
695
+     * </code>
696
+     *
697
+     * @param string $key The column to set.
698
+     * @param string $value The value, expression, placeholder, etc.
699
+     *
700
+     * @return $this This QueryBuilder instance.
701
+     */
702
+    public function set($key, $value) {
703
+        $this->queryBuilder->set(
704
+            $this->helper->quoteColumnName($key),
705
+            $this->helper->quoteColumnName($value)
706
+        );
707
+
708
+        return $this;
709
+    }
710
+
711
+    /**
712
+     * Specifies one or more restrictions to the query result.
713
+     * Replaces any previously specified restrictions, if any.
714
+     *
715
+     * <code>
716
+     *     $qb = $conn->getQueryBuilder()
717
+     *         ->select('u.name')
718
+     *         ->from('users', 'u')
719
+     *         ->where('u.id = ?');
720
+     *
721
+     *     // You can optionally programatically build and/or expressions
722
+     *     $qb = $conn->getQueryBuilder();
723
+     *
724
+     *     $or = $qb->expr()->orx();
725
+     *     $or->add($qb->expr()->eq('u.id', 1));
726
+     *     $or->add($qb->expr()->eq('u.id', 2));
727
+     *
728
+     *     $qb->update('users', 'u')
729
+     *         ->set('u.password', md5('password'))
730
+     *         ->where($or);
731
+     * </code>
732
+     *
733
+     * @param mixed ...$predicates The restriction predicates.
734
+     *
735
+     * @return $this This QueryBuilder instance.
736
+     */
737
+    public function where(...$predicates) {
738
+        call_user_func_array(
739
+            [$this->queryBuilder, 'where'],
740
+            $predicates
741
+        );
742
+
743
+        return $this;
744
+    }
745
+
746
+    /**
747
+     * Adds one or more restrictions to the query results, forming a logical
748
+     * conjunction with any previously specified restrictions.
749
+     *
750
+     * <code>
751
+     *     $qb = $conn->getQueryBuilder()
752
+     *         ->select('u')
753
+     *         ->from('users', 'u')
754
+     *         ->where('u.username LIKE ?')
755
+     *         ->andWhere('u.is_active = 1');
756
+     * </code>
757
+     *
758
+     * @param mixed ...$where The query restrictions.
759
+     *
760
+     * @return $this This QueryBuilder instance.
761
+     *
762
+     * @see where()
763
+     */
764
+    public function andWhere(...$where) {
765
+        call_user_func_array(
766
+            [$this->queryBuilder, 'andWhere'],
767
+            $where
768
+        );
769
+
770
+        return $this;
771
+    }
772
+
773
+    /**
774
+     * Adds one or more restrictions to the query results, forming a logical
775
+     * disjunction with any previously specified restrictions.
776
+     *
777
+     * <code>
778
+     *     $qb = $conn->getQueryBuilder()
779
+     *         ->select('u.name')
780
+     *         ->from('users', 'u')
781
+     *         ->where('u.id = 1')
782
+     *         ->orWhere('u.id = 2');
783
+     * </code>
784
+     *
785
+     * @param mixed ...$where The WHERE statement.
786
+     *
787
+     * @return $this This QueryBuilder instance.
788
+     *
789
+     * @see where()
790
+     */
791
+    public function orWhere(...$where) {
792
+        call_user_func_array(
793
+            [$this->queryBuilder, 'orWhere'],
794
+            $where
795
+        );
796
+
797
+        return $this;
798
+    }
799
+
800
+    /**
801
+     * Specifies a grouping over the results of the query.
802
+     * Replaces any previously specified groupings, if any.
803
+     *
804
+     * <code>
805
+     *     $qb = $conn->getQueryBuilder()
806
+     *         ->select('u.name')
807
+     *         ->from('users', 'u')
808
+     *         ->groupBy('u.id');
809
+     * </code>
810
+     *
811
+     * @param mixed ...$groupBys The grouping expression.
812
+     *
813
+     * @return $this This QueryBuilder instance.
814
+     */
815
+    public function groupBy(...$groupBys) {
816
+        if (count($groupBys) === 1 && is_array($groupBys[0])) {
817
+            $groupBys = $groupBys[0];
818
+        }
819
+
820
+        call_user_func_array(
821
+            [$this->queryBuilder, 'groupBy'],
822
+            $this->helper->quoteColumnNames($groupBys)
823
+        );
824
+
825
+        return $this;
826
+    }
827
+
828
+    /**
829
+     * Adds a grouping expression to the query.
830
+     *
831
+     * <code>
832
+     *     $qb = $conn->getQueryBuilder()
833
+     *         ->select('u.name')
834
+     *         ->from('users', 'u')
835
+     *         ->groupBy('u.lastLogin');
836
+     *         ->addGroupBy('u.createdAt')
837
+     * </code>
838
+     *
839
+     * @param mixed ...$groupBy The grouping expression.
840
+     *
841
+     * @return $this This QueryBuilder instance.
842
+     */
843
+    public function addGroupBy(...$groupBys) {
844
+        if (count($groupBys) === 1 && is_array($groupBys[0])) {
845
+            $$groupBys = $groupBys[0];
846
+        }
847
+
848
+        call_user_func_array(
849
+            [$this->queryBuilder, 'addGroupBy'],
850
+            $this->helper->quoteColumnNames($groupBys)
851
+        );
852
+
853
+        return $this;
854
+    }
855
+
856
+    /**
857
+     * Sets a value for a column in an insert query.
858
+     *
859
+     * <code>
860
+     *     $qb = $conn->getQueryBuilder()
861
+     *         ->insert('users')
862
+     *         ->values(
863
+     *             array(
864
+     *                 'name' => '?'
865
+     *             )
866
+     *         )
867
+     *         ->setValue('password', '?');
868
+     * </code>
869
+     *
870
+     * @param string $column The column into which the value should be inserted.
871
+     * @param string $value The value that should be inserted into the column.
872
+     *
873
+     * @return $this This QueryBuilder instance.
874
+     */
875
+    public function setValue($column, $value) {
876
+        $this->queryBuilder->setValue(
877
+            $this->helper->quoteColumnName($column),
878
+            $value
879
+        );
880
+
881
+        return $this;
882
+    }
883
+
884
+    /**
885
+     * Specifies values for an insert query indexed by column names.
886
+     * Replaces any previous values, if any.
887
+     *
888
+     * <code>
889
+     *     $qb = $conn->getQueryBuilder()
890
+     *         ->insert('users')
891
+     *         ->values(
892
+     *             array(
893
+     *                 'name' => '?',
894
+     *                 'password' => '?'
895
+     *             )
896
+     *         );
897
+     * </code>
898
+     *
899
+     * @param array $values The values to specify for the insert query indexed by column names.
900
+     *
901
+     * @return $this This QueryBuilder instance.
902
+     */
903
+    public function values(array $values) {
904
+        $quotedValues = [];
905
+        foreach ($values as $key => $value) {
906
+            $quotedValues[$this->helper->quoteColumnName($key)] = $value;
907
+        }
908
+
909
+        $this->queryBuilder->values($quotedValues);
910
+
911
+        return $this;
912
+    }
913
+
914
+    /**
915
+     * Specifies a restriction over the groups of the query.
916
+     * Replaces any previous having restrictions, if any.
917
+     *
918
+     * @param mixed ...$having The restriction over the groups.
919
+     *
920
+     * @return $this This QueryBuilder instance.
921
+     */
922
+    public function having(...$having) {
923
+        call_user_func_array(
924
+            [$this->queryBuilder, 'having'],
925
+            $having
926
+        );
927
+
928
+        return $this;
929
+    }
930
+
931
+    /**
932
+     * Adds a restriction over the groups of the query, forming a logical
933
+     * conjunction with any existing having restrictions.
934
+     *
935
+     * @param mixed ...$having The restriction to append.
936
+     *
937
+     * @return $this This QueryBuilder instance.
938
+     */
939
+    public function andHaving(...$having) {
940
+        call_user_func_array(
941
+            [$this->queryBuilder, 'andHaving'],
942
+            $having
943
+        );
944
+
945
+        return $this;
946
+    }
947
+
948
+    /**
949
+     * Adds a restriction over the groups of the query, forming a logical
950
+     * disjunction with any existing having restrictions.
951
+     *
952
+     * @param mixed ...$having The restriction to add.
953
+     *
954
+     * @return $this This QueryBuilder instance.
955
+     */
956
+    public function orHaving(...$having) {
957
+        call_user_func_array(
958
+            [$this->queryBuilder, 'orHaving'],
959
+            $having
960
+        );
961
+
962
+        return $this;
963
+    }
964
+
965
+    /**
966
+     * Specifies an ordering for the query results.
967
+     * Replaces any previously specified orderings, if any.
968
+     *
969
+     * @param string $sort The ordering expression.
970
+     * @param string $order The ordering direction.
971
+     *
972
+     * @return $this This QueryBuilder instance.
973
+     */
974
+    public function orderBy($sort, $order = null) {
975
+        $this->queryBuilder->orderBy(
976
+            $this->helper->quoteColumnName($sort),
977
+            $order
978
+        );
979
+
980
+        return $this;
981
+    }
982
+
983
+    /**
984
+     * Adds an ordering to the query results.
985
+     *
986
+     * @param string $sort The ordering expression.
987
+     * @param string $order The ordering direction.
988
+     *
989
+     * @return $this This QueryBuilder instance.
990
+     */
991
+    public function addOrderBy($sort, $order = null) {
992
+        $this->queryBuilder->addOrderBy(
993
+            $this->helper->quoteColumnName($sort),
994
+            $order
995
+        );
996
+
997
+        return $this;
998
+    }
999
+
1000
+    /**
1001
+     * Gets a query part by its name.
1002
+     *
1003
+     * @param string $queryPartName
1004
+     *
1005
+     * @return mixed
1006
+     */
1007
+    public function getQueryPart($queryPartName) {
1008
+        return $this->queryBuilder->getQueryPart($queryPartName);
1009
+    }
1010
+
1011
+    /**
1012
+     * Gets all query parts.
1013
+     *
1014
+     * @return array
1015
+     */
1016
+    public function getQueryParts() {
1017
+        return $this->queryBuilder->getQueryParts();
1018
+    }
1019
+
1020
+    /**
1021
+     * Resets SQL parts.
1022
+     *
1023
+     * @param array|null $queryPartNames
1024
+     *
1025
+     * @return $this This QueryBuilder instance.
1026
+     */
1027
+    public function resetQueryParts($queryPartNames = null) {
1028
+        $this->queryBuilder->resetQueryParts($queryPartNames);
1029
+
1030
+        return $this;
1031
+    }
1032
+
1033
+    /**
1034
+     * Resets a single SQL part.
1035
+     *
1036
+     * @param string $queryPartName
1037
+     *
1038
+     * @return $this This QueryBuilder instance.
1039
+     */
1040
+    public function resetQueryPart($queryPartName) {
1041
+        $this->queryBuilder->resetQueryPart($queryPartName);
1042
+
1043
+        return $this;
1044
+    }
1045
+
1046
+    /**
1047
+     * Creates a new named parameter and bind the value $value to it.
1048
+     *
1049
+     * This method provides a shortcut for PDOStatement::bindValue
1050
+     * when using prepared statements.
1051
+     *
1052
+     * The parameter $value specifies the value that you want to bind. If
1053
+     * $placeholder is not provided bindValue() will automatically create a
1054
+     * placeholder for you. An automatic placeholder will be of the name
1055
+     * ':dcValue1', ':dcValue2' etc.
1056
+     *
1057
+     * For more information see {@link http://php.net/pdostatement-bindparam}
1058
+     *
1059
+     * Example:
1060
+     * <code>
1061
+     * $value = 2;
1062
+     * $q->eq( 'id', $q->bindValue( $value ) );
1063
+     * $stmt = $q->executeQuery(); // executed with 'id = 2'
1064
+     * </code>
1065
+     *
1066
+     * @license New BSD License
1067
+     * @link http://www.zetacomponents.org
1068
+     *
1069
+     * @param mixed $value
1070
+     * @param mixed $type
1071
+     * @param string $placeHolder The name to bind with. The string must start with a colon ':'.
1072
+     *
1073
+     * @return IParameter the placeholder name used.
1074
+     */
1075
+    public function createNamedParameter($value, $type = IQueryBuilder::PARAM_STR, $placeHolder = null) {
1076
+        return new Parameter($this->queryBuilder->createNamedParameter($value, $type, $placeHolder));
1077
+    }
1078
+
1079
+    /**
1080
+     * Creates a new positional parameter and bind the given value to it.
1081
+     *
1082
+     * Attention: If you are using positional parameters with the query builder you have
1083
+     * to be very careful to bind all parameters in the order they appear in the SQL
1084
+     * statement , otherwise they get bound in the wrong order which can lead to serious
1085
+     * bugs in your code.
1086
+     *
1087
+     * Example:
1088
+     * <code>
1089
+     *  $qb = $conn->getQueryBuilder();
1090
+     *  $qb->select('u.*')
1091
+     *     ->from('users', 'u')
1092
+     *     ->where('u.username = ' . $qb->createPositionalParameter('Foo', IQueryBuilder::PARAM_STR))
1093
+     *     ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', IQueryBuilder::PARAM_STR))
1094
+     * </code>
1095
+     *
1096
+     * @param mixed $value
1097
+     * @param integer $type
1098
+     *
1099
+     * @return IParameter
1100
+     */
1101
+    public function createPositionalParameter($value, $type = IQueryBuilder::PARAM_STR) {
1102
+        return new Parameter($this->queryBuilder->createPositionalParameter($value, $type));
1103
+    }
1104
+
1105
+    /**
1106
+     * Creates a new parameter
1107
+     *
1108
+     * Example:
1109
+     * <code>
1110
+     *  $qb = $conn->getQueryBuilder();
1111
+     *  $qb->select('u.*')
1112
+     *     ->from('users', 'u')
1113
+     *     ->where('u.username = ' . $qb->createParameter('name'))
1114
+     *     ->setParameter('name', 'Bar', IQueryBuilder::PARAM_STR))
1115
+     * </code>
1116
+     *
1117
+     * @param string $name
1118
+     *
1119
+     * @return IParameter
1120
+     */
1121
+    public function createParameter($name) {
1122
+        return new Parameter(':' . $name);
1123
+    }
1124
+
1125
+    /**
1126
+     * Creates a new function
1127
+     *
1128
+     * Attention: Column names inside the call have to be quoted before hand
1129
+     *
1130
+     * Example:
1131
+     * <code>
1132
+     *  $qb = $conn->getQueryBuilder();
1133
+     *  $qb->select($qb->createFunction('COUNT(*)'))
1134
+     *     ->from('users', 'u')
1135
+     *  echo $qb->getSQL(); // SELECT COUNT(*) FROM `users` u
1136
+     * </code>
1137
+     * <code>
1138
+     *  $qb = $conn->getQueryBuilder();
1139
+     *  $qb->select($qb->createFunction('COUNT(`column`)'))
1140
+     *     ->from('users', 'u')
1141
+     *  echo $qb->getSQL(); // SELECT COUNT(`column`) FROM `users` u
1142
+     * </code>
1143
+     *
1144
+     * @param string $call
1145
+     *
1146
+     * @return IQueryFunction
1147
+     */
1148
+    public function createFunction($call) {
1149
+        return new QueryFunction($call);
1150
+    }
1151
+
1152
+    /**
1153
+     * Used to get the id of the last inserted element
1154
+     * @return int
1155
+     * @throws \BadMethodCallException When being called before an insert query has been run.
1156
+     */
1157
+    public function getLastInsertId() {
1158
+        if ($this->getType() === \Doctrine\DBAL\Query\QueryBuilder::INSERT && $this->lastInsertedTable) {
1159
+            // lastInsertId() needs the prefix but no quotes
1160
+            $table = $this->prefixTableName($this->lastInsertedTable);
1161
+            return (int) $this->connection->lastInsertId($table);
1162
+        }
1163
+
1164
+        throw new \BadMethodCallException('Invalid call to getLastInsertId without using insert() before.');
1165
+    }
1166
+
1167
+    /**
1168
+     * Returns the table name quoted and with database prefix as needed by the implementation
1169
+     *
1170
+     * @param string $table
1171
+     * @return string
1172
+     */
1173
+    public function getTableName($table) {
1174
+        if ($table instanceof IQueryFunction) {
1175
+            return (string) $table;
1176
+        }
1177
+
1178
+        $table = $this->prefixTableName($table);
1179
+        return $this->helper->quoteColumnName($table);
1180
+    }
1181
+
1182
+    /**
1183
+     * Returns the table name with database prefix as needed by the implementation
1184
+     *
1185
+     * @param string $table
1186
+     * @return string
1187
+     */
1188
+    protected function prefixTableName($table) {
1189
+        if ($this->automaticTablePrefix === false || strpos($table, '*PREFIX*') === 0) {
1190
+            return $table;
1191
+        }
1192
+
1193
+        return '*PREFIX*' . $table;
1194
+    }
1195
+
1196
+    /**
1197
+     * Returns the column name quoted and with table alias prefix as needed by the implementation
1198
+     *
1199
+     * @param string $column
1200
+     * @param string $tableAlias
1201
+     * @return string
1202
+     */
1203
+    public function getColumnName($column, $tableAlias = '') {
1204
+        if ($tableAlias !== '') {
1205
+            $tableAlias .= '.';
1206
+        }
1207
+
1208
+        return $this->helper->quoteColumnName($tableAlias . $column);
1209
+    }
1210
+
1211
+    /**
1212
+     * Returns the column name quoted and with table alias prefix as needed by the implementation
1213
+     *
1214
+     * @param string $alias
1215
+     * @return string
1216
+     */
1217
+    public function quoteAlias($alias) {
1218
+        if ($alias === '' || $alias === null) {
1219
+            return $alias;
1220
+        }
1221
+
1222
+        return $this->helper->quoteColumnName($alias);
1223
+    }
1224 1224
 }
Please login to merge, or discard this patch.
lib/private/DB/PgSqlTools.php 1 patch
Indentation   +32 added lines, -32 removed lines patch added patch discarded remove patch
@@ -31,41 +31,41 @@
 block discarded – undo
31 31
 */
32 32
 class PgSqlTools {
33 33
 
34
-	/** @var \OCP\IConfig */
35
-	private $config;
34
+    /** @var \OCP\IConfig */
35
+    private $config;
36 36
 
37
-	/**
38
-	 * @param \OCP\IConfig $config
39
-	 */
40
-	public function __construct(IConfig $config) {
41
-		$this->config = $config;
42
-	}
37
+    /**
38
+     * @param \OCP\IConfig $config
39
+     */
40
+    public function __construct(IConfig $config) {
41
+        $this->config = $config;
42
+    }
43 43
 
44
-	/**
45
-	* @brief Resynchronizes all sequences of a database after using INSERTs
46
-	*        without leaving out the auto-incremented column.
47
-	* @param \OC\DB\Connection $conn
48
-	* @return null
49
-	*/
50
-	public function resynchronizeDatabaseSequences(Connection $conn) {
51
-		$filterExpression = '/^' . preg_quote($this->config->getSystemValue('dbtableprefix', 'oc_')) . '/';
52
-		$databaseName = $conn->getDatabase();
53
-		$conn->getConfiguration()->setFilterSchemaAssetsExpression($filterExpression);
44
+    /**
45
+     * @brief Resynchronizes all sequences of a database after using INSERTs
46
+     *        without leaving out the auto-incremented column.
47
+     * @param \OC\DB\Connection $conn
48
+     * @return null
49
+     */
50
+    public function resynchronizeDatabaseSequences(Connection $conn) {
51
+        $filterExpression = '/^' . preg_quote($this->config->getSystemValue('dbtableprefix', 'oc_')) . '/';
52
+        $databaseName = $conn->getDatabase();
53
+        $conn->getConfiguration()->setFilterSchemaAssetsExpression($filterExpression);
54 54
 
55
-		foreach ($conn->getSchemaManager()->listSequences() as $sequence) {
56
-			$sequenceName = $sequence->getName();
57
-			$sqlInfo = 'SELECT table_schema, table_name, column_name
55
+        foreach ($conn->getSchemaManager()->listSequences() as $sequence) {
56
+            $sequenceName = $sequence->getName();
57
+            $sqlInfo = 'SELECT table_schema, table_name, column_name
58 58
 				FROM information_schema.columns
59 59
 				WHERE column_default = ? AND table_catalog = ?';
60
-			$sequenceInfo = $conn->fetchAssoc($sqlInfo, [
61
-				"nextval('$sequenceName'::regclass)",
62
-				$databaseName
63
-			]);
64
-			$tableName = $sequenceInfo['table_name'];
65
-			$columnName = $sequenceInfo['column_name'];
66
-			$sqlMaxId = "SELECT MAX($columnName) FROM $tableName";
67
-			$sqlSetval = "SELECT setval('$sequenceName', ($sqlMaxId))";
68
-			$conn->executeQuery($sqlSetval);
69
-		}
70
-	}
60
+            $sequenceInfo = $conn->fetchAssoc($sqlInfo, [
61
+                "nextval('$sequenceName'::regclass)",
62
+                $databaseName
63
+            ]);
64
+            $tableName = $sequenceInfo['table_name'];
65
+            $columnName = $sequenceInfo['column_name'];
66
+            $sqlMaxId = "SELECT MAX($columnName) FROM $tableName";
67
+            $sqlSetval = "SELECT setval('$sequenceName', ($sqlMaxId))";
68
+            $conn->executeQuery($sqlSetval);
69
+        }
70
+    }
71 71
 }
Please login to merge, or discard this patch.
lib/private/DB/Migrator.php 1 patch
Indentation   +268 added lines, -268 removed lines patch added patch discarded remove patch
@@ -46,272 +46,272 @@
 block discarded – undo
46 46
 
47 47
 class Migrator {
48 48
 
49
-	/** @var \Doctrine\DBAL\Connection */
50
-	protected $connection;
51
-
52
-	/** @var ISecureRandom */
53
-	private $random;
54
-
55
-	/** @var IConfig */
56
-	protected $config;
57
-
58
-	/** @var EventDispatcherInterface  */
59
-	private $dispatcher;
60
-
61
-	/** @var bool */
62
-	private $noEmit = false;
63
-
64
-	/**
65
-	 * @param \Doctrine\DBAL\Connection $connection
66
-	 * @param ISecureRandom $random
67
-	 * @param IConfig $config
68
-	 * @param EventDispatcherInterface $dispatcher
69
-	 */
70
-	public function __construct(\Doctrine\DBAL\Connection $connection,
71
-								ISecureRandom $random,
72
-								IConfig $config,
73
-								EventDispatcherInterface $dispatcher = null) {
74
-		$this->connection = $connection;
75
-		$this->random = $random;
76
-		$this->config = $config;
77
-		$this->dispatcher = $dispatcher;
78
-	}
79
-
80
-	/**
81
-	 * @param \Doctrine\DBAL\Schema\Schema $targetSchema
82
-	 */
83
-	public function migrate(Schema $targetSchema) {
84
-		$this->noEmit = true;
85
-		$this->applySchema($targetSchema);
86
-	}
87
-
88
-	/**
89
-	 * @param \Doctrine\DBAL\Schema\Schema $targetSchema
90
-	 * @return string
91
-	 */
92
-	public function generateChangeScript(Schema $targetSchema) {
93
-		$schemaDiff = $this->getDiff($targetSchema, $this->connection);
94
-
95
-		$script = '';
96
-		$sqls = $schemaDiff->toSql($this->connection->getDatabasePlatform());
97
-		foreach ($sqls as $sql) {
98
-			$script .= $this->convertStatementToScript($sql);
99
-		}
100
-
101
-		return $script;
102
-	}
103
-
104
-	/**
105
-	 * @param Schema $targetSchema
106
-	 * @throws \OC\DB\MigrationException
107
-	 */
108
-	public function checkMigrate(Schema $targetSchema) {
109
-		$this->noEmit = true;
110
-		/**@var \Doctrine\DBAL\Schema\Table[] $tables */
111
-		$tables = $targetSchema->getTables();
112
-		$filterExpression = $this->getFilterExpression();
113
-		$this->connection->getConfiguration()->
114
-			setFilterSchemaAssetsExpression($filterExpression);
115
-		$existingTables = $this->connection->getSchemaManager()->listTableNames();
116
-
117
-		$step = 0;
118
-		foreach ($tables as $table) {
119
-			if (strpos($table->getName(), '.')) {
120
-				list(, $tableName) = explode('.', $table->getName());
121
-			} else {
122
-				$tableName = $table->getName();
123
-			}
124
-			$this->emitCheckStep($tableName, $step++, count($tables));
125
-			// don't need to check for new tables
126
-			if (array_search($tableName, $existingTables) !== false) {
127
-				$this->checkTableMigrate($table);
128
-			}
129
-		}
130
-	}
131
-
132
-	/**
133
-	 * Create a unique name for the temporary table
134
-	 *
135
-	 * @param string $name
136
-	 * @return string
137
-	 */
138
-	protected function generateTemporaryTableName($name) {
139
-		return $this->config->getSystemValue('dbtableprefix', 'oc_') . $name . '_' . $this->random->generate(13, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS);
140
-	}
141
-
142
-	/**
143
-	 * Check the migration of a table on a copy so we can detect errors before messing with the real table
144
-	 *
145
-	 * @param \Doctrine\DBAL\Schema\Table $table
146
-	 * @throws \OC\DB\MigrationException
147
-	 */
148
-	protected function checkTableMigrate(Table $table) {
149
-		$name = $table->getName();
150
-		$tmpName = $this->generateTemporaryTableName($name);
151
-
152
-		$this->copyTable($name, $tmpName);
153
-
154
-		//create the migration schema for the temporary table
155
-		$tmpTable = $this->renameTableSchema($table, $tmpName);
156
-		$schemaConfig = new SchemaConfig();
157
-		$schemaConfig->setName($this->connection->getDatabase());
158
-		$schema = new Schema([$tmpTable], [], $schemaConfig);
159
-
160
-		try {
161
-			$this->applySchema($schema);
162
-			$this->dropTable($tmpName);
163
-		} catch (DBALException $e) {
164
-			// pgsql needs to commit it's failed transaction before doing anything else
165
-			if ($this->connection->isTransactionActive()) {
166
-				$this->connection->commit();
167
-			}
168
-			$this->dropTable($tmpName);
169
-			throw new MigrationException($table->getName(), $e->getMessage());
170
-		}
171
-	}
172
-
173
-	/**
174
-	 * @param \Doctrine\DBAL\Schema\Table $table
175
-	 * @param string $newName
176
-	 * @return \Doctrine\DBAL\Schema\Table
177
-	 */
178
-	protected function renameTableSchema(Table $table, $newName) {
179
-		/**
180
-		 * @var \Doctrine\DBAL\Schema\Index[] $indexes
181
-		 */
182
-		$indexes = $table->getIndexes();
183
-		$newIndexes = [];
184
-		foreach ($indexes as $index) {
185
-			if ($index->isPrimary()) {
186
-				// do not rename primary key
187
-				$indexName = $index->getName();
188
-			} else {
189
-				// avoid conflicts in index names
190
-				$indexName = $this->config->getSystemValue('dbtableprefix', 'oc_') . $this->random->generate(13, ISecureRandom::CHAR_LOWER);
191
-			}
192
-			$newIndexes[] = new Index($indexName, $index->getColumns(), $index->isUnique(), $index->isPrimary());
193
-		}
194
-
195
-		// foreign keys are not supported so we just set it to an empty array
196
-		return new Table($newName, $table->getColumns(), $newIndexes, [], 0, $table->getOptions());
197
-	}
198
-
199
-	public function createSchema() {
200
-		$filterExpression = $this->getFilterExpression();
201
-		$this->connection->getConfiguration()->setFilterSchemaAssetsExpression($filterExpression);
202
-		return $this->connection->getSchemaManager()->createSchema();
203
-	}
204
-
205
-	/**
206
-	 * @param Schema $targetSchema
207
-	 * @param \Doctrine\DBAL\Connection $connection
208
-	 * @return \Doctrine\DBAL\Schema\SchemaDiff
209
-	 * @throws DBALException
210
-	 */
211
-	protected function getDiff(Schema $targetSchema, \Doctrine\DBAL\Connection $connection) {
212
-		// adjust varchar columns with a length higher then getVarcharMaxLength to clob
213
-		foreach ($targetSchema->getTables() as $table) {
214
-			foreach ($table->getColumns() as $column) {
215
-				if ($column->getType() instanceof StringType) {
216
-					if ($column->getLength() > $connection->getDatabasePlatform()->getVarcharMaxLength()) {
217
-						$column->setType(Type::getType('text'));
218
-						$column->setLength(null);
219
-					}
220
-				}
221
-			}
222
-		}
223
-
224
-		$filterExpression = $this->getFilterExpression();
225
-		$this->connection->getConfiguration()->setFilterSchemaAssetsExpression($filterExpression);
226
-		$sourceSchema = $connection->getSchemaManager()->createSchema();
227
-
228
-		// remove tables we don't know about
229
-		/** @var $table \Doctrine\DBAL\Schema\Table */
230
-		foreach ($sourceSchema->getTables() as $table) {
231
-			if (!$targetSchema->hasTable($table->getName())) {
232
-				$sourceSchema->dropTable($table->getName());
233
-			}
234
-		}
235
-		// remove sequences we don't know about
236
-		foreach ($sourceSchema->getSequences() as $table) {
237
-			if (!$targetSchema->hasSequence($table->getName())) {
238
-				$sourceSchema->dropSequence($table->getName());
239
-			}
240
-		}
241
-
242
-		$comparator = new Comparator();
243
-		return $comparator->compare($sourceSchema, $targetSchema);
244
-	}
245
-
246
-	/**
247
-	 * @param \Doctrine\DBAL\Schema\Schema $targetSchema
248
-	 * @param \Doctrine\DBAL\Connection $connection
249
-	 */
250
-	protected function applySchema(Schema $targetSchema, \Doctrine\DBAL\Connection $connection = null) {
251
-		if (is_null($connection)) {
252
-			$connection = $this->connection;
253
-		}
254
-
255
-		$schemaDiff = $this->getDiff($targetSchema, $connection);
256
-
257
-		$connection->beginTransaction();
258
-		$sqls = $schemaDiff->toSql($connection->getDatabasePlatform());
259
-		$step = 0;
260
-		foreach ($sqls as $sql) {
261
-			$this->emit($sql, $step++, count($sqls));
262
-			$connection->query($sql);
263
-		}
264
-		$connection->commit();
265
-	}
266
-
267
-	/**
268
-	 * @param string $sourceName
269
-	 * @param string $targetName
270
-	 */
271
-	protected function copyTable($sourceName, $targetName) {
272
-		$quotedSource = $this->connection->quoteIdentifier($sourceName);
273
-		$quotedTarget = $this->connection->quoteIdentifier($targetName);
274
-
275
-		$this->connection->exec('CREATE TABLE ' . $quotedTarget . ' (LIKE ' . $quotedSource . ')');
276
-		$this->connection->exec('INSERT INTO ' . $quotedTarget . ' SELECT * FROM ' . $quotedSource);
277
-	}
278
-
279
-	/**
280
-	 * @param string $name
281
-	 */
282
-	protected function dropTable($name) {
283
-		$this->connection->exec('DROP TABLE ' . $this->connection->quoteIdentifier($name));
284
-	}
285
-
286
-	/**
287
-	 * @param $statement
288
-	 * @return string
289
-	 */
290
-	protected function convertStatementToScript($statement) {
291
-		$script = $statement . ';';
292
-		$script .= PHP_EOL;
293
-		$script .= PHP_EOL;
294
-		return $script;
295
-	}
296
-
297
-	protected function getFilterExpression() {
298
-		return '/^' . preg_quote($this->config->getSystemValue('dbtableprefix', 'oc_')) . '/';
299
-	}
300
-
301
-	protected function emit($sql, $step, $max) {
302
-		if ($this->noEmit) {
303
-			return;
304
-		}
305
-		if(is_null($this->dispatcher)) {
306
-			return;
307
-		}
308
-		$this->dispatcher->dispatch('\OC\DB\Migrator::executeSql', new GenericEvent($sql, [$step+1, $max]));
309
-	}
310
-
311
-	private function emitCheckStep($tableName, $step, $max) {
312
-		if(is_null($this->dispatcher)) {
313
-			return;
314
-		}
315
-		$this->dispatcher->dispatch('\OC\DB\Migrator::checkTable', new GenericEvent($tableName, [$step+1, $max]));
316
-	}
49
+    /** @var \Doctrine\DBAL\Connection */
50
+    protected $connection;
51
+
52
+    /** @var ISecureRandom */
53
+    private $random;
54
+
55
+    /** @var IConfig */
56
+    protected $config;
57
+
58
+    /** @var EventDispatcherInterface  */
59
+    private $dispatcher;
60
+
61
+    /** @var bool */
62
+    private $noEmit = false;
63
+
64
+    /**
65
+     * @param \Doctrine\DBAL\Connection $connection
66
+     * @param ISecureRandom $random
67
+     * @param IConfig $config
68
+     * @param EventDispatcherInterface $dispatcher
69
+     */
70
+    public function __construct(\Doctrine\DBAL\Connection $connection,
71
+                                ISecureRandom $random,
72
+                                IConfig $config,
73
+                                EventDispatcherInterface $dispatcher = null) {
74
+        $this->connection = $connection;
75
+        $this->random = $random;
76
+        $this->config = $config;
77
+        $this->dispatcher = $dispatcher;
78
+    }
79
+
80
+    /**
81
+     * @param \Doctrine\DBAL\Schema\Schema $targetSchema
82
+     */
83
+    public function migrate(Schema $targetSchema) {
84
+        $this->noEmit = true;
85
+        $this->applySchema($targetSchema);
86
+    }
87
+
88
+    /**
89
+     * @param \Doctrine\DBAL\Schema\Schema $targetSchema
90
+     * @return string
91
+     */
92
+    public function generateChangeScript(Schema $targetSchema) {
93
+        $schemaDiff = $this->getDiff($targetSchema, $this->connection);
94
+
95
+        $script = '';
96
+        $sqls = $schemaDiff->toSql($this->connection->getDatabasePlatform());
97
+        foreach ($sqls as $sql) {
98
+            $script .= $this->convertStatementToScript($sql);
99
+        }
100
+
101
+        return $script;
102
+    }
103
+
104
+    /**
105
+     * @param Schema $targetSchema
106
+     * @throws \OC\DB\MigrationException
107
+     */
108
+    public function checkMigrate(Schema $targetSchema) {
109
+        $this->noEmit = true;
110
+        /**@var \Doctrine\DBAL\Schema\Table[] $tables */
111
+        $tables = $targetSchema->getTables();
112
+        $filterExpression = $this->getFilterExpression();
113
+        $this->connection->getConfiguration()->
114
+            setFilterSchemaAssetsExpression($filterExpression);
115
+        $existingTables = $this->connection->getSchemaManager()->listTableNames();
116
+
117
+        $step = 0;
118
+        foreach ($tables as $table) {
119
+            if (strpos($table->getName(), '.')) {
120
+                list(, $tableName) = explode('.', $table->getName());
121
+            } else {
122
+                $tableName = $table->getName();
123
+            }
124
+            $this->emitCheckStep($tableName, $step++, count($tables));
125
+            // don't need to check for new tables
126
+            if (array_search($tableName, $existingTables) !== false) {
127
+                $this->checkTableMigrate($table);
128
+            }
129
+        }
130
+    }
131
+
132
+    /**
133
+     * Create a unique name for the temporary table
134
+     *
135
+     * @param string $name
136
+     * @return string
137
+     */
138
+    protected function generateTemporaryTableName($name) {
139
+        return $this->config->getSystemValue('dbtableprefix', 'oc_') . $name . '_' . $this->random->generate(13, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS);
140
+    }
141
+
142
+    /**
143
+     * Check the migration of a table on a copy so we can detect errors before messing with the real table
144
+     *
145
+     * @param \Doctrine\DBAL\Schema\Table $table
146
+     * @throws \OC\DB\MigrationException
147
+     */
148
+    protected function checkTableMigrate(Table $table) {
149
+        $name = $table->getName();
150
+        $tmpName = $this->generateTemporaryTableName($name);
151
+
152
+        $this->copyTable($name, $tmpName);
153
+
154
+        //create the migration schema for the temporary table
155
+        $tmpTable = $this->renameTableSchema($table, $tmpName);
156
+        $schemaConfig = new SchemaConfig();
157
+        $schemaConfig->setName($this->connection->getDatabase());
158
+        $schema = new Schema([$tmpTable], [], $schemaConfig);
159
+
160
+        try {
161
+            $this->applySchema($schema);
162
+            $this->dropTable($tmpName);
163
+        } catch (DBALException $e) {
164
+            // pgsql needs to commit it's failed transaction before doing anything else
165
+            if ($this->connection->isTransactionActive()) {
166
+                $this->connection->commit();
167
+            }
168
+            $this->dropTable($tmpName);
169
+            throw new MigrationException($table->getName(), $e->getMessage());
170
+        }
171
+    }
172
+
173
+    /**
174
+     * @param \Doctrine\DBAL\Schema\Table $table
175
+     * @param string $newName
176
+     * @return \Doctrine\DBAL\Schema\Table
177
+     */
178
+    protected function renameTableSchema(Table $table, $newName) {
179
+        /**
180
+         * @var \Doctrine\DBAL\Schema\Index[] $indexes
181
+         */
182
+        $indexes = $table->getIndexes();
183
+        $newIndexes = [];
184
+        foreach ($indexes as $index) {
185
+            if ($index->isPrimary()) {
186
+                // do not rename primary key
187
+                $indexName = $index->getName();
188
+            } else {
189
+                // avoid conflicts in index names
190
+                $indexName = $this->config->getSystemValue('dbtableprefix', 'oc_') . $this->random->generate(13, ISecureRandom::CHAR_LOWER);
191
+            }
192
+            $newIndexes[] = new Index($indexName, $index->getColumns(), $index->isUnique(), $index->isPrimary());
193
+        }
194
+
195
+        // foreign keys are not supported so we just set it to an empty array
196
+        return new Table($newName, $table->getColumns(), $newIndexes, [], 0, $table->getOptions());
197
+    }
198
+
199
+    public function createSchema() {
200
+        $filterExpression = $this->getFilterExpression();
201
+        $this->connection->getConfiguration()->setFilterSchemaAssetsExpression($filterExpression);
202
+        return $this->connection->getSchemaManager()->createSchema();
203
+    }
204
+
205
+    /**
206
+     * @param Schema $targetSchema
207
+     * @param \Doctrine\DBAL\Connection $connection
208
+     * @return \Doctrine\DBAL\Schema\SchemaDiff
209
+     * @throws DBALException
210
+     */
211
+    protected function getDiff(Schema $targetSchema, \Doctrine\DBAL\Connection $connection) {
212
+        // adjust varchar columns with a length higher then getVarcharMaxLength to clob
213
+        foreach ($targetSchema->getTables() as $table) {
214
+            foreach ($table->getColumns() as $column) {
215
+                if ($column->getType() instanceof StringType) {
216
+                    if ($column->getLength() > $connection->getDatabasePlatform()->getVarcharMaxLength()) {
217
+                        $column->setType(Type::getType('text'));
218
+                        $column->setLength(null);
219
+                    }
220
+                }
221
+            }
222
+        }
223
+
224
+        $filterExpression = $this->getFilterExpression();
225
+        $this->connection->getConfiguration()->setFilterSchemaAssetsExpression($filterExpression);
226
+        $sourceSchema = $connection->getSchemaManager()->createSchema();
227
+
228
+        // remove tables we don't know about
229
+        /** @var $table \Doctrine\DBAL\Schema\Table */
230
+        foreach ($sourceSchema->getTables() as $table) {
231
+            if (!$targetSchema->hasTable($table->getName())) {
232
+                $sourceSchema->dropTable($table->getName());
233
+            }
234
+        }
235
+        // remove sequences we don't know about
236
+        foreach ($sourceSchema->getSequences() as $table) {
237
+            if (!$targetSchema->hasSequence($table->getName())) {
238
+                $sourceSchema->dropSequence($table->getName());
239
+            }
240
+        }
241
+
242
+        $comparator = new Comparator();
243
+        return $comparator->compare($sourceSchema, $targetSchema);
244
+    }
245
+
246
+    /**
247
+     * @param \Doctrine\DBAL\Schema\Schema $targetSchema
248
+     * @param \Doctrine\DBAL\Connection $connection
249
+     */
250
+    protected function applySchema(Schema $targetSchema, \Doctrine\DBAL\Connection $connection = null) {
251
+        if (is_null($connection)) {
252
+            $connection = $this->connection;
253
+        }
254
+
255
+        $schemaDiff = $this->getDiff($targetSchema, $connection);
256
+
257
+        $connection->beginTransaction();
258
+        $sqls = $schemaDiff->toSql($connection->getDatabasePlatform());
259
+        $step = 0;
260
+        foreach ($sqls as $sql) {
261
+            $this->emit($sql, $step++, count($sqls));
262
+            $connection->query($sql);
263
+        }
264
+        $connection->commit();
265
+    }
266
+
267
+    /**
268
+     * @param string $sourceName
269
+     * @param string $targetName
270
+     */
271
+    protected function copyTable($sourceName, $targetName) {
272
+        $quotedSource = $this->connection->quoteIdentifier($sourceName);
273
+        $quotedTarget = $this->connection->quoteIdentifier($targetName);
274
+
275
+        $this->connection->exec('CREATE TABLE ' . $quotedTarget . ' (LIKE ' . $quotedSource . ')');
276
+        $this->connection->exec('INSERT INTO ' . $quotedTarget . ' SELECT * FROM ' . $quotedSource);
277
+    }
278
+
279
+    /**
280
+     * @param string $name
281
+     */
282
+    protected function dropTable($name) {
283
+        $this->connection->exec('DROP TABLE ' . $this->connection->quoteIdentifier($name));
284
+    }
285
+
286
+    /**
287
+     * @param $statement
288
+     * @return string
289
+     */
290
+    protected function convertStatementToScript($statement) {
291
+        $script = $statement . ';';
292
+        $script .= PHP_EOL;
293
+        $script .= PHP_EOL;
294
+        return $script;
295
+    }
296
+
297
+    protected function getFilterExpression() {
298
+        return '/^' . preg_quote($this->config->getSystemValue('dbtableprefix', 'oc_')) . '/';
299
+    }
300
+
301
+    protected function emit($sql, $step, $max) {
302
+        if ($this->noEmit) {
303
+            return;
304
+        }
305
+        if(is_null($this->dispatcher)) {
306
+            return;
307
+        }
308
+        $this->dispatcher->dispatch('\OC\DB\Migrator::executeSql', new GenericEvent($sql, [$step+1, $max]));
309
+    }
310
+
311
+    private function emitCheckStep($tableName, $step, $max) {
312
+        if(is_null($this->dispatcher)) {
313
+            return;
314
+        }
315
+        $this->dispatcher->dispatch('\OC\DB\Migrator::checkTable', new GenericEvent($tableName, [$step+1, $max]));
316
+    }
317 317
 }
Please login to merge, or discard this patch.
lib/private/L10N/Factory.php 1 patch
Indentation   +597 added lines, -597 removed lines patch added patch discarded remove patch
@@ -48,602 +48,602 @@
 block discarded – undo
48 48
  */
49 49
 class Factory implements IFactory {
50 50
 
51
-	/** @var string */
52
-	protected $requestLanguage = '';
53
-
54
-	/**
55
-	 * cached instances
56
-	 * @var array Structure: Lang => App => \OCP\IL10N
57
-	 */
58
-	protected $instances = [];
59
-
60
-	/**
61
-	 * @var array Structure: App => string[]
62
-	 */
63
-	protected $availableLanguages = [];
64
-
65
-	/**
66
-	 * @var array
67
-	 */
68
-	protected $availableLocales = [];
69
-
70
-	/**
71
-	 * @var array Structure: string => callable
72
-	 */
73
-	protected $pluralFunctions = [];
74
-
75
-	const COMMON_LANGUAGE_CODES = [
76
-		'en', 'es', 'fr', 'de', 'de_DE', 'ja', 'ar', 'ru', 'nl', 'it',
77
-		'pt_BR', 'pt_PT', 'da', 'fi_FI', 'nb_NO', 'sv', 'tr', 'zh_CN', 'ko'
78
-	];
79
-
80
-	/** @var IConfig */
81
-	protected $config;
82
-
83
-	/** @var IRequest */
84
-	protected $request;
85
-
86
-	/** @var IUserSession */
87
-	protected $userSession;
88
-
89
-	/** @var string */
90
-	protected $serverRoot;
91
-
92
-	/**
93
-	 * @param IConfig $config
94
-	 * @param IRequest $request
95
-	 * @param IUserSession $userSession
96
-	 * @param string $serverRoot
97
-	 */
98
-	public function __construct(IConfig $config,
99
-								IRequest $request,
100
-								IUserSession $userSession,
101
-								$serverRoot) {
102
-		$this->config = $config;
103
-		$this->request = $request;
104
-		$this->userSession = $userSession;
105
-		$this->serverRoot = $serverRoot;
106
-	}
107
-
108
-	/**
109
-	 * Get a language instance
110
-	 *
111
-	 * @param string $app
112
-	 * @param string|null $lang
113
-	 * @param string|null $locale
114
-	 * @return \OCP\IL10N
115
-	 */
116
-	public function get($app, $lang = null, $locale = null) {
117
-		return new LazyL10N(function() use ($app, $lang, $locale) {
118
-
119
-			$app = \OC_App::cleanAppId($app);
120
-			if ($lang !== null) {
121
-				$lang = str_replace(['\0', '/', '\\', '..'], '', (string)$lang);
122
-			}
123
-
124
-			$forceLang = $this->config->getSystemValue('force_language', false);
125
-			if (is_string($forceLang)) {
126
-				$lang = $forceLang;
127
-			}
128
-
129
-			$forceLocale = $this->config->getSystemValue('force_locale', false);
130
-			if (is_string($forceLocale)) {
131
-				$locale = $forceLocale;
132
-			}
133
-
134
-			if ($lang === null || !$this->languageExists($app, $lang)) {
135
-				$lang = $this->findLanguage($app);
136
-			}
137
-
138
-			if ($locale === null || !$this->localeExists($locale)) {
139
-				$locale = $this->findLocale($lang);
140
-			}
141
-
142
-			if (!isset($this->instances[$lang][$app])) {
143
-				$this->instances[$lang][$app] = new L10N(
144
-					$this, $app, $lang, $locale,
145
-					$this->getL10nFilesForApp($app, $lang)
146
-				);
147
-			}
148
-
149
-			return $this->instances[$lang][$app];
150
-		});
151
-	}
152
-
153
-	/**
154
-	 * Find the best language
155
-	 *
156
-	 * @param string|null $app App id or null for core
157
-	 * @return string language If nothing works it returns 'en'
158
-	 */
159
-	public function findLanguage($app = null) {
160
-		$forceLang = $this->config->getSystemValue('force_language', false);
161
-		if (is_string($forceLang)) {
162
-			$this->requestLanguage = $forceLang;
163
-		}
164
-
165
-		if ($this->requestLanguage !== '' && $this->languageExists($app, $this->requestLanguage)) {
166
-			return $this->requestLanguage;
167
-		}
168
-
169
-		/**
170
-		 * At this point Nextcloud might not yet be installed and thus the lookup
171
-		 * in the preferences table might fail. For this reason we need to check
172
-		 * whether the instance has already been installed
173
-		 *
174
-		 * @link https://github.com/owncloud/core/issues/21955
175
-		 */
176
-		if ($this->config->getSystemValue('installed', false)) {
177
-			$userId = !is_null($this->userSession->getUser()) ? $this->userSession->getUser()->getUID() :  null;
178
-			if (!is_null($userId)) {
179
-				$userLang = $this->config->getUserValue($userId, 'core', 'lang', null);
180
-			} else {
181
-				$userLang = null;
182
-			}
183
-		} else {
184
-			$userId = null;
185
-			$userLang = null;
186
-		}
187
-
188
-		if ($userLang) {
189
-			$this->requestLanguage = $userLang;
190
-			if ($this->languageExists($app, $userLang)) {
191
-				return $userLang;
192
-			}
193
-		}
194
-
195
-		try {
196
-			// Try to get the language from the Request
197
-			$lang = $this->getLanguageFromRequest($app);
198
-			if ($userId !== null && $app === null && !$userLang) {
199
-				$this->config->setUserValue($userId, 'core', 'lang', $lang);
200
-			}
201
-			return $lang;
202
-		} catch (LanguageNotFoundException $e) {
203
-			// Finding language from request failed fall back to default language
204
-			$defaultLanguage = $this->config->getSystemValue('default_language', false);
205
-			if ($defaultLanguage !== false && $this->languageExists($app, $defaultLanguage)) {
206
-				return $defaultLanguage;
207
-			}
208
-		}
209
-
210
-		// We could not find any language so fall back to english
211
-		return 'en';
212
-	}
213
-
214
-	/**
215
-	 * find the best locale
216
-	 *
217
-	 * @param string $lang
218
-	 * @return null|string
219
-	 */
220
-	public function findLocale($lang = null) {
221
-		$forceLocale = $this->config->getSystemValue('force_locale', false);
222
-		if (is_string($forceLocale) && $this->localeExists($forceLocale)) {
223
-			return $forceLocale;
224
-		}
225
-
226
-		if ($this->config->getSystemValue('installed', false)) {
227
-			$userId = null !== $this->userSession->getUser() ? $this->userSession->getUser()->getUID() :  null;
228
-			$userLocale = null;
229
-			if (null !== $userId) {
230
-				$userLocale = $this->config->getUserValue($userId, 'core', 'locale', null);
231
-			}
232
-		} else {
233
-			$userId = null;
234
-			$userLocale = null;
235
-		}
236
-
237
-		if ($userLocale && $this->localeExists($userLocale)) {
238
-			return $userLocale;
239
-		}
240
-
241
-		// Default : use system default locale
242
-		$defaultLocale = $this->config->getSystemValue('default_locale', false);
243
-		if ($defaultLocale !== false && $this->localeExists($defaultLocale)) {
244
-			return $defaultLocale;
245
-		}
246
-
247
-		// If no user locale set, use lang as locale
248
-		if (null !== $lang && $this->localeExists($lang)) {
249
-			return $lang;
250
-		}
251
-
252
-		// At last, return USA
253
-		return 'en_US';
254
-	}
255
-
256
-	/**
257
-	 * find the matching lang from the locale
258
-	 *
259
-	 * @param string $app
260
-	 * @param string $locale
261
-	 * @return null|string
262
-	 */
263
-	public function findLanguageFromLocale(string $app = 'core', string $locale = null) {
264
-		if ($this->languageExists($app, $locale)) {
265
-			return $locale;
266
-		}
51
+    /** @var string */
52
+    protected $requestLanguage = '';
53
+
54
+    /**
55
+     * cached instances
56
+     * @var array Structure: Lang => App => \OCP\IL10N
57
+     */
58
+    protected $instances = [];
59
+
60
+    /**
61
+     * @var array Structure: App => string[]
62
+     */
63
+    protected $availableLanguages = [];
64
+
65
+    /**
66
+     * @var array
67
+     */
68
+    protected $availableLocales = [];
69
+
70
+    /**
71
+     * @var array Structure: string => callable
72
+     */
73
+    protected $pluralFunctions = [];
74
+
75
+    const COMMON_LANGUAGE_CODES = [
76
+        'en', 'es', 'fr', 'de', 'de_DE', 'ja', 'ar', 'ru', 'nl', 'it',
77
+        'pt_BR', 'pt_PT', 'da', 'fi_FI', 'nb_NO', 'sv', 'tr', 'zh_CN', 'ko'
78
+    ];
79
+
80
+    /** @var IConfig */
81
+    protected $config;
82
+
83
+    /** @var IRequest */
84
+    protected $request;
85
+
86
+    /** @var IUserSession */
87
+    protected $userSession;
88
+
89
+    /** @var string */
90
+    protected $serverRoot;
91
+
92
+    /**
93
+     * @param IConfig $config
94
+     * @param IRequest $request
95
+     * @param IUserSession $userSession
96
+     * @param string $serverRoot
97
+     */
98
+    public function __construct(IConfig $config,
99
+                                IRequest $request,
100
+                                IUserSession $userSession,
101
+                                $serverRoot) {
102
+        $this->config = $config;
103
+        $this->request = $request;
104
+        $this->userSession = $userSession;
105
+        $this->serverRoot = $serverRoot;
106
+    }
107
+
108
+    /**
109
+     * Get a language instance
110
+     *
111
+     * @param string $app
112
+     * @param string|null $lang
113
+     * @param string|null $locale
114
+     * @return \OCP\IL10N
115
+     */
116
+    public function get($app, $lang = null, $locale = null) {
117
+        return new LazyL10N(function() use ($app, $lang, $locale) {
118
+
119
+            $app = \OC_App::cleanAppId($app);
120
+            if ($lang !== null) {
121
+                $lang = str_replace(['\0', '/', '\\', '..'], '', (string)$lang);
122
+            }
123
+
124
+            $forceLang = $this->config->getSystemValue('force_language', false);
125
+            if (is_string($forceLang)) {
126
+                $lang = $forceLang;
127
+            }
128
+
129
+            $forceLocale = $this->config->getSystemValue('force_locale', false);
130
+            if (is_string($forceLocale)) {
131
+                $locale = $forceLocale;
132
+            }
133
+
134
+            if ($lang === null || !$this->languageExists($app, $lang)) {
135
+                $lang = $this->findLanguage($app);
136
+            }
137
+
138
+            if ($locale === null || !$this->localeExists($locale)) {
139
+                $locale = $this->findLocale($lang);
140
+            }
141
+
142
+            if (!isset($this->instances[$lang][$app])) {
143
+                $this->instances[$lang][$app] = new L10N(
144
+                    $this, $app, $lang, $locale,
145
+                    $this->getL10nFilesForApp($app, $lang)
146
+                );
147
+            }
148
+
149
+            return $this->instances[$lang][$app];
150
+        });
151
+    }
152
+
153
+    /**
154
+     * Find the best language
155
+     *
156
+     * @param string|null $app App id or null for core
157
+     * @return string language If nothing works it returns 'en'
158
+     */
159
+    public function findLanguage($app = null) {
160
+        $forceLang = $this->config->getSystemValue('force_language', false);
161
+        if (is_string($forceLang)) {
162
+            $this->requestLanguage = $forceLang;
163
+        }
164
+
165
+        if ($this->requestLanguage !== '' && $this->languageExists($app, $this->requestLanguage)) {
166
+            return $this->requestLanguage;
167
+        }
168
+
169
+        /**
170
+         * At this point Nextcloud might not yet be installed and thus the lookup
171
+         * in the preferences table might fail. For this reason we need to check
172
+         * whether the instance has already been installed
173
+         *
174
+         * @link https://github.com/owncloud/core/issues/21955
175
+         */
176
+        if ($this->config->getSystemValue('installed', false)) {
177
+            $userId = !is_null($this->userSession->getUser()) ? $this->userSession->getUser()->getUID() :  null;
178
+            if (!is_null($userId)) {
179
+                $userLang = $this->config->getUserValue($userId, 'core', 'lang', null);
180
+            } else {
181
+                $userLang = null;
182
+            }
183
+        } else {
184
+            $userId = null;
185
+            $userLang = null;
186
+        }
187
+
188
+        if ($userLang) {
189
+            $this->requestLanguage = $userLang;
190
+            if ($this->languageExists($app, $userLang)) {
191
+                return $userLang;
192
+            }
193
+        }
194
+
195
+        try {
196
+            // Try to get the language from the Request
197
+            $lang = $this->getLanguageFromRequest($app);
198
+            if ($userId !== null && $app === null && !$userLang) {
199
+                $this->config->setUserValue($userId, 'core', 'lang', $lang);
200
+            }
201
+            return $lang;
202
+        } catch (LanguageNotFoundException $e) {
203
+            // Finding language from request failed fall back to default language
204
+            $defaultLanguage = $this->config->getSystemValue('default_language', false);
205
+            if ($defaultLanguage !== false && $this->languageExists($app, $defaultLanguage)) {
206
+                return $defaultLanguage;
207
+            }
208
+        }
209
+
210
+        // We could not find any language so fall back to english
211
+        return 'en';
212
+    }
213
+
214
+    /**
215
+     * find the best locale
216
+     *
217
+     * @param string $lang
218
+     * @return null|string
219
+     */
220
+    public function findLocale($lang = null) {
221
+        $forceLocale = $this->config->getSystemValue('force_locale', false);
222
+        if (is_string($forceLocale) && $this->localeExists($forceLocale)) {
223
+            return $forceLocale;
224
+        }
225
+
226
+        if ($this->config->getSystemValue('installed', false)) {
227
+            $userId = null !== $this->userSession->getUser() ? $this->userSession->getUser()->getUID() :  null;
228
+            $userLocale = null;
229
+            if (null !== $userId) {
230
+                $userLocale = $this->config->getUserValue($userId, 'core', 'locale', null);
231
+            }
232
+        } else {
233
+            $userId = null;
234
+            $userLocale = null;
235
+        }
236
+
237
+        if ($userLocale && $this->localeExists($userLocale)) {
238
+            return $userLocale;
239
+        }
240
+
241
+        // Default : use system default locale
242
+        $defaultLocale = $this->config->getSystemValue('default_locale', false);
243
+        if ($defaultLocale !== false && $this->localeExists($defaultLocale)) {
244
+            return $defaultLocale;
245
+        }
246
+
247
+        // If no user locale set, use lang as locale
248
+        if (null !== $lang && $this->localeExists($lang)) {
249
+            return $lang;
250
+        }
251
+
252
+        // At last, return USA
253
+        return 'en_US';
254
+    }
255
+
256
+    /**
257
+     * find the matching lang from the locale
258
+     *
259
+     * @param string $app
260
+     * @param string $locale
261
+     * @return null|string
262
+     */
263
+    public function findLanguageFromLocale(string $app = 'core', string $locale = null) {
264
+        if ($this->languageExists($app, $locale)) {
265
+            return $locale;
266
+        }
267 267
 		
268
-		// Try to split e.g: fr_FR => fr
269
-		$locale = explode('_', $locale)[0];
270
-		if ($this->languageExists($app, $locale)) {
271
-			return $locale;
272
-		}
273
-	}
274
-
275
-	/**
276
-	 * Find all available languages for an app
277
-	 *
278
-	 * @param string|null $app App id or null for core
279
-	 * @return array an array of available languages
280
-	 */
281
-	public function findAvailableLanguages($app = null) {
282
-		$key = $app;
283
-		if ($key === null) {
284
-			$key = 'null';
285
-		}
286
-
287
-		// also works with null as key
288
-		if (!empty($this->availableLanguages[$key])) {
289
-			return $this->availableLanguages[$key];
290
-		}
291
-
292
-		$available = ['en']; //english is always available
293
-		$dir = $this->findL10nDir($app);
294
-		if (is_dir($dir)) {
295
-			$files = scandir($dir);
296
-			if ($files !== false) {
297
-				foreach ($files as $file) {
298
-					if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') {
299
-						$available[] = substr($file, 0, -5);
300
-					}
301
-				}
302
-			}
303
-		}
304
-
305
-		// merge with translations from theme
306
-		$theme = $this->config->getSystemValue('theme');
307
-		if (!empty($theme)) {
308
-			$themeDir = $this->serverRoot . '/themes/' . $theme . substr($dir, strlen($this->serverRoot));
309
-
310
-			if (is_dir($themeDir)) {
311
-				$files = scandir($themeDir);
312
-				if ($files !== false) {
313
-					foreach ($files as $file) {
314
-						if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') {
315
-							$available[] = substr($file, 0, -5);
316
-						}
317
-					}
318
-				}
319
-			}
320
-		}
321
-
322
-		$this->availableLanguages[$key] = $available;
323
-		return $available;
324
-	}
325
-
326
-	/**
327
-	 * @return array|mixed
328
-	 */
329
-	public function findAvailableLocales() {
330
-		if (!empty($this->availableLocales)) {
331
-			return $this->availableLocales;
332
-		}
333
-
334
-		$localeData = file_get_contents(\OC::$SERVERROOT . '/resources/locales.json');
335
-		$this->availableLocales = \json_decode($localeData, true);
336
-
337
-		return $this->availableLocales;
338
-	}
339
-
340
-	/**
341
-	 * @param string|null $app App id or null for core
342
-	 * @param string $lang
343
-	 * @return bool
344
-	 */
345
-	public function languageExists($app, $lang) {
346
-		if ($lang === 'en') {//english is always available
347
-			return true;
348
-		}
349
-
350
-		$languages = $this->findAvailableLanguages($app);
351
-		return array_search($lang, $languages) !== false;
352
-	}
353
-
354
-	public function getLanguageIterator(IUser $user = null): ILanguageIterator {
355
-		$user = $user ?? $this->userSession->getUser();
356
-		if($user === null) {
357
-			throw new \RuntimeException('Failed to get an IUser instance');
358
-		}
359
-		return new LanguageIterator($user, $this->config);
360
-	}
361
-
362
-	/**
363
-	 * @param string $locale
364
-	 * @return bool
365
-	 */
366
-	public function localeExists($locale) {
367
-		if ($locale === 'en') { //english is always available
368
-			return true;
369
-		}
370
-
371
-		$locales = $this->findAvailableLocales();
372
-		$userLocale = array_filter($locales, function($value) use ($locale) {
373
-			return $locale === $value['code'];
374
-		});
375
-
376
-		return !empty($userLocale);
377
-	}
378
-
379
-	/**
380
-	 * @param string|null $app
381
-	 * @return string
382
-	 * @throws LanguageNotFoundException
383
-	 */
384
-	private function getLanguageFromRequest($app) {
385
-		$header = $this->request->getHeader('ACCEPT_LANGUAGE');
386
-		if ($header !== '') {
387
-			$available = $this->findAvailableLanguages($app);
388
-
389
-			// E.g. make sure that 'de' is before 'de_DE'.
390
-			sort($available);
391
-
392
-			$preferences = preg_split('/,\s*/', strtolower($header));
393
-			foreach ($preferences as $preference) {
394
-				list($preferred_language) = explode(';', $preference);
395
-				$preferred_language = str_replace('-', '_', $preferred_language);
396
-
397
-				foreach ($available as $available_language) {
398
-					if ($preferred_language === strtolower($available_language)) {
399
-						return $this->respectDefaultLanguage($app, $available_language);
400
-					}
401
-				}
402
-
403
-				// Fallback from de_De to de
404
-				foreach ($available as $available_language) {
405
-					if (substr($preferred_language, 0, 2) === $available_language) {
406
-						return $available_language;
407
-					}
408
-				}
409
-			}
410
-		}
411
-
412
-		throw new LanguageNotFoundException();
413
-	}
414
-
415
-	/**
416
-	 * if default language is set to de_DE (formal German) this should be
417
-	 * preferred to 'de' (non-formal German) if possible
418
-	 *
419
-	 * @param string|null $app
420
-	 * @param string $lang
421
-	 * @return string
422
-	 */
423
-	protected function respectDefaultLanguage($app, $lang) {
424
-		$result = $lang;
425
-		$defaultLanguage = $this->config->getSystemValue('default_language', false);
426
-
427
-		// use formal version of german ("Sie" instead of "Du") if the default
428
-		// language is set to 'de_DE' if possible
429
-		if (is_string($defaultLanguage) &&
430
-			strtolower($lang) === 'de' &&
431
-			strtolower($defaultLanguage) === 'de_de' &&
432
-			$this->languageExists($app, 'de_DE')
433
-		) {
434
-			$result = 'de_DE';
435
-		}
436
-
437
-		return $result;
438
-	}
439
-
440
-	/**
441
-	 * Checks if $sub is a subdirectory of $parent
442
-	 *
443
-	 * @param string $sub
444
-	 * @param string $parent
445
-	 * @return bool
446
-	 */
447
-	private function isSubDirectory($sub, $parent) {
448
-		// Check whether $sub contains no ".."
449
-		if (strpos($sub, '..') !== false) {
450
-			return false;
451
-		}
452
-
453
-		// Check whether $sub is a subdirectory of $parent
454
-		if (strpos($sub, $parent) === 0) {
455
-			return true;
456
-		}
457
-
458
-		return false;
459
-	}
460
-
461
-	/**
462
-	 * Get a list of language files that should be loaded
463
-	 *
464
-	 * @param string $app
465
-	 * @param string $lang
466
-	 * @return string[]
467
-	 */
468
-	// FIXME This method is only public, until OC_L10N does not need it anymore,
469
-	// FIXME This is also the reason, why it is not in the public interface
470
-	public function getL10nFilesForApp($app, $lang) {
471
-		$languageFiles = [];
472
-
473
-		$i18nDir = $this->findL10nDir($app);
474
-		$transFile = strip_tags($i18nDir) . strip_tags($lang) . '.json';
475
-
476
-		if (($this->isSubDirectory($transFile, $this->serverRoot . '/core/l10n/')
477
-				|| $this->isSubDirectory($transFile, $this->serverRoot . '/lib/l10n/')
478
-				|| $this->isSubDirectory($transFile, \OC_App::getAppPath($app) . '/l10n/')
479
-			)
480
-			&& file_exists($transFile)) {
481
-			// load the translations file
482
-			$languageFiles[] = $transFile;
483
-		}
484
-
485
-		// merge with translations from theme
486
-		$theme = $this->config->getSystemValue('theme');
487
-		if (!empty($theme)) {
488
-			$transFile = $this->serverRoot . '/themes/' . $theme . substr($transFile, strlen($this->serverRoot));
489
-			if (file_exists($transFile)) {
490
-				$languageFiles[] = $transFile;
491
-			}
492
-		}
493
-
494
-		return $languageFiles;
495
-	}
496
-
497
-	/**
498
-	 * find the l10n directory
499
-	 *
500
-	 * @param string $app App id or empty string for core
501
-	 * @return string directory
502
-	 */
503
-	protected function findL10nDir($app = null) {
504
-		if (in_array($app, ['core', 'lib'])) {
505
-			if (file_exists($this->serverRoot . '/' . $app . '/l10n/')) {
506
-				return $this->serverRoot . '/' . $app . '/l10n/';
507
-			}
508
-		} else if ($app && \OC_App::getAppPath($app) !== false) {
509
-			// Check if the app is in the app folder
510
-			return \OC_App::getAppPath($app) . '/l10n/';
511
-		}
512
-		return $this->serverRoot . '/core/l10n/';
513
-	}
514
-
515
-
516
-	/**
517
-	 * Creates a function from the plural string
518
-	 *
519
-	 * Parts of the code is copied from Habari:
520
-	 * https://github.com/habari/system/blob/master/classes/locale.php
521
-	 * @param string $string
522
-	 * @return string
523
-	 */
524
-	public function createPluralFunction($string) {
525
-		if (isset($this->pluralFunctions[$string])) {
526
-			return $this->pluralFunctions[$string];
527
-		}
528
-
529
-		if (preg_match( '/^\s*nplurals\s*=\s*(\d+)\s*;\s*plural=(.*)$/u', $string, $matches)) {
530
-			// sanitize
531
-			$nplurals = preg_replace( '/[^0-9]/', '', $matches[1] );
532
-			$plural = preg_replace( '#[^n0-9:\(\)\?\|\&=!<>+*/\%-]#', '', $matches[2] );
533
-
534
-			$body = str_replace(
535
-				[ 'plural', 'n', '$n$plurals', ],
536
-				[ '$plural', '$n', '$nplurals', ],
537
-				'nplurals='. $nplurals . '; plural=' . $plural
538
-			);
539
-
540
-			// add parents
541
-			// important since PHP's ternary evaluates from left to right
542
-			$body .= ';';
543
-			$res = '';
544
-			$p = 0;
545
-			$length = strlen($body);
546
-			for($i = 0; $i < $length; $i++) {
547
-				$ch = $body[$i];
548
-				switch ( $ch ) {
549
-					case '?':
550
-						$res .= ' ? (';
551
-						$p++;
552
-						break;
553
-					case ':':
554
-						$res .= ') : (';
555
-						break;
556
-					case ';':
557
-						$res .= str_repeat( ')', $p ) . ';';
558
-						$p = 0;
559
-						break;
560
-					default:
561
-						$res .= $ch;
562
-				}
563
-			}
564
-
565
-			$body = $res . 'return ($plural>=$nplurals?$nplurals-1:$plural);';
566
-			$function = create_function('$n', $body);
567
-			$this->pluralFunctions[$string] = $function;
568
-			return $function;
569
-		} else {
570
-			// default: one plural form for all cases but n==1 (english)
571
-			$function = create_function(
572
-				'$n',
573
-				'$nplurals=2;$plural=($n==1?0:1);return ($plural>=$nplurals?$nplurals-1:$plural);'
574
-			);
575
-			$this->pluralFunctions[$string] = $function;
576
-			return $function;
577
-		}
578
-	}
579
-
580
-	/**
581
-	 * returns the common language and other languages in an
582
-	 * associative array
583
-	 *
584
-	 * @return array
585
-	 */
586
-	public function getLanguages() {
587
-		$forceLanguage = $this->config->getSystemValue('force_language', false);
588
-		if ($forceLanguage !== false) {
589
-			return [];
590
-		}
591
-
592
-		$languageCodes = $this->findAvailableLanguages();
593
-
594
-		$commonLanguages = [];
595
-		$languages = [];
596
-
597
-		foreach($languageCodes as $lang) {
598
-			$l = $this->get('lib', $lang);
599
-			// TRANSLATORS this is the language name for the language switcher in the personal settings and should be the localized version
600
-			$potentialName = (string) $l->t('__language_name__');
601
-			if ($l->getLanguageCode() === $lang && $potentialName[0] !== '_') {//first check if the language name is in the translation file
602
-				$ln = [
603
-					'code' => $lang,
604
-					'name' => $potentialName
605
-				];
606
-			} else if ($lang === 'en') {
607
-				$ln = [
608
-					'code' => $lang,
609
-					'name' => 'English (US)'
610
-				];
611
-			} else {//fallback to language code
612
-				$ln = [
613
-					'code' => $lang,
614
-					'name' => $lang
615
-				];
616
-			}
617
-
618
-			// put appropriate languages into appropriate arrays, to print them sorted
619
-			// common languages -> divider -> other languages
620
-			if (in_array($lang, self::COMMON_LANGUAGE_CODES)) {
621
-				$commonLanguages[array_search($lang, self::COMMON_LANGUAGE_CODES)] = $ln;
622
-			} else {
623
-				$languages[] = $ln;
624
-			}
625
-		}
626
-
627
-		ksort($commonLanguages);
628
-
629
-		// sort now by displayed language not the iso-code
630
-		usort( $languages, function ($a, $b) {
631
-			if ($a['code'] === $a['name'] && $b['code'] !== $b['name']) {
632
-				// If a doesn't have a name, but b does, list b before a
633
-				return 1;
634
-			}
635
-			if ($a['code'] !== $a['name'] && $b['code'] === $b['name']) {
636
-				// If a does have a name, but b doesn't, list a before b
637
-				return -1;
638
-			}
639
-			// Otherwise compare the names
640
-			return strcmp($a['name'], $b['name']);
641
-		});
642
-
643
-		return [
644
-			// reset indexes
645
-			'commonlanguages' => array_values($commonLanguages),
646
-			'languages' => $languages
647
-		];
648
-	}
268
+        // Try to split e.g: fr_FR => fr
269
+        $locale = explode('_', $locale)[0];
270
+        if ($this->languageExists($app, $locale)) {
271
+            return $locale;
272
+        }
273
+    }
274
+
275
+    /**
276
+     * Find all available languages for an app
277
+     *
278
+     * @param string|null $app App id or null for core
279
+     * @return array an array of available languages
280
+     */
281
+    public function findAvailableLanguages($app = null) {
282
+        $key = $app;
283
+        if ($key === null) {
284
+            $key = 'null';
285
+        }
286
+
287
+        // also works with null as key
288
+        if (!empty($this->availableLanguages[$key])) {
289
+            return $this->availableLanguages[$key];
290
+        }
291
+
292
+        $available = ['en']; //english is always available
293
+        $dir = $this->findL10nDir($app);
294
+        if (is_dir($dir)) {
295
+            $files = scandir($dir);
296
+            if ($files !== false) {
297
+                foreach ($files as $file) {
298
+                    if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') {
299
+                        $available[] = substr($file, 0, -5);
300
+                    }
301
+                }
302
+            }
303
+        }
304
+
305
+        // merge with translations from theme
306
+        $theme = $this->config->getSystemValue('theme');
307
+        if (!empty($theme)) {
308
+            $themeDir = $this->serverRoot . '/themes/' . $theme . substr($dir, strlen($this->serverRoot));
309
+
310
+            if (is_dir($themeDir)) {
311
+                $files = scandir($themeDir);
312
+                if ($files !== false) {
313
+                    foreach ($files as $file) {
314
+                        if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') {
315
+                            $available[] = substr($file, 0, -5);
316
+                        }
317
+                    }
318
+                }
319
+            }
320
+        }
321
+
322
+        $this->availableLanguages[$key] = $available;
323
+        return $available;
324
+    }
325
+
326
+    /**
327
+     * @return array|mixed
328
+     */
329
+    public function findAvailableLocales() {
330
+        if (!empty($this->availableLocales)) {
331
+            return $this->availableLocales;
332
+        }
333
+
334
+        $localeData = file_get_contents(\OC::$SERVERROOT . '/resources/locales.json');
335
+        $this->availableLocales = \json_decode($localeData, true);
336
+
337
+        return $this->availableLocales;
338
+    }
339
+
340
+    /**
341
+     * @param string|null $app App id or null for core
342
+     * @param string $lang
343
+     * @return bool
344
+     */
345
+    public function languageExists($app, $lang) {
346
+        if ($lang === 'en') {//english is always available
347
+            return true;
348
+        }
349
+
350
+        $languages = $this->findAvailableLanguages($app);
351
+        return array_search($lang, $languages) !== false;
352
+    }
353
+
354
+    public function getLanguageIterator(IUser $user = null): ILanguageIterator {
355
+        $user = $user ?? $this->userSession->getUser();
356
+        if($user === null) {
357
+            throw new \RuntimeException('Failed to get an IUser instance');
358
+        }
359
+        return new LanguageIterator($user, $this->config);
360
+    }
361
+
362
+    /**
363
+     * @param string $locale
364
+     * @return bool
365
+     */
366
+    public function localeExists($locale) {
367
+        if ($locale === 'en') { //english is always available
368
+            return true;
369
+        }
370
+
371
+        $locales = $this->findAvailableLocales();
372
+        $userLocale = array_filter($locales, function($value) use ($locale) {
373
+            return $locale === $value['code'];
374
+        });
375
+
376
+        return !empty($userLocale);
377
+    }
378
+
379
+    /**
380
+     * @param string|null $app
381
+     * @return string
382
+     * @throws LanguageNotFoundException
383
+     */
384
+    private function getLanguageFromRequest($app) {
385
+        $header = $this->request->getHeader('ACCEPT_LANGUAGE');
386
+        if ($header !== '') {
387
+            $available = $this->findAvailableLanguages($app);
388
+
389
+            // E.g. make sure that 'de' is before 'de_DE'.
390
+            sort($available);
391
+
392
+            $preferences = preg_split('/,\s*/', strtolower($header));
393
+            foreach ($preferences as $preference) {
394
+                list($preferred_language) = explode(';', $preference);
395
+                $preferred_language = str_replace('-', '_', $preferred_language);
396
+
397
+                foreach ($available as $available_language) {
398
+                    if ($preferred_language === strtolower($available_language)) {
399
+                        return $this->respectDefaultLanguage($app, $available_language);
400
+                    }
401
+                }
402
+
403
+                // Fallback from de_De to de
404
+                foreach ($available as $available_language) {
405
+                    if (substr($preferred_language, 0, 2) === $available_language) {
406
+                        return $available_language;
407
+                    }
408
+                }
409
+            }
410
+        }
411
+
412
+        throw new LanguageNotFoundException();
413
+    }
414
+
415
+    /**
416
+     * if default language is set to de_DE (formal German) this should be
417
+     * preferred to 'de' (non-formal German) if possible
418
+     *
419
+     * @param string|null $app
420
+     * @param string $lang
421
+     * @return string
422
+     */
423
+    protected function respectDefaultLanguage($app, $lang) {
424
+        $result = $lang;
425
+        $defaultLanguage = $this->config->getSystemValue('default_language', false);
426
+
427
+        // use formal version of german ("Sie" instead of "Du") if the default
428
+        // language is set to 'de_DE' if possible
429
+        if (is_string($defaultLanguage) &&
430
+            strtolower($lang) === 'de' &&
431
+            strtolower($defaultLanguage) === 'de_de' &&
432
+            $this->languageExists($app, 'de_DE')
433
+        ) {
434
+            $result = 'de_DE';
435
+        }
436
+
437
+        return $result;
438
+    }
439
+
440
+    /**
441
+     * Checks if $sub is a subdirectory of $parent
442
+     *
443
+     * @param string $sub
444
+     * @param string $parent
445
+     * @return bool
446
+     */
447
+    private function isSubDirectory($sub, $parent) {
448
+        // Check whether $sub contains no ".."
449
+        if (strpos($sub, '..') !== false) {
450
+            return false;
451
+        }
452
+
453
+        // Check whether $sub is a subdirectory of $parent
454
+        if (strpos($sub, $parent) === 0) {
455
+            return true;
456
+        }
457
+
458
+        return false;
459
+    }
460
+
461
+    /**
462
+     * Get a list of language files that should be loaded
463
+     *
464
+     * @param string $app
465
+     * @param string $lang
466
+     * @return string[]
467
+     */
468
+    // FIXME This method is only public, until OC_L10N does not need it anymore,
469
+    // FIXME This is also the reason, why it is not in the public interface
470
+    public function getL10nFilesForApp($app, $lang) {
471
+        $languageFiles = [];
472
+
473
+        $i18nDir = $this->findL10nDir($app);
474
+        $transFile = strip_tags($i18nDir) . strip_tags($lang) . '.json';
475
+
476
+        if (($this->isSubDirectory($transFile, $this->serverRoot . '/core/l10n/')
477
+                || $this->isSubDirectory($transFile, $this->serverRoot . '/lib/l10n/')
478
+                || $this->isSubDirectory($transFile, \OC_App::getAppPath($app) . '/l10n/')
479
+            )
480
+            && file_exists($transFile)) {
481
+            // load the translations file
482
+            $languageFiles[] = $transFile;
483
+        }
484
+
485
+        // merge with translations from theme
486
+        $theme = $this->config->getSystemValue('theme');
487
+        if (!empty($theme)) {
488
+            $transFile = $this->serverRoot . '/themes/' . $theme . substr($transFile, strlen($this->serverRoot));
489
+            if (file_exists($transFile)) {
490
+                $languageFiles[] = $transFile;
491
+            }
492
+        }
493
+
494
+        return $languageFiles;
495
+    }
496
+
497
+    /**
498
+     * find the l10n directory
499
+     *
500
+     * @param string $app App id or empty string for core
501
+     * @return string directory
502
+     */
503
+    protected function findL10nDir($app = null) {
504
+        if (in_array($app, ['core', 'lib'])) {
505
+            if (file_exists($this->serverRoot . '/' . $app . '/l10n/')) {
506
+                return $this->serverRoot . '/' . $app . '/l10n/';
507
+            }
508
+        } else if ($app && \OC_App::getAppPath($app) !== false) {
509
+            // Check if the app is in the app folder
510
+            return \OC_App::getAppPath($app) . '/l10n/';
511
+        }
512
+        return $this->serverRoot . '/core/l10n/';
513
+    }
514
+
515
+
516
+    /**
517
+     * Creates a function from the plural string
518
+     *
519
+     * Parts of the code is copied from Habari:
520
+     * https://github.com/habari/system/blob/master/classes/locale.php
521
+     * @param string $string
522
+     * @return string
523
+     */
524
+    public function createPluralFunction($string) {
525
+        if (isset($this->pluralFunctions[$string])) {
526
+            return $this->pluralFunctions[$string];
527
+        }
528
+
529
+        if (preg_match( '/^\s*nplurals\s*=\s*(\d+)\s*;\s*plural=(.*)$/u', $string, $matches)) {
530
+            // sanitize
531
+            $nplurals = preg_replace( '/[^0-9]/', '', $matches[1] );
532
+            $plural = preg_replace( '#[^n0-9:\(\)\?\|\&=!<>+*/\%-]#', '', $matches[2] );
533
+
534
+            $body = str_replace(
535
+                [ 'plural', 'n', '$n$plurals', ],
536
+                [ '$plural', '$n', '$nplurals', ],
537
+                'nplurals='. $nplurals . '; plural=' . $plural
538
+            );
539
+
540
+            // add parents
541
+            // important since PHP's ternary evaluates from left to right
542
+            $body .= ';';
543
+            $res = '';
544
+            $p = 0;
545
+            $length = strlen($body);
546
+            for($i = 0; $i < $length; $i++) {
547
+                $ch = $body[$i];
548
+                switch ( $ch ) {
549
+                    case '?':
550
+                        $res .= ' ? (';
551
+                        $p++;
552
+                        break;
553
+                    case ':':
554
+                        $res .= ') : (';
555
+                        break;
556
+                    case ';':
557
+                        $res .= str_repeat( ')', $p ) . ';';
558
+                        $p = 0;
559
+                        break;
560
+                    default:
561
+                        $res .= $ch;
562
+                }
563
+            }
564
+
565
+            $body = $res . 'return ($plural>=$nplurals?$nplurals-1:$plural);';
566
+            $function = create_function('$n', $body);
567
+            $this->pluralFunctions[$string] = $function;
568
+            return $function;
569
+        } else {
570
+            // default: one plural form for all cases but n==1 (english)
571
+            $function = create_function(
572
+                '$n',
573
+                '$nplurals=2;$plural=($n==1?0:1);return ($plural>=$nplurals?$nplurals-1:$plural);'
574
+            );
575
+            $this->pluralFunctions[$string] = $function;
576
+            return $function;
577
+        }
578
+    }
579
+
580
+    /**
581
+     * returns the common language and other languages in an
582
+     * associative array
583
+     *
584
+     * @return array
585
+     */
586
+    public function getLanguages() {
587
+        $forceLanguage = $this->config->getSystemValue('force_language', false);
588
+        if ($forceLanguage !== false) {
589
+            return [];
590
+        }
591
+
592
+        $languageCodes = $this->findAvailableLanguages();
593
+
594
+        $commonLanguages = [];
595
+        $languages = [];
596
+
597
+        foreach($languageCodes as $lang) {
598
+            $l = $this->get('lib', $lang);
599
+            // TRANSLATORS this is the language name for the language switcher in the personal settings and should be the localized version
600
+            $potentialName = (string) $l->t('__language_name__');
601
+            if ($l->getLanguageCode() === $lang && $potentialName[0] !== '_') {//first check if the language name is in the translation file
602
+                $ln = [
603
+                    'code' => $lang,
604
+                    'name' => $potentialName
605
+                ];
606
+            } else if ($lang === 'en') {
607
+                $ln = [
608
+                    'code' => $lang,
609
+                    'name' => 'English (US)'
610
+                ];
611
+            } else {//fallback to language code
612
+                $ln = [
613
+                    'code' => $lang,
614
+                    'name' => $lang
615
+                ];
616
+            }
617
+
618
+            // put appropriate languages into appropriate arrays, to print them sorted
619
+            // common languages -> divider -> other languages
620
+            if (in_array($lang, self::COMMON_LANGUAGE_CODES)) {
621
+                $commonLanguages[array_search($lang, self::COMMON_LANGUAGE_CODES)] = $ln;
622
+            } else {
623
+                $languages[] = $ln;
624
+            }
625
+        }
626
+
627
+        ksort($commonLanguages);
628
+
629
+        // sort now by displayed language not the iso-code
630
+        usort( $languages, function ($a, $b) {
631
+            if ($a['code'] === $a['name'] && $b['code'] !== $b['name']) {
632
+                // If a doesn't have a name, but b does, list b before a
633
+                return 1;
634
+            }
635
+            if ($a['code'] !== $a['name'] && $b['code'] === $b['name']) {
636
+                // If a does have a name, but b doesn't, list a before b
637
+                return -1;
638
+            }
639
+            // Otherwise compare the names
640
+            return strcmp($a['name'], $b['name']);
641
+        });
642
+
643
+        return [
644
+            // reset indexes
645
+            'commonlanguages' => array_values($commonLanguages),
646
+            'languages' => $languages
647
+        ];
648
+    }
649 649
 }
Please login to merge, or discard this patch.
lib/private/Updater.php 1 patch
Indentation   +566 added lines, -566 removed lines patch added patch discarded remove patch
@@ -57,571 +57,571 @@
 block discarded – undo
57 57
  */
58 58
 class Updater extends BasicEmitter {
59 59
 
60
-	/** @var ILogger $log */
61
-	private $log;
62
-
63
-	/** @var IConfig */
64
-	private $config;
65
-
66
-	/** @var Checker */
67
-	private $checker;
68
-
69
-	/** @var Installer */
70
-	private $installer;
71
-
72
-	private $logLevelNames = [
73
-		0 => 'Debug',
74
-		1 => 'Info',
75
-		2 => 'Warning',
76
-		3 => 'Error',
77
-		4 => 'Fatal',
78
-	];
79
-
80
-	/**
81
-	 * @param IConfig $config
82
-	 * @param Checker $checker
83
-	 * @param ILogger $log
84
-	 * @param Installer $installer
85
-	 */
86
-	public function __construct(IConfig $config,
87
-								Checker $checker,
88
-								ILogger $log = null,
89
-								Installer $installer) {
90
-		$this->log = $log;
91
-		$this->config = $config;
92
-		$this->checker = $checker;
93
-		$this->installer = $installer;
94
-	}
95
-
96
-	/**
97
-	 * runs the update actions in maintenance mode, does not upgrade the source files
98
-	 * except the main .htaccess file
99
-	 *
100
-	 * @return bool true if the operation succeeded, false otherwise
101
-	 */
102
-	public function upgrade() {
103
-		$this->emitRepairEvents();
104
-		$this->logAllEvents();
105
-
106
-		$logLevel = $this->config->getSystemValue('loglevel', ILogger::WARN);
107
-		$this->emit('\OC\Updater', 'setDebugLogLevel', [ $logLevel, $this->logLevelNames[$logLevel] ]);
108
-		$this->config->setSystemValue('loglevel', ILogger::DEBUG);
109
-
110
-		$wasMaintenanceModeEnabled = $this->config->getSystemValueBool('maintenance');
111
-
112
-		if(!$wasMaintenanceModeEnabled) {
113
-			$this->config->setSystemValue('maintenance', true);
114
-			$this->emit('\OC\Updater', 'maintenanceEnabled');
115
-		}
116
-
117
-		// Clear CAN_INSTALL file if not on git
118
-		if (\OC_Util::getChannel() !== 'git' && is_file(\OC::$configDir.'/CAN_INSTALL')) {
119
-			if (!unlink(\OC::$configDir . '/CAN_INSTALL')) {
120
-				$this->log->error('Could not cleanup CAN_INSTALL from your config folder. Please remove this file manually.');
121
-			}
122
-		}
123
-
124
-		$installedVersion = $this->config->getSystemValue('version', '0.0.0');
125
-		$currentVersion = implode('.', \OCP\Util::getVersion());
126
-
127
-		$this->log->debug('starting upgrade from ' . $installedVersion . ' to ' . $currentVersion, ['app' => 'core']);
128
-
129
-		$success = true;
130
-		try {
131
-			$this->doUpgrade($currentVersion, $installedVersion);
132
-		} catch (HintException $exception) {
133
-			$this->log->logException($exception, ['app' => 'core']);
134
-			$this->emit('\OC\Updater', 'failure', [$exception->getMessage() . ': ' .$exception->getHint()]);
135
-			$success = false;
136
-		} catch (\Exception $exception) {
137
-			$this->log->logException($exception, ['app' => 'core']);
138
-			$this->emit('\OC\Updater', 'failure', [get_class($exception) . ': ' .$exception->getMessage()]);
139
-			$success = false;
140
-		}
141
-
142
-		$this->emit('\OC\Updater', 'updateEnd', [$success]);
143
-
144
-		if(!$wasMaintenanceModeEnabled && $success) {
145
-			$this->config->setSystemValue('maintenance', false);
146
-			$this->emit('\OC\Updater', 'maintenanceDisabled');
147
-		} else {
148
-			$this->emit('\OC\Updater', 'maintenanceActive');
149
-		}
150
-
151
-		$this->emit('\OC\Updater', 'resetLogLevel', [ $logLevel, $this->logLevelNames[$logLevel] ]);
152
-		$this->config->setSystemValue('loglevel', $logLevel);
153
-		$this->config->setSystemValue('installed', true);
154
-
155
-		return $success;
156
-	}
157
-
158
-	/**
159
-	 * Return version from which this version is allowed to upgrade from
160
-	 *
161
-	 * @return array allowed previous versions per vendor
162
-	 */
163
-	private function getAllowedPreviousVersions() {
164
-		// this should really be a JSON file
165
-		require \OC::$SERVERROOT . '/version.php';
166
-		/** @var array $OC_VersionCanBeUpgradedFrom */
167
-		return $OC_VersionCanBeUpgradedFrom;
168
-	}
169
-
170
-	/**
171
-	 * Return vendor from which this version was published
172
-	 *
173
-	 * @return string Get the vendor
174
-	 */
175
-	private function getVendor() {
176
-		// this should really be a JSON file
177
-		require \OC::$SERVERROOT . '/version.php';
178
-		/** @var string $vendor */
179
-		return (string) $vendor;
180
-	}
181
-
182
-	/**
183
-	 * Whether an upgrade to a specified version is possible
184
-	 * @param string $oldVersion
185
-	 * @param string $newVersion
186
-	 * @param array $allowedPreviousVersions
187
-	 * @return bool
188
-	 */
189
-	public function isUpgradePossible($oldVersion, $newVersion, array $allowedPreviousVersions) {
190
-		$version = explode('.', $oldVersion);
191
-		$majorMinor = $version[0] . '.' . $version[1];
192
-
193
-		$currentVendor = $this->config->getAppValue('core', 'vendor', '');
194
-
195
-		// Vendor was not set correctly on install, so we have to white-list known versions
196
-		if ($currentVendor === '' && isset($allowedPreviousVersions['owncloud'][$oldVersion])) {
197
-			$currentVendor = 'owncloud';
198
-		}
199
-
200
-		if ($currentVendor === 'nextcloud') {
201
-			return isset($allowedPreviousVersions[$currentVendor][$majorMinor])
202
-				&& (version_compare($oldVersion, $newVersion, '<=') ||
203
-					$this->config->getSystemValue('debug', false));
204
-		}
205
-
206
-		// Check if the instance can be migrated
207
-		return isset($allowedPreviousVersions[$currentVendor][$majorMinor]) ||
208
-			isset($allowedPreviousVersions[$currentVendor][$oldVersion]);
209
-	}
210
-
211
-	/**
212
-	 * runs the update actions in maintenance mode, does not upgrade the source files
213
-	 * except the main .htaccess file
214
-	 *
215
-	 * @param string $currentVersion current version to upgrade to
216
-	 * @param string $installedVersion previous version from which to upgrade from
217
-	 *
218
-	 * @throws \Exception
219
-	 */
220
-	private function doUpgrade($currentVersion, $installedVersion) {
221
-		// Stop update if the update is over several major versions
222
-		$allowedPreviousVersions = $this->getAllowedPreviousVersions();
223
-		if (!$this->isUpgradePossible($installedVersion, $currentVersion, $allowedPreviousVersions)) {
224
-			throw new \Exception('Updates between multiple major versions and downgrades are unsupported.');
225
-		}
226
-
227
-		// Update .htaccess files
228
-		try {
229
-			Setup::updateHtaccess();
230
-			Setup::protectDataDirectory();
231
-		} catch (\Exception $e) {
232
-			throw new \Exception($e->getMessage());
233
-		}
234
-
235
-		// create empty file in data dir, so we can later find
236
-		// out that this is indeed an ownCloud data directory
237
-		// (in case it didn't exist before)
238
-		file_put_contents($this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/.ocdata', '');
239
-
240
-		// pre-upgrade repairs
241
-		$repair = new Repair(Repair::getBeforeUpgradeRepairSteps(), \OC::$server->getEventDispatcher());
242
-		$repair->run();
243
-
244
-		$this->doCoreUpgrade();
245
-
246
-		try {
247
-			// TODO: replace with the new repair step mechanism https://github.com/owncloud/core/pull/24378
248
-			Setup::installBackgroundJobs();
249
-		} catch (\Exception $e) {
250
-			throw new \Exception($e->getMessage());
251
-		}
252
-
253
-		// update all shipped apps
254
-		$this->checkAppsRequirements();
255
-		$this->doAppUpgrade();
256
-
257
-		// Update the appfetchers version so it downloads the correct list from the appstore
258
-		\OC::$server->getAppFetcher()->setVersion($currentVersion);
259
-
260
-		// upgrade appstore apps
261
-		$this->upgradeAppStoreApps(\OC::$server->getAppManager()->getInstalledApps());
262
-		$autoDisabledApps = \OC::$server->getAppManager()->getAutoDisabledApps();
263
-		$this->upgradeAppStoreApps($autoDisabledApps, true);
264
-
265
-		// install new shipped apps on upgrade
266
-		OC_App::loadApps(['authentication']);
267
-		$errors = Installer::installShippedApps(true);
268
-		foreach ($errors as $appId => $exception) {
269
-			/** @var \Exception $exception */
270
-			$this->log->logException($exception, ['app' => $appId]);
271
-			$this->emit('\OC\Updater', 'failure', [$appId . ': ' . $exception->getMessage()]);
272
-		}
273
-
274
-		// post-upgrade repairs
275
-		$repair = new Repair(Repair::getRepairSteps(), \OC::$server->getEventDispatcher());
276
-		$repair->run();
277
-
278
-		//Invalidate update feed
279
-		$this->config->setAppValue('core', 'lastupdatedat', 0);
280
-
281
-		// Check for code integrity if not disabled
282
-		if(\OC::$server->getIntegrityCodeChecker()->isCodeCheckEnforced()) {
283
-			$this->emit('\OC\Updater', 'startCheckCodeIntegrity');
284
-			$this->checker->runInstanceVerification();
285
-			$this->emit('\OC\Updater', 'finishedCheckCodeIntegrity');
286
-		}
287
-
288
-		// only set the final version if everything went well
289
-		$this->config->setSystemValue('version', implode('.', Util::getVersion()));
290
-		$this->config->setAppValue('core', 'vendor', $this->getVendor());
291
-	}
292
-
293
-	protected function doCoreUpgrade() {
294
-		$this->emit('\OC\Updater', 'dbUpgradeBefore');
295
-
296
-		// execute core migrations
297
-		$ms = new MigrationService('core', \OC::$server->getDatabaseConnection());
298
-		$ms->migrate();
299
-
300
-		$this->emit('\OC\Updater', 'dbUpgrade');
301
-	}
302
-
303
-	/**
304
-	 * @param string $version the oc version to check app compatibility with
305
-	 */
306
-	protected function checkAppUpgrade($version) {
307
-		$apps = \OC_App::getEnabledApps();
308
-		$this->emit('\OC\Updater', 'appUpgradeCheckBefore');
309
-
310
-		$appManager = \OC::$server->getAppManager();
311
-		foreach ($apps as $appId) {
312
-			$info = \OC_App::getAppInfo($appId);
313
-			$compatible = \OC_App::isAppCompatible($version, $info);
314
-			$isShipped = $appManager->isShipped($appId);
315
-
316
-			if ($compatible && $isShipped && \OC_App::shouldUpgrade($appId)) {
317
-				/**
318
-				 * FIXME: The preupdate check is performed before the database migration, otherwise database changes
319
-				 * are not possible anymore within it. - Consider this when touching the code.
320
-				 * @link https://github.com/owncloud/core/issues/10980
321
-				 * @see \OC_App::updateApp
322
-				 */
323
-				if (file_exists(\OC_App::getAppPath($appId) . '/appinfo/preupdate.php')) {
324
-					$this->includePreUpdate($appId);
325
-				}
326
-				if (file_exists(\OC_App::getAppPath($appId) . '/appinfo/database.xml')) {
327
-					$this->emit('\OC\Updater', 'appSimulateUpdate', [$appId]);
328
-					\OC_DB::simulateUpdateDbFromStructure(\OC_App::getAppPath($appId) . '/appinfo/database.xml');
329
-				}
330
-			}
331
-		}
332
-
333
-		$this->emit('\OC\Updater', 'appUpgradeCheck');
334
-	}
335
-
336
-	/**
337
-	 * Includes the pre-update file. Done here to prevent namespace mixups.
338
-	 * @param string $appId
339
-	 */
340
-	private function includePreUpdate($appId) {
341
-		include \OC_App::getAppPath($appId) . '/appinfo/preupdate.php';
342
-	}
343
-
344
-	/**
345
-	 * upgrades all apps within a major ownCloud upgrade. Also loads "priority"
346
-	 * (types authentication, filesystem, logging, in that order) afterwards.
347
-	 *
348
-	 * @throws NeedsUpdateException
349
-	 */
350
-	protected function doAppUpgrade() {
351
-		$apps = \OC_App::getEnabledApps();
352
-		$priorityTypes = ['authentication', 'filesystem', 'logging'];
353
-		$pseudoOtherType = 'other';
354
-		$stacks = [$pseudoOtherType => []];
355
-
356
-		foreach ($apps as $appId) {
357
-			$priorityType = false;
358
-			foreach ($priorityTypes as $type) {
359
-				if(!isset($stacks[$type])) {
360
-					$stacks[$type] = [];
361
-				}
362
-				if (\OC_App::isType($appId, [$type])) {
363
-					$stacks[$type][] = $appId;
364
-					$priorityType = true;
365
-					break;
366
-				}
367
-			}
368
-			if (!$priorityType) {
369
-				$stacks[$pseudoOtherType][] = $appId;
370
-			}
371
-		}
372
-		foreach ($stacks as $type => $stack) {
373
-			foreach ($stack as $appId) {
374
-				if (\OC_App::shouldUpgrade($appId)) {
375
-					$this->emit('\OC\Updater', 'appUpgradeStarted', [$appId, \OC_App::getAppVersion($appId)]);
376
-					\OC_App::updateApp($appId);
377
-					$this->emit('\OC\Updater', 'appUpgrade', [$appId, \OC_App::getAppVersion($appId)]);
378
-				}
379
-				if($type !== $pseudoOtherType) {
380
-					// load authentication, filesystem and logging apps after
381
-					// upgrading them. Other apps my need to rely on modifying
382
-					// user and/or filesystem aspects.
383
-					\OC_App::loadApp($appId);
384
-				}
385
-			}
386
-		}
387
-	}
388
-
389
-	/**
390
-	 * check if the current enabled apps are compatible with the current
391
-	 * ownCloud version. disable them if not.
392
-	 * This is important if you upgrade ownCloud and have non ported 3rd
393
-	 * party apps installed.
394
-	 *
395
-	 * @return array
396
-	 * @throws \Exception
397
-	 */
398
-	private function checkAppsRequirements() {
399
-		$isCoreUpgrade = $this->isCodeUpgrade();
400
-		$apps = OC_App::getEnabledApps();
401
-		$version = implode('.', Util::getVersion());
402
-		$disabledApps = [];
403
-		$appManager = \OC::$server->getAppManager();
404
-		foreach ($apps as $app) {
405
-			// check if the app is compatible with this version of ownCloud
406
-			$info = OC_App::getAppInfo($app);
407
-			if($info === null || !OC_App::isAppCompatible($version, $info)) {
408
-				if ($appManager->isShipped($app)) {
409
-					throw new \UnexpectedValueException('The files of the app "' . $app . '" were not correctly replaced before running the update');
410
-				}
411
-				\OC::$server->getAppManager()->disableApp($app, true);
412
-				$this->emit('\OC\Updater', 'incompatibleAppDisabled', [$app]);
413
-			}
414
-			// no need to disable any app in case this is a non-core upgrade
415
-			if (!$isCoreUpgrade) {
416
-				continue;
417
-			}
418
-			// shipped apps will remain enabled
419
-			if ($appManager->isShipped($app)) {
420
-				continue;
421
-			}
422
-			// authentication and session apps will remain enabled as well
423
-			if (OC_App::isType($app, ['session', 'authentication'])) {
424
-				continue;
425
-			}
426
-		}
427
-		return $disabledApps;
428
-	}
429
-
430
-	/**
431
-	 * @return bool
432
-	 */
433
-	private function isCodeUpgrade() {
434
-		$installedVersion = $this->config->getSystemValue('version', '0.0.0');
435
-		$currentVersion = implode('.', Util::getVersion());
436
-		if (version_compare($currentVersion, $installedVersion, '>')) {
437
-			return true;
438
-		}
439
-		return false;
440
-	}
441
-
442
-	/**
443
-	 * @param array $disabledApps
444
-	 * @param bool $reenable
445
-	 * @throws \Exception
446
-	 */
447
-	private function upgradeAppStoreApps(array $disabledApps, $reenable = false) {
448
-		foreach($disabledApps as $app) {
449
-			try {
450
-				$this->emit('\OC\Updater', 'checkAppStoreAppBefore', [$app]);
451
-				if ($this->installer->isUpdateAvailable($app)) {
452
-					$this->emit('\OC\Updater', 'upgradeAppStoreApp', [$app]);
453
-					$this->installer->updateAppstoreApp($app);
454
-				}
455
-				$this->emit('\OC\Updater', 'checkAppStoreApp', [$app]);
456
-
457
-				if ($reenable) {
458
-					$ocApp = new \OC_App();
459
-					$ocApp->enable($app);
460
-				}
461
-			} catch (\Exception $ex) {
462
-				$this->log->logException($ex, ['app' => 'core']);
463
-			}
464
-		}
465
-	}
466
-
467
-	/**
468
-	 * Forward messages emitted by the repair routine
469
-	 */
470
-	private function emitRepairEvents() {
471
-		$dispatcher = \OC::$server->getEventDispatcher();
472
-		$dispatcher->addListener('\OC\Repair::warning', function ($event) {
473
-			if ($event instanceof GenericEvent) {
474
-				$this->emit('\OC\Updater', 'repairWarning', $event->getArguments());
475
-			}
476
-		});
477
-		$dispatcher->addListener('\OC\Repair::error', function ($event) {
478
-			if ($event instanceof GenericEvent) {
479
-				$this->emit('\OC\Updater', 'repairError', $event->getArguments());
480
-			}
481
-		});
482
-		$dispatcher->addListener('\OC\Repair::info', function ($event) {
483
-			if ($event instanceof GenericEvent) {
484
-				$this->emit('\OC\Updater', 'repairInfo', $event->getArguments());
485
-			}
486
-		});
487
-		$dispatcher->addListener('\OC\Repair::step', function ($event) {
488
-			if ($event instanceof GenericEvent) {
489
-				$this->emit('\OC\Updater', 'repairStep', $event->getArguments());
490
-			}
491
-		});
492
-	}
493
-
494
-	private function logAllEvents() {
495
-		$log = $this->log;
496
-
497
-		$dispatcher = \OC::$server->getEventDispatcher();
498
-		$dispatcher->addListener('\OC\DB\Migrator::executeSql', function($event) use ($log) {
499
-			if (!$event instanceof GenericEvent) {
500
-				return;
501
-			}
502
-			$log->info('\OC\DB\Migrator::executeSql: ' . $event->getSubject() . ' (' . $event->getArgument(0) . ' of ' . $event->getArgument(1) . ')', ['app' => 'updater']);
503
-		});
504
-		$dispatcher->addListener('\OC\DB\Migrator::checkTable', function($event) use ($log) {
505
-			if (!$event instanceof GenericEvent) {
506
-				return;
507
-			}
508
-			$log->info('\OC\DB\Migrator::checkTable: ' . $event->getSubject() . ' (' . $event->getArgument(0) . ' of ' . $event->getArgument(1) . ')', ['app' => 'updater']);
509
-		});
510
-
511
-		$repairListener = function($event) use ($log) {
512
-			if (!$event instanceof GenericEvent) {
513
-				return;
514
-			}
515
-			switch ($event->getSubject()) {
516
-				case '\OC\Repair::startProgress':
517
-					$log->info('\OC\Repair::startProgress: Starting ... ' . $event->getArgument(1) .  ' (' . $event->getArgument(0) . ')', ['app' => 'updater']);
518
-					break;
519
-				case '\OC\Repair::advance':
520
-					$desc = $event->getArgument(1);
521
-					if (empty($desc)) {
522
-						$desc = '';
523
-					}
524
-					$log->info('\OC\Repair::advance: ' . $desc . ' (' . $event->getArgument(0) . ')', ['app' => 'updater']);
525
-
526
-					break;
527
-				case '\OC\Repair::finishProgress':
528
-					$log->info('\OC\Repair::finishProgress', ['app' => 'updater']);
529
-					break;
530
-				case '\OC\Repair::step':
531
-					$log->info('\OC\Repair::step: Repair step: ' . $event->getArgument(0), ['app' => 'updater']);
532
-					break;
533
-				case '\OC\Repair::info':
534
-					$log->info('\OC\Repair::info: Repair info: ' . $event->getArgument(0), ['app' => 'updater']);
535
-					break;
536
-				case '\OC\Repair::warning':
537
-					$log->warning('\OC\Repair::warning: Repair warning: ' . $event->getArgument(0), ['app' => 'updater']);
538
-					break;
539
-				case '\OC\Repair::error':
540
-					$log->error('\OC\Repair::error: Repair error: ' . $event->getArgument(0), ['app' => 'updater']);
541
-					break;
542
-			}
543
-		};
544
-
545
-		$dispatcher->addListener('\OC\Repair::startProgress', $repairListener);
546
-		$dispatcher->addListener('\OC\Repair::advance', $repairListener);
547
-		$dispatcher->addListener('\OC\Repair::finishProgress', $repairListener);
548
-		$dispatcher->addListener('\OC\Repair::step', $repairListener);
549
-		$dispatcher->addListener('\OC\Repair::info', $repairListener);
550
-		$dispatcher->addListener('\OC\Repair::warning', $repairListener);
551
-		$dispatcher->addListener('\OC\Repair::error', $repairListener);
552
-
553
-
554
-		$this->listen('\OC\Updater', 'maintenanceEnabled', function () use($log) {
555
-			$log->info('\OC\Updater::maintenanceEnabled: Turned on maintenance mode', ['app' => 'updater']);
556
-		});
557
-		$this->listen('\OC\Updater', 'maintenanceDisabled', function () use($log) {
558
-			$log->info('\OC\Updater::maintenanceDisabled: Turned off maintenance mode', ['app' => 'updater']);
559
-		});
560
-		$this->listen('\OC\Updater', 'maintenanceActive', function () use($log) {
561
-			$log->info('\OC\Updater::maintenanceActive: Maintenance mode is kept active', ['app' => 'updater']);
562
-		});
563
-		$this->listen('\OC\Updater', 'updateEnd', function ($success) use($log) {
564
-			if ($success) {
565
-				$log->info('\OC\Updater::updateEnd: Update successful', ['app' => 'updater']);
566
-			} else {
567
-				$log->error('\OC\Updater::updateEnd: Update failed', ['app' => 'updater']);
568
-			}
569
-		});
570
-		$this->listen('\OC\Updater', 'dbUpgradeBefore', function () use($log) {
571
-			$log->info('\OC\Updater::dbUpgradeBefore: Updating database schema', ['app' => 'updater']);
572
-		});
573
-		$this->listen('\OC\Updater', 'dbUpgrade', function () use($log) {
574
-			$log->info('\OC\Updater::dbUpgrade: Updated database', ['app' => 'updater']);
575
-		});
576
-		$this->listen('\OC\Updater', 'dbSimulateUpgradeBefore', function () use($log) {
577
-			$log->info('\OC\Updater::dbSimulateUpgradeBefore: Checking whether the database schema can be updated (this can take a long time depending on the database size)', ['app' => 'updater']);
578
-		});
579
-		$this->listen('\OC\Updater', 'dbSimulateUpgrade', function () use($log) {
580
-			$log->info('\OC\Updater::dbSimulateUpgrade: Checked database schema update', ['app' => 'updater']);
581
-		});
582
-		$this->listen('\OC\Updater', 'incompatibleAppDisabled', function ($app) use($log) {
583
-			$log->info('\OC\Updater::incompatibleAppDisabled: Disabled incompatible app: ' . $app, ['app' => 'updater']);
584
-		});
585
-		$this->listen('\OC\Updater', 'checkAppStoreAppBefore', function ($app) use($log) {
586
-			$log->info('\OC\Updater::checkAppStoreAppBefore: Checking for update of app "' . $app . '" in appstore', ['app' => 'updater']);
587
-		});
588
-		$this->listen('\OC\Updater', 'upgradeAppStoreApp', function ($app) use($log) {
589
-			$log->info('\OC\Updater::upgradeAppStoreApp: Update app "' . $app . '" from appstore', ['app' => 'updater']);
590
-		});
591
-		$this->listen('\OC\Updater', 'checkAppStoreApp', function ($app) use($log) {
592
-			$log->info('\OC\Updater::checkAppStoreApp: Checked for update of app "' . $app . '" in appstore', ['app' => 'updater']);
593
-		});
594
-		$this->listen('\OC\Updater', 'appUpgradeCheckBefore', function () use ($log) {
595
-			$log->info('\OC\Updater::appUpgradeCheckBefore: Checking updates of apps', ['app' => 'updater']);
596
-		});
597
-		$this->listen('\OC\Updater', 'appSimulateUpdate', function ($app) use ($log) {
598
-			$log->info('\OC\Updater::appSimulateUpdate: Checking whether the database schema for <' . $app . '> can be updated (this can take a long time depending on the database size)', ['app' => 'updater']);
599
-		});
600
-		$this->listen('\OC\Updater', 'appUpgradeCheck', function () use ($log) {
601
-			$log->info('\OC\Updater::appUpgradeCheck: Checked database schema update for apps', ['app' => 'updater']);
602
-		});
603
-		$this->listen('\OC\Updater', 'appUpgradeStarted', function ($app) use ($log) {
604
-			$log->info('\OC\Updater::appUpgradeStarted: Updating <' . $app . '> ...', ['app' => 'updater']);
605
-		});
606
-		$this->listen('\OC\Updater', 'appUpgrade', function ($app, $version) use ($log) {
607
-			$log->info('\OC\Updater::appUpgrade: Updated <' . $app . '> to ' . $version, ['app' => 'updater']);
608
-		});
609
-		$this->listen('\OC\Updater', 'failure', function ($message) use($log) {
610
-			$log->error('\OC\Updater::failure: ' . $message, ['app' => 'updater']);
611
-		});
612
-		$this->listen('\OC\Updater', 'setDebugLogLevel', function () use($log) {
613
-			$log->info('\OC\Updater::setDebugLogLevel: Set log level to debug', ['app' => 'updater']);
614
-		});
615
-		$this->listen('\OC\Updater', 'resetLogLevel', function ($logLevel, $logLevelName) use($log) {
616
-			$log->info('\OC\Updater::resetLogLevel: Reset log level to ' . $logLevelName . '(' . $logLevel . ')', ['app' => 'updater']);
617
-		});
618
-		$this->listen('\OC\Updater', 'startCheckCodeIntegrity', function () use($log) {
619
-			$log->info('\OC\Updater::startCheckCodeIntegrity: Starting code integrity check...', ['app' => 'updater']);
620
-		});
621
-		$this->listen('\OC\Updater', 'finishedCheckCodeIntegrity', function () use($log) {
622
-			$log->info('\OC\Updater::finishedCheckCodeIntegrity: Finished code integrity check', ['app' => 'updater']);
623
-		});
624
-
625
-	}
60
+    /** @var ILogger $log */
61
+    private $log;
62
+
63
+    /** @var IConfig */
64
+    private $config;
65
+
66
+    /** @var Checker */
67
+    private $checker;
68
+
69
+    /** @var Installer */
70
+    private $installer;
71
+
72
+    private $logLevelNames = [
73
+        0 => 'Debug',
74
+        1 => 'Info',
75
+        2 => 'Warning',
76
+        3 => 'Error',
77
+        4 => 'Fatal',
78
+    ];
79
+
80
+    /**
81
+     * @param IConfig $config
82
+     * @param Checker $checker
83
+     * @param ILogger $log
84
+     * @param Installer $installer
85
+     */
86
+    public function __construct(IConfig $config,
87
+                                Checker $checker,
88
+                                ILogger $log = null,
89
+                                Installer $installer) {
90
+        $this->log = $log;
91
+        $this->config = $config;
92
+        $this->checker = $checker;
93
+        $this->installer = $installer;
94
+    }
95
+
96
+    /**
97
+     * runs the update actions in maintenance mode, does not upgrade the source files
98
+     * except the main .htaccess file
99
+     *
100
+     * @return bool true if the operation succeeded, false otherwise
101
+     */
102
+    public function upgrade() {
103
+        $this->emitRepairEvents();
104
+        $this->logAllEvents();
105
+
106
+        $logLevel = $this->config->getSystemValue('loglevel', ILogger::WARN);
107
+        $this->emit('\OC\Updater', 'setDebugLogLevel', [ $logLevel, $this->logLevelNames[$logLevel] ]);
108
+        $this->config->setSystemValue('loglevel', ILogger::DEBUG);
109
+
110
+        $wasMaintenanceModeEnabled = $this->config->getSystemValueBool('maintenance');
111
+
112
+        if(!$wasMaintenanceModeEnabled) {
113
+            $this->config->setSystemValue('maintenance', true);
114
+            $this->emit('\OC\Updater', 'maintenanceEnabled');
115
+        }
116
+
117
+        // Clear CAN_INSTALL file if not on git
118
+        if (\OC_Util::getChannel() !== 'git' && is_file(\OC::$configDir.'/CAN_INSTALL')) {
119
+            if (!unlink(\OC::$configDir . '/CAN_INSTALL')) {
120
+                $this->log->error('Could not cleanup CAN_INSTALL from your config folder. Please remove this file manually.');
121
+            }
122
+        }
123
+
124
+        $installedVersion = $this->config->getSystemValue('version', '0.0.0');
125
+        $currentVersion = implode('.', \OCP\Util::getVersion());
126
+
127
+        $this->log->debug('starting upgrade from ' . $installedVersion . ' to ' . $currentVersion, ['app' => 'core']);
128
+
129
+        $success = true;
130
+        try {
131
+            $this->doUpgrade($currentVersion, $installedVersion);
132
+        } catch (HintException $exception) {
133
+            $this->log->logException($exception, ['app' => 'core']);
134
+            $this->emit('\OC\Updater', 'failure', [$exception->getMessage() . ': ' .$exception->getHint()]);
135
+            $success = false;
136
+        } catch (\Exception $exception) {
137
+            $this->log->logException($exception, ['app' => 'core']);
138
+            $this->emit('\OC\Updater', 'failure', [get_class($exception) . ': ' .$exception->getMessage()]);
139
+            $success = false;
140
+        }
141
+
142
+        $this->emit('\OC\Updater', 'updateEnd', [$success]);
143
+
144
+        if(!$wasMaintenanceModeEnabled && $success) {
145
+            $this->config->setSystemValue('maintenance', false);
146
+            $this->emit('\OC\Updater', 'maintenanceDisabled');
147
+        } else {
148
+            $this->emit('\OC\Updater', 'maintenanceActive');
149
+        }
150
+
151
+        $this->emit('\OC\Updater', 'resetLogLevel', [ $logLevel, $this->logLevelNames[$logLevel] ]);
152
+        $this->config->setSystemValue('loglevel', $logLevel);
153
+        $this->config->setSystemValue('installed', true);
154
+
155
+        return $success;
156
+    }
157
+
158
+    /**
159
+     * Return version from which this version is allowed to upgrade from
160
+     *
161
+     * @return array allowed previous versions per vendor
162
+     */
163
+    private function getAllowedPreviousVersions() {
164
+        // this should really be a JSON file
165
+        require \OC::$SERVERROOT . '/version.php';
166
+        /** @var array $OC_VersionCanBeUpgradedFrom */
167
+        return $OC_VersionCanBeUpgradedFrom;
168
+    }
169
+
170
+    /**
171
+     * Return vendor from which this version was published
172
+     *
173
+     * @return string Get the vendor
174
+     */
175
+    private function getVendor() {
176
+        // this should really be a JSON file
177
+        require \OC::$SERVERROOT . '/version.php';
178
+        /** @var string $vendor */
179
+        return (string) $vendor;
180
+    }
181
+
182
+    /**
183
+     * Whether an upgrade to a specified version is possible
184
+     * @param string $oldVersion
185
+     * @param string $newVersion
186
+     * @param array $allowedPreviousVersions
187
+     * @return bool
188
+     */
189
+    public function isUpgradePossible($oldVersion, $newVersion, array $allowedPreviousVersions) {
190
+        $version = explode('.', $oldVersion);
191
+        $majorMinor = $version[0] . '.' . $version[1];
192
+
193
+        $currentVendor = $this->config->getAppValue('core', 'vendor', '');
194
+
195
+        // Vendor was not set correctly on install, so we have to white-list known versions
196
+        if ($currentVendor === '' && isset($allowedPreviousVersions['owncloud'][$oldVersion])) {
197
+            $currentVendor = 'owncloud';
198
+        }
199
+
200
+        if ($currentVendor === 'nextcloud') {
201
+            return isset($allowedPreviousVersions[$currentVendor][$majorMinor])
202
+                && (version_compare($oldVersion, $newVersion, '<=') ||
203
+                    $this->config->getSystemValue('debug', false));
204
+        }
205
+
206
+        // Check if the instance can be migrated
207
+        return isset($allowedPreviousVersions[$currentVendor][$majorMinor]) ||
208
+            isset($allowedPreviousVersions[$currentVendor][$oldVersion]);
209
+    }
210
+
211
+    /**
212
+     * runs the update actions in maintenance mode, does not upgrade the source files
213
+     * except the main .htaccess file
214
+     *
215
+     * @param string $currentVersion current version to upgrade to
216
+     * @param string $installedVersion previous version from which to upgrade from
217
+     *
218
+     * @throws \Exception
219
+     */
220
+    private function doUpgrade($currentVersion, $installedVersion) {
221
+        // Stop update if the update is over several major versions
222
+        $allowedPreviousVersions = $this->getAllowedPreviousVersions();
223
+        if (!$this->isUpgradePossible($installedVersion, $currentVersion, $allowedPreviousVersions)) {
224
+            throw new \Exception('Updates between multiple major versions and downgrades are unsupported.');
225
+        }
226
+
227
+        // Update .htaccess files
228
+        try {
229
+            Setup::updateHtaccess();
230
+            Setup::protectDataDirectory();
231
+        } catch (\Exception $e) {
232
+            throw new \Exception($e->getMessage());
233
+        }
234
+
235
+        // create empty file in data dir, so we can later find
236
+        // out that this is indeed an ownCloud data directory
237
+        // (in case it didn't exist before)
238
+        file_put_contents($this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/.ocdata', '');
239
+
240
+        // pre-upgrade repairs
241
+        $repair = new Repair(Repair::getBeforeUpgradeRepairSteps(), \OC::$server->getEventDispatcher());
242
+        $repair->run();
243
+
244
+        $this->doCoreUpgrade();
245
+
246
+        try {
247
+            // TODO: replace with the new repair step mechanism https://github.com/owncloud/core/pull/24378
248
+            Setup::installBackgroundJobs();
249
+        } catch (\Exception $e) {
250
+            throw new \Exception($e->getMessage());
251
+        }
252
+
253
+        // update all shipped apps
254
+        $this->checkAppsRequirements();
255
+        $this->doAppUpgrade();
256
+
257
+        // Update the appfetchers version so it downloads the correct list from the appstore
258
+        \OC::$server->getAppFetcher()->setVersion($currentVersion);
259
+
260
+        // upgrade appstore apps
261
+        $this->upgradeAppStoreApps(\OC::$server->getAppManager()->getInstalledApps());
262
+        $autoDisabledApps = \OC::$server->getAppManager()->getAutoDisabledApps();
263
+        $this->upgradeAppStoreApps($autoDisabledApps, true);
264
+
265
+        // install new shipped apps on upgrade
266
+        OC_App::loadApps(['authentication']);
267
+        $errors = Installer::installShippedApps(true);
268
+        foreach ($errors as $appId => $exception) {
269
+            /** @var \Exception $exception */
270
+            $this->log->logException($exception, ['app' => $appId]);
271
+            $this->emit('\OC\Updater', 'failure', [$appId . ': ' . $exception->getMessage()]);
272
+        }
273
+
274
+        // post-upgrade repairs
275
+        $repair = new Repair(Repair::getRepairSteps(), \OC::$server->getEventDispatcher());
276
+        $repair->run();
277
+
278
+        //Invalidate update feed
279
+        $this->config->setAppValue('core', 'lastupdatedat', 0);
280
+
281
+        // Check for code integrity if not disabled
282
+        if(\OC::$server->getIntegrityCodeChecker()->isCodeCheckEnforced()) {
283
+            $this->emit('\OC\Updater', 'startCheckCodeIntegrity');
284
+            $this->checker->runInstanceVerification();
285
+            $this->emit('\OC\Updater', 'finishedCheckCodeIntegrity');
286
+        }
287
+
288
+        // only set the final version if everything went well
289
+        $this->config->setSystemValue('version', implode('.', Util::getVersion()));
290
+        $this->config->setAppValue('core', 'vendor', $this->getVendor());
291
+    }
292
+
293
+    protected function doCoreUpgrade() {
294
+        $this->emit('\OC\Updater', 'dbUpgradeBefore');
295
+
296
+        // execute core migrations
297
+        $ms = new MigrationService('core', \OC::$server->getDatabaseConnection());
298
+        $ms->migrate();
299
+
300
+        $this->emit('\OC\Updater', 'dbUpgrade');
301
+    }
302
+
303
+    /**
304
+     * @param string $version the oc version to check app compatibility with
305
+     */
306
+    protected function checkAppUpgrade($version) {
307
+        $apps = \OC_App::getEnabledApps();
308
+        $this->emit('\OC\Updater', 'appUpgradeCheckBefore');
309
+
310
+        $appManager = \OC::$server->getAppManager();
311
+        foreach ($apps as $appId) {
312
+            $info = \OC_App::getAppInfo($appId);
313
+            $compatible = \OC_App::isAppCompatible($version, $info);
314
+            $isShipped = $appManager->isShipped($appId);
315
+
316
+            if ($compatible && $isShipped && \OC_App::shouldUpgrade($appId)) {
317
+                /**
318
+                 * FIXME: The preupdate check is performed before the database migration, otherwise database changes
319
+                 * are not possible anymore within it. - Consider this when touching the code.
320
+                 * @link https://github.com/owncloud/core/issues/10980
321
+                 * @see \OC_App::updateApp
322
+                 */
323
+                if (file_exists(\OC_App::getAppPath($appId) . '/appinfo/preupdate.php')) {
324
+                    $this->includePreUpdate($appId);
325
+                }
326
+                if (file_exists(\OC_App::getAppPath($appId) . '/appinfo/database.xml')) {
327
+                    $this->emit('\OC\Updater', 'appSimulateUpdate', [$appId]);
328
+                    \OC_DB::simulateUpdateDbFromStructure(\OC_App::getAppPath($appId) . '/appinfo/database.xml');
329
+                }
330
+            }
331
+        }
332
+
333
+        $this->emit('\OC\Updater', 'appUpgradeCheck');
334
+    }
335
+
336
+    /**
337
+     * Includes the pre-update file. Done here to prevent namespace mixups.
338
+     * @param string $appId
339
+     */
340
+    private function includePreUpdate($appId) {
341
+        include \OC_App::getAppPath($appId) . '/appinfo/preupdate.php';
342
+    }
343
+
344
+    /**
345
+     * upgrades all apps within a major ownCloud upgrade. Also loads "priority"
346
+     * (types authentication, filesystem, logging, in that order) afterwards.
347
+     *
348
+     * @throws NeedsUpdateException
349
+     */
350
+    protected function doAppUpgrade() {
351
+        $apps = \OC_App::getEnabledApps();
352
+        $priorityTypes = ['authentication', 'filesystem', 'logging'];
353
+        $pseudoOtherType = 'other';
354
+        $stacks = [$pseudoOtherType => []];
355
+
356
+        foreach ($apps as $appId) {
357
+            $priorityType = false;
358
+            foreach ($priorityTypes as $type) {
359
+                if(!isset($stacks[$type])) {
360
+                    $stacks[$type] = [];
361
+                }
362
+                if (\OC_App::isType($appId, [$type])) {
363
+                    $stacks[$type][] = $appId;
364
+                    $priorityType = true;
365
+                    break;
366
+                }
367
+            }
368
+            if (!$priorityType) {
369
+                $stacks[$pseudoOtherType][] = $appId;
370
+            }
371
+        }
372
+        foreach ($stacks as $type => $stack) {
373
+            foreach ($stack as $appId) {
374
+                if (\OC_App::shouldUpgrade($appId)) {
375
+                    $this->emit('\OC\Updater', 'appUpgradeStarted', [$appId, \OC_App::getAppVersion($appId)]);
376
+                    \OC_App::updateApp($appId);
377
+                    $this->emit('\OC\Updater', 'appUpgrade', [$appId, \OC_App::getAppVersion($appId)]);
378
+                }
379
+                if($type !== $pseudoOtherType) {
380
+                    // load authentication, filesystem and logging apps after
381
+                    // upgrading them. Other apps my need to rely on modifying
382
+                    // user and/or filesystem aspects.
383
+                    \OC_App::loadApp($appId);
384
+                }
385
+            }
386
+        }
387
+    }
388
+
389
+    /**
390
+     * check if the current enabled apps are compatible with the current
391
+     * ownCloud version. disable them if not.
392
+     * This is important if you upgrade ownCloud and have non ported 3rd
393
+     * party apps installed.
394
+     *
395
+     * @return array
396
+     * @throws \Exception
397
+     */
398
+    private function checkAppsRequirements() {
399
+        $isCoreUpgrade = $this->isCodeUpgrade();
400
+        $apps = OC_App::getEnabledApps();
401
+        $version = implode('.', Util::getVersion());
402
+        $disabledApps = [];
403
+        $appManager = \OC::$server->getAppManager();
404
+        foreach ($apps as $app) {
405
+            // check if the app is compatible with this version of ownCloud
406
+            $info = OC_App::getAppInfo($app);
407
+            if($info === null || !OC_App::isAppCompatible($version, $info)) {
408
+                if ($appManager->isShipped($app)) {
409
+                    throw new \UnexpectedValueException('The files of the app "' . $app . '" were not correctly replaced before running the update');
410
+                }
411
+                \OC::$server->getAppManager()->disableApp($app, true);
412
+                $this->emit('\OC\Updater', 'incompatibleAppDisabled', [$app]);
413
+            }
414
+            // no need to disable any app in case this is a non-core upgrade
415
+            if (!$isCoreUpgrade) {
416
+                continue;
417
+            }
418
+            // shipped apps will remain enabled
419
+            if ($appManager->isShipped($app)) {
420
+                continue;
421
+            }
422
+            // authentication and session apps will remain enabled as well
423
+            if (OC_App::isType($app, ['session', 'authentication'])) {
424
+                continue;
425
+            }
426
+        }
427
+        return $disabledApps;
428
+    }
429
+
430
+    /**
431
+     * @return bool
432
+     */
433
+    private function isCodeUpgrade() {
434
+        $installedVersion = $this->config->getSystemValue('version', '0.0.0');
435
+        $currentVersion = implode('.', Util::getVersion());
436
+        if (version_compare($currentVersion, $installedVersion, '>')) {
437
+            return true;
438
+        }
439
+        return false;
440
+    }
441
+
442
+    /**
443
+     * @param array $disabledApps
444
+     * @param bool $reenable
445
+     * @throws \Exception
446
+     */
447
+    private function upgradeAppStoreApps(array $disabledApps, $reenable = false) {
448
+        foreach($disabledApps as $app) {
449
+            try {
450
+                $this->emit('\OC\Updater', 'checkAppStoreAppBefore', [$app]);
451
+                if ($this->installer->isUpdateAvailable($app)) {
452
+                    $this->emit('\OC\Updater', 'upgradeAppStoreApp', [$app]);
453
+                    $this->installer->updateAppstoreApp($app);
454
+                }
455
+                $this->emit('\OC\Updater', 'checkAppStoreApp', [$app]);
456
+
457
+                if ($reenable) {
458
+                    $ocApp = new \OC_App();
459
+                    $ocApp->enable($app);
460
+                }
461
+            } catch (\Exception $ex) {
462
+                $this->log->logException($ex, ['app' => 'core']);
463
+            }
464
+        }
465
+    }
466
+
467
+    /**
468
+     * Forward messages emitted by the repair routine
469
+     */
470
+    private function emitRepairEvents() {
471
+        $dispatcher = \OC::$server->getEventDispatcher();
472
+        $dispatcher->addListener('\OC\Repair::warning', function ($event) {
473
+            if ($event instanceof GenericEvent) {
474
+                $this->emit('\OC\Updater', 'repairWarning', $event->getArguments());
475
+            }
476
+        });
477
+        $dispatcher->addListener('\OC\Repair::error', function ($event) {
478
+            if ($event instanceof GenericEvent) {
479
+                $this->emit('\OC\Updater', 'repairError', $event->getArguments());
480
+            }
481
+        });
482
+        $dispatcher->addListener('\OC\Repair::info', function ($event) {
483
+            if ($event instanceof GenericEvent) {
484
+                $this->emit('\OC\Updater', 'repairInfo', $event->getArguments());
485
+            }
486
+        });
487
+        $dispatcher->addListener('\OC\Repair::step', function ($event) {
488
+            if ($event instanceof GenericEvent) {
489
+                $this->emit('\OC\Updater', 'repairStep', $event->getArguments());
490
+            }
491
+        });
492
+    }
493
+
494
+    private function logAllEvents() {
495
+        $log = $this->log;
496
+
497
+        $dispatcher = \OC::$server->getEventDispatcher();
498
+        $dispatcher->addListener('\OC\DB\Migrator::executeSql', function($event) use ($log) {
499
+            if (!$event instanceof GenericEvent) {
500
+                return;
501
+            }
502
+            $log->info('\OC\DB\Migrator::executeSql: ' . $event->getSubject() . ' (' . $event->getArgument(0) . ' of ' . $event->getArgument(1) . ')', ['app' => 'updater']);
503
+        });
504
+        $dispatcher->addListener('\OC\DB\Migrator::checkTable', function($event) use ($log) {
505
+            if (!$event instanceof GenericEvent) {
506
+                return;
507
+            }
508
+            $log->info('\OC\DB\Migrator::checkTable: ' . $event->getSubject() . ' (' . $event->getArgument(0) . ' of ' . $event->getArgument(1) . ')', ['app' => 'updater']);
509
+        });
510
+
511
+        $repairListener = function($event) use ($log) {
512
+            if (!$event instanceof GenericEvent) {
513
+                return;
514
+            }
515
+            switch ($event->getSubject()) {
516
+                case '\OC\Repair::startProgress':
517
+                    $log->info('\OC\Repair::startProgress: Starting ... ' . $event->getArgument(1) .  ' (' . $event->getArgument(0) . ')', ['app' => 'updater']);
518
+                    break;
519
+                case '\OC\Repair::advance':
520
+                    $desc = $event->getArgument(1);
521
+                    if (empty($desc)) {
522
+                        $desc = '';
523
+                    }
524
+                    $log->info('\OC\Repair::advance: ' . $desc . ' (' . $event->getArgument(0) . ')', ['app' => 'updater']);
525
+
526
+                    break;
527
+                case '\OC\Repair::finishProgress':
528
+                    $log->info('\OC\Repair::finishProgress', ['app' => 'updater']);
529
+                    break;
530
+                case '\OC\Repair::step':
531
+                    $log->info('\OC\Repair::step: Repair step: ' . $event->getArgument(0), ['app' => 'updater']);
532
+                    break;
533
+                case '\OC\Repair::info':
534
+                    $log->info('\OC\Repair::info: Repair info: ' . $event->getArgument(0), ['app' => 'updater']);
535
+                    break;
536
+                case '\OC\Repair::warning':
537
+                    $log->warning('\OC\Repair::warning: Repair warning: ' . $event->getArgument(0), ['app' => 'updater']);
538
+                    break;
539
+                case '\OC\Repair::error':
540
+                    $log->error('\OC\Repair::error: Repair error: ' . $event->getArgument(0), ['app' => 'updater']);
541
+                    break;
542
+            }
543
+        };
544
+
545
+        $dispatcher->addListener('\OC\Repair::startProgress', $repairListener);
546
+        $dispatcher->addListener('\OC\Repair::advance', $repairListener);
547
+        $dispatcher->addListener('\OC\Repair::finishProgress', $repairListener);
548
+        $dispatcher->addListener('\OC\Repair::step', $repairListener);
549
+        $dispatcher->addListener('\OC\Repair::info', $repairListener);
550
+        $dispatcher->addListener('\OC\Repair::warning', $repairListener);
551
+        $dispatcher->addListener('\OC\Repair::error', $repairListener);
552
+
553
+
554
+        $this->listen('\OC\Updater', 'maintenanceEnabled', function () use($log) {
555
+            $log->info('\OC\Updater::maintenanceEnabled: Turned on maintenance mode', ['app' => 'updater']);
556
+        });
557
+        $this->listen('\OC\Updater', 'maintenanceDisabled', function () use($log) {
558
+            $log->info('\OC\Updater::maintenanceDisabled: Turned off maintenance mode', ['app' => 'updater']);
559
+        });
560
+        $this->listen('\OC\Updater', 'maintenanceActive', function () use($log) {
561
+            $log->info('\OC\Updater::maintenanceActive: Maintenance mode is kept active', ['app' => 'updater']);
562
+        });
563
+        $this->listen('\OC\Updater', 'updateEnd', function ($success) use($log) {
564
+            if ($success) {
565
+                $log->info('\OC\Updater::updateEnd: Update successful', ['app' => 'updater']);
566
+            } else {
567
+                $log->error('\OC\Updater::updateEnd: Update failed', ['app' => 'updater']);
568
+            }
569
+        });
570
+        $this->listen('\OC\Updater', 'dbUpgradeBefore', function () use($log) {
571
+            $log->info('\OC\Updater::dbUpgradeBefore: Updating database schema', ['app' => 'updater']);
572
+        });
573
+        $this->listen('\OC\Updater', 'dbUpgrade', function () use($log) {
574
+            $log->info('\OC\Updater::dbUpgrade: Updated database', ['app' => 'updater']);
575
+        });
576
+        $this->listen('\OC\Updater', 'dbSimulateUpgradeBefore', function () use($log) {
577
+            $log->info('\OC\Updater::dbSimulateUpgradeBefore: Checking whether the database schema can be updated (this can take a long time depending on the database size)', ['app' => 'updater']);
578
+        });
579
+        $this->listen('\OC\Updater', 'dbSimulateUpgrade', function () use($log) {
580
+            $log->info('\OC\Updater::dbSimulateUpgrade: Checked database schema update', ['app' => 'updater']);
581
+        });
582
+        $this->listen('\OC\Updater', 'incompatibleAppDisabled', function ($app) use($log) {
583
+            $log->info('\OC\Updater::incompatibleAppDisabled: Disabled incompatible app: ' . $app, ['app' => 'updater']);
584
+        });
585
+        $this->listen('\OC\Updater', 'checkAppStoreAppBefore', function ($app) use($log) {
586
+            $log->info('\OC\Updater::checkAppStoreAppBefore: Checking for update of app "' . $app . '" in appstore', ['app' => 'updater']);
587
+        });
588
+        $this->listen('\OC\Updater', 'upgradeAppStoreApp', function ($app) use($log) {
589
+            $log->info('\OC\Updater::upgradeAppStoreApp: Update app "' . $app . '" from appstore', ['app' => 'updater']);
590
+        });
591
+        $this->listen('\OC\Updater', 'checkAppStoreApp', function ($app) use($log) {
592
+            $log->info('\OC\Updater::checkAppStoreApp: Checked for update of app "' . $app . '" in appstore', ['app' => 'updater']);
593
+        });
594
+        $this->listen('\OC\Updater', 'appUpgradeCheckBefore', function () use ($log) {
595
+            $log->info('\OC\Updater::appUpgradeCheckBefore: Checking updates of apps', ['app' => 'updater']);
596
+        });
597
+        $this->listen('\OC\Updater', 'appSimulateUpdate', function ($app) use ($log) {
598
+            $log->info('\OC\Updater::appSimulateUpdate: Checking whether the database schema for <' . $app . '> can be updated (this can take a long time depending on the database size)', ['app' => 'updater']);
599
+        });
600
+        $this->listen('\OC\Updater', 'appUpgradeCheck', function () use ($log) {
601
+            $log->info('\OC\Updater::appUpgradeCheck: Checked database schema update for apps', ['app' => 'updater']);
602
+        });
603
+        $this->listen('\OC\Updater', 'appUpgradeStarted', function ($app) use ($log) {
604
+            $log->info('\OC\Updater::appUpgradeStarted: Updating <' . $app . '> ...', ['app' => 'updater']);
605
+        });
606
+        $this->listen('\OC\Updater', 'appUpgrade', function ($app, $version) use ($log) {
607
+            $log->info('\OC\Updater::appUpgrade: Updated <' . $app . '> to ' . $version, ['app' => 'updater']);
608
+        });
609
+        $this->listen('\OC\Updater', 'failure', function ($message) use($log) {
610
+            $log->error('\OC\Updater::failure: ' . $message, ['app' => 'updater']);
611
+        });
612
+        $this->listen('\OC\Updater', 'setDebugLogLevel', function () use($log) {
613
+            $log->info('\OC\Updater::setDebugLogLevel: Set log level to debug', ['app' => 'updater']);
614
+        });
615
+        $this->listen('\OC\Updater', 'resetLogLevel', function ($logLevel, $logLevelName) use($log) {
616
+            $log->info('\OC\Updater::resetLogLevel: Reset log level to ' . $logLevelName . '(' . $logLevel . ')', ['app' => 'updater']);
617
+        });
618
+        $this->listen('\OC\Updater', 'startCheckCodeIntegrity', function () use($log) {
619
+            $log->info('\OC\Updater::startCheckCodeIntegrity: Starting code integrity check...', ['app' => 'updater']);
620
+        });
621
+        $this->listen('\OC\Updater', 'finishedCheckCodeIntegrity', function () use($log) {
622
+            $log->info('\OC\Updater::finishedCheckCodeIntegrity: Finished code integrity check', ['app' => 'updater']);
623
+        });
624
+
625
+    }
626 626
 
627 627
 }
Please login to merge, or discard this patch.
lib/private/Files/Node/Node.php 1 patch
Indentation   +424 added lines, -424 removed lines patch added patch discarded remove patch
@@ -40,429 +40,429 @@
 block discarded – undo
40 40
 
41 41
 // FIXME: this class really should be abstract
42 42
 class Node implements \OCP\Files\Node {
43
-	/**
44
-	 * @var \OC\Files\View $view
45
-	 */
46
-	protected $view;
47
-
48
-	/**
49
-	 * @var \OC\Files\Node\Root $root
50
-	 */
51
-	protected $root;
52
-
53
-	/**
54
-	 * @var string $path
55
-	 */
56
-	protected $path;
57
-
58
-	/**
59
-	 * @var \OCP\Files\FileInfo
60
-	 */
61
-	protected $fileInfo;
62
-
63
-	/**
64
-	 * @param \OC\Files\View $view
65
-	 * @param \OCP\Files\IRootFolder $root
66
-	 * @param string $path
67
-	 * @param FileInfo $fileInfo
68
-	 */
69
-	public function __construct($root, $view, $path, $fileInfo = null) {
70
-		$this->view = $view;
71
-		$this->root = $root;
72
-		$this->path = $path;
73
-		$this->fileInfo = $fileInfo;
74
-	}
75
-
76
-	/**
77
-	 * Creates a Node of the same type that represents a non-existing path
78
-	 *
79
-	 * @param string $path path
80
-	 * @return string non-existing node class
81
-	 * @throws \Exception
82
-	 */
83
-	protected function createNonExistingNode($path) {
84
-		throw new \Exception('Must be implemented by subclasses');
85
-	}
86
-
87
-	/**
88
-	 * Returns the matching file info
89
-	 *
90
-	 * @return FileInfo
91
-	 * @throws InvalidPathException
92
-	 * @throws NotFoundException
93
-	 */
94
-	public function getFileInfo() {
95
-		if (!Filesystem::isValidPath($this->path)) {
96
-			throw new InvalidPathException();
97
-		}
98
-		if (!$this->fileInfo) {
99
-			$fileInfo = $this->view->getFileInfo($this->path);
100
-			if ($fileInfo instanceof FileInfo) {
101
-				$this->fileInfo = $fileInfo;
102
-			} else {
103
-				throw new NotFoundException();
104
-			}
105
-		}
106
-		return $this->fileInfo;
107
-	}
108
-
109
-	/**
110
-	 * @param string[] $hooks
111
-	 */
112
-	protected function sendHooks($hooks, array $args = null) {
113
-		$args = !empty($args) ? $args : [$this];
114
-		$dispatcher = \OC::$server->getEventDispatcher();
115
-		foreach ($hooks as $hook) {
116
-			$this->root->emit('\OC\Files', $hook, $args);
117
-			$dispatcher->dispatch('\OCP\Files::' . $hook, new GenericEvent($args));
118
-		}
119
-	}
120
-
121
-	/**
122
-	 * @param int $permissions
123
-	 * @return bool
124
-	 * @throws InvalidPathException
125
-	 * @throws NotFoundException
126
-	 */
127
-	protected function checkPermissions($permissions) {
128
-		return ($this->getPermissions() & $permissions) === $permissions;
129
-	}
130
-
131
-	public function delete() {
132
-	}
133
-
134
-	/**
135
-	 * @param int $mtime
136
-	 * @throws InvalidPathException
137
-	 * @throws NotFoundException
138
-	 * @throws NotPermittedException
139
-	 */
140
-	public function touch($mtime = null) {
141
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE)) {
142
-			$this->sendHooks(['preTouch']);
143
-			$this->view->touch($this->path, $mtime);
144
-			$this->sendHooks(['postTouch']);
145
-			if ($this->fileInfo) {
146
-				if (is_null($mtime)) {
147
-					$mtime = time();
148
-				}
149
-				$this->fileInfo['mtime'] = $mtime;
150
-			}
151
-		} else {
152
-			throw new NotPermittedException();
153
-		}
154
-	}
155
-
156
-	/**
157
-	 * @return \OC\Files\Storage\Storage
158
-	 * @throws \OCP\Files\NotFoundException
159
-	 */
160
-	public function getStorage() {
161
-		list($storage,) = $this->view->resolvePath($this->path);
162
-		return $storage;
163
-	}
164
-
165
-	/**
166
-	 * @return string
167
-	 */
168
-	public function getPath() {
169
-		return $this->path;
170
-	}
171
-
172
-	/**
173
-	 * @return string
174
-	 */
175
-	public function getInternalPath() {
176
-		list(, $internalPath) = $this->view->resolvePath($this->path);
177
-		return $internalPath;
178
-	}
179
-
180
-	/**
181
-	 * @return int
182
-	 * @throws InvalidPathException
183
-	 * @throws NotFoundException
184
-	 */
185
-	public function getId() {
186
-		return $this->getFileInfo()->getId();
187
-	}
188
-
189
-	/**
190
-	 * @return array
191
-	 */
192
-	public function stat() {
193
-		return $this->view->stat($this->path);
194
-	}
195
-
196
-	/**
197
-	 * @return int
198
-	 * @throws InvalidPathException
199
-	 * @throws NotFoundException
200
-	 */
201
-	public function getMTime() {
202
-		return $this->getFileInfo()->getMTime();
203
-	}
204
-
205
-	/**
206
-	 * @param bool $includeMounts
207
-	 * @return int
208
-	 * @throws InvalidPathException
209
-	 * @throws NotFoundException
210
-	 */
211
-	public function getSize($includeMounts = true) {
212
-		return $this->getFileInfo()->getSize($includeMounts);
213
-	}
214
-
215
-	/**
216
-	 * @return string
217
-	 * @throws InvalidPathException
218
-	 * @throws NotFoundException
219
-	 */
220
-	public function getEtag() {
221
-		return $this->getFileInfo()->getEtag();
222
-	}
223
-
224
-	/**
225
-	 * @return int
226
-	 * @throws InvalidPathException
227
-	 * @throws NotFoundException
228
-	 */
229
-	public function getPermissions() {
230
-		return $this->getFileInfo()->getPermissions();
231
-	}
232
-
233
-	/**
234
-	 * @return bool
235
-	 * @throws InvalidPathException
236
-	 * @throws NotFoundException
237
-	 */
238
-	public function isReadable() {
239
-		return $this->getFileInfo()->isReadable();
240
-	}
241
-
242
-	/**
243
-	 * @return bool
244
-	 * @throws InvalidPathException
245
-	 * @throws NotFoundException
246
-	 */
247
-	public function isUpdateable() {
248
-		return $this->getFileInfo()->isUpdateable();
249
-	}
250
-
251
-	/**
252
-	 * @return bool
253
-	 * @throws InvalidPathException
254
-	 * @throws NotFoundException
255
-	 */
256
-	public function isDeletable() {
257
-		return $this->getFileInfo()->isDeletable();
258
-	}
259
-
260
-	/**
261
-	 * @return bool
262
-	 * @throws InvalidPathException
263
-	 * @throws NotFoundException
264
-	 */
265
-	public function isShareable() {
266
-		return $this->getFileInfo()->isShareable();
267
-	}
268
-
269
-	/**
270
-	 * @return bool
271
-	 * @throws InvalidPathException
272
-	 * @throws NotFoundException
273
-	 */
274
-	public function isCreatable() {
275
-		return $this->getFileInfo()->isCreatable();
276
-	}
277
-
278
-	/**
279
-	 * @return Node
280
-	 */
281
-	public function getParent() {
282
-		$newPath = dirname($this->path);
283
-		if ($newPath === '' || $newPath === '.' || $newPath === '/') {
284
-			return $this->root;
285
-		}
286
-		return $this->root->get($newPath);
287
-	}
288
-
289
-	/**
290
-	 * @return string
291
-	 */
292
-	public function getName() {
293
-		return basename($this->path);
294
-	}
295
-
296
-	/**
297
-	 * @param string $path
298
-	 * @return string
299
-	 */
300
-	protected function normalizePath($path) {
301
-		if ($path === '' or $path === '/') {
302
-			return '/';
303
-		}
304
-		//no windows style slashes
305
-		$path = str_replace('\\', '/', $path);
306
-		//add leading slash
307
-		if ($path[0] !== '/') {
308
-			$path = '/' . $path;
309
-		}
310
-		//remove duplicate slashes
311
-		while (strpos($path, '//') !== false) {
312
-			$path = str_replace('//', '/', $path);
313
-		}
314
-		//remove trailing slash
315
-		$path = rtrim($path, '/');
316
-
317
-		return $path;
318
-	}
319
-
320
-	/**
321
-	 * check if the requested path is valid
322
-	 *
323
-	 * @param string $path
324
-	 * @return bool
325
-	 */
326
-	public function isValidPath($path) {
327
-		if (!$path || $path[0] !== '/') {
328
-			$path = '/' . $path;
329
-		}
330
-		if (strstr($path, '/../') || strrchr($path, '/') === '/..') {
331
-			return false;
332
-		}
333
-		return true;
334
-	}
335
-
336
-	public function isMounted() {
337
-		return $this->getFileInfo()->isMounted();
338
-	}
339
-
340
-	public function isShared() {
341
-		return $this->getFileInfo()->isShared();
342
-	}
343
-
344
-	public function getMimeType() {
345
-		return $this->getFileInfo()->getMimetype();
346
-	}
347
-
348
-	public function getMimePart() {
349
-		return $this->getFileInfo()->getMimePart();
350
-	}
351
-
352
-	public function getType() {
353
-		return $this->getFileInfo()->getType();
354
-	}
355
-
356
-	public function isEncrypted() {
357
-		return $this->getFileInfo()->isEncrypted();
358
-	}
359
-
360
-	public function getMountPoint() {
361
-		return $this->getFileInfo()->getMountPoint();
362
-	}
363
-
364
-	public function getOwner() {
365
-		return $this->getFileInfo()->getOwner();
366
-	}
367
-
368
-	public function getChecksum() {
369
-	}
370
-
371
-	public function getExtension(): string {
372
-		return $this->getFileInfo()->getExtension();
373
-	}
374
-
375
-	/**
376
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
377
-	 * @throws LockedException
378
-	 */
379
-	public function lock($type) {
380
-		$this->view->lockFile($this->path, $type);
381
-	}
382
-
383
-	/**
384
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
385
-	 * @throws LockedException
386
-	 */
387
-	public function changeLock($type) {
388
-		$this->view->changeLock($this->path, $type);
389
-	}
390
-
391
-	/**
392
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
393
-	 * @throws LockedException
394
-	 */
395
-	public function unlock($type) {
396
-		$this->view->unlockFile($this->path, $type);
397
-	}
398
-
399
-	/**
400
-	 * @param string $targetPath
401
-	 * @return \OC\Files\Node\Node
402
-	 * @throws InvalidPathException
403
-	 * @throws NotFoundException
404
-	 * @throws NotPermittedException if copy not allowed or failed
405
-	 */
406
-	public function copy($targetPath) {
407
-		$targetPath = $this->normalizePath($targetPath);
408
-		$parent = $this->root->get(dirname($targetPath));
409
-		if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) {
410
-			$nonExisting = $this->createNonExistingNode($targetPath);
411
-			$this->sendHooks(['preCopy'], [$this, $nonExisting]);
412
-			$this->sendHooks(['preWrite'], [$nonExisting]);
413
-			if (!$this->view->copy($this->path, $targetPath)) {
414
-				throw new NotPermittedException('Could not copy ' . $this->path . ' to ' . $targetPath);
415
-			}
416
-			$targetNode = $this->root->get($targetPath);
417
-			$this->sendHooks(['postCopy'], [$this, $targetNode]);
418
-			$this->sendHooks(['postWrite'], [$targetNode]);
419
-			return $targetNode;
420
-		} else {
421
-			throw new NotPermittedException('No permission to copy to path ' . $targetPath);
422
-		}
423
-	}
424
-
425
-	/**
426
-	 * @param string $targetPath
427
-	 * @return \OC\Files\Node\Node
428
-	 * @throws InvalidPathException
429
-	 * @throws NotFoundException
430
-	 * @throws NotPermittedException if move not allowed or failed
431
-	 * @throws LockedException
432
-	 */
433
-	public function move($targetPath) {
434
-		$targetPath = $this->normalizePath($targetPath);
435
-		$parent = $this->root->get(dirname($targetPath));
436
-		if (
437
-			$parent instanceof Folder and
438
-			$this->isValidPath($targetPath) and
439
-			(
440
-				$parent->isCreatable() ||
441
-				($parent->getInternalPath() === '' && $parent->getMountPoint() instanceof MoveableMount)
442
-			)
443
-		) {
444
-			$nonExisting = $this->createNonExistingNode($targetPath);
445
-			$this->sendHooks(['preRename'], [$this, $nonExisting]);
446
-			$this->sendHooks(['preWrite'], [$nonExisting]);
447
-			if (!$this->view->rename($this->path, $targetPath)) {
448
-				throw new NotPermittedException('Could not move ' . $this->path . ' to ' . $targetPath);
449
-			}
450
-			$targetNode = $this->root->get($targetPath);
451
-			$this->sendHooks(['postRename'], [$this, $targetNode]);
452
-			$this->sendHooks(['postWrite'], [$targetNode]);
453
-			$this->path = $targetPath;
454
-			return $targetNode;
455
-		} else {
456
-			throw new NotPermittedException('No permission to move to path ' . $targetPath);
457
-		}
458
-	}
459
-
460
-	public function getCreationTime(): int {
461
-		return $this->getFileInfo()->getCreationTime();
462
-	}
463
-
464
-	public function getUploadTime(): int {
465
-		return $this->getFileInfo()->getUploadTime();
466
-	}
43
+    /**
44
+     * @var \OC\Files\View $view
45
+     */
46
+    protected $view;
47
+
48
+    /**
49
+     * @var \OC\Files\Node\Root $root
50
+     */
51
+    protected $root;
52
+
53
+    /**
54
+     * @var string $path
55
+     */
56
+    protected $path;
57
+
58
+    /**
59
+     * @var \OCP\Files\FileInfo
60
+     */
61
+    protected $fileInfo;
62
+
63
+    /**
64
+     * @param \OC\Files\View $view
65
+     * @param \OCP\Files\IRootFolder $root
66
+     * @param string $path
67
+     * @param FileInfo $fileInfo
68
+     */
69
+    public function __construct($root, $view, $path, $fileInfo = null) {
70
+        $this->view = $view;
71
+        $this->root = $root;
72
+        $this->path = $path;
73
+        $this->fileInfo = $fileInfo;
74
+    }
75
+
76
+    /**
77
+     * Creates a Node of the same type that represents a non-existing path
78
+     *
79
+     * @param string $path path
80
+     * @return string non-existing node class
81
+     * @throws \Exception
82
+     */
83
+    protected function createNonExistingNode($path) {
84
+        throw new \Exception('Must be implemented by subclasses');
85
+    }
86
+
87
+    /**
88
+     * Returns the matching file info
89
+     *
90
+     * @return FileInfo
91
+     * @throws InvalidPathException
92
+     * @throws NotFoundException
93
+     */
94
+    public function getFileInfo() {
95
+        if (!Filesystem::isValidPath($this->path)) {
96
+            throw new InvalidPathException();
97
+        }
98
+        if (!$this->fileInfo) {
99
+            $fileInfo = $this->view->getFileInfo($this->path);
100
+            if ($fileInfo instanceof FileInfo) {
101
+                $this->fileInfo = $fileInfo;
102
+            } else {
103
+                throw new NotFoundException();
104
+            }
105
+        }
106
+        return $this->fileInfo;
107
+    }
108
+
109
+    /**
110
+     * @param string[] $hooks
111
+     */
112
+    protected function sendHooks($hooks, array $args = null) {
113
+        $args = !empty($args) ? $args : [$this];
114
+        $dispatcher = \OC::$server->getEventDispatcher();
115
+        foreach ($hooks as $hook) {
116
+            $this->root->emit('\OC\Files', $hook, $args);
117
+            $dispatcher->dispatch('\OCP\Files::' . $hook, new GenericEvent($args));
118
+        }
119
+    }
120
+
121
+    /**
122
+     * @param int $permissions
123
+     * @return bool
124
+     * @throws InvalidPathException
125
+     * @throws NotFoundException
126
+     */
127
+    protected function checkPermissions($permissions) {
128
+        return ($this->getPermissions() & $permissions) === $permissions;
129
+    }
130
+
131
+    public function delete() {
132
+    }
133
+
134
+    /**
135
+     * @param int $mtime
136
+     * @throws InvalidPathException
137
+     * @throws NotFoundException
138
+     * @throws NotPermittedException
139
+     */
140
+    public function touch($mtime = null) {
141
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE)) {
142
+            $this->sendHooks(['preTouch']);
143
+            $this->view->touch($this->path, $mtime);
144
+            $this->sendHooks(['postTouch']);
145
+            if ($this->fileInfo) {
146
+                if (is_null($mtime)) {
147
+                    $mtime = time();
148
+                }
149
+                $this->fileInfo['mtime'] = $mtime;
150
+            }
151
+        } else {
152
+            throw new NotPermittedException();
153
+        }
154
+    }
155
+
156
+    /**
157
+     * @return \OC\Files\Storage\Storage
158
+     * @throws \OCP\Files\NotFoundException
159
+     */
160
+    public function getStorage() {
161
+        list($storage,) = $this->view->resolvePath($this->path);
162
+        return $storage;
163
+    }
164
+
165
+    /**
166
+     * @return string
167
+     */
168
+    public function getPath() {
169
+        return $this->path;
170
+    }
171
+
172
+    /**
173
+     * @return string
174
+     */
175
+    public function getInternalPath() {
176
+        list(, $internalPath) = $this->view->resolvePath($this->path);
177
+        return $internalPath;
178
+    }
179
+
180
+    /**
181
+     * @return int
182
+     * @throws InvalidPathException
183
+     * @throws NotFoundException
184
+     */
185
+    public function getId() {
186
+        return $this->getFileInfo()->getId();
187
+    }
188
+
189
+    /**
190
+     * @return array
191
+     */
192
+    public function stat() {
193
+        return $this->view->stat($this->path);
194
+    }
195
+
196
+    /**
197
+     * @return int
198
+     * @throws InvalidPathException
199
+     * @throws NotFoundException
200
+     */
201
+    public function getMTime() {
202
+        return $this->getFileInfo()->getMTime();
203
+    }
204
+
205
+    /**
206
+     * @param bool $includeMounts
207
+     * @return int
208
+     * @throws InvalidPathException
209
+     * @throws NotFoundException
210
+     */
211
+    public function getSize($includeMounts = true) {
212
+        return $this->getFileInfo()->getSize($includeMounts);
213
+    }
214
+
215
+    /**
216
+     * @return string
217
+     * @throws InvalidPathException
218
+     * @throws NotFoundException
219
+     */
220
+    public function getEtag() {
221
+        return $this->getFileInfo()->getEtag();
222
+    }
223
+
224
+    /**
225
+     * @return int
226
+     * @throws InvalidPathException
227
+     * @throws NotFoundException
228
+     */
229
+    public function getPermissions() {
230
+        return $this->getFileInfo()->getPermissions();
231
+    }
232
+
233
+    /**
234
+     * @return bool
235
+     * @throws InvalidPathException
236
+     * @throws NotFoundException
237
+     */
238
+    public function isReadable() {
239
+        return $this->getFileInfo()->isReadable();
240
+    }
241
+
242
+    /**
243
+     * @return bool
244
+     * @throws InvalidPathException
245
+     * @throws NotFoundException
246
+     */
247
+    public function isUpdateable() {
248
+        return $this->getFileInfo()->isUpdateable();
249
+    }
250
+
251
+    /**
252
+     * @return bool
253
+     * @throws InvalidPathException
254
+     * @throws NotFoundException
255
+     */
256
+    public function isDeletable() {
257
+        return $this->getFileInfo()->isDeletable();
258
+    }
259
+
260
+    /**
261
+     * @return bool
262
+     * @throws InvalidPathException
263
+     * @throws NotFoundException
264
+     */
265
+    public function isShareable() {
266
+        return $this->getFileInfo()->isShareable();
267
+    }
268
+
269
+    /**
270
+     * @return bool
271
+     * @throws InvalidPathException
272
+     * @throws NotFoundException
273
+     */
274
+    public function isCreatable() {
275
+        return $this->getFileInfo()->isCreatable();
276
+    }
277
+
278
+    /**
279
+     * @return Node
280
+     */
281
+    public function getParent() {
282
+        $newPath = dirname($this->path);
283
+        if ($newPath === '' || $newPath === '.' || $newPath === '/') {
284
+            return $this->root;
285
+        }
286
+        return $this->root->get($newPath);
287
+    }
288
+
289
+    /**
290
+     * @return string
291
+     */
292
+    public function getName() {
293
+        return basename($this->path);
294
+    }
295
+
296
+    /**
297
+     * @param string $path
298
+     * @return string
299
+     */
300
+    protected function normalizePath($path) {
301
+        if ($path === '' or $path === '/') {
302
+            return '/';
303
+        }
304
+        //no windows style slashes
305
+        $path = str_replace('\\', '/', $path);
306
+        //add leading slash
307
+        if ($path[0] !== '/') {
308
+            $path = '/' . $path;
309
+        }
310
+        //remove duplicate slashes
311
+        while (strpos($path, '//') !== false) {
312
+            $path = str_replace('//', '/', $path);
313
+        }
314
+        //remove trailing slash
315
+        $path = rtrim($path, '/');
316
+
317
+        return $path;
318
+    }
319
+
320
+    /**
321
+     * check if the requested path is valid
322
+     *
323
+     * @param string $path
324
+     * @return bool
325
+     */
326
+    public function isValidPath($path) {
327
+        if (!$path || $path[0] !== '/') {
328
+            $path = '/' . $path;
329
+        }
330
+        if (strstr($path, '/../') || strrchr($path, '/') === '/..') {
331
+            return false;
332
+        }
333
+        return true;
334
+    }
335
+
336
+    public function isMounted() {
337
+        return $this->getFileInfo()->isMounted();
338
+    }
339
+
340
+    public function isShared() {
341
+        return $this->getFileInfo()->isShared();
342
+    }
343
+
344
+    public function getMimeType() {
345
+        return $this->getFileInfo()->getMimetype();
346
+    }
347
+
348
+    public function getMimePart() {
349
+        return $this->getFileInfo()->getMimePart();
350
+    }
351
+
352
+    public function getType() {
353
+        return $this->getFileInfo()->getType();
354
+    }
355
+
356
+    public function isEncrypted() {
357
+        return $this->getFileInfo()->isEncrypted();
358
+    }
359
+
360
+    public function getMountPoint() {
361
+        return $this->getFileInfo()->getMountPoint();
362
+    }
363
+
364
+    public function getOwner() {
365
+        return $this->getFileInfo()->getOwner();
366
+    }
367
+
368
+    public function getChecksum() {
369
+    }
370
+
371
+    public function getExtension(): string {
372
+        return $this->getFileInfo()->getExtension();
373
+    }
374
+
375
+    /**
376
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
377
+     * @throws LockedException
378
+     */
379
+    public function lock($type) {
380
+        $this->view->lockFile($this->path, $type);
381
+    }
382
+
383
+    /**
384
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
385
+     * @throws LockedException
386
+     */
387
+    public function changeLock($type) {
388
+        $this->view->changeLock($this->path, $type);
389
+    }
390
+
391
+    /**
392
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
393
+     * @throws LockedException
394
+     */
395
+    public function unlock($type) {
396
+        $this->view->unlockFile($this->path, $type);
397
+    }
398
+
399
+    /**
400
+     * @param string $targetPath
401
+     * @return \OC\Files\Node\Node
402
+     * @throws InvalidPathException
403
+     * @throws NotFoundException
404
+     * @throws NotPermittedException if copy not allowed or failed
405
+     */
406
+    public function copy($targetPath) {
407
+        $targetPath = $this->normalizePath($targetPath);
408
+        $parent = $this->root->get(dirname($targetPath));
409
+        if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) {
410
+            $nonExisting = $this->createNonExistingNode($targetPath);
411
+            $this->sendHooks(['preCopy'], [$this, $nonExisting]);
412
+            $this->sendHooks(['preWrite'], [$nonExisting]);
413
+            if (!$this->view->copy($this->path, $targetPath)) {
414
+                throw new NotPermittedException('Could not copy ' . $this->path . ' to ' . $targetPath);
415
+            }
416
+            $targetNode = $this->root->get($targetPath);
417
+            $this->sendHooks(['postCopy'], [$this, $targetNode]);
418
+            $this->sendHooks(['postWrite'], [$targetNode]);
419
+            return $targetNode;
420
+        } else {
421
+            throw new NotPermittedException('No permission to copy to path ' . $targetPath);
422
+        }
423
+    }
424
+
425
+    /**
426
+     * @param string $targetPath
427
+     * @return \OC\Files\Node\Node
428
+     * @throws InvalidPathException
429
+     * @throws NotFoundException
430
+     * @throws NotPermittedException if move not allowed or failed
431
+     * @throws LockedException
432
+     */
433
+    public function move($targetPath) {
434
+        $targetPath = $this->normalizePath($targetPath);
435
+        $parent = $this->root->get(dirname($targetPath));
436
+        if (
437
+            $parent instanceof Folder and
438
+            $this->isValidPath($targetPath) and
439
+            (
440
+                $parent->isCreatable() ||
441
+                ($parent->getInternalPath() === '' && $parent->getMountPoint() instanceof MoveableMount)
442
+            )
443
+        ) {
444
+            $nonExisting = $this->createNonExistingNode($targetPath);
445
+            $this->sendHooks(['preRename'], [$this, $nonExisting]);
446
+            $this->sendHooks(['preWrite'], [$nonExisting]);
447
+            if (!$this->view->rename($this->path, $targetPath)) {
448
+                throw new NotPermittedException('Could not move ' . $this->path . ' to ' . $targetPath);
449
+            }
450
+            $targetNode = $this->root->get($targetPath);
451
+            $this->sendHooks(['postRename'], [$this, $targetNode]);
452
+            $this->sendHooks(['postWrite'], [$targetNode]);
453
+            $this->path = $targetPath;
454
+            return $targetNode;
455
+        } else {
456
+            throw new NotPermittedException('No permission to move to path ' . $targetPath);
457
+        }
458
+    }
459
+
460
+    public function getCreationTime(): int {
461
+        return $this->getFileInfo()->getCreationTime();
462
+    }
463
+
464
+    public function getUploadTime(): int {
465
+        return $this->getFileInfo()->getUploadTime();
466
+    }
467 467
 
468 468
 }
Please login to merge, or discard this patch.
lib/private/Files/Node/Folder.php 1 patch
Indentation   +489 added lines, -489 removed lines patch added patch discarded remove patch
@@ -40,493 +40,493 @@
 block discarded – undo
40 40
 use OCP\Files\Search\ISearchQuery;
41 41
 
42 42
 class Folder extends Node implements \OCP\Files\Folder {
43
-	/**
44
-	 * Creates a Folder that represents a non-existing path
45
-	 *
46
-	 * @param string $path path
47
-	 * @return string non-existing node class
48
-	 */
49
-	protected function createNonExistingNode($path) {
50
-		return new NonExistingFolder($this->root, $this->view, $path);
51
-	}
52
-
53
-	/**
54
-	 * @param string $path path relative to the folder
55
-	 * @return string
56
-	 * @throws \OCP\Files\NotPermittedException
57
-	 */
58
-	public function getFullPath($path) {
59
-		if (!$this->isValidPath($path)) {
60
-			throw new NotPermittedException('Invalid path');
61
-		}
62
-		return $this->path . $this->normalizePath($path);
63
-	}
64
-
65
-	/**
66
-	 * @param string $path
67
-	 * @return string
68
-	 */
69
-	public function getRelativePath($path) {
70
-		if ($this->path === '' or $this->path === '/') {
71
-			return $this->normalizePath($path);
72
-		}
73
-		if ($path === $this->path) {
74
-			return '/';
75
-		} else if (strpos($path, $this->path . '/') !== 0) {
76
-			return null;
77
-		} else {
78
-			$path = substr($path, strlen($this->path));
79
-			return $this->normalizePath($path);
80
-		}
81
-	}
82
-
83
-	/**
84
-	 * check if a node is a (grand-)child of the folder
85
-	 *
86
-	 * @param \OC\Files\Node\Node $node
87
-	 * @return bool
88
-	 */
89
-	public function isSubNode($node) {
90
-		return strpos($node->getPath(), $this->path . '/') === 0;
91
-	}
92
-
93
-	/**
94
-	 * get the content of this directory
95
-	 *
96
-	 * @throws \OCP\Files\NotFoundException
97
-	 * @return Node[]
98
-	 */
99
-	public function getDirectoryListing() {
100
-		$folderContent = $this->view->getDirectoryContent($this->path);
101
-
102
-		return array_map(function (FileInfo $info) {
103
-			if ($info->getMimetype() === 'httpd/unix-directory') {
104
-				return new Folder($this->root, $this->view, $info->getPath(), $info);
105
-			} else {
106
-				return new File($this->root, $this->view, $info->getPath(), $info);
107
-			}
108
-		}, $folderContent);
109
-	}
110
-
111
-	/**
112
-	 * @param string $path
113
-	 * @param FileInfo $info
114
-	 * @return File|Folder
115
-	 */
116
-	protected function createNode($path, FileInfo $info = null) {
117
-		if (is_null($info)) {
118
-			$isDir = $this->view->is_dir($path);
119
-		} else {
120
-			$isDir = $info->getType() === FileInfo::TYPE_FOLDER;
121
-		}
122
-		if ($isDir) {
123
-			return new Folder($this->root, $this->view, $path, $info);
124
-		} else {
125
-			return new File($this->root, $this->view, $path, $info);
126
-		}
127
-	}
128
-
129
-	/**
130
-	 * Get the node at $path
131
-	 *
132
-	 * @param string $path
133
-	 * @return \OC\Files\Node\Node
134
-	 * @throws \OCP\Files\NotFoundException
135
-	 */
136
-	public function get($path) {
137
-		return $this->root->get($this->getFullPath($path));
138
-	}
139
-
140
-	/**
141
-	 * @param string $path
142
-	 * @return bool
143
-	 */
144
-	public function nodeExists($path) {
145
-		try {
146
-			$this->get($path);
147
-			return true;
148
-		} catch (NotFoundException $e) {
149
-			return false;
150
-		}
151
-	}
152
-
153
-	/**
154
-	 * @param string $path
155
-	 * @return \OC\Files\Node\Folder
156
-	 * @throws \OCP\Files\NotPermittedException
157
-	 */
158
-	public function newFolder($path) {
159
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
160
-			$fullPath = $this->getFullPath($path);
161
-			$nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
162
-			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
163
-			if(!$this->view->mkdir($fullPath)) {
164
-				throw new NotPermittedException('Could not create folder');
165
-			}
166
-			$node = new Folder($this->root, $this->view, $fullPath);
167
-			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
168
-			return $node;
169
-		} else {
170
-			throw new NotPermittedException('No create permission for folder');
171
-		}
172
-	}
173
-
174
-	/**
175
-	 * @param string $path
176
-	 * @param string | resource | null $content
177
-	 * @return \OC\Files\Node\File
178
-	 * @throws \OCP\Files\NotPermittedException
179
-	 */
180
-	public function newFile($path, $content = null) {
181
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
182
-			$fullPath = $this->getFullPath($path);
183
-			$nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
184
-			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
185
-			if ($content !== null) {
186
-				$result = $this->view->file_put_contents($fullPath, $content);
187
-			} else {
188
-				$result = $this->view->touch($fullPath);
189
-			}
190
-			if (!$result) {
191
-				throw new NotPermittedException('Could not create path');
192
-			}
193
-			$node = new File($this->root, $this->view, $fullPath);
194
-			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
195
-			return $node;
196
-		}
197
-		throw new NotPermittedException('No create permission for path');
198
-	}
199
-
200
-	/**
201
-	 * search for files with the name matching $query
202
-	 *
203
-	 * @param string|ISearchQuery $query
204
-	 * @return \OC\Files\Node\Node[]
205
-	 */
206
-	public function search($query) {
207
-		if (is_string($query)) {
208
-			return $this->searchCommon('search', ['%' . $query . '%']);
209
-		} else {
210
-			return $this->searchCommon('searchQuery', [$query]);
211
-		}
212
-	}
213
-
214
-	/**
215
-	 * search for files by mimetype
216
-	 *
217
-	 * @param string $mimetype
218
-	 * @return Node[]
219
-	 */
220
-	public function searchByMime($mimetype) {
221
-		return $this->searchCommon('searchByMime', [$mimetype]);
222
-	}
223
-
224
-	/**
225
-	 * search for files by tag
226
-	 *
227
-	 * @param string|int $tag name or tag id
228
-	 * @param string $userId owner of the tags
229
-	 * @return Node[]
230
-	 */
231
-	public function searchByTag($tag, $userId) {
232
-		return $this->searchCommon('searchByTag', [$tag, $userId]);
233
-	}
234
-
235
-	/**
236
-	 * @param string $method cache method
237
-	 * @param array $args call args
238
-	 * @return \OC\Files\Node\Node[]
239
-	 */
240
-	private function searchCommon($method, $args) {
241
-		$limitToHome = ($method === 'searchQuery')? $args[0]->limitToHome(): false;
242
-		if ($limitToHome && count(explode('/', $this->path)) !== 3) {
243
-			throw new \InvalidArgumentException('searching by owner is only allows on the users home folder');
244
-		}
245
-
246
-		$files = [];
247
-		$rootLength = strlen($this->path);
248
-		$mount = $this->root->getMount($this->path);
249
-		$storage = $mount->getStorage();
250
-		$internalPath = $mount->getInternalPath($this->path);
251
-		$internalPath = rtrim($internalPath, '/');
252
-		if ($internalPath !== '') {
253
-			$internalPath = $internalPath . '/';
254
-		}
255
-		$internalRootLength = strlen($internalPath);
256
-
257
-		$cache = $storage->getCache('');
258
-
259
-		$results = call_user_func_array([$cache, $method], $args);
260
-		foreach ($results as $result) {
261
-			if ($internalRootLength === 0 or substr($result['path'], 0, $internalRootLength) === $internalPath) {
262
-				$result['internalPath'] = $result['path'];
263
-				$result['path'] = substr($result['path'], $internalRootLength);
264
-				$result['storage'] = $storage;
265
-				$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount);
266
-			}
267
-		}
268
-
269
-		if (!$limitToHome) {
270
-			$mounts = $this->root->getMountsIn($this->path);
271
-			foreach ($mounts as $mount) {
272
-				$storage = $mount->getStorage();
273
-				if ($storage) {
274
-					$cache = $storage->getCache('');
275
-
276
-					$relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
277
-					$results = call_user_func_array([$cache, $method], $args);
278
-					foreach ($results as $result) {
279
-						$result['internalPath'] = $result['path'];
280
-						$result['path'] = $relativeMountPoint . $result['path'];
281
-						$result['storage'] = $storage;
282
-						$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage,
283
-							$result['internalPath'], $result, $mount);
284
-					}
285
-				}
286
-			}
287
-		}
288
-
289
-		return array_map(function (FileInfo $file) {
290
-			return $this->createNode($file->getPath(), $file);
291
-		}, $files);
292
-	}
293
-
294
-	/**
295
-	 * @param int $id
296
-	 * @return \OC\Files\Node\Node[]
297
-	 */
298
-	public function getById($id) {
299
-		$mountCache = $this->root->getUserMountCache();
300
-		if (strpos($this->getPath(), '/', 1) > 0) {
301
-			list(, $user) = explode('/', $this->getPath());
302
-		} else {
303
-			$user = null;
304
-		}
305
-		$mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user);
306
-		$mounts = $this->root->getMountsIn($this->path);
307
-		$mounts[] = $this->root->getMount($this->path);
308
-		/** @var IMountPoint[] $folderMounts */
309
-		$folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) {
310
-			return $mountPoint->getMountPoint();
311
-		}, $mounts), $mounts);
312
-
313
-		/** @var ICachedMountInfo[] $mountsContainingFile */
314
-		$mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) {
315
-			return isset($folderMounts[$cachedMountInfo->getMountPoint()]);
316
-		}));
317
-
318
-		if (count($mountsContainingFile) === 0) {
319
-			if ($user === $this->getAppDataDirectoryName()) {
320
-				return $this->getByIdInRootMount((int) $id);
321
-			}
322
-			return [];
323
-		}
324
-
325
-		$nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) {
326
-			$mount = $folderMounts[$cachedMountInfo->getMountPoint()];
327
-			$cacheEntry = $mount->getStorage()->getCache()->get((int)$id);
328
-			if (!$cacheEntry) {
329
-				return null;
330
-			}
331
-
332
-			// cache jails will hide the "true" internal path
333
-			$internalPath = ltrim($cachedMountInfo->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/');
334
-			$pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath()));
335
-			$pathRelativeToMount = ltrim($pathRelativeToMount, '/');
336
-			$absolutePath = rtrim($cachedMountInfo->getMountPoint() . $pathRelativeToMount, '/');
337
-			return $this->root->createNode($absolutePath, new \OC\Files\FileInfo(
338
-				$absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount,
339
-				\OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount))
340
-			));
341
-		}, $mountsContainingFile);
342
-
343
-		$nodes = array_filter($nodes);
344
-
345
-		return array_filter($nodes, function (Node $node) {
346
-			return $this->getRelativePath($node->getPath());
347
-		});
348
-	}
349
-
350
-	protected function getAppDataDirectoryName(): string {
351
-		$instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
352
-		return 'appdata_' . $instanceId;
353
-	}
354
-
355
-	/**
356
-	 * In case the path we are currently in is inside the appdata_* folder,
357
-	 * the original getById method does not work, because it can only look inside
358
-	 * the user's mount points. But the user has no mount point for the root storage.
359
-	 *
360
-	 * So in that case we directly check the mount of the root if it contains
361
-	 * the id. If it does we check if the path is inside the path we are working
362
-	 * in.
363
-	 *
364
-	 * @param int $id
365
-	 * @return array
366
-	 */
367
-	protected function getByIdInRootMount(int $id): array {
368
-		$mount = $this->root->getMount('');
369
-		$cacheEntry = $mount->getStorage()->getCache($this->path)->get($id);
370
-		if (!$cacheEntry) {
371
-			return [];
372
-		}
373
-
374
-		$absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
375
-		$currentPath = rtrim($this->path, '/') . '/';
376
-
377
-		if (strpos($absolutePath, $currentPath) !== 0) {
378
-			return [];
379
-		}
380
-
381
-		return [$this->root->createNode(
382
-			$absolutePath, new \OC\Files\FileInfo(
383
-				$absolutePath,
384
-				$mount->getStorage(),
385
-				$cacheEntry->getPath(),
386
-				$cacheEntry,
387
-				$mount
388
-		))];
389
-	}
390
-
391
-	public function getFreeSpace() {
392
-		return $this->view->free_space($this->path);
393
-	}
394
-
395
-	public function delete() {
396
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
397
-			$this->sendHooks(['preDelete']);
398
-			$fileInfo = $this->getFileInfo();
399
-			$this->view->rmdir($this->path);
400
-			$nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
401
-			$this->sendHooks(['postDelete'], [$nonExisting]);
402
-			$this->exists = false;
403
-		} else {
404
-			throw new NotPermittedException('No delete permission for path');
405
-		}
406
-	}
407
-
408
-	/**
409
-	 * Add a suffix to the name in case the file exists
410
-	 *
411
-	 * @param string $name
412
-	 * @return string
413
-	 * @throws NotPermittedException
414
-	 */
415
-	public function getNonExistingName($name) {
416
-		$uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
417
-		return trim($this->getRelativePath($uniqueName), '/');
418
-	}
419
-
420
-	/**
421
-	 * @param int $limit
422
-	 * @param int $offset
423
-	 * @return \OCP\Files\Node[]
424
-	 */
425
-	public function getRecent($limit, $offset = 0) {
426
-		$mimetypeLoader = \OC::$server->getMimeTypeLoader();
427
-		$mounts = $this->root->getMountsIn($this->path);
428
-		$mounts[] = $this->getMountPoint();
429
-
430
-		$mounts = array_filter($mounts, function (IMountPoint $mount) {
431
-			return $mount->getStorage();
432
-		});
433
-		$storageIds = array_map(function (IMountPoint $mount) {
434
-			return $mount->getStorage()->getCache()->getNumericStorageId();
435
-		}, $mounts);
436
-		/** @var IMountPoint[] $mountMap */
437
-		$mountMap = array_combine($storageIds, $mounts);
438
-		$folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER);
439
-
440
-		// Search in batches of 500 entries
441
-		$searchLimit = 500;
442
-		$results = [];
443
-		$searchResultCount = 0;
444
-		$count = 0;
445
-		do {
446
-			$searchResult = $this->recentSearch($searchLimit, $offset, $storageIds, $folderMimetype);
447
-
448
-			// Exit condition if there are no more results
449
-			if (count($searchResult) === 0) {
450
-				break;
451
-			}
452
-
453
-			$searchResultCount += count($searchResult);
454
-
455
-			$parseResult = $this->recentParse($searchResult, $mountMap, $mimetypeLoader);
456
-
457
-			foreach ($parseResult as $result) {
458
-				$results[] = $result;
459
-			}
460
-
461
-			$offset += $searchLimit;
462
-			$count++;
463
-		} while (count($results) < $limit && ($searchResultCount < (3 * $limit) || $count < 5));
464
-
465
-		return array_slice($results, 0, $limit);
466
-	}
467
-
468
-	private function recentSearch($limit, $offset, $storageIds, $folderMimetype) {
469
-		$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
470
-		$query = $builder
471
-			->select('f.*')
472
-			->from('filecache', 'f')
473
-			->andWhere($builder->expr()->in('f.storage', $builder->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)))
474
-			->andWhere($builder->expr()->orX(
475
-			// handle non empty folders separate
476
-				$builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)),
477
-				$builder->expr()->eq('f.size', new Literal(0))
478
-			))
479
-			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_versions/%')))
480
-			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_trashbin/%')))
481
-			->orderBy('f.mtime', 'DESC')
482
-			->setMaxResults($limit)
483
-			->setFirstResult($offset);
484
-		return $query->execute()->fetchAll();
485
-	}
486
-
487
-	private function recentParse($result, $mountMap, $mimetypeLoader) {
488
-		$files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) {
489
-			$mount = $mountMap[$entry['storage']];
490
-			$entry['internalPath'] = $entry['path'];
491
-			$entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']);
492
-			$entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']);
493
-			$path = $this->getAbsolutePath($mount, $entry['path']);
494
-			if (is_null($path)) {
495
-				return null;
496
-			}
497
-			$fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount);
498
-			return $this->root->createNode($fileInfo->getPath(), $fileInfo);
499
-		}, $result));
500
-
501
-		return array_values(array_filter($files, function (Node $node) {
502
-			$cacheEntry = $node->getMountPoint()->getStorage()->getCache()->get($node->getId());
503
-			if (!$cacheEntry) {
504
-				return false;
505
-			}
506
-			$relative = $this->getRelativePath($node->getPath());
507
-			return $relative !== null && $relative !== '/'
508
-				&& ($cacheEntry->getPermissions() & \OCP\Constants::PERMISSION_READ) === \OCP\Constants::PERMISSION_READ;
509
-		}));
510
-	}
511
-
512
-	private function getAbsolutePath(IMountPoint $mount, $path) {
513
-		$storage = $mount->getStorage();
514
-		if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) {
515
-			if ($storage->instanceOfStorage(SharedStorage::class)) {
516
-				$storage->getSourceStorage();
517
-			}
518
-			/** @var \OC\Files\Storage\Wrapper\Jail $storage */
519
-			$jailRoot = $storage->getUnjailedPath('');
520
-			$rootLength = strlen($jailRoot) + 1;
521
-			if ($path === $jailRoot) {
522
-				return $mount->getMountPoint();
523
-			} else if (substr($path, 0, $rootLength) === $jailRoot . '/') {
524
-				return $mount->getMountPoint() . substr($path, $rootLength);
525
-			} else {
526
-				return null;
527
-			}
528
-		} else {
529
-			return $mount->getMountPoint() . $path;
530
-		}
531
-	}
43
+    /**
44
+     * Creates a Folder that represents a non-existing path
45
+     *
46
+     * @param string $path path
47
+     * @return string non-existing node class
48
+     */
49
+    protected function createNonExistingNode($path) {
50
+        return new NonExistingFolder($this->root, $this->view, $path);
51
+    }
52
+
53
+    /**
54
+     * @param string $path path relative to the folder
55
+     * @return string
56
+     * @throws \OCP\Files\NotPermittedException
57
+     */
58
+    public function getFullPath($path) {
59
+        if (!$this->isValidPath($path)) {
60
+            throw new NotPermittedException('Invalid path');
61
+        }
62
+        return $this->path . $this->normalizePath($path);
63
+    }
64
+
65
+    /**
66
+     * @param string $path
67
+     * @return string
68
+     */
69
+    public function getRelativePath($path) {
70
+        if ($this->path === '' or $this->path === '/') {
71
+            return $this->normalizePath($path);
72
+        }
73
+        if ($path === $this->path) {
74
+            return '/';
75
+        } else if (strpos($path, $this->path . '/') !== 0) {
76
+            return null;
77
+        } else {
78
+            $path = substr($path, strlen($this->path));
79
+            return $this->normalizePath($path);
80
+        }
81
+    }
82
+
83
+    /**
84
+     * check if a node is a (grand-)child of the folder
85
+     *
86
+     * @param \OC\Files\Node\Node $node
87
+     * @return bool
88
+     */
89
+    public function isSubNode($node) {
90
+        return strpos($node->getPath(), $this->path . '/') === 0;
91
+    }
92
+
93
+    /**
94
+     * get the content of this directory
95
+     *
96
+     * @throws \OCP\Files\NotFoundException
97
+     * @return Node[]
98
+     */
99
+    public function getDirectoryListing() {
100
+        $folderContent = $this->view->getDirectoryContent($this->path);
101
+
102
+        return array_map(function (FileInfo $info) {
103
+            if ($info->getMimetype() === 'httpd/unix-directory') {
104
+                return new Folder($this->root, $this->view, $info->getPath(), $info);
105
+            } else {
106
+                return new File($this->root, $this->view, $info->getPath(), $info);
107
+            }
108
+        }, $folderContent);
109
+    }
110
+
111
+    /**
112
+     * @param string $path
113
+     * @param FileInfo $info
114
+     * @return File|Folder
115
+     */
116
+    protected function createNode($path, FileInfo $info = null) {
117
+        if (is_null($info)) {
118
+            $isDir = $this->view->is_dir($path);
119
+        } else {
120
+            $isDir = $info->getType() === FileInfo::TYPE_FOLDER;
121
+        }
122
+        if ($isDir) {
123
+            return new Folder($this->root, $this->view, $path, $info);
124
+        } else {
125
+            return new File($this->root, $this->view, $path, $info);
126
+        }
127
+    }
128
+
129
+    /**
130
+     * Get the node at $path
131
+     *
132
+     * @param string $path
133
+     * @return \OC\Files\Node\Node
134
+     * @throws \OCP\Files\NotFoundException
135
+     */
136
+    public function get($path) {
137
+        return $this->root->get($this->getFullPath($path));
138
+    }
139
+
140
+    /**
141
+     * @param string $path
142
+     * @return bool
143
+     */
144
+    public function nodeExists($path) {
145
+        try {
146
+            $this->get($path);
147
+            return true;
148
+        } catch (NotFoundException $e) {
149
+            return false;
150
+        }
151
+    }
152
+
153
+    /**
154
+     * @param string $path
155
+     * @return \OC\Files\Node\Folder
156
+     * @throws \OCP\Files\NotPermittedException
157
+     */
158
+    public function newFolder($path) {
159
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
160
+            $fullPath = $this->getFullPath($path);
161
+            $nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
162
+            $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
163
+            if(!$this->view->mkdir($fullPath)) {
164
+                throw new NotPermittedException('Could not create folder');
165
+            }
166
+            $node = new Folder($this->root, $this->view, $fullPath);
167
+            $this->sendHooks(['postWrite', 'postCreate'], [$node]);
168
+            return $node;
169
+        } else {
170
+            throw new NotPermittedException('No create permission for folder');
171
+        }
172
+    }
173
+
174
+    /**
175
+     * @param string $path
176
+     * @param string | resource | null $content
177
+     * @return \OC\Files\Node\File
178
+     * @throws \OCP\Files\NotPermittedException
179
+     */
180
+    public function newFile($path, $content = null) {
181
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
182
+            $fullPath = $this->getFullPath($path);
183
+            $nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
184
+            $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
185
+            if ($content !== null) {
186
+                $result = $this->view->file_put_contents($fullPath, $content);
187
+            } else {
188
+                $result = $this->view->touch($fullPath);
189
+            }
190
+            if (!$result) {
191
+                throw new NotPermittedException('Could not create path');
192
+            }
193
+            $node = new File($this->root, $this->view, $fullPath);
194
+            $this->sendHooks(['postWrite', 'postCreate'], [$node]);
195
+            return $node;
196
+        }
197
+        throw new NotPermittedException('No create permission for path');
198
+    }
199
+
200
+    /**
201
+     * search for files with the name matching $query
202
+     *
203
+     * @param string|ISearchQuery $query
204
+     * @return \OC\Files\Node\Node[]
205
+     */
206
+    public function search($query) {
207
+        if (is_string($query)) {
208
+            return $this->searchCommon('search', ['%' . $query . '%']);
209
+        } else {
210
+            return $this->searchCommon('searchQuery', [$query]);
211
+        }
212
+    }
213
+
214
+    /**
215
+     * search for files by mimetype
216
+     *
217
+     * @param string $mimetype
218
+     * @return Node[]
219
+     */
220
+    public function searchByMime($mimetype) {
221
+        return $this->searchCommon('searchByMime', [$mimetype]);
222
+    }
223
+
224
+    /**
225
+     * search for files by tag
226
+     *
227
+     * @param string|int $tag name or tag id
228
+     * @param string $userId owner of the tags
229
+     * @return Node[]
230
+     */
231
+    public function searchByTag($tag, $userId) {
232
+        return $this->searchCommon('searchByTag', [$tag, $userId]);
233
+    }
234
+
235
+    /**
236
+     * @param string $method cache method
237
+     * @param array $args call args
238
+     * @return \OC\Files\Node\Node[]
239
+     */
240
+    private function searchCommon($method, $args) {
241
+        $limitToHome = ($method === 'searchQuery')? $args[0]->limitToHome(): false;
242
+        if ($limitToHome && count(explode('/', $this->path)) !== 3) {
243
+            throw new \InvalidArgumentException('searching by owner is only allows on the users home folder');
244
+        }
245
+
246
+        $files = [];
247
+        $rootLength = strlen($this->path);
248
+        $mount = $this->root->getMount($this->path);
249
+        $storage = $mount->getStorage();
250
+        $internalPath = $mount->getInternalPath($this->path);
251
+        $internalPath = rtrim($internalPath, '/');
252
+        if ($internalPath !== '') {
253
+            $internalPath = $internalPath . '/';
254
+        }
255
+        $internalRootLength = strlen($internalPath);
256
+
257
+        $cache = $storage->getCache('');
258
+
259
+        $results = call_user_func_array([$cache, $method], $args);
260
+        foreach ($results as $result) {
261
+            if ($internalRootLength === 0 or substr($result['path'], 0, $internalRootLength) === $internalPath) {
262
+                $result['internalPath'] = $result['path'];
263
+                $result['path'] = substr($result['path'], $internalRootLength);
264
+                $result['storage'] = $storage;
265
+                $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount);
266
+            }
267
+        }
268
+
269
+        if (!$limitToHome) {
270
+            $mounts = $this->root->getMountsIn($this->path);
271
+            foreach ($mounts as $mount) {
272
+                $storage = $mount->getStorage();
273
+                if ($storage) {
274
+                    $cache = $storage->getCache('');
275
+
276
+                    $relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
277
+                    $results = call_user_func_array([$cache, $method], $args);
278
+                    foreach ($results as $result) {
279
+                        $result['internalPath'] = $result['path'];
280
+                        $result['path'] = $relativeMountPoint . $result['path'];
281
+                        $result['storage'] = $storage;
282
+                        $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage,
283
+                            $result['internalPath'], $result, $mount);
284
+                    }
285
+                }
286
+            }
287
+        }
288
+
289
+        return array_map(function (FileInfo $file) {
290
+            return $this->createNode($file->getPath(), $file);
291
+        }, $files);
292
+    }
293
+
294
+    /**
295
+     * @param int $id
296
+     * @return \OC\Files\Node\Node[]
297
+     */
298
+    public function getById($id) {
299
+        $mountCache = $this->root->getUserMountCache();
300
+        if (strpos($this->getPath(), '/', 1) > 0) {
301
+            list(, $user) = explode('/', $this->getPath());
302
+        } else {
303
+            $user = null;
304
+        }
305
+        $mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user);
306
+        $mounts = $this->root->getMountsIn($this->path);
307
+        $mounts[] = $this->root->getMount($this->path);
308
+        /** @var IMountPoint[] $folderMounts */
309
+        $folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) {
310
+            return $mountPoint->getMountPoint();
311
+        }, $mounts), $mounts);
312
+
313
+        /** @var ICachedMountInfo[] $mountsContainingFile */
314
+        $mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) {
315
+            return isset($folderMounts[$cachedMountInfo->getMountPoint()]);
316
+        }));
317
+
318
+        if (count($mountsContainingFile) === 0) {
319
+            if ($user === $this->getAppDataDirectoryName()) {
320
+                return $this->getByIdInRootMount((int) $id);
321
+            }
322
+            return [];
323
+        }
324
+
325
+        $nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) {
326
+            $mount = $folderMounts[$cachedMountInfo->getMountPoint()];
327
+            $cacheEntry = $mount->getStorage()->getCache()->get((int)$id);
328
+            if (!$cacheEntry) {
329
+                return null;
330
+            }
331
+
332
+            // cache jails will hide the "true" internal path
333
+            $internalPath = ltrim($cachedMountInfo->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/');
334
+            $pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath()));
335
+            $pathRelativeToMount = ltrim($pathRelativeToMount, '/');
336
+            $absolutePath = rtrim($cachedMountInfo->getMountPoint() . $pathRelativeToMount, '/');
337
+            return $this->root->createNode($absolutePath, new \OC\Files\FileInfo(
338
+                $absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount,
339
+                \OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount))
340
+            ));
341
+        }, $mountsContainingFile);
342
+
343
+        $nodes = array_filter($nodes);
344
+
345
+        return array_filter($nodes, function (Node $node) {
346
+            return $this->getRelativePath($node->getPath());
347
+        });
348
+    }
349
+
350
+    protected function getAppDataDirectoryName(): string {
351
+        $instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
352
+        return 'appdata_' . $instanceId;
353
+    }
354
+
355
+    /**
356
+     * In case the path we are currently in is inside the appdata_* folder,
357
+     * the original getById method does not work, because it can only look inside
358
+     * the user's mount points. But the user has no mount point for the root storage.
359
+     *
360
+     * So in that case we directly check the mount of the root if it contains
361
+     * the id. If it does we check if the path is inside the path we are working
362
+     * in.
363
+     *
364
+     * @param int $id
365
+     * @return array
366
+     */
367
+    protected function getByIdInRootMount(int $id): array {
368
+        $mount = $this->root->getMount('');
369
+        $cacheEntry = $mount->getStorage()->getCache($this->path)->get($id);
370
+        if (!$cacheEntry) {
371
+            return [];
372
+        }
373
+
374
+        $absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
375
+        $currentPath = rtrim($this->path, '/') . '/';
376
+
377
+        if (strpos($absolutePath, $currentPath) !== 0) {
378
+            return [];
379
+        }
380
+
381
+        return [$this->root->createNode(
382
+            $absolutePath, new \OC\Files\FileInfo(
383
+                $absolutePath,
384
+                $mount->getStorage(),
385
+                $cacheEntry->getPath(),
386
+                $cacheEntry,
387
+                $mount
388
+        ))];
389
+    }
390
+
391
+    public function getFreeSpace() {
392
+        return $this->view->free_space($this->path);
393
+    }
394
+
395
+    public function delete() {
396
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
397
+            $this->sendHooks(['preDelete']);
398
+            $fileInfo = $this->getFileInfo();
399
+            $this->view->rmdir($this->path);
400
+            $nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
401
+            $this->sendHooks(['postDelete'], [$nonExisting]);
402
+            $this->exists = false;
403
+        } else {
404
+            throw new NotPermittedException('No delete permission for path');
405
+        }
406
+    }
407
+
408
+    /**
409
+     * Add a suffix to the name in case the file exists
410
+     *
411
+     * @param string $name
412
+     * @return string
413
+     * @throws NotPermittedException
414
+     */
415
+    public function getNonExistingName($name) {
416
+        $uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
417
+        return trim($this->getRelativePath($uniqueName), '/');
418
+    }
419
+
420
+    /**
421
+     * @param int $limit
422
+     * @param int $offset
423
+     * @return \OCP\Files\Node[]
424
+     */
425
+    public function getRecent($limit, $offset = 0) {
426
+        $mimetypeLoader = \OC::$server->getMimeTypeLoader();
427
+        $mounts = $this->root->getMountsIn($this->path);
428
+        $mounts[] = $this->getMountPoint();
429
+
430
+        $mounts = array_filter($mounts, function (IMountPoint $mount) {
431
+            return $mount->getStorage();
432
+        });
433
+        $storageIds = array_map(function (IMountPoint $mount) {
434
+            return $mount->getStorage()->getCache()->getNumericStorageId();
435
+        }, $mounts);
436
+        /** @var IMountPoint[] $mountMap */
437
+        $mountMap = array_combine($storageIds, $mounts);
438
+        $folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER);
439
+
440
+        // Search in batches of 500 entries
441
+        $searchLimit = 500;
442
+        $results = [];
443
+        $searchResultCount = 0;
444
+        $count = 0;
445
+        do {
446
+            $searchResult = $this->recentSearch($searchLimit, $offset, $storageIds, $folderMimetype);
447
+
448
+            // Exit condition if there are no more results
449
+            if (count($searchResult) === 0) {
450
+                break;
451
+            }
452
+
453
+            $searchResultCount += count($searchResult);
454
+
455
+            $parseResult = $this->recentParse($searchResult, $mountMap, $mimetypeLoader);
456
+
457
+            foreach ($parseResult as $result) {
458
+                $results[] = $result;
459
+            }
460
+
461
+            $offset += $searchLimit;
462
+            $count++;
463
+        } while (count($results) < $limit && ($searchResultCount < (3 * $limit) || $count < 5));
464
+
465
+        return array_slice($results, 0, $limit);
466
+    }
467
+
468
+    private function recentSearch($limit, $offset, $storageIds, $folderMimetype) {
469
+        $builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
470
+        $query = $builder
471
+            ->select('f.*')
472
+            ->from('filecache', 'f')
473
+            ->andWhere($builder->expr()->in('f.storage', $builder->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)))
474
+            ->andWhere($builder->expr()->orX(
475
+            // handle non empty folders separate
476
+                $builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)),
477
+                $builder->expr()->eq('f.size', new Literal(0))
478
+            ))
479
+            ->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_versions/%')))
480
+            ->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_trashbin/%')))
481
+            ->orderBy('f.mtime', 'DESC')
482
+            ->setMaxResults($limit)
483
+            ->setFirstResult($offset);
484
+        return $query->execute()->fetchAll();
485
+    }
486
+
487
+    private function recentParse($result, $mountMap, $mimetypeLoader) {
488
+        $files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) {
489
+            $mount = $mountMap[$entry['storage']];
490
+            $entry['internalPath'] = $entry['path'];
491
+            $entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']);
492
+            $entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']);
493
+            $path = $this->getAbsolutePath($mount, $entry['path']);
494
+            if (is_null($path)) {
495
+                return null;
496
+            }
497
+            $fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount);
498
+            return $this->root->createNode($fileInfo->getPath(), $fileInfo);
499
+        }, $result));
500
+
501
+        return array_values(array_filter($files, function (Node $node) {
502
+            $cacheEntry = $node->getMountPoint()->getStorage()->getCache()->get($node->getId());
503
+            if (!$cacheEntry) {
504
+                return false;
505
+            }
506
+            $relative = $this->getRelativePath($node->getPath());
507
+            return $relative !== null && $relative !== '/'
508
+                && ($cacheEntry->getPermissions() & \OCP\Constants::PERMISSION_READ) === \OCP\Constants::PERMISSION_READ;
509
+        }));
510
+    }
511
+
512
+    private function getAbsolutePath(IMountPoint $mount, $path) {
513
+        $storage = $mount->getStorage();
514
+        if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) {
515
+            if ($storage->instanceOfStorage(SharedStorage::class)) {
516
+                $storage->getSourceStorage();
517
+            }
518
+            /** @var \OC\Files\Storage\Wrapper\Jail $storage */
519
+            $jailRoot = $storage->getUnjailedPath('');
520
+            $rootLength = strlen($jailRoot) + 1;
521
+            if ($path === $jailRoot) {
522
+                return $mount->getMountPoint();
523
+            } else if (substr($path, 0, $rootLength) === $jailRoot . '/') {
524
+                return $mount->getMountPoint() . substr($path, $rootLength);
525
+            } else {
526
+                return null;
527
+            }
528
+        } else {
529
+            return $mount->getMountPoint() . $path;
530
+        }
531
+    }
532 532
 }
Please login to merge, or discard this patch.