3 namespace wcf\data\style
;
5 use wcf\data\DatabaseObject
;
6 use wcf\system\application\ApplicationHandler
;
9 use wcf\util\ImageUtil
;
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
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'
41 class Style
extends DatabaseObject
46 protected $pageLogoSmallHeight = 0;
51 protected $pageLogoSmallWidth = 0;
54 * list of style variables
57 protected $variables = [];
60 * list of supported API versions
63 public static $supportedApiVersions = ['3.0', '3.1', '5.2'];
65 const API_VERSION
= '5.2';
67 const PREVIEW_IMAGE_MAX_HEIGHT
= 64;
69 const PREVIEW_IMAGE_MAX_WIDTH
= 102;
71 const FAVICON_IMAGE_HEIGHT
= 256;
73 const FAVICON_IMAGE_WIDTH
= 256;
75 const BASE_ASSET_PATH
= WCF_DIR
. 'images/';
78 * Returns the name of this style.
82 public function __toString()
84 return $this->styleName
;
88 * Returns the absolute path to the style's asset folder.
93 public function getAssetPath()
95 return FileUtil
::addTrailingSlash(static::BASE_ASSET_PATH
. 'style-' . $this->styleID
);
99 * Returns the styles variables of this style.
103 public function getVariables()
105 $this->loadVariables();
107 return $this->variables
;
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
116 * @param string $variableName
118 * @return string|null
120 public function getVariable($variableName, $toHex = false)
122 if (isset($this->variables
[$variableName])) {
123 // check if variable is empty
124 if ($this->variables
[$variableName] == '~""') {
129 $toHex && \
preg_match(
130 '/^rgba\((\d+), (\d+), (\d+), (1|0?\.\d+)\)$/',
131 $this->variables
[$variableName],
138 $a = \floatval
($matches[4]);
140 // calculate alpha value assuming a white canvas, source rgb will be (255,255,255) or #fff
141 // see https://stackoverflow.com/a/2049362
143 $r = ((1 - $a) * 255) +
($a * $r);
144 $g = ((1 - $a) * 255) +
($a * $g);
145 $b = ((1 - $a) * 255) +
($a * $b);
147 $clamp = static function ($v) {
148 return \
max(0, \
min(255, \
intval($v)));
156 return \
sprintf('#%02x%02x%02x', $r, $g, $b);
159 return $this->variables
[$variableName];
166 * Loads style-specific variables.
168 public function loadVariables()
170 if (!empty($this->variables
)) {
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
177 ON value.variableID = variable.variableID
178 AND value.styleID = ?
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'];
186 $this->variables
[$variableName] = $variableValue;
189 // see https://github.com/WoltLab/WCF/issues/2636
190 if (empty($this->variables
['wcfPageThemeColor'])) {
191 $this->variables
['wcfPageThemeColor'] = $this->variables
['wcfHeaderBackground'];
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
;
202 $filename .= $this->getVariable('pageLogoMobile');
205 if (\file_exists
($filename)) {
206 $data = \
getimagesize($filename);
207 if ($data !== false) {
208 $this->pageLogoSmallWidth
= $data[0];
209 $this->pageLogoSmallHeight
= $data[1];
215 * Returns the style preview image path.
219 public function getPreviewImage()
221 if ($this->image
&& \file_exists
(WCF_DIR
. 'images/' . $this->image
)) {
222 return WCF
::getPath() . 'images/' . $this->image
;
225 return WCF
::getPath() . 'images/stylePreview.png';
229 * Returns the style preview image path (2x version).
233 public function getPreviewImage2x()
235 if ($this->image2x
&& \file_exists
(WCF_DIR
. 'images/' . $this->image2x
)) {
236 return WCF
::getPath() . 'images/' . $this->image2x
;
239 return WCF
::getPath() . 'images/stylePreview@2x.png';
243 * Returns the absolute path to the apple touch icon.
247 public function getFaviconAppleTouchIcon()
249 return $this->getFaviconPath('apple-touch-icon.png');
253 * Returns the absolute path to the `manifest.json` file.
257 public function getFaviconManifest()
259 return $this->getFaviconPath('manifest.json');
263 * Returns the absolute path to the `browserconfig.xml` file.
267 public function getFaviconBrowserconfig()
269 return $this->getFaviconPath('browserconfig.xml');
273 * Returns the relative path to the favicon.
277 public function getRelativeFavicon()
279 return $this->getFaviconPath('favicon.ico', false);
283 * Returns the cover photo filename.
286 public function getCoverPhoto(?
bool $forceWebP = null): string
288 $useWebP = $this->useWebP($forceWebP);
290 if ($this->coverPhotoExtension
) {
291 return 'coverPhoto.' . ($useWebP ?
'webp' : $this->coverPhotoExtension
);
294 return 'default.' . ($useWebP ?
'webp' : 'jpg');
300 public function getCoverPhotoLocation(?
bool $forceWebP = null): string
302 $useWebP = $this->useWebP($forceWebP);
304 if ($this->coverPhotoExtension
) {
305 return $this->getAssetPath() . 'coverPhoto.' . ($useWebP ?
'webp' : $this->coverPhotoExtension
);
308 return WCF_DIR
. 'images/coverPhotos/default.' . ($useWebP ?
'webp' : 'jpg');
314 public function getCoverPhotoUrl(?
bool $forceWebP = null): string
316 $useWebP = $this->useWebP($forceWebP);
318 if ($this->coverPhotoExtension
) {
319 return WCF
::getPath() . FileUtil
::getRelativePath(
321 $this->getAssetPath()
322 ) . 'coverPhoto.' . ($useWebP ?
'webp' : $this->coverPhotoExtension
);
325 return WCF
::getPath() . 'images/coverPhotos/' . $this->getCoverPhoto();
331 public function getPageLogoSmallHeight(): int
333 return $this->pageLogoSmallHeight
;
339 public function getPageLogoSmallWidth(): int
341 return $this->pageLogoSmallWidth
;
345 * Serve the WebP variant of the cover photo if the browser supports
346 * it and the original cover photo is not a GIF.
350 protected function useWebP($forceWebP = null): bool
352 if ($this->coverPhotoExtension
=== "gif") {
356 return $forceWebP ||
($forceWebP === null && ImageUtil
::browserSupportsWebP());
360 * Returns the path to a favicon-related file.
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
366 protected function getFaviconPath($filename, $absolutePath = true)
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 ?
'&styleID=' . $this->styleID
: '');
374 if ($this->hasFavicon
) {
375 $path = FileUtil
::getRelativePath(WCF_DIR
, $this->getAssetPath()) . $filename;
377 $path = 'images/favicon/default.' . $filename;
381 return WCF
::getPath() . $path;
388 * Splits the less variables string.
390 * @param string $variables
394 public static function splitLessVariables($variables)
396 $tmp = \
explode("/* WCF_STYLE_CUSTOM_USER_MODIFICATIONS */\n", $variables, 2);
400 'custom' => $tmp[1] ??
'',
405 * Joins the less variables.
407 * @param string $preset
408 * @param string $custom
412 public static function joinLessVariables($preset, $custom)
414 if (empty($custom)) {
418 return $preset . "/* WCF_STYLE_CUSTOM_USER_MODIFICATIONS */\n" . $custom;