Minor code style adjustments
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / style / Style.class.php
CommitLineData
158bd3ca 1<?php
a9229942 2
158bd3ca 3namespace wcf\data\style;
a9229942 4
158bd3ca 5use wcf\data\DatabaseObject;
e9dfcb4b 6use wcf\system\application\ApplicationHandler;
158bd3ca 7use wcf\system\WCF;
d261146e 8use wcf\util\FileUtil;
d4cf0997 9use wcf\util\ImageUtil;
158bd3ca
TD
10
11/**
12 * Represents a style.
e9335ed9 13 *
a9229942
TD
14 * @author Marcel Werk
15 * @copyright 2001-2019 WoltLab GmbH
16 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
17 * @package WoltLabSuite\Core\Data\Style
18 *
19 * @property-read int $styleID unique id of the style
20 * @property-read int $packageID id of the package which delivers the style
21 * @property-read string $styleName name of style
22 * @property-read int $templateGroupID id of the template group used for the style or `0` if the style uses no specific template group
23 * @property-read int $isDefault is `1` if the style is the default style for guests and users, otherwise `0`
24 * @property-read int $isDisabled is `1` if the style is disabled and thus cannot be used without having the specific permission to do so, otherwise `0`
25 * @property-read string $styleDescription description of the style or name of the language item which contains the description
26 * @property-read string $styleVersion version number of the style
27 * @property-read string $styleDate date when the used version of the style has been published
28 * @property-read string $image link or path (relative to `WCF_DIR`) to the preview image of the style
29 * @property-read string $image2x link or path (relative to `WCF_DIR`) to the preview image of the style (2x version)
30 * @property-read string $copyright copyright text of the style
31 * @property-read string $license name of the style's license
32 * @property-read string $authorName name(s) of the style's author(s)
33 * @property-read string $authorURL link to the author's website
34 * @property-read string $imagePath path (relative to `WCF_DIR`) to the images used by the style or empty if style has no special image path
35 * @property-read string $packageName package identifier used to export the style as a package or empty (thus style cannot be exported as package)
36 * @property-read int $isTainted is `0` if the original declarations of an imported or installed style are not and cannot be altered, otherwise `1`
37 * @property-read int $hasFavicon is `0` if the default favicon data should be used
38 * @property-read int $coverPhotoExtension extension of the style's cover photo file
39 * @property-read string $apiVersion the style's compatibility version, possible values: '3.0' or '3.1'
158bd3ca 40 */
a9229942
TD
41class Style extends DatabaseObject
42{
e038e9bd
AE
43 /**
44 * @since 5.4
45 */
46 protected $pageLogoSmallHeight = 0;
47
48 /**
49 * @since 5.4
50 */
51 protected $pageLogoSmallWidth = 0;
52
a9229942
TD
53 /**
54 * list of style variables
55 * @var string[]
56 */
57 protected $variables = [];
58
59 /**
60 * list of supported API versions
61 * @var string[]
62 */
63 public static $supportedApiVersions = ['3.0', '3.1', '5.2'];
64
65 const API_VERSION = '5.2';
66
67 const PREVIEW_IMAGE_MAX_HEIGHT = 64;
68
69 const PREVIEW_IMAGE_MAX_WIDTH = 102;
70
71 const FAVICON_IMAGE_HEIGHT = 256;
72
73 const FAVICON_IMAGE_WIDTH = 256;
74
75 const BASE_ASSET_PATH = WCF_DIR . 'images/';
76
77 /**
78 * Returns the name of this style.
79 *
80 * @return string
81 */
82 public function __toString()
83 {
84 return $this->styleName;
85 }
86
87 /**
88 * Returns the absolute path to the style's asset folder.
89 *
90 * @return string
91 * @since 5.3
92 */
93 public function getAssetPath()
94 {
95 return FileUtil::addTrailingSlash(static::BASE_ASSET_PATH . 'style-' . $this->styleID);
96 }
97
98 /**
99 * Returns the styles variables of this style.
100 *
101 * @return string[]
102 */
103 public function getVariables()
104 {
105 $this->loadVariables();
106
107 return $this->variables;
108 }
109
110 /**
111 * Returns a specific style variable or null if not found.
112 * If $toHex is set to true the color defined by the variable
113 * will be converted to the hexadecimal notation (e.g. for use
114 * in emails)
115 *
116 * @param string $variableName
117 * @param bool $toHex
5227ebc7 118 * @return string|null
a9229942
TD
119 */
120 public function getVariable($variableName, $toHex = false)
121 {
122 if (isset($this->variables[$variableName])) {
123 // check if variable is empty
124 if ($this->variables[$variableName] == '~""') {
125 return '';
126 }
127
128 if (
129 $toHex && \preg_match(
130 '/^rgba\((\d+), (\d+), (\d+), (1|0?\.\d+)\)$/',
131 $this->variables[$variableName],
132 $matches
133 )
134 ) {
135 $r = $matches[1];
136 $g = $matches[2];
137 $b = $matches[3];
138 $a = \floatval($matches[4]);
139
140 // calculate alpha value assuming a white canvas, source rgb will be (255,255,255) or #fff
141 // see https://stackoverflow.com/a/2049362
142 if ($a < 1) {
143 $r = ((1 - $a) * 255) + ($a * $r);
144 $g = ((1 - $a) * 255) + ($a * $g);
145 $b = ((1 - $a) * 255) + ($a * $b);
146
147 $clamp = static function ($v) {
148 return \max(0, \min(255, \intval($v)));
149 };
150
151 $r = $clamp($r);
152 $g = $clamp($g);
153 $b = $clamp($b);
154 }
155
156 return \sprintf('#%02x%02x%02x', $r, $g, $b);
157 }
158
159 return $this->variables[$variableName];
160 }
5227ebc7
MS
161
162 return null;
a9229942
TD
163 }
164
165 /**
166 * Loads style-specific variables.
167 */
168 public function loadVariables()
169 {
170 if (!empty($this->variables)) {
171 return;
172 }
173
174 $sql = "SELECT variable.variableName, variable.defaultValue, value.variableValue
175 FROM wcf" . WCF_N . "_style_variable variable
176 LEFT JOIN wcf" . WCF_N . "_style_variable_value value
c240c98a
MS
177 ON value.variableID = variable.variableID
178 AND value.styleID = ?
a9229942
TD
179 ORDER BY variable.variableID ASC";
180 $statement = WCF::getDB()->prepareStatement($sql);
181 $statement->execute([$this->styleID]);
182 while ($row = $statement->fetchArray()) {
183 $variableName = $row['variableName'];
184 $variableValue = $row['variableValue'] ?? $row['defaultValue'];
185
186 $this->variables[$variableName] = $variableValue;
187 }
188
189 // see https://github.com/WoltLab/WCF/issues/2636
190 if (empty($this->variables['wcfPageThemeColor'])) {
191 $this->variables['wcfPageThemeColor'] = $this->variables['wcfHeaderBackground'];
192 }
e038e9bd
AE
193
194 // Fetch the dimensions of the small logo, avoding calls to `getimagesize` with every request.
195 $filename = \WCF_DIR . 'images/default-logo-small.png';
196 if ($this->getVariable('pageLogoMobile')) {
197 $filename = \WCF_DIR;
198 if ($this->imagePath) {
199 $filename .= $this->imagePath;
200 }
201
202 $filename .= $this->getVariable('pageLogoMobile');
203 }
204
205 if (\file_exists($filename)) {
206 $data = \getimagesize($filename);
207 if ($data !== false) {
208 $this->pageLogoSmallWidth = $data[0];
209 $this->pageLogoSmallHeight = $data[1];
210 }
211 }
a9229942
TD
212 }
213
214 /**
215 * Returns the style preview image path.
216 *
217 * @return string
218 */
219 public function getPreviewImage()
220 {
221 if ($this->image && \file_exists(WCF_DIR . 'images/' . $this->image)) {
222 return WCF::getPath() . 'images/' . $this->image;
223 }
224
225 return WCF::getPath() . 'images/stylePreview.png';
226 }
227
228 /**
229 * Returns the style preview image path (2x version).
230 *
231 * @return string
232 */
233 public function getPreviewImage2x()
234 {
235 if ($this->image2x && \file_exists(WCF_DIR . 'images/' . $this->image2x)) {
236 return WCF::getPath() . 'images/' . $this->image2x;
237 }
238
239 return WCF::getPath() . 'images/stylePreview@2x.png';
240 }
241
242 /**
243 * Returns the absolute path to the apple touch icon.
244 *
245 * @return string
246 */
247 public function getFaviconAppleTouchIcon()
248 {
249 return $this->getFaviconPath('apple-touch-icon.png');
250 }
251
252 /**
253 * Returns the absolute path to the `manifest.json` file.
254 *
255 * @return string
256 */
257 public function getFaviconManifest()
258 {
259 return $this->getFaviconPath('manifest.json');
260 }
261
262 /**
263 * Returns the absolute path to the `browserconfig.xml` file.
264 *
265 * @return string
266 */
267 public function getFaviconBrowserconfig()
268 {
269 return $this->getFaviconPath('browserconfig.xml');
270 }
271
272 /**
273 * Returns the relative path to the favicon.
274 *
275 * @return string
276 */
277 public function getRelativeFavicon()
278 {
279 return $this->getFaviconPath('favicon.ico', false);
280 }
281
282 /**
283 * Returns the cover photo filename.
a9229942
TD
284 * @since 3.1
285 */
d4cf0997 286 public function getCoverPhoto(?bool $forceWebP = null): string
a9229942 287 {
d4cf0997
AE
288 $useWebP = $this->useWebP($forceWebP);
289
a9229942 290 if ($this->coverPhotoExtension) {
d4cf0997 291 return 'coverPhoto.' . ($useWebP ? 'webp' : $this->coverPhotoExtension);
a9229942
TD
292 }
293
d4cf0997 294 return 'default.' . ($useWebP ? 'webp' : 'jpg');
a9229942
TD
295 }
296
297 /**
a9229942
TD
298 * @since 5.2
299 */
d4cf0997 300 public function getCoverPhotoLocation(?bool $forceWebP = null): string
a9229942 301 {
d4cf0997
AE
302 $useWebP = $this->useWebP($forceWebP);
303
a9229942 304 if ($this->coverPhotoExtension) {
d4cf0997 305 return $this->getAssetPath() . 'coverPhoto.' . ($useWebP ? 'webp' : $this->coverPhotoExtension);
a9229942
TD
306 }
307
d4cf0997 308 return WCF_DIR . 'images/coverPhotos/default.' . ($useWebP ? 'webp' : 'jpg');
a9229942
TD
309 }
310
311 /**
a9229942
TD
312 * @since 5.2
313 */
d4cf0997 314 public function getCoverPhotoUrl(?bool $forceWebP = null): string
a9229942 315 {
d4cf0997
AE
316 $useWebP = $this->useWebP($forceWebP);
317
a9229942
TD
318 if ($this->coverPhotoExtension) {
319 return WCF::getPath() . FileUtil::getRelativePath(
320 WCF_DIR,
321 $this->getAssetPath()
d4cf0997 322 ) . 'coverPhoto.' . ($useWebP ? 'webp' : $this->coverPhotoExtension);
a9229942
TD
323 }
324
325 return WCF::getPath() . 'images/coverPhotos/' . $this->getCoverPhoto();
326 }
327
e038e9bd
AE
328 /**
329 * @since 5.4
330 */
97e21991
AE
331 public function getPageLogoSmallHeight(): int
332 {
e038e9bd
AE
333 return $this->pageLogoSmallHeight;
334 }
335
336 /**
337 * @since 5.4
338 */
339 public function getPageLogoSmallWidth(): int
340 {
341 return $this->pageLogoSmallWidth;
342 }
343
d4cf0997
AE
344 /**
345 * Serve the WebP variant of the cover photo if the browser supports
346 * it and the original cover photo is not a GIF.
4cc0a9b2 347 *
d4cf0997
AE
348 * @since 5.4
349 */
350 protected function useWebP($forceWebP = null): bool
351 {
352 if ($this->coverPhotoExtension === "gif") {
353 return false;
354 }
355
356 return $forceWebP || ($forceWebP === null && ImageUtil::browserSupportsWebP());
357 }
358
a9229942
TD
359 /**
360 * Returns the path to a favicon-related file.
361 *
362 * @param string $filename name of the file
363 * @param bool $absolutePath if `true`, the absolute path is returned, otherwise the path relative to WCF is returned
364 * @return string
365 */
366 protected function getFaviconPath($filename, $absolutePath = true)
367 {
368 if ($filename === 'manifest.json') {
369 if (ApplicationHandler::getInstance()->getActiveApplication()->domainName !== ApplicationHandler::getInstance()->getApplicationByID(1)->domainName) {
370 return WCF::getPath() . 'images/favicon/corsProxy.php?type=manifest' . ($this->hasFavicon ? '&amp;styleID=' . $this->styleID : '');
371 }
372 }
373
374 if ($this->hasFavicon) {
375 $path = FileUtil::getRelativePath(WCF_DIR, $this->getAssetPath()) . $filename;
376 } else {
377 $path = 'images/favicon/default.' . $filename;
378 }
379
380 if ($absolutePath) {
381 return WCF::getPath() . $path;
382 }
383
384 return $path;
385 }
386
387 /**
388 * Splits the less variables string.
389 *
390 * @param string $variables
391 * @return array
392 * @since 3.0
393 */
394 public static function splitLessVariables($variables)
395 {
396 $tmp = \explode("/* WCF_STYLE_CUSTOM_USER_MODIFICATIONS */\n", $variables, 2);
397
398 return [
399 'preset' => $tmp[0],
400 'custom' => $tmp[1] ?? '',
401 ];
402 }
403
404 /**
405 * Joins the less variables.
406 *
407 * @param string $preset
408 * @param string $custom
409 * @return string
410 * @since 3.0
411 */
412 public static function joinLessVariables($preset, $custom)
413 {
414 if (empty($custom)) {
415 return $preset;
416 }
417
418 return $preset . "/* WCF_STYLE_CUSTOM_USER_MODIFICATIONS */\n" . $custom;
419 }
dcb3a44c 420}