Completed
Push — master ( 830834...005b3d )
by Thomas
10:38
created

TemplateLayout::generateCssAsset()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 33
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 24
nc 2
nop 1
dl 0
loc 33
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Adam Williamson <[email protected]>
4
 * @author Bart Visscher <[email protected]>
5
 * @author Christopher Schäpers <[email protected]>
6
 * @author Clark Tomlinson <[email protected]>
7
 * @author Hendrik Leppelsack <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Jörn Friedrich Dreyer <[email protected]>
10
 * @author Lukas Reschke <[email protected]>
11
 * @author Michael Gapczynski <[email protected]>
12
 * @author Morris Jobke <[email protected]>
13
 * @author Remco Brenninkmeijer <[email protected]>
14
 * @author Robin Appelman <[email protected]>
15
 * @author Robin McCorkell <[email protected]>
16
 * @author Roeland Jago Douma <[email protected]>
17
 * @author Thomas Müller <[email protected]>
18
 * @author Victor Dubiniuk <[email protected]>
19
 *
20
 * @copyright Copyright (c) 2016, ownCloud GmbH.
21
 * @license AGPL-3.0
22
 *
23
 * This code is free software: you can redistribute it and/or modify
24
 * it under the terms of the GNU Affero General Public License, version 3,
25
 * as published by the Free Software Foundation.
26
 *
27
 * This program is distributed in the hope that it will be useful,
28
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30
 * GNU Affero General Public License for more details.
31
 *
32
 * You should have received a copy of the GNU Affero General Public License, version 3,
33
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
34
 *
35
 */
36
namespace OC;
37
38
use Assetic\Asset\AssetCollection;
39
use Assetic\Asset\FileAsset;
40
use Assetic\AssetWriter;
41
use Assetic\Filter\CssImportFilter;
42
use Assetic\Filter\CssMinFilter;
43
use Assetic\Filter\CssRewriteFilter;
44
use Assetic\Filter\JSqueezeFilter;
45
use Assetic\Filter\SeparatorFilter;
46
47
class TemplateLayout extends \OC_Template {
48
49
	private static $versionHash = '';
50
51
	/**
52
	 * @var \OCP\IConfig
53
	 */
54
	private $config;
55
56
	/**
57
	 * @param string $renderAs
58
	 * @param string $appId application id
59
	 */
60
	public function __construct( $renderAs, $appId = '' ) {
61
62
		// yes - should be injected ....
63
		$this->config = \OC::$server->getConfig();
64
65
		// Decide which page we show
66
		if($renderAs == 'user') {
67
			parent::__construct( 'core', 'layout.user' );
68
			if(in_array(\OC_App::getCurrentApp(), ['settings','admin', 'help']) !== false) {
69
				$this->assign('bodyid', 'body-settings');
70
			}else{
71
				$this->assign('bodyid', 'body-user');
72
			}
73
74
			// Code integrity notification
75
			$integrityChecker = \OC::$server->getIntegrityCodeChecker();
76
			if(\OC_User::isAdminUser(\OC_User::getUser()) && $integrityChecker->isCodeCheckEnforced() && !$integrityChecker->hasPassedCheck()) {
77
				\OCP\Util::addScript('core', 'integritycheck-failed-notification');
78
			}
79
80
			// Add navigation entry
81
			$this->assign( 'application', '');
82
			$this->assign( 'appid', $appId );
83
			$navigation = \OC_App::getNavigation();
84
			$this->assign( 'navigation', $navigation);
85
			$settingsNavigation = \OC_App::getSettingsNavigation();
86
			$this->assign( 'settingsnavigation', $settingsNavigation);
87
			foreach($navigation as $entry) {
88
				if ($entry['active']) {
89
					$this->assign( 'application', $entry['name'] );
90
					break;
91
				}
92
			}
93
			
94
			foreach($settingsNavigation as $entry) {
95
				if ($entry['active']) {
96
					$this->assign( 'application', $entry['name'] );
97
					break;
98
				}
99
			}
100
			$userDisplayName = \OC_User::getDisplayName();
101
			$appsMgmtActive = strpos(\OC::$server->getRequest()->getRequestUri(), \OC::$server->getURLGenerator()->linkToRoute('settings.AppSettings.viewApps')) === 0;
102
			if ($appsMgmtActive) {
103
				$l = \OC::$server->getL10N('lib');
104
				$this->assign('application', $l->t('Apps'));
105
			}
106
			$this->assign('user_displayname', $userDisplayName);
107
			$this->assign('user_uid', \OC_User::getUser());
108
			$this->assign('appsmanagement_active', $appsMgmtActive);
109
			$this->assign('enableAvatars', $this->config->getSystemValue('enable_avatars', true) === true);
110
111
			if (\OC_User::getUser() === false) {
112
				$this->assign('userAvatarSet', false);
113
			} else {
114
				$this->assign('userAvatarSet', \OC::$server->getAvatarManager()->getAvatar(\OC_User::getUser())->exists());
115
			}
116
117
		} else if ($renderAs == 'error') {
118
			parent::__construct('core', 'layout.guest', '', false);
119
			$this->assign('bodyid', 'body-login');
120
		} else if ($renderAs == 'guest') {
121
			parent::__construct('core', 'layout.guest');
122
			$this->assign('bodyid', 'body-login');
123
		} else {
124
			parent::__construct('core', 'layout.base');
125
126
		}
127
		// Send the language to our layouts
128
		$this->assign('language', \OC_L10N::findLanguage());
129
130
		if(\OC::$server->getSystemConfig()->getValue('installed', false)) {
131
			if (empty(self::$versionHash)) {
132
				$v = \OC_App::getAppVersions();
133
				$v['core'] = implode('.', \OCP\Util::getVersion());
134
				self::$versionHash = md5(implode(',', $v));
135
			}
136
		} else {
137
			self::$versionHash = md5('not installed');
138
		}
139
140
		$useAssetPipeline = self::isAssetPipelineEnabled();
141
		if ($useAssetPipeline) {
142
			$this->append( 'jsfiles', \OC::$server->getURLGenerator()->linkToRoute('js_config', ['v' => self::$versionHash]));
143
			$this->generateAssets();
144
		} else {
145
			// Add the js files
146
			$jsFiles = self::findJavascriptFiles(\OC_Util::$scripts);
147
			$this->assign('jsfiles', array());
148
			if ($this->config->getSystemValue('installed', false) && $renderAs != 'error') {
149
				$this->append( 'jsfiles', \OC::$server->getURLGenerator()->linkToRoute('js_config', ['v' => self::$versionHash]));
150
			}
151
			foreach($jsFiles as $info) {
152
				$web = $info[1];
153
				$file = $info[2];
154
				$this->append( 'jsfiles', $web.'/'.$file . '?v=' . self::$versionHash);
155
			}
156
157
			// Add the css files
158
			$cssFiles = self::findStylesheetFiles(\OC_Util::$styles);
159
			$this->assign('cssfiles', array());
160
			$this->assign('printcssfiles', []);
161
			foreach($cssFiles as $info) {
162
				$web = $info[1];
163
				$file = $info[2];
164
165
			if (substr($file, -strlen('print.css')) === 'print.css') {
166
					$this->append( 'printcssfiles', $web.'/'.$file . '?v=' . self::$versionHash);
167
				} else {
168
					$this->append( 'cssfiles', $web.'/'.$file . '?v=' . self::$versionHash);
169
				}
170
			}
171
		}
172
	}
173
174
	/**
175
	 * @param array $styles
176
	 * @return array
177
	 */
178 View Code Duplication
	static public function findStylesheetFiles($styles) {
179
		// Read the selected theme from the config file
180
		$theme = \OC_Util::getTheme();
181
182
		$locator = new \OC\Template\CSSResourceLocator(
183
			\OC::$server->getLogger(),
184
			$theme,
185
			array( \OC::$SERVERROOT => \OC::$WEBROOT ),
186
			array( \OC::$SERVERROOT => \OC::$WEBROOT ));
187
		$locator->find($styles);
188
		return $locator->getResources();
189
	}
190
191
	/**
192
	 * @param array $scripts
193
	 * @return array
194
	 */
195 View Code Duplication
	static public function findJavascriptFiles($scripts) {
196
		// Read the selected theme from the config file
197
		$theme = \OC_Util::getTheme();
198
199
		$locator = new \OC\Template\JSResourceLocator(
200
			\OC::$server->getLogger(),
201
			$theme,
202
			array( \OC::$SERVERROOT => \OC::$WEBROOT ),
203
			array( \OC::$SERVERROOT => \OC::$WEBROOT ));
204
		$locator->find($scripts);
205
		return $locator->getResources();
206
	}
207
208
	public function generateAssets() {
209
		$assetDir = \OC::$server->getConfig()->getSystemValue('assetdirectory', \OC::$SERVERROOT);
210
		$jsFiles = self::findJavascriptFiles(\OC_Util::$scripts);
211
		$jsHash = self::hashFileNames($jsFiles);
212
213
		if (!file_exists("$assetDir/assets/$jsHash.js")) {
214
			$jsFiles = array_map(function ($item) {
215
				$root = $item[0];
216
				$file = $item[2];
217
				// no need to minifiy minified files
218
				if (substr($file, -strlen('.min.js')) === '.min.js') {
219
					return new FileAsset($root . '/' . $file, array(
220
						new SeparatorFilter(';')
221
					), $root, $file);
222
				}
223
				return new FileAsset($root . '/' . $file, array(
224
					new JSqueezeFilter(),
225
					new SeparatorFilter(';')
226
				), $root, $file);
227
			}, $jsFiles);
228
			$jsCollection = new AssetCollection($jsFiles);
229
			$jsCollection->setTargetPath("assets/$jsHash.js");
230
231
			$writer = new AssetWriter($assetDir);
232
			$writer->writeAsset($jsCollection);
233
		}
234
235
		$cssFiles = self::findStylesheetFiles(\OC_Util::$styles);
236
237
		// differentiate between screen stylesheets and printer stylesheets
238
		$screenCssFiles = array_filter($cssFiles, function($cssFile) {
239
			return substr_compare($cssFile[2], 'print.css', -strlen('print.css')) !== 0;
240
		});
241
		$screenCssAsset = $this->generateCssAsset($screenCssFiles);
242
243
		$printCssFiles = array_filter($cssFiles, function($cssFile) {
244
			return substr_compare($cssFile[2], 'print.css', -strlen('print.css')) === 0;
245
		});
246
		$printCssAsset = $this->generateCssAsset($printCssFiles);
247
248
		$this->append('jsfiles', \OC::$server->getURLGenerator()->linkTo('assets', "$jsHash.js"));
249
		$this->append('cssfiles', $screenCssAsset);
250
		$this->append('printcssfiles', $printCssAsset);
251
	}
252
253
	/**
254
	 * generates a single css asset file from an array of css files if at least one of them has changed
255
	 * otherwise it just returns the path to the old asset file
256
	 * @param $files
257
	 * @return string
258
	 */
259
	private function generateCssAsset($files) {
260
		$assetDir = \OC::$server->getConfig()->getSystemValue('assetdirectory', \OC::$SERVERROOT);
261
		$hash = self::hashFileNames($files);
262
263
		if (!file_exists("$assetDir/assets/$hash.css")) {
264
			$files = array_map(function ($item) {
265
				$root = $item[0];
266
				$file = $item[2];
267
				$assetPath = $root . '/' . $file;
268
				$sourceRoot =  \OC::$SERVERROOT;
269
				$sourcePath = substr($assetPath, strlen(\OC::$SERVERROOT));
270
				return new FileAsset(
271
					$assetPath,
272
					array(
273
						new CssRewriteFilter(),
274
						new CssMinFilter(),
275
						new CssImportFilter()
276
					),
277
					$sourceRoot,
278
					$sourcePath
279
				);
280
			}, $files);
281
282
			$cssCollection = new AssetCollection($files);
283
			$cssCollection->setTargetPath("assets/$hash.css");
284
285
			$writer = new AssetWriter($assetDir);
286
			$writer->writeAsset($cssCollection);
287
288
		}
289
290
		return \OC::$server->getURLGenerator()->linkTo('assets', "$hash.css");
291
	}
292
293
	/**
294
	 * Converts the absolute file path to a relative path from \OC::$SERVERROOT
295
	 * @param string $filePath Absolute path
296
	 * @return string Relative path
297
	 * @throws \Exception If $filePath is not under \OC::$SERVERROOT
298
	 */
299
	public static function convertToRelativePath($filePath) {
300
		$relativePath = explode(\OC::$SERVERROOT, $filePath);
301
		if(count($relativePath) !== 2) {
302
			throw new \Exception('$filePath is not under the \OC::$SERVERROOT');
303
		}
304
305
		return $relativePath[1];
306
	}
307
308
	/**
309
	 * @param array $files
310
	 * @return string
311
	 */
312
313
	private static function hashFileNames($files) {
314
		foreach($files as $i => $file) {
315
			try {
316
				$files[$i] = self::convertToRelativePath($file[0]).'/'.$file[2];
317
			} catch (\Exception $e) {
318
				$files[$i] = $file[0].'/'.$file[2];
319
			}
320
		}
321
322
		sort($files);
323
		// include the apps' versions hash to invalidate the cached assets
324
		$files[] = self::$versionHash;
325
		return hash('md5', implode('', $files));
326
	}
327
}
328