Update copyright date for 2020
[civicrm-core.git] / Civi / Core / Themes.php
CommitLineData
d89d2545
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
f299f7db 4 | CiviCRM version 5 |
d89d2545 5 +--------------------------------------------------------------------+
f299f7db 6 | Copyright CiviCRM LLC (c) 2004-2020 |
d89d2545
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28namespace Civi\Core;
29
30use Civi;
31
32/**
33 *
34 * @package CiviCRM_Hook
f299f7db 35 * @copyright CiviCRM LLC (c) 2004-2020
d89d2545
TO
36 */
37class Themes {
38
39 /**
40 * The "default" theme adapts based on the latest recommendation from civicrm.org
41 * by switching to DEFAULT_THEME at runtime.
42 */
43 const DEFAULT_THEME = 'greenwich';
44
45 /**
46 * Fallback is a pseudotheme which can be included in "search_order".
47 * It locates files in the core/extension (non-theme) codebase.
48 */
49 const FALLBACK_THEME = '_fallback_';
50
51 const PASSTHRU = 'PASSTHRU';
52
53 /**
54 * @var string
55 * Ex: 'judy', 'liza'.
56 */
57 private $activeThemeKey = NULL;
58
59 /**
60 * @var array
61 * Array(string $themeKey => array $themeSpec).
62 */
63 private $themes = NULL;
64
65 /**
66 * @var \CRM_Utils_Cache_Interface
67 */
68 private $cache = NULL;
69
70 /**
71 * Theme constructor.
72 * @param \CRM_Utils_Cache_Interface $cache
73 */
74 public function __construct($cache = NULL) {
75 $this->cache = $cache ? $cache : Civi::cache('long');
76 }
77
78 /**
79 * Determine the name of active theme.
80 *
81 * @return string
82 * Ex: "greenwich".
83 */
84 public function getActiveThemeKey() {
85 if ($this->activeThemeKey === NULL) {
86 // Ambivalent: is it better to use $config->userFrameworkFrontend or $template->get('urlIsPublic')?
87 $config = \CRM_Core_Config::singleton();
68e3bc34 88 $settingKey = $config->userSystem->isFrontEndPage() ? 'theme_frontend' : 'theme_backend';
d89d2545
TO
89
90 $themeKey = Civi::settings()->get($settingKey);
91 if ($themeKey === 'default') {
92 $themeKey = self::DEFAULT_THEME;
93 }
94
95 \CRM_Utils_Hook::activeTheme($themeKey, [
96 'themes' => $this,
97 'page' => \CRM_Utils_Array::value(\CRM_Core_Config::singleton()->userFrameworkURLVar, $_GET),
98 ]);
99
100 $themes = $this->getAll();
101 $this->activeThemeKey = isset($themes[$themeKey]) ? $themeKey : self::DEFAULT_THEME;
102 }
103 return $this->activeThemeKey;
104 }
105
106 /**
107 * Get the definition of the theme.
108 *
109 * @param string $themeKey
110 * Ex: 'greenwich', 'shoreditch'.
111 * @return array|NULL
112 * @see CRM_Utils_Hook::themes
113 */
114 public function get($themeKey) {
115 $all = $this->getAll();
116 return isset($all[$themeKey]) ? $all[$themeKey] : NULL;
117 }
118
119 /**
120 * Get a list of all known themes, including hidden base themes.
121 *
122 * @return array
123 * List of themes, keyed by name. Same format as CRM_Utils_Hook::themes(),
124 * but any default values are filled in.
125 * @see CRM_Utils_Hook::themes
126 */
127 public function getAll() {
128 if ($this->themes === NULL) {
129 // Cache includes URLs/paths, which change with runtime.
130 $cacheKey = 'theme_list_' . \CRM_Core_Config_Runtime::getId();
131 $this->themes = $this->cache->get($cacheKey);
132 if ($this->themes === NULL) {
133 $this->themes = $this->buildAll();
134 $this->cache->set($cacheKey, $this->themes);
135 }
136 }
137 return $this->themes;
138 }
139
140 /**
141 * Get a list of available themes, excluding hidden base themes.
142 *
143 * This is the same as getAll(), but abstract themes like "_fallback_"
144 * or "_newyork_base_" are omitted.
145 *
146 * @return array
147 * List of themes.
148 * Ex: ['greenwich' => 'Greenwich', 'shoreditch' => 'Shoreditch'].
149 * @see CRM_Utils_Hook::themes
150 */
151 public function getAvailable() {
152 $result = array();
153 foreach ($this->getAll() as $key => $theme) {
154 if ($key{0} !== '_') {
155 $result[$key] = $theme['title'];
156 }
157 }
158 return $result;
159 }
160
161 /**
162 * Get the URL(s) for a themed CSS file.
163 *
164 * This implements a prioritized search, in order:
165 * - Check for the specified theme.
166 * - If that doesn't exist, check for the default theme.
167 * - If that doesn't exist, use the 'none' theme.
168 *
169 * @param string $active
170 * Active theme key.
171 * Ex: 'greenwich'.
172 * @param string $cssExt
173 * Ex: 'civicrm'.
174 * @param string $cssFile
175 * Ex: 'css/bootstrap.css' or 'css/civicrm.css'.
176 * @return array
177 * List of URLs to display.
178 * Ex: array(string $url)
179 */
180 public function resolveUrls($active, $cssExt, $cssFile) {
181 $all = $this->getAll();
182 if (!isset($all[$active])) {
183 return array();
184 }
185
186 $cssId = $this->cssId($cssExt, $cssFile);
187
188 foreach ($all[$active]['search_order'] as $themeKey) {
189 if (isset($all[$themeKey]['excludes']) && in_array($cssId, $all[$themeKey]['excludes'])) {
190 $result = array();
191 }
192 else {
193 $result = Civi\Core\Resolver::singleton()
194 ->call($all[$themeKey]['url_callback'], array($this, $themeKey, $cssExt, $cssFile));
195 }
196
197 if ($result !== self::PASSTHRU) {
198 return $result;
199 }
200 }
201
202 throw new \RuntimeException("Failed to resolve URL. Theme metadata may be incomplete.");
203 }
204
205 /**
206 * Construct the list of available themes.
207 *
208 * @return array
209 * List of themes, keyed by name.
210 * @see CRM_Utils_Hook::themes
211 */
212 protected function buildAll() {
213 $themes = array(
214 'default' => array(
215 'ext' => 'civicrm',
216 'title' => ts('Automatic'),
217 'help' => ts('Determine a system default automatically'),
218 // This is an alias. url_callback, search_order don't matter.
219 ),
220 'greenwich' => array(
221 'ext' => 'civicrm',
222 'title' => 'Greenwich',
223 'help' => ts('CiviCRM 4.x look-and-feel'),
224 ),
225 'none' => array(
226 'ext' => 'civicrm',
227 'title' => ts('None (Unstyled)'),
228 'help' => ts('Disable CiviCRM\'s built-in CSS files.'),
229 'search_order' => array('none', self::FALLBACK_THEME),
230 'excludes' => array(
231 "css/civicrm.css",
232 "css/bootstrap.css",
233 ),
234 ),
235 self::FALLBACK_THEME => array(
236 'ext' => 'civicrm',
237 'title' => 'Fallback (Abstract Base Theme)',
238 'url_callback' => '\Civi\Core\Themes\Resolvers::fallback',
239 'search_order' => array(self::FALLBACK_THEME),
240 ),
241 );
242
243 \CRM_Utils_Hook::themes($themes);
244
245 foreach (array_keys($themes) as $themeKey) {
246 $themes[$themeKey] = $this->build($themeKey, $themes[$themeKey]);
247 }
248
249 return $themes;
250 }
251
252 /**
253 * Apply defaults for a given them.
254 *
255 * @param string $themeKey
256 * The name of the theme. Ex: 'greenwich'.
257 * @param array $theme
258 * The original theme definition of the theme (per CRM_Utils_Hook::themes).
259 * @return array
260 * The full theme definition of the theme (per CRM_Utils_Hook::themes).
261 * @see CRM_Utils_Hook::themes
262 */
263 protected function build($themeKey, $theme) {
264 $defaults = array(
265 'name' => $themeKey,
266 'url_callback' => '\Civi\Core\Themes\Resolvers::simple',
267 'search_order' => array($themeKey, self::FALLBACK_THEME),
268 );
269 $theme = array_merge($defaults, $theme);
270
271 return $theme;
272 }
273
274 /**
275 * @param string $cssExt
276 * @param string $cssFile
277 * @return string
278 */
279 public function cssId($cssExt, $cssFile) {
280 return ($cssExt === 'civicrm') ? $cssFile : "$cssExt-$cssFile";
281 }
282
283}