Completed
Push — master ( c2b1bd...a7ad7c )
by Joas
109:41 queued 92:38
created
lib/private/legacy/template.php 2 patches
Indentation   +339 added lines, -339 removed lines patch added patch discarded remove patch
@@ -46,343 +46,343 @@
 block discarded – undo
46 46
  */
47 47
 class OC_Template extends \OC\Template\Base {
48 48
 
49
-	/** @var string */
50
-	private $renderAs; // Create a full page?
51
-
52
-	/** @var string */
53
-	private $path; // The path to the template
54
-
55
-	/** @var array */
56
-	private $headers = array(); //custom headers
57
-
58
-	/** @var string */
59
-	protected $app; // app id
60
-
61
-	protected static $initTemplateEngineFirstRun = true;
62
-
63
-	/**
64
-	 * Constructor
65
-	 *
66
-	 * @param string $app app providing the template
67
-	 * @param string $name of the template file (without suffix)
68
-	 * @param string $renderAs If $renderAs is set, OC_Template will try to
69
-	 *                         produce a full page in the according layout. For
70
-	 *                         now, $renderAs can be set to "guest", "user" or
71
-	 *                         "admin".
72
-	 * @param bool $registerCall = true
73
-	 */
74
-	public function __construct( $app, $name, $renderAs = "", $registerCall = true ) {
75
-		// Read the selected theme from the config file
76
-		self::initTemplateEngine($renderAs);
77
-
78
-		$theme = OC_Util::getTheme();
79
-
80
-		$requestToken = (OC::$server->getSession() && $registerCall) ? \OCP\Util::callRegister() : '';
81
-
82
-		$parts = explode('/', $app); // fix translation when app is something like core/lostpassword
83
-		$l10n = \OC::$server->getL10N($parts[0]);
84
-		/** @var \OCP\Defaults $themeDefaults */
85
-		$themeDefaults = \OC::$server->query(\OCP\Defaults::class);
86
-
87
-		list($path, $template) = $this->findTemplate($theme, $app, $name);
88
-
89
-		// Set the private data
90
-		$this->renderAs = $renderAs;
91
-		$this->path = $path;
92
-		$this->app = $app;
93
-
94
-		parent::__construct($template, $requestToken, $l10n, $themeDefaults);
95
-	}
96
-
97
-	/**
98
-	 * @param string $renderAs
99
-	 */
100
-	public static function initTemplateEngine($renderAs) {
101
-		if (self::$initTemplateEngineFirstRun){
102
-
103
-			//apps that started before the template initialization can load their own scripts/styles
104
-			//so to make sure this scripts/styles here are loaded first we use OC_Util::addScript() with $prepend=true
105
-			//meaning the last script/style in this list will be loaded first
106
-			if (\OC::$server->getSystemConfig()->getValue ('installed', false) && $renderAs !== 'error' && !\OCP\Util::needUpgrade()) {
107
-				if (\OC::$server->getConfig ()->getAppValue ( 'core', 'backgroundjobs_mode', 'ajax' ) == 'ajax') {
108
-					OC_Util::addScript ( 'backgroundjobs', null, true );
109
-				}
110
-			}
111
-
112
-			OC_Util::addStyle('server', null, true);
113
-			OC_Util::addStyle('jquery-ui-fixes',null,true);
114
-			OC_Util::addVendorStyle('jquery-ui/themes/base/jquery-ui',null,true);
115
-			OC_Util::addVendorStyle('select2/select2', null, true);
116
-			OC_Util::addStyle('jquery.ocdialog');
117
-			OC_Util::addTranslations("core", null, true);
118
-			OC_Util::addScript('search', 'search', true);
119
-			OC_Util::addScript('merged-template-prepend', null, true);
120
-			OC_Util::addScript('jquery-ui-fixes');
121
-			OC_Util::addScript('files/fileinfo');
122
-			OC_Util::addScript('files/client');
123
-			OC_Util::addScript('contactsmenu');
124
-
125
-			if (\OC::$server->getConfig()->getSystemValue('debug')) {
126
-				// Add the stuff we need always
127
-				// following logic will import all vendor libraries that are
128
-				// specified in core/js/core.json
129
-				$fileContent = file_get_contents(OC::$SERVERROOT . '/core/js/core.json');
130
-				if($fileContent !== false) {
131
-					$coreDependencies = json_decode($fileContent, true);
132
-					foreach(array_reverse($coreDependencies['vendor']) as $vendorLibrary) {
133
-						//remove trailing ".js" as addVendorScript will append it
134
-						OC_Util::addVendorScript(
135
-							substr($vendorLibrary, 0, -3),null,true);
136
-						}
137
- 				} else {
138
-					throw new \Exception('Cannot read core/js/core.json');
139
-				}
140
-			} else {
141
-				// Import all (combined) default vendor libraries
142
-				OC_Util::addVendorScript('core', null, true);
143
-			}
144
-
145
-			if (\OC::$server->getRequest()->isUserAgent([\OC\AppFramework\Http\Request::USER_AGENT_IE])) {
146
-				// polyfill for btoa/atob for IE friends
147
-				OC_Util::addVendorScript('base64/base64');
148
-				// shim for the davclient.js library
149
-				\OCP\Util::addScript('files/iedavclient');
150
-			}
151
-
152
-			self::$initTemplateEngineFirstRun = false;
153
-		}
154
-
155
-	}
156
-
157
-
158
-	/**
159
-	 * find the template with the given name
160
-	 * @param string $name of the template file (without suffix)
161
-	 *
162
-	 * Will select the template file for the selected theme.
163
-	 * Checking all the possible locations.
164
-	 * @param string $theme
165
-	 * @param string $app
166
-	 * @return string[]
167
-	 */
168
-	protected function findTemplate($theme, $app, $name) {
169
-		// Check if it is a app template or not.
170
-		if( $app !== '' ) {
171
-			$dirs = $this->getAppTemplateDirs($theme, $app, OC::$SERVERROOT, OC_App::getAppPath($app));
172
-		} else {
173
-			$dirs = $this->getCoreTemplateDirs($theme, OC::$SERVERROOT);
174
-		}
175
-		$locator = new \OC\Template\TemplateFileLocator( $dirs );
176
-		$template = $locator->find($name);
177
-		$path = $locator->getPath();
178
-		return array($path, $template);
179
-	}
180
-
181
-	/**
182
-	 * Add a custom element to the header
183
-	 * @param string $tag tag name of the element
184
-	 * @param array $attributes array of attributes for the element
185
-	 * @param string $text the text content for the element. If $text is null then the
186
-	 * element will be written as empty element. So use "" to get a closing tag.
187
-	 */
188
-	public function addHeader($tag, $attributes, $text=null) {
189
-		$this->headers[]= array(
190
-			'tag' => $tag,
191
-			'attributes' => $attributes,
192
-			'text' => $text
193
-		);
194
-	}
195
-
196
-	/**
197
-	 * Process the template
198
-	 * @return boolean|string
199
-	 *
200
-	 * This function process the template. If $this->renderAs is set, it
201
-	 * will produce a full page.
202
-	 */
203
-	public function fetchPage($additionalParams = null) {
204
-		$data = parent::fetchPage($additionalParams);
205
-
206
-		if( $this->renderAs ) {
207
-			$page = new TemplateLayout($this->renderAs, $this->app);
208
-
209
-			// Add custom headers
210
-			$headers = '';
211
-			foreach(OC_Util::$headers as $header) {
212
-				$headers .= '<'.\OCP\Util::sanitizeHTML($header['tag']);
213
-				if ( strcasecmp($header['tag'], 'script') === 0 && in_array('src', array_map('strtolower', array_keys($header['attributes']))) ) {
214
-					$headers .= ' defer';
215
-				}
216
-				foreach($header['attributes'] as $name=>$value) {
217
-					$headers .= ' '.\OCP\Util::sanitizeHTML($name).'="'.\OCP\Util::sanitizeHTML($value).'"';
218
-				}
219
-				if ($header['text'] !== null) {
220
-					$headers .= '>'.\OCP\Util::sanitizeHTML($header['text']).'</'.\OCP\Util::sanitizeHTML($header['tag']).'>';
221
-				} else {
222
-					$headers .= '/>';
223
-				}
224
-			}
225
-
226
-			$page->assign('headers', $headers);
227
-
228
-			$page->assign('content', $data);
229
-			return $page->fetchPage();
230
-		}
231
-
232
-		return $data;
233
-	}
234
-
235
-	/**
236
-	 * Include template
237
-	 *
238
-	 * @param string $file
239
-	 * @param array|null $additionalParams
240
-	 * @return string returns content of included template
241
-	 *
242
-	 * Includes another template. use <?php echo $this->inc('template'); ?> to
243
-	 * do this.
244
-	 */
245
-	public function inc( $file, $additionalParams = null ) {
246
-		return $this->load($this->path.$file.'.php', $additionalParams);
247
-	}
248
-
249
-	/**
250
-	 * Shortcut to print a simple page for users
251
-	 * @param string $application The application we render the template for
252
-	 * @param string $name Name of the template
253
-	 * @param array $parameters Parameters for the template
254
-	 * @return boolean|null
255
-	 */
256
-	public static function printUserPage( $application, $name, $parameters = array() ) {
257
-		$content = new OC_Template( $application, $name, "user" );
258
-		foreach( $parameters as $key => $value ) {
259
-			$content->assign( $key, $value );
260
-		}
261
-		print $content->printPage();
262
-	}
263
-
264
-	/**
265
-	 * Shortcut to print a simple page for admins
266
-	 * @param string $application The application we render the template for
267
-	 * @param string $name Name of the template
268
-	 * @param array $parameters Parameters for the template
269
-	 * @return bool
270
-	 */
271
-	public static function printAdminPage( $application, $name, $parameters = array() ) {
272
-		$content = new OC_Template( $application, $name, "admin" );
273
-		foreach( $parameters as $key => $value ) {
274
-			$content->assign( $key, $value );
275
-		}
276
-		return $content->printPage();
277
-	}
278
-
279
-	/**
280
-	 * Shortcut to print a simple page for guests
281
-	 * @param string $application The application we render the template for
282
-	 * @param string $name Name of the template
283
-	 * @param array|string $parameters Parameters for the template
284
-	 * @return bool
285
-	 */
286
-	public static function printGuestPage( $application, $name, $parameters = array() ) {
287
-		$content = new OC_Template( $application, $name, "guest" );
288
-		foreach( $parameters as $key => $value ) {
289
-			$content->assign( $key, $value );
290
-		}
291
-		return $content->printPage();
292
-	}
293
-
294
-	/**
295
-	 * Print a fatal error page and terminates the script
296
-	 * @param string $error_msg The error message to show
297
-	 * @param string $hint An optional hint message - needs to be properly escape
298
-	 * @suppress PhanAccessMethodInternal
299
-	 */
300
-	public static function printErrorPage( $error_msg, $hint = '' ) {
301
-		if (\OC::$server->getAppManager()->isEnabledForUser('theming') && !\OC_App::isAppLoaded('theming')) {
302
-			\OC_App::loadApp('theming');
303
-		}
304
-
305
-
306
-		if ($error_msg === $hint) {
307
-			// If the hint is the same as the message there is no need to display it twice.
308
-			$hint = '';
309
-		}
310
-
311
-		try {
312
-			$content = new \OC_Template( '', 'error', 'error', false );
313
-			$errors = array(array('error' => $error_msg, 'hint' => $hint));
314
-			$content->assign( 'errors', $errors );
315
-			$content->printPage();
316
-		} catch (\Exception $e) {
317
-			$logger = \OC::$server->getLogger();
318
-			$logger->error("$error_msg $hint", ['app' => 'core']);
319
-			$logger->logException($e, ['app' => 'core']);
320
-
321
-			header(self::getHttpProtocol() . ' 500 Internal Server Error');
322
-			header('Content-Type: text/plain; charset=utf-8');
323
-			print("$error_msg $hint");
324
-		}
325
-		die();
326
-	}
327
-
328
-	/**
329
-	 * print error page using Exception details
330
-	 * @param Exception|Throwable $exception
331
-	 * @param bool $fetchPage
332
-	 * @return bool|string
333
-	 * @suppress PhanAccessMethodInternal
334
-	 */
335
-	public static function printExceptionErrorPage($exception, $fetchPage = false) {
336
-		try {
337
-			$request = \OC::$server->getRequest();
338
-			$content = new \OC_Template('', 'exception', 'error', false);
339
-			$content->assign('errorClass', get_class($exception));
340
-			$content->assign('errorMsg', $exception->getMessage());
341
-			$content->assign('errorCode', $exception->getCode());
342
-			$content->assign('file', $exception->getFile());
343
-			$content->assign('line', $exception->getLine());
344
-			$content->assign('trace', $exception->getTraceAsString());
345
-			$content->assign('debugMode', \OC::$server->getSystemConfig()->getValue('debug', false));
346
-			$content->assign('remoteAddr', $request->getRemoteAddress());
347
-			$content->assign('requestID', $request->getId());
348
-			if ($fetchPage) {
349
-				return $content->fetchPage();
350
-			}
351
-			$content->printPage();
352
-		} catch (\Exception $e) {
353
-			$logger = \OC::$server->getLogger();
354
-			$logger->logException($exception, ['app' => 'core']);
355
-			$logger->logException($e, ['app' => 'core']);
356
-
357
-			header(self::getHttpProtocol() . ' 500 Internal Server Error');
358
-			header('Content-Type: text/plain; charset=utf-8');
359
-			print("Internal Server Error\n\n");
360
-			print("The server encountered an internal error and was unable to complete your request.\n");
361
-			print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n");
362
-			print("More details can be found in the server log.\n");
363
-		}
364
-		die();
365
-	}
366
-
367
-	/**
368
-	 * This is only here to reduce the dependencies in case of an exception to
369
-	 * still be able to print a plain error message.
370
-	 *
371
-	 * Returns the used HTTP protocol.
372
-	 *
373
-	 * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0.
374
-	 * @internal Don't use this - use AppFramework\Http\Request->getHttpProtocol instead
375
-	 */
376
-	protected static function getHttpProtocol() {
377
-		$claimedProtocol = strtoupper($_SERVER['SERVER_PROTOCOL']);
378
-		$validProtocols = [
379
-			'HTTP/1.0',
380
-			'HTTP/1.1',
381
-			'HTTP/2',
382
-		];
383
-		if(in_array($claimedProtocol, $validProtocols, true)) {
384
-			return $claimedProtocol;
385
-		}
386
-		return 'HTTP/1.1';
387
-	}
49
+    /** @var string */
50
+    private $renderAs; // Create a full page?
51
+
52
+    /** @var string */
53
+    private $path; // The path to the template
54
+
55
+    /** @var array */
56
+    private $headers = array(); //custom headers
57
+
58
+    /** @var string */
59
+    protected $app; // app id
60
+
61
+    protected static $initTemplateEngineFirstRun = true;
62
+
63
+    /**
64
+     * Constructor
65
+     *
66
+     * @param string $app app providing the template
67
+     * @param string $name of the template file (without suffix)
68
+     * @param string $renderAs If $renderAs is set, OC_Template will try to
69
+     *                         produce a full page in the according layout. For
70
+     *                         now, $renderAs can be set to "guest", "user" or
71
+     *                         "admin".
72
+     * @param bool $registerCall = true
73
+     */
74
+    public function __construct( $app, $name, $renderAs = "", $registerCall = true ) {
75
+        // Read the selected theme from the config file
76
+        self::initTemplateEngine($renderAs);
77
+
78
+        $theme = OC_Util::getTheme();
79
+
80
+        $requestToken = (OC::$server->getSession() && $registerCall) ? \OCP\Util::callRegister() : '';
81
+
82
+        $parts = explode('/', $app); // fix translation when app is something like core/lostpassword
83
+        $l10n = \OC::$server->getL10N($parts[0]);
84
+        /** @var \OCP\Defaults $themeDefaults */
85
+        $themeDefaults = \OC::$server->query(\OCP\Defaults::class);
86
+
87
+        list($path, $template) = $this->findTemplate($theme, $app, $name);
88
+
89
+        // Set the private data
90
+        $this->renderAs = $renderAs;
91
+        $this->path = $path;
92
+        $this->app = $app;
93
+
94
+        parent::__construct($template, $requestToken, $l10n, $themeDefaults);
95
+    }
96
+
97
+    /**
98
+     * @param string $renderAs
99
+     */
100
+    public static function initTemplateEngine($renderAs) {
101
+        if (self::$initTemplateEngineFirstRun){
102
+
103
+            //apps that started before the template initialization can load their own scripts/styles
104
+            //so to make sure this scripts/styles here are loaded first we use OC_Util::addScript() with $prepend=true
105
+            //meaning the last script/style in this list will be loaded first
106
+            if (\OC::$server->getSystemConfig()->getValue ('installed', false) && $renderAs !== 'error' && !\OCP\Util::needUpgrade()) {
107
+                if (\OC::$server->getConfig ()->getAppValue ( 'core', 'backgroundjobs_mode', 'ajax' ) == 'ajax') {
108
+                    OC_Util::addScript ( 'backgroundjobs', null, true );
109
+                }
110
+            }
111
+
112
+            OC_Util::addStyle('server', null, true);
113
+            OC_Util::addStyle('jquery-ui-fixes',null,true);
114
+            OC_Util::addVendorStyle('jquery-ui/themes/base/jquery-ui',null,true);
115
+            OC_Util::addVendorStyle('select2/select2', null, true);
116
+            OC_Util::addStyle('jquery.ocdialog');
117
+            OC_Util::addTranslations("core", null, true);
118
+            OC_Util::addScript('search', 'search', true);
119
+            OC_Util::addScript('merged-template-prepend', null, true);
120
+            OC_Util::addScript('jquery-ui-fixes');
121
+            OC_Util::addScript('files/fileinfo');
122
+            OC_Util::addScript('files/client');
123
+            OC_Util::addScript('contactsmenu');
124
+
125
+            if (\OC::$server->getConfig()->getSystemValue('debug')) {
126
+                // Add the stuff we need always
127
+                // following logic will import all vendor libraries that are
128
+                // specified in core/js/core.json
129
+                $fileContent = file_get_contents(OC::$SERVERROOT . '/core/js/core.json');
130
+                if($fileContent !== false) {
131
+                    $coreDependencies = json_decode($fileContent, true);
132
+                    foreach(array_reverse($coreDependencies['vendor']) as $vendorLibrary) {
133
+                        //remove trailing ".js" as addVendorScript will append it
134
+                        OC_Util::addVendorScript(
135
+                            substr($vendorLibrary, 0, -3),null,true);
136
+                        }
137
+                    } else {
138
+                    throw new \Exception('Cannot read core/js/core.json');
139
+                }
140
+            } else {
141
+                // Import all (combined) default vendor libraries
142
+                OC_Util::addVendorScript('core', null, true);
143
+            }
144
+
145
+            if (\OC::$server->getRequest()->isUserAgent([\OC\AppFramework\Http\Request::USER_AGENT_IE])) {
146
+                // polyfill for btoa/atob for IE friends
147
+                OC_Util::addVendorScript('base64/base64');
148
+                // shim for the davclient.js library
149
+                \OCP\Util::addScript('files/iedavclient');
150
+            }
151
+
152
+            self::$initTemplateEngineFirstRun = false;
153
+        }
154
+
155
+    }
156
+
157
+
158
+    /**
159
+     * find the template with the given name
160
+     * @param string $name of the template file (without suffix)
161
+     *
162
+     * Will select the template file for the selected theme.
163
+     * Checking all the possible locations.
164
+     * @param string $theme
165
+     * @param string $app
166
+     * @return string[]
167
+     */
168
+    protected function findTemplate($theme, $app, $name) {
169
+        // Check if it is a app template or not.
170
+        if( $app !== '' ) {
171
+            $dirs = $this->getAppTemplateDirs($theme, $app, OC::$SERVERROOT, OC_App::getAppPath($app));
172
+        } else {
173
+            $dirs = $this->getCoreTemplateDirs($theme, OC::$SERVERROOT);
174
+        }
175
+        $locator = new \OC\Template\TemplateFileLocator( $dirs );
176
+        $template = $locator->find($name);
177
+        $path = $locator->getPath();
178
+        return array($path, $template);
179
+    }
180
+
181
+    /**
182
+     * Add a custom element to the header
183
+     * @param string $tag tag name of the element
184
+     * @param array $attributes array of attributes for the element
185
+     * @param string $text the text content for the element. If $text is null then the
186
+     * element will be written as empty element. So use "" to get a closing tag.
187
+     */
188
+    public function addHeader($tag, $attributes, $text=null) {
189
+        $this->headers[]= array(
190
+            'tag' => $tag,
191
+            'attributes' => $attributes,
192
+            'text' => $text
193
+        );
194
+    }
195
+
196
+    /**
197
+     * Process the template
198
+     * @return boolean|string
199
+     *
200
+     * This function process the template. If $this->renderAs is set, it
201
+     * will produce a full page.
202
+     */
203
+    public function fetchPage($additionalParams = null) {
204
+        $data = parent::fetchPage($additionalParams);
205
+
206
+        if( $this->renderAs ) {
207
+            $page = new TemplateLayout($this->renderAs, $this->app);
208
+
209
+            // Add custom headers
210
+            $headers = '';
211
+            foreach(OC_Util::$headers as $header) {
212
+                $headers .= '<'.\OCP\Util::sanitizeHTML($header['tag']);
213
+                if ( strcasecmp($header['tag'], 'script') === 0 && in_array('src', array_map('strtolower', array_keys($header['attributes']))) ) {
214
+                    $headers .= ' defer';
215
+                }
216
+                foreach($header['attributes'] as $name=>$value) {
217
+                    $headers .= ' '.\OCP\Util::sanitizeHTML($name).'="'.\OCP\Util::sanitizeHTML($value).'"';
218
+                }
219
+                if ($header['text'] !== null) {
220
+                    $headers .= '>'.\OCP\Util::sanitizeHTML($header['text']).'</'.\OCP\Util::sanitizeHTML($header['tag']).'>';
221
+                } else {
222
+                    $headers .= '/>';
223
+                }
224
+            }
225
+
226
+            $page->assign('headers', $headers);
227
+
228
+            $page->assign('content', $data);
229
+            return $page->fetchPage();
230
+        }
231
+
232
+        return $data;
233
+    }
234
+
235
+    /**
236
+     * Include template
237
+     *
238
+     * @param string $file
239
+     * @param array|null $additionalParams
240
+     * @return string returns content of included template
241
+     *
242
+     * Includes another template. use <?php echo $this->inc('template'); ?> to
243
+     * do this.
244
+     */
245
+    public function inc( $file, $additionalParams = null ) {
246
+        return $this->load($this->path.$file.'.php', $additionalParams);
247
+    }
248
+
249
+    /**
250
+     * Shortcut to print a simple page for users
251
+     * @param string $application The application we render the template for
252
+     * @param string $name Name of the template
253
+     * @param array $parameters Parameters for the template
254
+     * @return boolean|null
255
+     */
256
+    public static function printUserPage( $application, $name, $parameters = array() ) {
257
+        $content = new OC_Template( $application, $name, "user" );
258
+        foreach( $parameters as $key => $value ) {
259
+            $content->assign( $key, $value );
260
+        }
261
+        print $content->printPage();
262
+    }
263
+
264
+    /**
265
+     * Shortcut to print a simple page for admins
266
+     * @param string $application The application we render the template for
267
+     * @param string $name Name of the template
268
+     * @param array $parameters Parameters for the template
269
+     * @return bool
270
+     */
271
+    public static function printAdminPage( $application, $name, $parameters = array() ) {
272
+        $content = new OC_Template( $application, $name, "admin" );
273
+        foreach( $parameters as $key => $value ) {
274
+            $content->assign( $key, $value );
275
+        }
276
+        return $content->printPage();
277
+    }
278
+
279
+    /**
280
+     * Shortcut to print a simple page for guests
281
+     * @param string $application The application we render the template for
282
+     * @param string $name Name of the template
283
+     * @param array|string $parameters Parameters for the template
284
+     * @return bool
285
+     */
286
+    public static function printGuestPage( $application, $name, $parameters = array() ) {
287
+        $content = new OC_Template( $application, $name, "guest" );
288
+        foreach( $parameters as $key => $value ) {
289
+            $content->assign( $key, $value );
290
+        }
291
+        return $content->printPage();
292
+    }
293
+
294
+    /**
295
+     * Print a fatal error page and terminates the script
296
+     * @param string $error_msg The error message to show
297
+     * @param string $hint An optional hint message - needs to be properly escape
298
+     * @suppress PhanAccessMethodInternal
299
+     */
300
+    public static function printErrorPage( $error_msg, $hint = '' ) {
301
+        if (\OC::$server->getAppManager()->isEnabledForUser('theming') && !\OC_App::isAppLoaded('theming')) {
302
+            \OC_App::loadApp('theming');
303
+        }
304
+
305
+
306
+        if ($error_msg === $hint) {
307
+            // If the hint is the same as the message there is no need to display it twice.
308
+            $hint = '';
309
+        }
310
+
311
+        try {
312
+            $content = new \OC_Template( '', 'error', 'error', false );
313
+            $errors = array(array('error' => $error_msg, 'hint' => $hint));
314
+            $content->assign( 'errors', $errors );
315
+            $content->printPage();
316
+        } catch (\Exception $e) {
317
+            $logger = \OC::$server->getLogger();
318
+            $logger->error("$error_msg $hint", ['app' => 'core']);
319
+            $logger->logException($e, ['app' => 'core']);
320
+
321
+            header(self::getHttpProtocol() . ' 500 Internal Server Error');
322
+            header('Content-Type: text/plain; charset=utf-8');
323
+            print("$error_msg $hint");
324
+        }
325
+        die();
326
+    }
327
+
328
+    /**
329
+     * print error page using Exception details
330
+     * @param Exception|Throwable $exception
331
+     * @param bool $fetchPage
332
+     * @return bool|string
333
+     * @suppress PhanAccessMethodInternal
334
+     */
335
+    public static function printExceptionErrorPage($exception, $fetchPage = false) {
336
+        try {
337
+            $request = \OC::$server->getRequest();
338
+            $content = new \OC_Template('', 'exception', 'error', false);
339
+            $content->assign('errorClass', get_class($exception));
340
+            $content->assign('errorMsg', $exception->getMessage());
341
+            $content->assign('errorCode', $exception->getCode());
342
+            $content->assign('file', $exception->getFile());
343
+            $content->assign('line', $exception->getLine());
344
+            $content->assign('trace', $exception->getTraceAsString());
345
+            $content->assign('debugMode', \OC::$server->getSystemConfig()->getValue('debug', false));
346
+            $content->assign('remoteAddr', $request->getRemoteAddress());
347
+            $content->assign('requestID', $request->getId());
348
+            if ($fetchPage) {
349
+                return $content->fetchPage();
350
+            }
351
+            $content->printPage();
352
+        } catch (\Exception $e) {
353
+            $logger = \OC::$server->getLogger();
354
+            $logger->logException($exception, ['app' => 'core']);
355
+            $logger->logException($e, ['app' => 'core']);
356
+
357
+            header(self::getHttpProtocol() . ' 500 Internal Server Error');
358
+            header('Content-Type: text/plain; charset=utf-8');
359
+            print("Internal Server Error\n\n");
360
+            print("The server encountered an internal error and was unable to complete your request.\n");
361
+            print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n");
362
+            print("More details can be found in the server log.\n");
363
+        }
364
+        die();
365
+    }
366
+
367
+    /**
368
+     * This is only here to reduce the dependencies in case of an exception to
369
+     * still be able to print a plain error message.
370
+     *
371
+     * Returns the used HTTP protocol.
372
+     *
373
+     * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0.
374
+     * @internal Don't use this - use AppFramework\Http\Request->getHttpProtocol instead
375
+     */
376
+    protected static function getHttpProtocol() {
377
+        $claimedProtocol = strtoupper($_SERVER['SERVER_PROTOCOL']);
378
+        $validProtocols = [
379
+            'HTTP/1.0',
380
+            'HTTP/1.1',
381
+            'HTTP/2',
382
+        ];
383
+        if(in_array($claimedProtocol, $validProtocols, true)) {
384
+            return $claimedProtocol;
385
+        }
386
+        return 'HTTP/1.1';
387
+    }
388 388
 }
Please login to merge, or discard this patch.
Spacing   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -71,7 +71,7 @@  discard block
 block discarded – undo
71 71
 	 *                         "admin".
72 72
 	 * @param bool $registerCall = true
73 73
 	 */
74
-	public function __construct( $app, $name, $renderAs = "", $registerCall = true ) {
74
+	public function __construct($app, $name, $renderAs = "", $registerCall = true) {
75 75
 		// Read the selected theme from the config file
76 76
 		self::initTemplateEngine($renderAs);
77 77
 
@@ -98,20 +98,20 @@  discard block
 block discarded – undo
98 98
 	 * @param string $renderAs
99 99
 	 */
100 100
 	public static function initTemplateEngine($renderAs) {
101
-		if (self::$initTemplateEngineFirstRun){
101
+		if (self::$initTemplateEngineFirstRun) {
102 102
 
103 103
 			//apps that started before the template initialization can load their own scripts/styles
104 104
 			//so to make sure this scripts/styles here are loaded first we use OC_Util::addScript() with $prepend=true
105 105
 			//meaning the last script/style in this list will be loaded first
106
-			if (\OC::$server->getSystemConfig()->getValue ('installed', false) && $renderAs !== 'error' && !\OCP\Util::needUpgrade()) {
107
-				if (\OC::$server->getConfig ()->getAppValue ( 'core', 'backgroundjobs_mode', 'ajax' ) == 'ajax') {
108
-					OC_Util::addScript ( 'backgroundjobs', null, true );
106
+			if (\OC::$server->getSystemConfig()->getValue('installed', false) && $renderAs !== 'error' && !\OCP\Util::needUpgrade()) {
107
+				if (\OC::$server->getConfig()->getAppValue('core', 'backgroundjobs_mode', 'ajax') == 'ajax') {
108
+					OC_Util::addScript('backgroundjobs', null, true);
109 109
 				}
110 110
 			}
111 111
 
112 112
 			OC_Util::addStyle('server', null, true);
113
-			OC_Util::addStyle('jquery-ui-fixes',null,true);
114
-			OC_Util::addVendorStyle('jquery-ui/themes/base/jquery-ui',null,true);
113
+			OC_Util::addStyle('jquery-ui-fixes', null, true);
114
+			OC_Util::addVendorStyle('jquery-ui/themes/base/jquery-ui', null, true);
115 115
 			OC_Util::addVendorStyle('select2/select2', null, true);
116 116
 			OC_Util::addStyle('jquery.ocdialog');
117 117
 			OC_Util::addTranslations("core", null, true);
@@ -126,13 +126,13 @@  discard block
 block discarded – undo
126 126
 				// Add the stuff we need always
127 127
 				// following logic will import all vendor libraries that are
128 128
 				// specified in core/js/core.json
129
-				$fileContent = file_get_contents(OC::$SERVERROOT . '/core/js/core.json');
130
-				if($fileContent !== false) {
129
+				$fileContent = file_get_contents(OC::$SERVERROOT.'/core/js/core.json');
130
+				if ($fileContent !== false) {
131 131
 					$coreDependencies = json_decode($fileContent, true);
132
-					foreach(array_reverse($coreDependencies['vendor']) as $vendorLibrary) {
132
+					foreach (array_reverse($coreDependencies['vendor']) as $vendorLibrary) {
133 133
 						//remove trailing ".js" as addVendorScript will append it
134 134
 						OC_Util::addVendorScript(
135
-							substr($vendorLibrary, 0, -3),null,true);
135
+							substr($vendorLibrary, 0, -3), null, true);
136 136
 						}
137 137
  				} else {
138 138
 					throw new \Exception('Cannot read core/js/core.json');
@@ -167,12 +167,12 @@  discard block
 block discarded – undo
167 167
 	 */
168 168
 	protected function findTemplate($theme, $app, $name) {
169 169
 		// Check if it is a app template or not.
170
-		if( $app !== '' ) {
170
+		if ($app !== '') {
171 171
 			$dirs = $this->getAppTemplateDirs($theme, $app, OC::$SERVERROOT, OC_App::getAppPath($app));
172 172
 		} else {
173 173
 			$dirs = $this->getCoreTemplateDirs($theme, OC::$SERVERROOT);
174 174
 		}
175
-		$locator = new \OC\Template\TemplateFileLocator( $dirs );
175
+		$locator = new \OC\Template\TemplateFileLocator($dirs);
176 176
 		$template = $locator->find($name);
177 177
 		$path = $locator->getPath();
178 178
 		return array($path, $template);
@@ -185,8 +185,8 @@  discard block
 block discarded – undo
185 185
 	 * @param string $text the text content for the element. If $text is null then the
186 186
 	 * element will be written as empty element. So use "" to get a closing tag.
187 187
 	 */
188
-	public function addHeader($tag, $attributes, $text=null) {
189
-		$this->headers[]= array(
188
+	public function addHeader($tag, $attributes, $text = null) {
189
+		$this->headers[] = array(
190 190
 			'tag' => $tag,
191 191
 			'attributes' => $attributes,
192 192
 			'text' => $text
@@ -203,17 +203,17 @@  discard block
 block discarded – undo
203 203
 	public function fetchPage($additionalParams = null) {
204 204
 		$data = parent::fetchPage($additionalParams);
205 205
 
206
-		if( $this->renderAs ) {
206
+		if ($this->renderAs) {
207 207
 			$page = new TemplateLayout($this->renderAs, $this->app);
208 208
 
209 209
 			// Add custom headers
210 210
 			$headers = '';
211
-			foreach(OC_Util::$headers as $header) {
211
+			foreach (OC_Util::$headers as $header) {
212 212
 				$headers .= '<'.\OCP\Util::sanitizeHTML($header['tag']);
213
-				if ( strcasecmp($header['tag'], 'script') === 0 && in_array('src', array_map('strtolower', array_keys($header['attributes']))) ) {
213
+				if (strcasecmp($header['tag'], 'script') === 0 && in_array('src', array_map('strtolower', array_keys($header['attributes'])))) {
214 214
 					$headers .= ' defer';
215 215
 				}
216
-				foreach($header['attributes'] as $name=>$value) {
216
+				foreach ($header['attributes'] as $name=>$value) {
217 217
 					$headers .= ' '.\OCP\Util::sanitizeHTML($name).'="'.\OCP\Util::sanitizeHTML($value).'"';
218 218
 				}
219 219
 				if ($header['text'] !== null) {
@@ -242,7 +242,7 @@  discard block
 block discarded – undo
242 242
 	 * Includes another template. use <?php echo $this->inc('template'); ?> to
243 243
 	 * do this.
244 244
 	 */
245
-	public function inc( $file, $additionalParams = null ) {
245
+	public function inc($file, $additionalParams = null) {
246 246
 		return $this->load($this->path.$file.'.php', $additionalParams);
247 247
 	}
248 248
 
@@ -253,10 +253,10 @@  discard block
 block discarded – undo
253 253
 	 * @param array $parameters Parameters for the template
254 254
 	 * @return boolean|null
255 255
 	 */
256
-	public static function printUserPage( $application, $name, $parameters = array() ) {
257
-		$content = new OC_Template( $application, $name, "user" );
258
-		foreach( $parameters as $key => $value ) {
259
-			$content->assign( $key, $value );
256
+	public static function printUserPage($application, $name, $parameters = array()) {
257
+		$content = new OC_Template($application, $name, "user");
258
+		foreach ($parameters as $key => $value) {
259
+			$content->assign($key, $value);
260 260
 		}
261 261
 		print $content->printPage();
262 262
 	}
@@ -268,10 +268,10 @@  discard block
 block discarded – undo
268 268
 	 * @param array $parameters Parameters for the template
269 269
 	 * @return bool
270 270
 	 */
271
-	public static function printAdminPage( $application, $name, $parameters = array() ) {
272
-		$content = new OC_Template( $application, $name, "admin" );
273
-		foreach( $parameters as $key => $value ) {
274
-			$content->assign( $key, $value );
271
+	public static function printAdminPage($application, $name, $parameters = array()) {
272
+		$content = new OC_Template($application, $name, "admin");
273
+		foreach ($parameters as $key => $value) {
274
+			$content->assign($key, $value);
275 275
 		}
276 276
 		return $content->printPage();
277 277
 	}
@@ -283,10 +283,10 @@  discard block
 block discarded – undo
283 283
 	 * @param array|string $parameters Parameters for the template
284 284
 	 * @return bool
285 285
 	 */
286
-	public static function printGuestPage( $application, $name, $parameters = array() ) {
287
-		$content = new OC_Template( $application, $name, "guest" );
288
-		foreach( $parameters as $key => $value ) {
289
-			$content->assign( $key, $value );
286
+	public static function printGuestPage($application, $name, $parameters = array()) {
287
+		$content = new OC_Template($application, $name, "guest");
288
+		foreach ($parameters as $key => $value) {
289
+			$content->assign($key, $value);
290 290
 		}
291 291
 		return $content->printPage();
292 292
 	}
@@ -297,7 +297,7 @@  discard block
 block discarded – undo
297 297
 	 * @param string $hint An optional hint message - needs to be properly escape
298 298
 	 * @suppress PhanAccessMethodInternal
299 299
 	 */
300
-	public static function printErrorPage( $error_msg, $hint = '' ) {
300
+	public static function printErrorPage($error_msg, $hint = '') {
301 301
 		if (\OC::$server->getAppManager()->isEnabledForUser('theming') && !\OC_App::isAppLoaded('theming')) {
302 302
 			\OC_App::loadApp('theming');
303 303
 		}
@@ -309,16 +309,16 @@  discard block
 block discarded – undo
309 309
 		}
310 310
 
311 311
 		try {
312
-			$content = new \OC_Template( '', 'error', 'error', false );
312
+			$content = new \OC_Template('', 'error', 'error', false);
313 313
 			$errors = array(array('error' => $error_msg, 'hint' => $hint));
314
-			$content->assign( 'errors', $errors );
314
+			$content->assign('errors', $errors);
315 315
 			$content->printPage();
316 316
 		} catch (\Exception $e) {
317 317
 			$logger = \OC::$server->getLogger();
318 318
 			$logger->error("$error_msg $hint", ['app' => 'core']);
319 319
 			$logger->logException($e, ['app' => 'core']);
320 320
 
321
-			header(self::getHttpProtocol() . ' 500 Internal Server Error');
321
+			header(self::getHttpProtocol().' 500 Internal Server Error');
322 322
 			header('Content-Type: text/plain; charset=utf-8');
323 323
 			print("$error_msg $hint");
324 324
 		}
@@ -354,7 +354,7 @@  discard block
 block discarded – undo
354 354
 			$logger->logException($exception, ['app' => 'core']);
355 355
 			$logger->logException($e, ['app' => 'core']);
356 356
 
357
-			header(self::getHttpProtocol() . ' 500 Internal Server Error');
357
+			header(self::getHttpProtocol().' 500 Internal Server Error');
358 358
 			header('Content-Type: text/plain; charset=utf-8');
359 359
 			print("Internal Server Error\n\n");
360 360
 			print("The server encountered an internal error and was unable to complete your request.\n");
@@ -380,7 +380,7 @@  discard block
 block discarded – undo
380 380
 			'HTTP/1.1',
381 381
 			'HTTP/2',
382 382
 		];
383
-		if(in_array($claimedProtocol, $validProtocols, true)) {
383
+		if (in_array($claimedProtocol, $validProtocols, true)) {
384 384
 			return $claimedProtocol;
385 385
 		}
386 386
 		return 'HTTP/1.1';
Please login to merge, or discard this patch.
lib/private/DB/AdapterSqlite.php 1 patch
Indentation   +50 added lines, -50 removed lines patch added patch discarded remove patch
@@ -29,59 +29,59 @@
 block discarded – undo
29 29
 
30 30
 class AdapterSqlite extends Adapter {
31 31
 
32
-	/**
33
-	 * @param string $tableName
34
-	 */
35
-	public function lockTable($tableName) {
36
-		$this->conn->executeUpdate('BEGIN EXCLUSIVE TRANSACTION');
37
-	}
32
+    /**
33
+     * @param string $tableName
34
+     */
35
+    public function lockTable($tableName) {
36
+        $this->conn->executeUpdate('BEGIN EXCLUSIVE TRANSACTION');
37
+    }
38 38
 
39
-	public function unlockTable() {
40
-		$this->conn->executeUpdate('COMMIT TRANSACTION');
41
-	}
39
+    public function unlockTable() {
40
+        $this->conn->executeUpdate('COMMIT TRANSACTION');
41
+    }
42 42
 
43
-	public function fixupStatement($statement) {
44
-		$statement = preg_replace('/`(\w+)` ILIKE \?/', 'LOWER($1) LIKE LOWER(?)', $statement);
45
-		$statement = str_replace( '`', '"', $statement );
46
-		$statement = str_ireplace( 'NOW()', 'datetime(\'now\')', $statement );
47
-		$statement = str_ireplace('GREATEST(', 'MAX(', $statement);
48
-		$statement = str_ireplace( 'UNIX_TIMESTAMP()', 'strftime(\'%s\',\'now\')', $statement );
49
-		return $statement;
50
-	}
43
+    public function fixupStatement($statement) {
44
+        $statement = preg_replace('/`(\w+)` ILIKE \?/', 'LOWER($1) LIKE LOWER(?)', $statement);
45
+        $statement = str_replace( '`', '"', $statement );
46
+        $statement = str_ireplace( 'NOW()', 'datetime(\'now\')', $statement );
47
+        $statement = str_ireplace('GREATEST(', 'MAX(', $statement);
48
+        $statement = str_ireplace( 'UNIX_TIMESTAMP()', 'strftime(\'%s\',\'now\')', $statement );
49
+        return $statement;
50
+    }
51 51
 
52
-	/**
53
-	 * Insert a row if the matching row does not exists.
54
-	 *
55
-	 * @param string $table The table name (will replace *PREFIX* with the actual prefix)
56
-	 * @param array $input data that should be inserted into the table  (column name => value)
57
-	 * @param array|null $compare List of values that should be checked for "if not exists"
58
-	 *				If this is null or an empty array, all keys of $input will be compared
59
-	 *				Please note: text fields (clob) must not be used in the compare array
60
-	 * @return int number of inserted rows
61
-	 * @throws \Doctrine\DBAL\DBALException
62
-	 */
63
-	public function insertIfNotExist($table, $input, array $compare = null) {
64
-		if (empty($compare)) {
65
-			$compare = array_keys($input);
66
-		}
67
-		$fieldList = '`' . implode('`,`', array_keys($input)) . '`';
68
-		$query = "INSERT INTO `$table` ($fieldList) SELECT "
69
-			. str_repeat('?,', count($input)-1).'? '
70
-			. " WHERE NOT EXISTS (SELECT 1 FROM `$table` WHERE ";
52
+    /**
53
+     * Insert a row if the matching row does not exists.
54
+     *
55
+     * @param string $table The table name (will replace *PREFIX* with the actual prefix)
56
+     * @param array $input data that should be inserted into the table  (column name => value)
57
+     * @param array|null $compare List of values that should be checked for "if not exists"
58
+     *				If this is null or an empty array, all keys of $input will be compared
59
+     *				Please note: text fields (clob) must not be used in the compare array
60
+     * @return int number of inserted rows
61
+     * @throws \Doctrine\DBAL\DBALException
62
+     */
63
+    public function insertIfNotExist($table, $input, array $compare = null) {
64
+        if (empty($compare)) {
65
+            $compare = array_keys($input);
66
+        }
67
+        $fieldList = '`' . implode('`,`', array_keys($input)) . '`';
68
+        $query = "INSERT INTO `$table` ($fieldList) SELECT "
69
+            . str_repeat('?,', count($input)-1).'? '
70
+            . " WHERE NOT EXISTS (SELECT 1 FROM `$table` WHERE ";
71 71
 
72
-		$inserts = array_values($input);
73
-		foreach($compare as $key) {
74
-			$query .= '`' . $key . '`';
75
-			if (is_null($input[$key])) {
76
-				$query .= ' IS NULL AND ';
77
-			} else {
78
-				$inserts[] = $input[$key];
79
-				$query .= ' = ? AND ';
80
-			}
81
-		}
82
-		$query = substr($query, 0, -5);
83
-		$query .= ')';
72
+        $inserts = array_values($input);
73
+        foreach($compare as $key) {
74
+            $query .= '`' . $key . '`';
75
+            if (is_null($input[$key])) {
76
+                $query .= ' IS NULL AND ';
77
+            } else {
78
+                $inserts[] = $input[$key];
79
+                $query .= ' = ? AND ';
80
+            }
81
+        }
82
+        $query = substr($query, 0, -5);
83
+        $query .= ')';
84 84
 
85
-		return $this->conn->executeUpdate($query, $inserts);
86
-	}
85
+        return $this->conn->executeUpdate($query, $inserts);
86
+    }
87 87
 }
Please login to merge, or discard this patch.
lib/private/DB/Adapter.php 1 patch
Indentation   +72 added lines, -72 removed lines patch added patch discarded remove patch
@@ -33,84 +33,84 @@
 block discarded – undo
33 33
  */
34 34
 class Adapter {
35 35
 
36
-	/**
37
-	 * @var \OC\DB\Connection $conn
38
-	 */
39
-	protected $conn;
36
+    /**
37
+     * @var \OC\DB\Connection $conn
38
+     */
39
+    protected $conn;
40 40
 
41
-	public function __construct($conn) {
42
-		$this->conn = $conn;
43
-	}
41
+    public function __construct($conn) {
42
+        $this->conn = $conn;
43
+    }
44 44
 
45
-	/**
46
-	 * @param string $table name
47
-	 * @return int id of last insert statement
48
-	 */
49
-	public function lastInsertId($table) {
50
-		return $this->conn->realLastInsertId($table);
51
-	}
45
+    /**
46
+     * @param string $table name
47
+     * @return int id of last insert statement
48
+     */
49
+    public function lastInsertId($table) {
50
+        return $this->conn->realLastInsertId($table);
51
+    }
52 52
 
53
-	/**
54
-	 * @param string $statement that needs to be changed so the db can handle it
55
-	 * @return string changed statement
56
-	 */
57
-	public function fixupStatement($statement) {
58
-		return $statement;
59
-	}
53
+    /**
54
+     * @param string $statement that needs to be changed so the db can handle it
55
+     * @return string changed statement
56
+     */
57
+    public function fixupStatement($statement) {
58
+        return $statement;
59
+    }
60 60
 
61
-	/**
62
-	 * Create an exclusive read+write lock on a table
63
-	 *
64
-	 * @param string $tableName
65
-	 * @since 9.1.0
66
-	 */
67
-	public function lockTable($tableName) {
68
-		$this->conn->beginTransaction();
69
-		$this->conn->executeUpdate('LOCK TABLE `' .$tableName . '` IN EXCLUSIVE MODE');
70
-	}
61
+    /**
62
+     * Create an exclusive read+write lock on a table
63
+     *
64
+     * @param string $tableName
65
+     * @since 9.1.0
66
+     */
67
+    public function lockTable($tableName) {
68
+        $this->conn->beginTransaction();
69
+        $this->conn->executeUpdate('LOCK TABLE `' .$tableName . '` IN EXCLUSIVE MODE');
70
+    }
71 71
 
72
-	/**
73
-	 * Release a previous acquired lock again
74
-	 *
75
-	 * @since 9.1.0
76
-	 */
77
-	public function unlockTable() {
78
-		$this->conn->commit();
79
-	}
72
+    /**
73
+     * Release a previous acquired lock again
74
+     *
75
+     * @since 9.1.0
76
+     */
77
+    public function unlockTable() {
78
+        $this->conn->commit();
79
+    }
80 80
 
81
-	/**
82
-	 * Insert a row if the matching row does not exists.
83
-	 *
84
-	 * @param string $table The table name (will replace *PREFIX* with the actual prefix)
85
-	 * @param array $input data that should be inserted into the table  (column name => value)
86
-	 * @param array|null $compare List of values that should be checked for "if not exists"
87
-	 *				If this is null or an empty array, all keys of $input will be compared
88
-	 *				Please note: text fields (clob) must not be used in the compare array
89
-	 * @return int number of inserted rows
90
-	 * @throws \Doctrine\DBAL\DBALException
91
-	 */
92
-	public function insertIfNotExist($table, $input, array $compare = null) {
93
-		if (empty($compare)) {
94
-			$compare = array_keys($input);
95
-		}
96
-		$query = 'INSERT INTO `' .$table . '` (`'
97
-			. implode('`,`', array_keys($input)) . '`) SELECT '
98
-			. str_repeat('?,', count($input)-1).'? ' // Is there a prettier alternative?
99
-			. 'FROM `' . $table . '` WHERE ';
81
+    /**
82
+     * Insert a row if the matching row does not exists.
83
+     *
84
+     * @param string $table The table name (will replace *PREFIX* with the actual prefix)
85
+     * @param array $input data that should be inserted into the table  (column name => value)
86
+     * @param array|null $compare List of values that should be checked for "if not exists"
87
+     *				If this is null or an empty array, all keys of $input will be compared
88
+     *				Please note: text fields (clob) must not be used in the compare array
89
+     * @return int number of inserted rows
90
+     * @throws \Doctrine\DBAL\DBALException
91
+     */
92
+    public function insertIfNotExist($table, $input, array $compare = null) {
93
+        if (empty($compare)) {
94
+            $compare = array_keys($input);
95
+        }
96
+        $query = 'INSERT INTO `' .$table . '` (`'
97
+            . implode('`,`', array_keys($input)) . '`) SELECT '
98
+            . str_repeat('?,', count($input)-1).'? ' // Is there a prettier alternative?
99
+            . 'FROM `' . $table . '` WHERE ';
100 100
 
101
-		$inserts = array_values($input);
102
-		foreach($compare as $key) {
103
-			$query .= '`' . $key . '`';
104
-			if (is_null($input[$key])) {
105
-				$query .= ' IS NULL AND ';
106
-			} else {
107
-				$inserts[] = $input[$key];
108
-				$query .= ' = ? AND ';
109
-			}
110
-		}
111
-		$query = substr($query, 0, -5);
112
-		$query .= ' HAVING COUNT(*) = 0';
101
+        $inserts = array_values($input);
102
+        foreach($compare as $key) {
103
+            $query .= '`' . $key . '`';
104
+            if (is_null($input[$key])) {
105
+                $query .= ' IS NULL AND ';
106
+            } else {
107
+                $inserts[] = $input[$key];
108
+                $query .= ' = ? AND ';
109
+            }
110
+        }
111
+        $query = substr($query, 0, -5);
112
+        $query .= ' HAVING COUNT(*) = 0';
113 113
 
114
-		return $this->conn->executeUpdate($query, $inserts);
115
-	}
114
+        return $this->conn->executeUpdate($query, $inserts);
115
+    }
116 116
 }
Please login to merge, or discard this patch.
apps/files_versions/lib/Storage.php 1 patch
Indentation   +792 added lines, -792 removed lines patch added patch discarded remove patch
@@ -54,797 +54,797 @@
 block discarded – undo
54 54
 
55 55
 class Storage {
56 56
 
57
-	const DEFAULTENABLED=true;
58
-	const DEFAULTMAXSIZE=50; // unit: percentage; 50% of available disk space/quota
59
-	const VERSIONS_ROOT = 'files_versions/';
60
-
61
-	const DELETE_TRIGGER_MASTER_REMOVED = 0;
62
-	const DELETE_TRIGGER_RETENTION_CONSTRAINT = 1;
63
-	const DELETE_TRIGGER_QUOTA_EXCEEDED = 2;
64
-
65
-	// files for which we can remove the versions after the delete operation was successful
66
-	private static $deletedFiles = array();
67
-
68
-	private static $sourcePathAndUser = array();
69
-
70
-	private static $max_versions_per_interval = array(
71
-		//first 10sec, one version every 2sec
72
-		1 => array('intervalEndsAfter' => 10,      'step' => 2),
73
-		//next minute, one version every 10sec
74
-		2 => array('intervalEndsAfter' => 60,      'step' => 10),
75
-		//next hour, one version every minute
76
-		3 => array('intervalEndsAfter' => 3600,    'step' => 60),
77
-		//next 24h, one version every hour
78
-		4 => array('intervalEndsAfter' => 86400,   'step' => 3600),
79
-		//next 30days, one version per day
80
-		5 => array('intervalEndsAfter' => 2592000, 'step' => 86400),
81
-		//until the end one version per week
82
-		6 => array('intervalEndsAfter' => -1,      'step' => 604800),
83
-	);
84
-
85
-	/** @var \OCA\Files_Versions\AppInfo\Application */
86
-	private static $application;
87
-
88
-	/**
89
-	 * get the UID of the owner of the file and the path to the file relative to
90
-	 * owners files folder
91
-	 *
92
-	 * @param string $filename
93
-	 * @return array
94
-	 * @throws \OC\User\NoUserException
95
-	 */
96
-	public static function getUidAndFilename($filename) {
97
-		$uid = Filesystem::getOwner($filename);
98
-		$userManager = \OC::$server->getUserManager();
99
-		// if the user with the UID doesn't exists, e.g. because the UID points
100
-		// to a remote user with a federated cloud ID we use the current logged-in
101
-		// user. We need a valid local user to create the versions
102
-		if (!$userManager->userExists($uid)) {
103
-			$uid = User::getUser();
104
-		}
105
-		Filesystem::initMountPoints($uid);
106
-		if ( $uid !== User::getUser() ) {
107
-			$info = Filesystem::getFileInfo($filename);
108
-			$ownerView = new View('/'.$uid.'/files');
109
-			try {
110
-				$filename = $ownerView->getPath($info['fileid']);
111
-				// make sure that the file name doesn't end with a trailing slash
112
-				// can for example happen single files shared across servers
113
-				$filename = rtrim($filename, '/');
114
-			} catch (NotFoundException $e) {
115
-				$filename = null;
116
-			}
117
-		}
118
-		return [$uid, $filename];
119
-	}
120
-
121
-	/**
122
-	 * Remember the owner and the owner path of the source file
123
-	 *
124
-	 * @param string $source source path
125
-	 */
126
-	public static function setSourcePathAndUser($source) {
127
-		list($uid, $path) = self::getUidAndFilename($source);
128
-		self::$sourcePathAndUser[$source] = array('uid' => $uid, 'path' => $path);
129
-	}
130
-
131
-	/**
132
-	 * Gets the owner and the owner path from the source path
133
-	 *
134
-	 * @param string $source source path
135
-	 * @return array with user id and path
136
-	 */
137
-	public static function getSourcePathAndUser($source) {
138
-
139
-		if (isset(self::$sourcePathAndUser[$source])) {
140
-			$uid = self::$sourcePathAndUser[$source]['uid'];
141
-			$path = self::$sourcePathAndUser[$source]['path'];
142
-			unset(self::$sourcePathAndUser[$source]);
143
-		} else {
144
-			$uid = $path = false;
145
-		}
146
-		return array($uid, $path);
147
-	}
148
-
149
-	/**
150
-	 * get current size of all versions from a given user
151
-	 *
152
-	 * @param string $user user who owns the versions
153
-	 * @return int versions size
154
-	 */
155
-	private static function getVersionsSize($user) {
156
-		$view = new View('/' . $user);
157
-		$fileInfo = $view->getFileInfo('/files_versions');
158
-		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
159
-	}
160
-
161
-	/**
162
-	 * store a new version of a file.
163
-	 */
164
-	public static function store($filename) {
165
-
166
-		// if the file gets streamed we need to remove the .part extension
167
-		// to get the right target
168
-		$ext = pathinfo($filename, PATHINFO_EXTENSION);
169
-		if ($ext === 'part') {
170
-			$filename = substr($filename, 0, -5);
171
-		}
172
-
173
-		// we only handle existing files
174
-		if (! Filesystem::file_exists($filename) || Filesystem::is_dir($filename)) {
175
-			return false;
176
-		}
177
-
178
-		list($uid, $filename) = self::getUidAndFilename($filename);
179
-
180
-		$files_view = new View('/'.$uid .'/files');
181
-		$users_view = new View('/'.$uid);
182
-
183
-		$eventDispatcher = \OC::$server->getEventDispatcher();
184
-		$id = $files_view->getFileInfo($filename)->getId();
185
-		$nodes = \OC::$server->getRootFolder()->getById($id);
186
-		foreach ($nodes as $node) {
187
-			$event = new CreateVersionEvent($node);
188
-			$eventDispatcher->dispatch('OCA\Files_Versions::createVersion', $event);
189
-			if ($event->shouldCreateVersion() === false) {
190
-				return false;
191
-			}
192
-		}
193
-
194
-		// no use making versions for empty files
195
-		if ($files_view->filesize($filename) === 0) {
196
-			return false;
197
-		}
198
-
199
-		// create all parent folders
200
-		self::createMissingDirectories($filename, $users_view);
201
-
202
-		self::scheduleExpire($uid, $filename);
203
-
204
-		// store a new version of a file
205
-		$mtime = $users_view->filemtime('files/' . $filename);
206
-		$users_view->copy('files/' . $filename, 'files_versions/' . $filename . '.v' . $mtime);
207
-		// call getFileInfo to enforce a file cache entry for the new version
208
-		$users_view->getFileInfo('files_versions/' . $filename . '.v' . $mtime);
209
-	}
210
-
211
-
212
-	/**
213
-	 * mark file as deleted so that we can remove the versions if the file is gone
214
-	 * @param string $path
215
-	 */
216
-	public static function markDeletedFile($path) {
217
-		list($uid, $filename) = self::getUidAndFilename($path);
218
-		self::$deletedFiles[$path] = array(
219
-			'uid' => $uid,
220
-			'filename' => $filename);
221
-	}
222
-
223
-	/**
224
-	 * delete the version from the storage and cache
225
-	 *
226
-	 * @param View $view
227
-	 * @param string $path
228
-	 */
229
-	protected static function deleteVersion($view, $path) {
230
-		$view->unlink($path);
231
-		/**
232
-		 * @var \OC\Files\Storage\Storage $storage
233
-		 * @var string $internalPath
234
-		 */
235
-		list($storage, $internalPath) = $view->resolvePath($path);
236
-		$cache = $storage->getCache($internalPath);
237
-		$cache->remove($internalPath);
238
-	}
239
-
240
-	/**
241
-	 * Delete versions of a file
242
-	 */
243
-	public static function delete($path) {
244
-
245
-		$deletedFile = self::$deletedFiles[$path];
246
-		$uid = $deletedFile['uid'];
247
-		$filename = $deletedFile['filename'];
248
-
249
-		if (!Filesystem::file_exists($path)) {
250
-
251
-			$view = new View('/' . $uid . '/files_versions');
252
-
253
-			$versions = self::getVersions($uid, $filename);
254
-			if (!empty($versions)) {
255
-				foreach ($versions as $v) {
256
-					\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
257
-					self::deleteVersion($view, $filename . '.v' . $v['version']);
258
-					\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
259
-				}
260
-			}
261
-		}
262
-		unset(self::$deletedFiles[$path]);
263
-	}
264
-
265
-	/**
266
-	 * Rename or copy versions of a file of the given paths
267
-	 *
268
-	 * @param string $sourcePath source path of the file to move, relative to
269
-	 * the currently logged in user's "files" folder
270
-	 * @param string $targetPath target path of the file to move, relative to
271
-	 * the currently logged in user's "files" folder
272
-	 * @param string $operation can be 'copy' or 'rename'
273
-	 */
274
-	public static function renameOrCopy($sourcePath, $targetPath, $operation) {
275
-		list($sourceOwner, $sourcePath) = self::getSourcePathAndUser($sourcePath);
276
-
277
-		// it was a upload of a existing file if no old path exists
278
-		// in this case the pre-hook already called the store method and we can
279
-		// stop here
280
-		if ($sourcePath === false) {
281
-			return true;
282
-		}
283
-
284
-		list($targetOwner, $targetPath) = self::getUidAndFilename($targetPath);
285
-
286
-		$sourcePath = ltrim($sourcePath, '/');
287
-		$targetPath = ltrim($targetPath, '/');
288
-
289
-		$rootView = new View('');
290
-
291
-		// did we move a directory ?
292
-		if ($rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
293
-			// does the directory exists for versions too ?
294
-			if ($rootView->is_dir('/' . $sourceOwner . '/files_versions/' . $sourcePath)) {
295
-				// create missing dirs if necessary
296
-				self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
297
-
298
-				// move the directory containing the versions
299
-				$rootView->$operation(
300
-					'/' . $sourceOwner . '/files_versions/' . $sourcePath,
301
-					'/' . $targetOwner . '/files_versions/' . $targetPath
302
-				);
303
-			}
304
-		} else if ($versions = Storage::getVersions($sourceOwner, '/' . $sourcePath)) {
305
-			// create missing dirs if necessary
306
-			self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
307
-
308
-			foreach ($versions as $v) {
309
-				// move each version one by one to the target directory
310
-				$rootView->$operation(
311
-					'/' . $sourceOwner . '/files_versions/' . $sourcePath.'.v' . $v['version'],
312
-					'/' . $targetOwner . '/files_versions/' . $targetPath.'.v'.$v['version']
313
-				);
314
-			}
315
-		}
316
-
317
-		// if we moved versions directly for a file, schedule expiration check for that file
318
-		if (!$rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
319
-			self::scheduleExpire($targetOwner, $targetPath);
320
-		}
321
-
322
-	}
323
-
324
-	/**
325
-	 * Rollback to an old version of a file.
326
-	 *
327
-	 * @param string $file file name
328
-	 * @param int $revision revision timestamp
329
-	 * @return bool
330
-	 */
331
-	public static function rollback($file, $revision) {
332
-
333
-		// add expected leading slash
334
-		$file = '/' . ltrim($file, '/');
335
-		list($uid, $filename) = self::getUidAndFilename($file);
336
-		if ($uid === null || trim($filename, '/') === '') {
337
-			return false;
338
-		}
339
-
340
-		$users_view = new View('/'.$uid);
341
-		$files_view = new View('/'. User::getUser().'/files');
342
-
343
-		$versionCreated = false;
344
-
345
-		$fileInfo = $files_view->getFileInfo($file);
346
-
347
-		// check if user has the permissions to revert a version
348
-		if (!$fileInfo->isUpdateable()) {
349
-			return false;
350
-		}
351
-
352
-		//first create a new version
353
-		$version = 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename);
354
-		if (!$users_view->file_exists($version)) {
355
-			$users_view->copy('files'.$filename, 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename));
356
-			$versionCreated = true;
357
-		}
358
-
359
-		$fileToRestore =  'files_versions' . $filename . '.v' . $revision;
360
-
361
-		// Restore encrypted version of the old file for the newly restored file
362
-		// This has to happen manually here since the file is manually copied below
363
-		$oldVersion = $users_view->getFileInfo($fileToRestore)->getEncryptedVersion();
364
-		$oldFileInfo = $users_view->getFileInfo($fileToRestore);
365
-		$cache = $fileInfo->getStorage()->getCache();
366
-		$cache->update(
367
-			$fileInfo->getId(), [
368
-				'encrypted' => $oldVersion,
369
-				'encryptedVersion' => $oldVersion,
370
-				'size' => $oldFileInfo->getSize()
371
-			]
372
-		);
373
-
374
-		// rollback
375
-		if (self::copyFileContents($users_view, $fileToRestore, 'files' . $filename)) {
376
-			$files_view->touch($file, $revision);
377
-			Storage::scheduleExpire($uid, $file);
378
-			\OC_Hook::emit('\OCP\Versions', 'rollback', array(
379
-				'path' => $filename,
380
-				'revision' => $revision,
381
-			));
382
-			return true;
383
-		} else if ($versionCreated) {
384
-			self::deleteVersion($users_view, $version);
385
-		}
386
-
387
-		return false;
388
-
389
-	}
390
-
391
-	/**
392
-	 * Stream copy file contents from $path1 to $path2
393
-	 *
394
-	 * @param View $view view to use for copying
395
-	 * @param string $path1 source file to copy
396
-	 * @param string $path2 target file
397
-	 *
398
-	 * @return bool true for success, false otherwise
399
-	 */
400
-	private static function copyFileContents($view, $path1, $path2) {
401
-		/** @var \OC\Files\Storage\Storage $storage1 */
402
-		list($storage1, $internalPath1) = $view->resolvePath($path1);
403
-		/** @var \OC\Files\Storage\Storage $storage2 */
404
-		list($storage2, $internalPath2) = $view->resolvePath($path2);
405
-
406
-		$view->lockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
407
-		$view->lockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
408
-
409
-		// TODO add a proper way of overwriting a file while maintaining file ids
410
-		if ($storage1->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage') || $storage2->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage')) {
411
-			$source = $storage1->fopen($internalPath1, 'r');
412
-			$target = $storage2->fopen($internalPath2, 'w');
413
-			list(, $result) = \OC_Helper::streamCopy($source, $target);
414
-			fclose($source);
415
-			fclose($target);
416
-
417
-			if ($result !== false) {
418
-				$storage1->unlink($internalPath1);
419
-			}
420
-		} else {
421
-			$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
422
-		}
423
-
424
-		$view->unlockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
425
-		$view->unlockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
426
-
427
-		return ($result !== false);
428
-	}
429
-
430
-	/**
431
-	 * get a list of all available versions of a file in descending chronological order
432
-	 * @param string $uid user id from the owner of the file
433
-	 * @param string $filename file to find versions of, relative to the user files dir
434
-	 * @param string $userFullPath
435
-	 * @return array versions newest version first
436
-	 */
437
-	public static function getVersions($uid, $filename, $userFullPath = '') {
438
-		$versions = array();
439
-		if (empty($filename)) {
440
-			return $versions;
441
-		}
442
-		// fetch for old versions
443
-		$view = new View('/' . $uid . '/');
444
-
445
-		$pathinfo = pathinfo($filename);
446
-		$versionedFile = $pathinfo['basename'];
447
-
448
-		$dir = Filesystem::normalizePath(self::VERSIONS_ROOT . '/' . $pathinfo['dirname']);
449
-
450
-		$dirContent = false;
451
-		if ($view->is_dir($dir)) {
452
-			$dirContent = $view->opendir($dir);
453
-		}
454
-
455
-		if ($dirContent === false) {
456
-			return $versions;
457
-		}
458
-
459
-		if (is_resource($dirContent)) {
460
-			while (($entryName = readdir($dirContent)) !== false) {
461
-				if (!Filesystem::isIgnoredDir($entryName)) {
462
-					$pathparts = pathinfo($entryName);
463
-					$filename = $pathparts['filename'];
464
-					if ($filename === $versionedFile) {
465
-						$pathparts = pathinfo($entryName);
466
-						$timestamp = substr($pathparts['extension'], 1);
467
-						$filename = $pathparts['filename'];
468
-						$key = $timestamp . '#' . $filename;
469
-						$versions[$key]['version'] = $timestamp;
470
-						$versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($timestamp);
471
-						if (empty($userFullPath)) {
472
-							$versions[$key]['preview'] = '';
473
-						} else {
474
-							$versions[$key]['preview'] = \OC::$server->getURLGenerator('files_version.Preview.getPreview', ['file' => $userFullPath, 'version' => $timestamp]);
475
-						}
476
-						$versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'] . '/' . $filename);
477
-						$versions[$key]['name'] = $versionedFile;
478
-						$versions[$key]['size'] = $view->filesize($dir . '/' . $entryName);
479
-						$versions[$key]['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($versionedFile);
480
-					}
481
-				}
482
-			}
483
-			closedir($dirContent);
484
-		}
485
-
486
-		// sort with newest version first
487
-		krsort($versions);
488
-
489
-		return $versions;
490
-	}
491
-
492
-	/**
493
-	 * Expire versions that older than max version retention time
494
-	 * @param string $uid
495
-	 */
496
-	public static function expireOlderThanMaxForUser($uid){
497
-		$expiration = self::getExpiration();
498
-		$threshold = $expiration->getMaxAgeAsTimestamp();
499
-		$versions = self::getAllVersions($uid);
500
-		if (!$threshold || !array_key_exists('all', $versions)) {
501
-			return;
502
-		}
503
-
504
-		$toDelete = [];
505
-		foreach (array_reverse($versions['all']) as $key => $version) {
506
-			if (intval($version['version'])<$threshold) {
507
-				$toDelete[$key] = $version;
508
-			} else {
509
-				//Versions are sorted by time - nothing mo to iterate.
510
-				break;
511
-			}
512
-		}
513
-
514
-		$view = new View('/' . $uid . '/files_versions');
515
-		if (!empty($toDelete)) {
516
-			foreach ($toDelete as $version) {
517
-				\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
518
-				self::deleteVersion($view, $version['path'] . '.v' . $version['version']);
519
-				\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
520
-			}
521
-		}
522
-	}
523
-
524
-	/**
525
-	 * translate a timestamp into a string like "5 days ago"
526
-	 * @param int $timestamp
527
-	 * @return string for example "5 days ago"
528
-	 */
529
-	private static function getHumanReadableTimestamp($timestamp) {
530
-
531
-		$diff = time() - $timestamp;
532
-
533
-		if ($diff < 60) { // first minute
534
-			return  $diff . " seconds ago";
535
-		} elseif ($diff < 3600) { //first hour
536
-			return round($diff / 60) . " minutes ago";
537
-		} elseif ($diff < 86400) { // first day
538
-			return round($diff / 3600) . " hours ago";
539
-		} elseif ($diff < 604800) { //first week
540
-			return round($diff / 86400) . " days ago";
541
-		} elseif ($diff < 2419200) { //first month
542
-			return round($diff / 604800) . " weeks ago";
543
-		} elseif ($diff < 29030400) { // first year
544
-			return round($diff / 2419200) . " months ago";
545
-		} else {
546
-			return round($diff / 29030400) . " years ago";
547
-		}
548
-
549
-	}
550
-
551
-	/**
552
-	 * returns all stored file versions from a given user
553
-	 * @param string $uid id of the user
554
-	 * @return array with contains two arrays 'all' which contains all versions sorted by age and 'by_file' which contains all versions sorted by filename
555
-	 */
556
-	private static function getAllVersions($uid) {
557
-		$view = new View('/' . $uid . '/');
558
-		$dirs = array(self::VERSIONS_ROOT);
559
-		$versions = array();
560
-
561
-		while (!empty($dirs)) {
562
-			$dir = array_pop($dirs);
563
-			$files = $view->getDirectoryContent($dir);
564
-
565
-			foreach ($files as $file) {
566
-				$fileData = $file->getData();
567
-				$filePath = $dir . '/' . $fileData['name'];
568
-				if ($file['type'] === 'dir') {
569
-					$dirs[] = $filePath;
570
-				} else {
571
-					$versionsBegin = strrpos($filePath, '.v');
572
-					$relPathStart = strlen(self::VERSIONS_ROOT);
573
-					$version = substr($filePath, $versionsBegin + 2);
574
-					$relpath = substr($filePath, $relPathStart, $versionsBegin - $relPathStart);
575
-					$key = $version . '#' . $relpath;
576
-					$versions[$key] = array('path' => $relpath, 'timestamp' => $version);
577
-				}
578
-			}
579
-		}
580
-
581
-		// newest version first
582
-		krsort($versions);
583
-
584
-		$result = array();
585
-
586
-		foreach ($versions as $key => $value) {
587
-			$size = $view->filesize(self::VERSIONS_ROOT.'/'.$value['path'].'.v'.$value['timestamp']);
588
-			$filename = $value['path'];
589
-
590
-			$result['all'][$key]['version'] = $value['timestamp'];
591
-			$result['all'][$key]['path'] = $filename;
592
-			$result['all'][$key]['size'] = $size;
593
-
594
-			$result['by_file'][$filename][$key]['version'] = $value['timestamp'];
595
-			$result['by_file'][$filename][$key]['path'] = $filename;
596
-			$result['by_file'][$filename][$key]['size'] = $size;
597
-		}
598
-
599
-		return $result;
600
-	}
601
-
602
-	/**
603
-	 * get list of files we want to expire
604
-	 * @param array $versions list of versions
605
-	 * @param integer $time
606
-	 * @param bool $quotaExceeded is versions storage limit reached
607
-	 * @return array containing the list of to deleted versions and the size of them
608
-	 */
609
-	protected static function getExpireList($time, $versions, $quotaExceeded = false) {
610
-		$expiration = self::getExpiration();
611
-
612
-		if ($expiration->shouldAutoExpire()) {
613
-			list($toDelete, $size) = self::getAutoExpireList($time, $versions);
614
-		} else {
615
-			$size = 0;
616
-			$toDelete = [];  // versions we want to delete
617
-		}
618
-
619
-		foreach ($versions as $key => $version) {
620
-			if ($expiration->isExpired($version['version'], $quotaExceeded) && !isset($toDelete[$key])) {
621
-				$size += $version['size'];
622
-				$toDelete[$key] = $version['path'] . '.v' . $version['version'];
623
-			}
624
-		}
625
-
626
-		return [$toDelete, $size];
627
-	}
628
-
629
-	/**
630
-	 * get list of files we want to expire
631
-	 * @param array $versions list of versions
632
-	 * @param integer $time
633
-	 * @return array containing the list of to deleted versions and the size of them
634
-	 */
635
-	protected static function getAutoExpireList($time, $versions) {
636
-		$size = 0;
637
-		$toDelete = array();  // versions we want to delete
638
-
639
-		$interval = 1;
640
-		$step = Storage::$max_versions_per_interval[$interval]['step'];
641
-		if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
642
-			$nextInterval = -1;
643
-		} else {
644
-			$nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
645
-		}
646
-
647
-		$firstVersion = reset($versions);
648
-		$firstKey = key($versions);
649
-		$prevTimestamp = $firstVersion['version'];
650
-		$nextVersion = $firstVersion['version'] - $step;
651
-		unset($versions[$firstKey]);
652
-
653
-		foreach ($versions as $key => $version) {
654
-			$newInterval = true;
655
-			while ($newInterval) {
656
-				if ($nextInterval === -1 || $prevTimestamp > $nextInterval) {
657
-					if ($version['version'] > $nextVersion) {
658
-						//distance between two version too small, mark to delete
659
-						$toDelete[$key] = $version['path'] . '.v' . $version['version'];
660
-						$size += $version['size'];
661
-						\OCP\Util::writeLog('files_versions', 'Mark to expire '. $version['path'] .' next version should be ' . $nextVersion . " or smaller. (prevTimestamp: " . $prevTimestamp . "; step: " . $step, \OCP\Util::INFO);
662
-					} else {
663
-						$nextVersion = $version['version'] - $step;
664
-						$prevTimestamp = $version['version'];
665
-					}
666
-					$newInterval = false; // version checked so we can move to the next one
667
-				} else { // time to move on to the next interval
668
-					$interval++;
669
-					$step = Storage::$max_versions_per_interval[$interval]['step'];
670
-					$nextVersion = $prevTimestamp - $step;
671
-					if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
672
-						$nextInterval = -1;
673
-					} else {
674
-						$nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
675
-					}
676
-					$newInterval = true; // we changed the interval -> check same version with new interval
677
-				}
678
-			}
679
-		}
680
-
681
-		return array($toDelete, $size);
682
-	}
683
-
684
-	/**
685
-	 * Schedule versions expiration for the given file
686
-	 *
687
-	 * @param string $uid owner of the file
688
-	 * @param string $fileName file/folder for which to schedule expiration
689
-	 */
690
-	private static function scheduleExpire($uid, $fileName) {
691
-		// let the admin disable auto expire
692
-		$expiration = self::getExpiration();
693
-		if ($expiration->isEnabled()) {
694
-			$command = new Expire($uid, $fileName);
695
-			\OC::$server->getCommandBus()->push($command);
696
-		}
697
-	}
698
-
699
-	/**
700
-	 * Expire versions which exceed the quota.
701
-	 *
702
-	 * This will setup the filesystem for the given user but will not
703
-	 * tear it down afterwards.
704
-	 *
705
-	 * @param string $filename path to file to expire
706
-	 * @param string $uid user for which to expire the version
707
-	 * @return bool|int|null
708
-	 */
709
-	public static function expire($filename, $uid) {
710
-		$expiration = self::getExpiration();
711
-
712
-		if ($expiration->isEnabled()) {
713
-			// get available disk space for user
714
-			$user = \OC::$server->getUserManager()->get($uid);
715
-			if (is_null($user)) {
716
-				\OCP\Util::writeLog('files_versions', 'Backends provided no user object for ' . $uid, \OCP\Util::ERROR);
717
-				throw new \OC\User\NoUserException('Backends provided no user object for ' . $uid);
718
-			}
719
-
720
-			\OC_Util::setupFS($uid);
721
-
722
-			if (!Filesystem::file_exists($filename)) {
723
-				return false;
724
-			}
725
-
726
-			if (empty($filename)) {
727
-				// file maybe renamed or deleted
728
-				return false;
729
-			}
730
-			$versionsFileview = new View('/'.$uid.'/files_versions');
731
-
732
-			$softQuota = true;
733
-			$quota = $user->getQuota();
734
-			if ( $quota === null || $quota === 'none' ) {
735
-				$quota = Filesystem::free_space('/');
736
-				$softQuota = false;
737
-			} else {
738
-				$quota = \OCP\Util::computerFileSize($quota);
739
-			}
740
-
741
-			// make sure that we have the current size of the version history
742
-			$versionsSize = self::getVersionsSize($uid);
743
-
744
-			// calculate available space for version history
745
-			// subtract size of files and current versions size from quota
746
-			if ($quota >= 0) {
747
-				if ($softQuota) {
748
-					$files_view = new View('/' . $uid . '/files');
749
-					$rootInfo = $files_view->getFileInfo('/', false);
750
-					$free = $quota - $rootInfo['size']; // remaining free space for user
751
-					if ($free > 0) {
752
-						$availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $versionsSize; // how much space can be used for versions
753
-					} else {
754
-						$availableSpace = $free - $versionsSize;
755
-					}
756
-				} else {
757
-					$availableSpace = $quota;
758
-				}
759
-			} else {
760
-				$availableSpace = PHP_INT_MAX;
761
-			}
762
-
763
-			$allVersions = Storage::getVersions($uid, $filename);
764
-
765
-			$time = time();
766
-			list($toDelete, $sizeOfDeletedVersions) = self::getExpireList($time, $allVersions, $availableSpace <= 0);
767
-
768
-			$availableSpace = $availableSpace + $sizeOfDeletedVersions;
769
-			$versionsSize = $versionsSize - $sizeOfDeletedVersions;
770
-
771
-			// if still not enough free space we rearrange the versions from all files
772
-			if ($availableSpace <= 0) {
773
-				$result = Storage::getAllVersions($uid);
774
-				$allVersions = $result['all'];
775
-
776
-				foreach ($result['by_file'] as $versions) {
777
-					list($toDeleteNew, $size) = self::getExpireList($time, $versions, $availableSpace <= 0);
778
-					$toDelete = array_merge($toDelete, $toDeleteNew);
779
-					$sizeOfDeletedVersions += $size;
780
-				}
781
-				$availableSpace = $availableSpace + $sizeOfDeletedVersions;
782
-				$versionsSize = $versionsSize - $sizeOfDeletedVersions;
783
-			}
784
-
785
-			foreach($toDelete as $key => $path) {
786
-				\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
787
-				self::deleteVersion($versionsFileview, $path);
788
-				\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
789
-				unset($allVersions[$key]); // update array with the versions we keep
790
-				\OCP\Util::writeLog('files_versions', "Expire: " . $path, \OCP\Util::INFO);
791
-			}
792
-
793
-			// Check if enough space is available after versions are rearranged.
794
-			// If not we delete the oldest versions until we meet the size limit for versions,
795
-			// but always keep the two latest versions
796
-			$numOfVersions = count($allVersions) -2 ;
797
-			$i = 0;
798
-			// sort oldest first and make sure that we start at the first element
799
-			ksort($allVersions);
800
-			reset($allVersions);
801
-			while ($availableSpace < 0 && $i < $numOfVersions) {
802
-				$version = current($allVersions);
803
-				\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
804
-				self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']);
805
-				\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
806
-				\OCP\Util::writeLog('files_versions', 'running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'] , \OCP\Util::INFO);
807
-				$versionsSize -= $version['size'];
808
-				$availableSpace += $version['size'];
809
-				next($allVersions);
810
-				$i++;
811
-			}
812
-
813
-			return $versionsSize; // finally return the new size of the version history
814
-		}
815
-
816
-		return false;
817
-	}
818
-
819
-	/**
820
-	 * Create recursively missing directories inside of files_versions
821
-	 * that match the given path to a file.
822
-	 *
823
-	 * @param string $filename $path to a file, relative to the user's
824
-	 * "files" folder
825
-	 * @param View $view view on data/user/
826
-	 */
827
-	private static function createMissingDirectories($filename, $view) {
828
-		$dirname = Filesystem::normalizePath(dirname($filename));
829
-		$dirParts = explode('/', $dirname);
830
-		$dir = "/files_versions";
831
-		foreach ($dirParts as $part) {
832
-			$dir = $dir . '/' . $part;
833
-			if (!$view->file_exists($dir)) {
834
-				$view->mkdir($dir);
835
-			}
836
-		}
837
-	}
838
-
839
-	/**
840
-	 * Static workaround
841
-	 * @return Expiration
842
-	 */
843
-	protected static function getExpiration(){
844
-		if (is_null(self::$application)) {
845
-			self::$application = new Application();
846
-		}
847
-		return self::$application->getContainer()->query('Expiration');
848
-	}
57
+    const DEFAULTENABLED=true;
58
+    const DEFAULTMAXSIZE=50; // unit: percentage; 50% of available disk space/quota
59
+    const VERSIONS_ROOT = 'files_versions/';
60
+
61
+    const DELETE_TRIGGER_MASTER_REMOVED = 0;
62
+    const DELETE_TRIGGER_RETENTION_CONSTRAINT = 1;
63
+    const DELETE_TRIGGER_QUOTA_EXCEEDED = 2;
64
+
65
+    // files for which we can remove the versions after the delete operation was successful
66
+    private static $deletedFiles = array();
67
+
68
+    private static $sourcePathAndUser = array();
69
+
70
+    private static $max_versions_per_interval = array(
71
+        //first 10sec, one version every 2sec
72
+        1 => array('intervalEndsAfter' => 10,      'step' => 2),
73
+        //next minute, one version every 10sec
74
+        2 => array('intervalEndsAfter' => 60,      'step' => 10),
75
+        //next hour, one version every minute
76
+        3 => array('intervalEndsAfter' => 3600,    'step' => 60),
77
+        //next 24h, one version every hour
78
+        4 => array('intervalEndsAfter' => 86400,   'step' => 3600),
79
+        //next 30days, one version per day
80
+        5 => array('intervalEndsAfter' => 2592000, 'step' => 86400),
81
+        //until the end one version per week
82
+        6 => array('intervalEndsAfter' => -1,      'step' => 604800),
83
+    );
84
+
85
+    /** @var \OCA\Files_Versions\AppInfo\Application */
86
+    private static $application;
87
+
88
+    /**
89
+     * get the UID of the owner of the file and the path to the file relative to
90
+     * owners files folder
91
+     *
92
+     * @param string $filename
93
+     * @return array
94
+     * @throws \OC\User\NoUserException
95
+     */
96
+    public static function getUidAndFilename($filename) {
97
+        $uid = Filesystem::getOwner($filename);
98
+        $userManager = \OC::$server->getUserManager();
99
+        // if the user with the UID doesn't exists, e.g. because the UID points
100
+        // to a remote user with a federated cloud ID we use the current logged-in
101
+        // user. We need a valid local user to create the versions
102
+        if (!$userManager->userExists($uid)) {
103
+            $uid = User::getUser();
104
+        }
105
+        Filesystem::initMountPoints($uid);
106
+        if ( $uid !== User::getUser() ) {
107
+            $info = Filesystem::getFileInfo($filename);
108
+            $ownerView = new View('/'.$uid.'/files');
109
+            try {
110
+                $filename = $ownerView->getPath($info['fileid']);
111
+                // make sure that the file name doesn't end with a trailing slash
112
+                // can for example happen single files shared across servers
113
+                $filename = rtrim($filename, '/');
114
+            } catch (NotFoundException $e) {
115
+                $filename = null;
116
+            }
117
+        }
118
+        return [$uid, $filename];
119
+    }
120
+
121
+    /**
122
+     * Remember the owner and the owner path of the source file
123
+     *
124
+     * @param string $source source path
125
+     */
126
+    public static function setSourcePathAndUser($source) {
127
+        list($uid, $path) = self::getUidAndFilename($source);
128
+        self::$sourcePathAndUser[$source] = array('uid' => $uid, 'path' => $path);
129
+    }
130
+
131
+    /**
132
+     * Gets the owner and the owner path from the source path
133
+     *
134
+     * @param string $source source path
135
+     * @return array with user id and path
136
+     */
137
+    public static function getSourcePathAndUser($source) {
138
+
139
+        if (isset(self::$sourcePathAndUser[$source])) {
140
+            $uid = self::$sourcePathAndUser[$source]['uid'];
141
+            $path = self::$sourcePathAndUser[$source]['path'];
142
+            unset(self::$sourcePathAndUser[$source]);
143
+        } else {
144
+            $uid = $path = false;
145
+        }
146
+        return array($uid, $path);
147
+    }
148
+
149
+    /**
150
+     * get current size of all versions from a given user
151
+     *
152
+     * @param string $user user who owns the versions
153
+     * @return int versions size
154
+     */
155
+    private static function getVersionsSize($user) {
156
+        $view = new View('/' . $user);
157
+        $fileInfo = $view->getFileInfo('/files_versions');
158
+        return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
159
+    }
160
+
161
+    /**
162
+     * store a new version of a file.
163
+     */
164
+    public static function store($filename) {
165
+
166
+        // if the file gets streamed we need to remove the .part extension
167
+        // to get the right target
168
+        $ext = pathinfo($filename, PATHINFO_EXTENSION);
169
+        if ($ext === 'part') {
170
+            $filename = substr($filename, 0, -5);
171
+        }
172
+
173
+        // we only handle existing files
174
+        if (! Filesystem::file_exists($filename) || Filesystem::is_dir($filename)) {
175
+            return false;
176
+        }
177
+
178
+        list($uid, $filename) = self::getUidAndFilename($filename);
179
+
180
+        $files_view = new View('/'.$uid .'/files');
181
+        $users_view = new View('/'.$uid);
182
+
183
+        $eventDispatcher = \OC::$server->getEventDispatcher();
184
+        $id = $files_view->getFileInfo($filename)->getId();
185
+        $nodes = \OC::$server->getRootFolder()->getById($id);
186
+        foreach ($nodes as $node) {
187
+            $event = new CreateVersionEvent($node);
188
+            $eventDispatcher->dispatch('OCA\Files_Versions::createVersion', $event);
189
+            if ($event->shouldCreateVersion() === false) {
190
+                return false;
191
+            }
192
+        }
193
+
194
+        // no use making versions for empty files
195
+        if ($files_view->filesize($filename) === 0) {
196
+            return false;
197
+        }
198
+
199
+        // create all parent folders
200
+        self::createMissingDirectories($filename, $users_view);
201
+
202
+        self::scheduleExpire($uid, $filename);
203
+
204
+        // store a new version of a file
205
+        $mtime = $users_view->filemtime('files/' . $filename);
206
+        $users_view->copy('files/' . $filename, 'files_versions/' . $filename . '.v' . $mtime);
207
+        // call getFileInfo to enforce a file cache entry for the new version
208
+        $users_view->getFileInfo('files_versions/' . $filename . '.v' . $mtime);
209
+    }
210
+
211
+
212
+    /**
213
+     * mark file as deleted so that we can remove the versions if the file is gone
214
+     * @param string $path
215
+     */
216
+    public static function markDeletedFile($path) {
217
+        list($uid, $filename) = self::getUidAndFilename($path);
218
+        self::$deletedFiles[$path] = array(
219
+            'uid' => $uid,
220
+            'filename' => $filename);
221
+    }
222
+
223
+    /**
224
+     * delete the version from the storage and cache
225
+     *
226
+     * @param View $view
227
+     * @param string $path
228
+     */
229
+    protected static function deleteVersion($view, $path) {
230
+        $view->unlink($path);
231
+        /**
232
+         * @var \OC\Files\Storage\Storage $storage
233
+         * @var string $internalPath
234
+         */
235
+        list($storage, $internalPath) = $view->resolvePath($path);
236
+        $cache = $storage->getCache($internalPath);
237
+        $cache->remove($internalPath);
238
+    }
239
+
240
+    /**
241
+     * Delete versions of a file
242
+     */
243
+    public static function delete($path) {
244
+
245
+        $deletedFile = self::$deletedFiles[$path];
246
+        $uid = $deletedFile['uid'];
247
+        $filename = $deletedFile['filename'];
248
+
249
+        if (!Filesystem::file_exists($path)) {
250
+
251
+            $view = new View('/' . $uid . '/files_versions');
252
+
253
+            $versions = self::getVersions($uid, $filename);
254
+            if (!empty($versions)) {
255
+                foreach ($versions as $v) {
256
+                    \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
257
+                    self::deleteVersion($view, $filename . '.v' . $v['version']);
258
+                    \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
259
+                }
260
+            }
261
+        }
262
+        unset(self::$deletedFiles[$path]);
263
+    }
264
+
265
+    /**
266
+     * Rename or copy versions of a file of the given paths
267
+     *
268
+     * @param string $sourcePath source path of the file to move, relative to
269
+     * the currently logged in user's "files" folder
270
+     * @param string $targetPath target path of the file to move, relative to
271
+     * the currently logged in user's "files" folder
272
+     * @param string $operation can be 'copy' or 'rename'
273
+     */
274
+    public static function renameOrCopy($sourcePath, $targetPath, $operation) {
275
+        list($sourceOwner, $sourcePath) = self::getSourcePathAndUser($sourcePath);
276
+
277
+        // it was a upload of a existing file if no old path exists
278
+        // in this case the pre-hook already called the store method and we can
279
+        // stop here
280
+        if ($sourcePath === false) {
281
+            return true;
282
+        }
283
+
284
+        list($targetOwner, $targetPath) = self::getUidAndFilename($targetPath);
285
+
286
+        $sourcePath = ltrim($sourcePath, '/');
287
+        $targetPath = ltrim($targetPath, '/');
288
+
289
+        $rootView = new View('');
290
+
291
+        // did we move a directory ?
292
+        if ($rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
293
+            // does the directory exists for versions too ?
294
+            if ($rootView->is_dir('/' . $sourceOwner . '/files_versions/' . $sourcePath)) {
295
+                // create missing dirs if necessary
296
+                self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
297
+
298
+                // move the directory containing the versions
299
+                $rootView->$operation(
300
+                    '/' . $sourceOwner . '/files_versions/' . $sourcePath,
301
+                    '/' . $targetOwner . '/files_versions/' . $targetPath
302
+                );
303
+            }
304
+        } else if ($versions = Storage::getVersions($sourceOwner, '/' . $sourcePath)) {
305
+            // create missing dirs if necessary
306
+            self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
307
+
308
+            foreach ($versions as $v) {
309
+                // move each version one by one to the target directory
310
+                $rootView->$operation(
311
+                    '/' . $sourceOwner . '/files_versions/' . $sourcePath.'.v' . $v['version'],
312
+                    '/' . $targetOwner . '/files_versions/' . $targetPath.'.v'.$v['version']
313
+                );
314
+            }
315
+        }
316
+
317
+        // if we moved versions directly for a file, schedule expiration check for that file
318
+        if (!$rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
319
+            self::scheduleExpire($targetOwner, $targetPath);
320
+        }
321
+
322
+    }
323
+
324
+    /**
325
+     * Rollback to an old version of a file.
326
+     *
327
+     * @param string $file file name
328
+     * @param int $revision revision timestamp
329
+     * @return bool
330
+     */
331
+    public static function rollback($file, $revision) {
332
+
333
+        // add expected leading slash
334
+        $file = '/' . ltrim($file, '/');
335
+        list($uid, $filename) = self::getUidAndFilename($file);
336
+        if ($uid === null || trim($filename, '/') === '') {
337
+            return false;
338
+        }
339
+
340
+        $users_view = new View('/'.$uid);
341
+        $files_view = new View('/'. User::getUser().'/files');
342
+
343
+        $versionCreated = false;
344
+
345
+        $fileInfo = $files_view->getFileInfo($file);
346
+
347
+        // check if user has the permissions to revert a version
348
+        if (!$fileInfo->isUpdateable()) {
349
+            return false;
350
+        }
351
+
352
+        //first create a new version
353
+        $version = 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename);
354
+        if (!$users_view->file_exists($version)) {
355
+            $users_view->copy('files'.$filename, 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename));
356
+            $versionCreated = true;
357
+        }
358
+
359
+        $fileToRestore =  'files_versions' . $filename . '.v' . $revision;
360
+
361
+        // Restore encrypted version of the old file for the newly restored file
362
+        // This has to happen manually here since the file is manually copied below
363
+        $oldVersion = $users_view->getFileInfo($fileToRestore)->getEncryptedVersion();
364
+        $oldFileInfo = $users_view->getFileInfo($fileToRestore);
365
+        $cache = $fileInfo->getStorage()->getCache();
366
+        $cache->update(
367
+            $fileInfo->getId(), [
368
+                'encrypted' => $oldVersion,
369
+                'encryptedVersion' => $oldVersion,
370
+                'size' => $oldFileInfo->getSize()
371
+            ]
372
+        );
373
+
374
+        // rollback
375
+        if (self::copyFileContents($users_view, $fileToRestore, 'files' . $filename)) {
376
+            $files_view->touch($file, $revision);
377
+            Storage::scheduleExpire($uid, $file);
378
+            \OC_Hook::emit('\OCP\Versions', 'rollback', array(
379
+                'path' => $filename,
380
+                'revision' => $revision,
381
+            ));
382
+            return true;
383
+        } else if ($versionCreated) {
384
+            self::deleteVersion($users_view, $version);
385
+        }
386
+
387
+        return false;
388
+
389
+    }
390
+
391
+    /**
392
+     * Stream copy file contents from $path1 to $path2
393
+     *
394
+     * @param View $view view to use for copying
395
+     * @param string $path1 source file to copy
396
+     * @param string $path2 target file
397
+     *
398
+     * @return bool true for success, false otherwise
399
+     */
400
+    private static function copyFileContents($view, $path1, $path2) {
401
+        /** @var \OC\Files\Storage\Storage $storage1 */
402
+        list($storage1, $internalPath1) = $view->resolvePath($path1);
403
+        /** @var \OC\Files\Storage\Storage $storage2 */
404
+        list($storage2, $internalPath2) = $view->resolvePath($path2);
405
+
406
+        $view->lockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
407
+        $view->lockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
408
+
409
+        // TODO add a proper way of overwriting a file while maintaining file ids
410
+        if ($storage1->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage') || $storage2->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage')) {
411
+            $source = $storage1->fopen($internalPath1, 'r');
412
+            $target = $storage2->fopen($internalPath2, 'w');
413
+            list(, $result) = \OC_Helper::streamCopy($source, $target);
414
+            fclose($source);
415
+            fclose($target);
416
+
417
+            if ($result !== false) {
418
+                $storage1->unlink($internalPath1);
419
+            }
420
+        } else {
421
+            $result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
422
+        }
423
+
424
+        $view->unlockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
425
+        $view->unlockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
426
+
427
+        return ($result !== false);
428
+    }
429
+
430
+    /**
431
+     * get a list of all available versions of a file in descending chronological order
432
+     * @param string $uid user id from the owner of the file
433
+     * @param string $filename file to find versions of, relative to the user files dir
434
+     * @param string $userFullPath
435
+     * @return array versions newest version first
436
+     */
437
+    public static function getVersions($uid, $filename, $userFullPath = '') {
438
+        $versions = array();
439
+        if (empty($filename)) {
440
+            return $versions;
441
+        }
442
+        // fetch for old versions
443
+        $view = new View('/' . $uid . '/');
444
+
445
+        $pathinfo = pathinfo($filename);
446
+        $versionedFile = $pathinfo['basename'];
447
+
448
+        $dir = Filesystem::normalizePath(self::VERSIONS_ROOT . '/' . $pathinfo['dirname']);
449
+
450
+        $dirContent = false;
451
+        if ($view->is_dir($dir)) {
452
+            $dirContent = $view->opendir($dir);
453
+        }
454
+
455
+        if ($dirContent === false) {
456
+            return $versions;
457
+        }
458
+
459
+        if (is_resource($dirContent)) {
460
+            while (($entryName = readdir($dirContent)) !== false) {
461
+                if (!Filesystem::isIgnoredDir($entryName)) {
462
+                    $pathparts = pathinfo($entryName);
463
+                    $filename = $pathparts['filename'];
464
+                    if ($filename === $versionedFile) {
465
+                        $pathparts = pathinfo($entryName);
466
+                        $timestamp = substr($pathparts['extension'], 1);
467
+                        $filename = $pathparts['filename'];
468
+                        $key = $timestamp . '#' . $filename;
469
+                        $versions[$key]['version'] = $timestamp;
470
+                        $versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($timestamp);
471
+                        if (empty($userFullPath)) {
472
+                            $versions[$key]['preview'] = '';
473
+                        } else {
474
+                            $versions[$key]['preview'] = \OC::$server->getURLGenerator('files_version.Preview.getPreview', ['file' => $userFullPath, 'version' => $timestamp]);
475
+                        }
476
+                        $versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'] . '/' . $filename);
477
+                        $versions[$key]['name'] = $versionedFile;
478
+                        $versions[$key]['size'] = $view->filesize($dir . '/' . $entryName);
479
+                        $versions[$key]['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($versionedFile);
480
+                    }
481
+                }
482
+            }
483
+            closedir($dirContent);
484
+        }
485
+
486
+        // sort with newest version first
487
+        krsort($versions);
488
+
489
+        return $versions;
490
+    }
491
+
492
+    /**
493
+     * Expire versions that older than max version retention time
494
+     * @param string $uid
495
+     */
496
+    public static function expireOlderThanMaxForUser($uid){
497
+        $expiration = self::getExpiration();
498
+        $threshold = $expiration->getMaxAgeAsTimestamp();
499
+        $versions = self::getAllVersions($uid);
500
+        if (!$threshold || !array_key_exists('all', $versions)) {
501
+            return;
502
+        }
503
+
504
+        $toDelete = [];
505
+        foreach (array_reverse($versions['all']) as $key => $version) {
506
+            if (intval($version['version'])<$threshold) {
507
+                $toDelete[$key] = $version;
508
+            } else {
509
+                //Versions are sorted by time - nothing mo to iterate.
510
+                break;
511
+            }
512
+        }
513
+
514
+        $view = new View('/' . $uid . '/files_versions');
515
+        if (!empty($toDelete)) {
516
+            foreach ($toDelete as $version) {
517
+                \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
518
+                self::deleteVersion($view, $version['path'] . '.v' . $version['version']);
519
+                \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
520
+            }
521
+        }
522
+    }
523
+
524
+    /**
525
+     * translate a timestamp into a string like "5 days ago"
526
+     * @param int $timestamp
527
+     * @return string for example "5 days ago"
528
+     */
529
+    private static function getHumanReadableTimestamp($timestamp) {
530
+
531
+        $diff = time() - $timestamp;
532
+
533
+        if ($diff < 60) { // first minute
534
+            return  $diff . " seconds ago";
535
+        } elseif ($diff < 3600) { //first hour
536
+            return round($diff / 60) . " minutes ago";
537
+        } elseif ($diff < 86400) { // first day
538
+            return round($diff / 3600) . " hours ago";
539
+        } elseif ($diff < 604800) { //first week
540
+            return round($diff / 86400) . " days ago";
541
+        } elseif ($diff < 2419200) { //first month
542
+            return round($diff / 604800) . " weeks ago";
543
+        } elseif ($diff < 29030400) { // first year
544
+            return round($diff / 2419200) . " months ago";
545
+        } else {
546
+            return round($diff / 29030400) . " years ago";
547
+        }
548
+
549
+    }
550
+
551
+    /**
552
+     * returns all stored file versions from a given user
553
+     * @param string $uid id of the user
554
+     * @return array with contains two arrays 'all' which contains all versions sorted by age and 'by_file' which contains all versions sorted by filename
555
+     */
556
+    private static function getAllVersions($uid) {
557
+        $view = new View('/' . $uid . '/');
558
+        $dirs = array(self::VERSIONS_ROOT);
559
+        $versions = array();
560
+
561
+        while (!empty($dirs)) {
562
+            $dir = array_pop($dirs);
563
+            $files = $view->getDirectoryContent($dir);
564
+
565
+            foreach ($files as $file) {
566
+                $fileData = $file->getData();
567
+                $filePath = $dir . '/' . $fileData['name'];
568
+                if ($file['type'] === 'dir') {
569
+                    $dirs[] = $filePath;
570
+                } else {
571
+                    $versionsBegin = strrpos($filePath, '.v');
572
+                    $relPathStart = strlen(self::VERSIONS_ROOT);
573
+                    $version = substr($filePath, $versionsBegin + 2);
574
+                    $relpath = substr($filePath, $relPathStart, $versionsBegin - $relPathStart);
575
+                    $key = $version . '#' . $relpath;
576
+                    $versions[$key] = array('path' => $relpath, 'timestamp' => $version);
577
+                }
578
+            }
579
+        }
580
+
581
+        // newest version first
582
+        krsort($versions);
583
+
584
+        $result = array();
585
+
586
+        foreach ($versions as $key => $value) {
587
+            $size = $view->filesize(self::VERSIONS_ROOT.'/'.$value['path'].'.v'.$value['timestamp']);
588
+            $filename = $value['path'];
589
+
590
+            $result['all'][$key]['version'] = $value['timestamp'];
591
+            $result['all'][$key]['path'] = $filename;
592
+            $result['all'][$key]['size'] = $size;
593
+
594
+            $result['by_file'][$filename][$key]['version'] = $value['timestamp'];
595
+            $result['by_file'][$filename][$key]['path'] = $filename;
596
+            $result['by_file'][$filename][$key]['size'] = $size;
597
+        }
598
+
599
+        return $result;
600
+    }
601
+
602
+    /**
603
+     * get list of files we want to expire
604
+     * @param array $versions list of versions
605
+     * @param integer $time
606
+     * @param bool $quotaExceeded is versions storage limit reached
607
+     * @return array containing the list of to deleted versions and the size of them
608
+     */
609
+    protected static function getExpireList($time, $versions, $quotaExceeded = false) {
610
+        $expiration = self::getExpiration();
611
+
612
+        if ($expiration->shouldAutoExpire()) {
613
+            list($toDelete, $size) = self::getAutoExpireList($time, $versions);
614
+        } else {
615
+            $size = 0;
616
+            $toDelete = [];  // versions we want to delete
617
+        }
618
+
619
+        foreach ($versions as $key => $version) {
620
+            if ($expiration->isExpired($version['version'], $quotaExceeded) && !isset($toDelete[$key])) {
621
+                $size += $version['size'];
622
+                $toDelete[$key] = $version['path'] . '.v' . $version['version'];
623
+            }
624
+        }
625
+
626
+        return [$toDelete, $size];
627
+    }
628
+
629
+    /**
630
+     * get list of files we want to expire
631
+     * @param array $versions list of versions
632
+     * @param integer $time
633
+     * @return array containing the list of to deleted versions and the size of them
634
+     */
635
+    protected static function getAutoExpireList($time, $versions) {
636
+        $size = 0;
637
+        $toDelete = array();  // versions we want to delete
638
+
639
+        $interval = 1;
640
+        $step = Storage::$max_versions_per_interval[$interval]['step'];
641
+        if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
642
+            $nextInterval = -1;
643
+        } else {
644
+            $nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
645
+        }
646
+
647
+        $firstVersion = reset($versions);
648
+        $firstKey = key($versions);
649
+        $prevTimestamp = $firstVersion['version'];
650
+        $nextVersion = $firstVersion['version'] - $step;
651
+        unset($versions[$firstKey]);
652
+
653
+        foreach ($versions as $key => $version) {
654
+            $newInterval = true;
655
+            while ($newInterval) {
656
+                if ($nextInterval === -1 || $prevTimestamp > $nextInterval) {
657
+                    if ($version['version'] > $nextVersion) {
658
+                        //distance between two version too small, mark to delete
659
+                        $toDelete[$key] = $version['path'] . '.v' . $version['version'];
660
+                        $size += $version['size'];
661
+                        \OCP\Util::writeLog('files_versions', 'Mark to expire '. $version['path'] .' next version should be ' . $nextVersion . " or smaller. (prevTimestamp: " . $prevTimestamp . "; step: " . $step, \OCP\Util::INFO);
662
+                    } else {
663
+                        $nextVersion = $version['version'] - $step;
664
+                        $prevTimestamp = $version['version'];
665
+                    }
666
+                    $newInterval = false; // version checked so we can move to the next one
667
+                } else { // time to move on to the next interval
668
+                    $interval++;
669
+                    $step = Storage::$max_versions_per_interval[$interval]['step'];
670
+                    $nextVersion = $prevTimestamp - $step;
671
+                    if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
672
+                        $nextInterval = -1;
673
+                    } else {
674
+                        $nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
675
+                    }
676
+                    $newInterval = true; // we changed the interval -> check same version with new interval
677
+                }
678
+            }
679
+        }
680
+
681
+        return array($toDelete, $size);
682
+    }
683
+
684
+    /**
685
+     * Schedule versions expiration for the given file
686
+     *
687
+     * @param string $uid owner of the file
688
+     * @param string $fileName file/folder for which to schedule expiration
689
+     */
690
+    private static function scheduleExpire($uid, $fileName) {
691
+        // let the admin disable auto expire
692
+        $expiration = self::getExpiration();
693
+        if ($expiration->isEnabled()) {
694
+            $command = new Expire($uid, $fileName);
695
+            \OC::$server->getCommandBus()->push($command);
696
+        }
697
+    }
698
+
699
+    /**
700
+     * Expire versions which exceed the quota.
701
+     *
702
+     * This will setup the filesystem for the given user but will not
703
+     * tear it down afterwards.
704
+     *
705
+     * @param string $filename path to file to expire
706
+     * @param string $uid user for which to expire the version
707
+     * @return bool|int|null
708
+     */
709
+    public static function expire($filename, $uid) {
710
+        $expiration = self::getExpiration();
711
+
712
+        if ($expiration->isEnabled()) {
713
+            // get available disk space for user
714
+            $user = \OC::$server->getUserManager()->get($uid);
715
+            if (is_null($user)) {
716
+                \OCP\Util::writeLog('files_versions', 'Backends provided no user object for ' . $uid, \OCP\Util::ERROR);
717
+                throw new \OC\User\NoUserException('Backends provided no user object for ' . $uid);
718
+            }
719
+
720
+            \OC_Util::setupFS($uid);
721
+
722
+            if (!Filesystem::file_exists($filename)) {
723
+                return false;
724
+            }
725
+
726
+            if (empty($filename)) {
727
+                // file maybe renamed or deleted
728
+                return false;
729
+            }
730
+            $versionsFileview = new View('/'.$uid.'/files_versions');
731
+
732
+            $softQuota = true;
733
+            $quota = $user->getQuota();
734
+            if ( $quota === null || $quota === 'none' ) {
735
+                $quota = Filesystem::free_space('/');
736
+                $softQuota = false;
737
+            } else {
738
+                $quota = \OCP\Util::computerFileSize($quota);
739
+            }
740
+
741
+            // make sure that we have the current size of the version history
742
+            $versionsSize = self::getVersionsSize($uid);
743
+
744
+            // calculate available space for version history
745
+            // subtract size of files and current versions size from quota
746
+            if ($quota >= 0) {
747
+                if ($softQuota) {
748
+                    $files_view = new View('/' . $uid . '/files');
749
+                    $rootInfo = $files_view->getFileInfo('/', false);
750
+                    $free = $quota - $rootInfo['size']; // remaining free space for user
751
+                    if ($free > 0) {
752
+                        $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $versionsSize; // how much space can be used for versions
753
+                    } else {
754
+                        $availableSpace = $free - $versionsSize;
755
+                    }
756
+                } else {
757
+                    $availableSpace = $quota;
758
+                }
759
+            } else {
760
+                $availableSpace = PHP_INT_MAX;
761
+            }
762
+
763
+            $allVersions = Storage::getVersions($uid, $filename);
764
+
765
+            $time = time();
766
+            list($toDelete, $sizeOfDeletedVersions) = self::getExpireList($time, $allVersions, $availableSpace <= 0);
767
+
768
+            $availableSpace = $availableSpace + $sizeOfDeletedVersions;
769
+            $versionsSize = $versionsSize - $sizeOfDeletedVersions;
770
+
771
+            // if still not enough free space we rearrange the versions from all files
772
+            if ($availableSpace <= 0) {
773
+                $result = Storage::getAllVersions($uid);
774
+                $allVersions = $result['all'];
775
+
776
+                foreach ($result['by_file'] as $versions) {
777
+                    list($toDeleteNew, $size) = self::getExpireList($time, $versions, $availableSpace <= 0);
778
+                    $toDelete = array_merge($toDelete, $toDeleteNew);
779
+                    $sizeOfDeletedVersions += $size;
780
+                }
781
+                $availableSpace = $availableSpace + $sizeOfDeletedVersions;
782
+                $versionsSize = $versionsSize - $sizeOfDeletedVersions;
783
+            }
784
+
785
+            foreach($toDelete as $key => $path) {
786
+                \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
787
+                self::deleteVersion($versionsFileview, $path);
788
+                \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
789
+                unset($allVersions[$key]); // update array with the versions we keep
790
+                \OCP\Util::writeLog('files_versions', "Expire: " . $path, \OCP\Util::INFO);
791
+            }
792
+
793
+            // Check if enough space is available after versions are rearranged.
794
+            // If not we delete the oldest versions until we meet the size limit for versions,
795
+            // but always keep the two latest versions
796
+            $numOfVersions = count($allVersions) -2 ;
797
+            $i = 0;
798
+            // sort oldest first and make sure that we start at the first element
799
+            ksort($allVersions);
800
+            reset($allVersions);
801
+            while ($availableSpace < 0 && $i < $numOfVersions) {
802
+                $version = current($allVersions);
803
+                \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
804
+                self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']);
805
+                \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
806
+                \OCP\Util::writeLog('files_versions', 'running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'] , \OCP\Util::INFO);
807
+                $versionsSize -= $version['size'];
808
+                $availableSpace += $version['size'];
809
+                next($allVersions);
810
+                $i++;
811
+            }
812
+
813
+            return $versionsSize; // finally return the new size of the version history
814
+        }
815
+
816
+        return false;
817
+    }
818
+
819
+    /**
820
+     * Create recursively missing directories inside of files_versions
821
+     * that match the given path to a file.
822
+     *
823
+     * @param string $filename $path to a file, relative to the user's
824
+     * "files" folder
825
+     * @param View $view view on data/user/
826
+     */
827
+    private static function createMissingDirectories($filename, $view) {
828
+        $dirname = Filesystem::normalizePath(dirname($filename));
829
+        $dirParts = explode('/', $dirname);
830
+        $dir = "/files_versions";
831
+        foreach ($dirParts as $part) {
832
+            $dir = $dir . '/' . $part;
833
+            if (!$view->file_exists($dir)) {
834
+                $view->mkdir($dir);
835
+            }
836
+        }
837
+    }
838
+
839
+    /**
840
+     * Static workaround
841
+     * @return Expiration
842
+     */
843
+    protected static function getExpiration(){
844
+        if (is_null(self::$application)) {
845
+            self::$application = new Application();
846
+        }
847
+        return self::$application->getContainer()->query('Expiration');
848
+    }
849 849
 
850 850
 }
Please login to merge, or discard this patch.