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