3 * @copyright Copyright (C) 2010-2022, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 namespace Friendica\Core;
25 use Friendica\Model\Profile;
26 use Friendica\Util\Strings;
29 * Some functions to handle themes
33 public static function getAllowedList(): array
35 $allowed_themes_str = DI::config()->get('system', 'allowed_themes');
36 $allowed_themes_raw = explode(',', str_replace(' ', '', $allowed_themes_str));
38 if (count($allowed_themes_raw)) {
39 foreach ($allowed_themes_raw as $theme) {
40 $theme = Strings::sanitizeFilePathItem(trim($theme));
41 if (strlen($theme) && is_dir("view/theme/$theme")) {
42 $allowed_themes[] = $theme;
47 return array_unique($allowed_themes);
50 public static function setAllowedList(array $allowed_themes)
52 DI::config()->set('system', 'allowed_themes', implode(',', array_unique($allowed_themes)));
56 * Parse theme comment in search of theme infos.
61 * * Description: My Cool Theme
63 * * Author: John <profile url>
64 * * Maintainer: Jane <profile url>
67 * @param string $theme the name of the theme
70 public static function getInfo(string $theme): array
72 $theme = Strings::sanitizeFilePathItem($theme);
81 'experimental' => file_exists("view/theme/$theme/experimental"),
82 'unsupported' => file_exists("view/theme/$theme/unsupported")
85 if (!is_file("view/theme/$theme/theme.php")) {
89 DI::profiler()->startRecording('file');
90 $theme_file = file_get_contents("view/theme/$theme/theme.php");
91 DI::profiler()->stopRecording();
93 $result = preg_match("|/\*.*\*/|msU", $theme_file, $matches);
96 $comment_lines = explode("\n", $matches[0]);
97 foreach ($comment_lines as $comment_line) {
98 $comment_line = trim($comment_line, "\t\n\r */");
99 if (strpos($comment_line, ':') !== false) {
100 list($key, $value) = array_map("trim", explode(":", $comment_line, 2));
101 $key = strtolower($key);
102 if ($key == "author") {
103 $result = preg_match("|([^<]+)<([^>]+)>|", $value, $matches);
105 $info['author'][] = ['name' => $matches[1], 'link' => $matches[2]];
107 $info['author'][] = ['name' => $value];
109 } elseif ($key == "maintainer") {
110 $result = preg_match("|([^<]+)<([^>]+)>|", $value, $matches);
112 $info['maintainer'][] = ['name' => $matches[1], 'link' => $matches[2]];
114 $info['maintainer'][] = ['name' => $value];
116 } elseif (array_key_exists($key, $info)) {
117 $info[$key] = $value;
126 * Returns the theme's screenshot.
128 * The screenshot is expected as view/theme/$theme/screenshot.[png|jpg].
130 * @param string $theme The name of the theme
132 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
134 public static function getScreenshot(string $theme): string
136 $theme = Strings::sanitizeFilePathItem($theme);
138 $exts = ['.png', '.jpg'];
139 foreach ($exts as $ext) {
140 if (file_exists('view/theme/' . $theme . '/screenshot' . $ext)) {
141 return DI::baseUrl() . '/view/theme/' . $theme . '/screenshot' . $ext;
144 return DI::baseUrl() . '/images/blank.png';
148 * Uninstalls given theme name
150 * @param string $theme Name of theme
151 * @return bool true on success
153 public static function uninstall(string $theme)
155 $theme = Strings::sanitizeFilePathItem($theme);
157 // silently fail if theme was removed or if $theme is funky
158 if (file_exists("view/theme/$theme/theme.php")) {
159 include_once "view/theme/$theme/theme.php";
161 $func = "{$theme}_uninstall";
162 if (function_exists($func)) {
166 Hook::delete(['file' => "view/theme/$theme/theme.php"]);
169 $allowed_themes = Theme::getAllowedList();
170 $key = array_search($theme, $allowed_themes);
171 if ($key !== false) {
172 unset($allowed_themes[$key]);
173 Theme::setAllowedList($allowed_themes);
180 * Installs given theme name
182 * @param string $theme Name of theme
183 * @return bool true on success
185 public static function install(string $theme): bool
187 $theme = Strings::sanitizeFilePathItem($theme);
189 // silently fail if theme was removed or if $theme is funky
190 if (!file_exists("view/theme/$theme/theme.php")) {
195 include_once "view/theme/$theme/theme.php";
197 $func = "{$theme}_install";
198 if (function_exists($func)) {
202 $allowed_themes = Theme::getAllowedList();
203 $allowed_themes[] = $theme;
204 Theme::setAllowedList($allowed_themes);
207 } catch (\Exception $e) {
208 Logger::error('Theme installation failed', ['theme' => $theme, 'error' => $e->getMessage()]);
214 * Get the full path to relevant theme files by filename
216 * This function searches in order in the current theme directory, in the current theme parent directory, and lastly
217 * in the base view/ folder.
219 * @param string $file Filename
220 * @return string Path to the file or empty string if the file isn't found
223 public static function getPathForFile(string $file): string
227 $theme = $a->getCurrentTheme();
229 $parent = Strings::sanitizeFilePathItem($a->getThemeInfoValue('extends', $theme));
232 "view/theme/$theme/$file",
233 "view/theme/$parent/$file",
237 foreach ($paths as $path) {
238 if (file_exists($path)) {
247 * Return relative path to theme stylesheet file
249 * Provide a sane default if nothing is chosen or the specified theme does not exist.
251 * @param string $theme Theme name
254 public static function getStylesheetPath(string $theme): string
256 $theme = Strings::sanitizeFilePathItem($theme);
258 if (!file_exists('view/theme/' . $theme . '/style.php')) {
259 return 'view/theme/' . $theme . '/style.css';
266 $puid = Profile::getThemeUid($a);
268 $query_params['puid'] = $puid;
271 return 'view/theme/' . $theme . '/style.pcss' . (!empty($query_params) ? '?' . http_build_query($query_params) : '');
275 * Returns the path of the provided theme
277 * @param string $theme Theme name
278 * @return string|null
280 public static function getConfigFile(string $theme)
282 $theme = Strings::sanitizeFilePathItem($theme);
285 $base_theme = $a->getThemeInfoValue('extends') ?? '';
287 if (file_exists("view/theme/$theme/config.php")) {
288 return "view/theme/$theme/config.php";
290 if ($base_theme && file_exists("view/theme/$base_theme/config.php")) {
291 return "view/theme/$base_theme/config.php";
297 * Returns the background color of the provided theme if available.
299 * @param string $theme Theme name
300 * @param int|null $uid Current logged-in user id
301 * @return string|null
303 public static function getBackgroundColor(string $theme, int $uid = null)
305 $theme = Strings::sanitizeFilePathItem($theme);
309 // silently fail if theme was removed or if $theme is funky
310 if (file_exists("view/theme/$theme/theme.php")) {
311 include_once "view/theme/$theme/theme.php";
313 $func = "{$theme}_get_background_color";
314 if (function_exists($func)) {
315 $return = $func($uid);
323 * Returns the theme color of the provided theme if available.
325 * @param string $theme
326 * @param int|null $uid Current logged-in user id
327 * @return string|null
329 public static function getThemeColor(string $theme, int $uid = null)
331 $theme = Strings::sanitizeFilePathItem($theme);
335 // silently fail if theme was removed or if $theme is funky
336 if (file_exists("view/theme/$theme/theme.php")) {
337 include_once "view/theme/$theme/theme.php";
339 $func = "{$theme}_get_theme_color";
340 if (function_exists($func)) {
341 $return = $func($uid);