Total Complexity | 90 |
Total Lines | 771 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like Theming often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Theming, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
14 | class Theming { |
||
15 | /** |
||
16 | * A hash that is used to cache if a theme is a json theme. |
||
17 | * |
||
18 | * @property |
||
19 | */ |
||
20 | private static $isJsonThemeCache = []; |
||
21 | |||
22 | /** |
||
23 | * A hash that is used to cache the properties of json themes. |
||
24 | * |
||
25 | * @property |
||
26 | */ |
||
27 | private static $jsonThemePropsCache = []; |
||
28 | |||
29 | /** |
||
30 | * Retrieves all installed json themes. |
||
31 | * |
||
32 | * @return array An array with the directory names of the json themes as keys and their display names |
||
33 | * as values |
||
34 | */ |
||
35 | public static function getJsonThemes() { |
||
36 | $themes = []; |
||
37 | $directoryIterator = new DirectoryIterator(BASE_PATH . PATH_PLUGIN_DIR); |
||
38 | foreach ($directoryIterator as $info) { |
||
39 | if ($info->isDot() || !$info->isDir()) { |
||
40 | continue; |
||
41 | } |
||
42 | |||
43 | if (!Theming::isJsonTheme($info->getFileName())) { |
||
44 | continue; |
||
45 | } |
||
46 | |||
47 | $themeProps = Theming::getJsonThemeProps($info->getFileName()); |
||
48 | if (empty($themeProps)) { |
||
49 | continue; |
||
50 | } |
||
51 | |||
52 | $themes[$info->getFileName()] = $themeProps['display-name'] ?? $info->getFileName(); |
||
53 | } |
||
54 | |||
55 | return $themes; |
||
56 | } |
||
57 | |||
58 | /** |
||
59 | * Returns the name of the active theme if one was found, and false otherwise. |
||
60 | * The active theme can be set by the admin in the config.php, or by |
||
61 | * the user in his settings. |
||
62 | * |
||
63 | * @return bool|string |
||
64 | */ |
||
65 | public static function getActiveTheme() { |
||
66 | $theme = false; |
||
67 | $themePath = BASE_PATH . constant('THEME_PATH_' . DEBUG_LOADER); |
||
68 | |||
69 | // First check if a theme was set by this user in his settings |
||
70 | if (WebAppAuthentication::isAuthenticated()) { |
||
71 | if (ENABLE_THEMES === false) { |
||
|
|||
72 | $theme = THEME !== "" ? THEME : 'basic'; |
||
73 | } |
||
74 | else { |
||
75 | $theme = $GLOBALS['settings']->get('zarafa/v1/main/active_theme'); |
||
76 | } |
||
77 | |||
78 | // If a theme was found, check if the theme is still installed |
||
79 | // Remember that 'basic' is not a real theme, but the name for the default look of grommunio Web |
||
80 | // Note 1: We will first try to find the a core theme with this name, only |
||
81 | // when we don't find one, we will try to find a theme plugin. |
||
82 | // Note 2: we do not use the pluginExists method of the PluginManager, because that |
||
83 | // would not find packs with multiple plugins in it. So instead we just check if |
||
84 | // the directory exists. |
||
85 | if ( |
||
86 | isset($theme) && !empty($theme) && $theme !== 'basic' && |
||
87 | !is_dir($themePath . '/' . $theme) && |
||
88 | !is_dir(BASE_PATH . PATH_PLUGIN_DIR . '/' . $theme) |
||
89 | ) { |
||
90 | $theme = false; |
||
91 | } |
||
92 | } |
||
93 | |||
94 | // If a valid theme was not found in the settings of the user, let's see if a valid theme |
||
95 | // was defined by the admin. |
||
96 | if (!$theme && defined('THEME') && THEME) { |
||
97 | $theme = is_dir($themePath . '/' . THEME) || is_dir(BASE_PATH . PATH_PLUGIN_DIR . '/' . THEME) ? THEME : false; |
||
98 | } |
||
99 | |||
100 | if (Theming::isJsonTheme($theme) && !is_array(Theming::getJsonThemeProps($theme))) { |
||
101 | // Someone made an error, we cannot read this json theme |
||
102 | return false; |
||
103 | } |
||
104 | |||
105 | return $theme; |
||
106 | } |
||
107 | |||
108 | /** |
||
109 | * Returns the path to the favicon if included with the theme. If found the |
||
110 | * path to it will be returned. Otherwise false. |
||
111 | * |
||
112 | * @param string $theme the name of the theme for which the css will be returned. |
||
113 | * Note: This is the directory name of the theme plugin. |
||
114 | * |
||
115 | * * @return bool|string |
||
116 | */ |
||
117 | public static function getFavicon($theme) { |
||
118 | $themePath = constant('THEME_PATH_' . DEBUG_LOADER); |
||
119 | |||
120 | // First check if we can find a core theme with this name |
||
121 | if ($theme && is_dir(BASE_PATH . $themePath . '/' . $theme) && is_file(BASE_PATH . $themePath . '/' . $theme . '/favicon.ico')) { |
||
122 | // Add a date as GET parameter, so we will fetch a new icon every day |
||
123 | // This way themes can update the favicon and it will show the next day latest. |
||
124 | return $themePath . '/' . $theme . '/favicon.ico?' . date('Ymd'); |
||
125 | } |
||
126 | |||
127 | // If no core theme was found, let's try to find a theme plugin with this name |
||
128 | if ($theme && is_dir(BASE_PATH . PATH_PLUGIN_DIR . '/' . $theme) && is_file(BASE_PATH . PATH_PLUGIN_DIR . '/' . $theme . '/favicon.ico')) { |
||
129 | // Add a date as GET parameter, so we will fetch a new icon every day |
||
130 | // This way themes can update the favicon and it will show the next day latest. |
||
131 | return PATH_PLUGIN_DIR . '/' . $theme . '/favicon.ico?' . date('Ymd'); |
||
132 | } |
||
133 | |||
134 | return false; |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * Returns the contents of the css files in the $theme as a string. |
||
139 | * |
||
140 | * @param string $theme the name of the theme for which the css will be returned. |
||
141 | * Note: This is the directory name of the theme plugin. |
||
142 | * |
||
143 | * @return string |
||
144 | */ |
||
145 | public static function getCss($theme) { |
||
146 | $themePathCoreThemes = BASE_PATH . constant('THEME_PATH_' . DEBUG_LOADER); |
||
147 | $cssFiles = []; |
||
148 | |||
149 | // First check if this is a core theme, and if it isn't, check if it is a theme plugin |
||
150 | if ($theme && is_dir($themePathCoreThemes . '/' . $theme)) { |
||
151 | $themePath = $themePathCoreThemes . '/' . $theme; |
||
152 | } |
||
153 | elseif ($theme && is_dir(BASE_PATH . PATH_PLUGIN_DIR . '/' . $theme)) { |
||
154 | if (Theming::isJsonTheme($theme)) { |
||
155 | return []; |
||
156 | } |
||
157 | $themePath = BASE_PATH . PATH_PLUGIN_DIR . '/' . $theme; |
||
158 | } |
||
159 | |||
160 | if (isset($themePath)) { |
||
161 | // Use SPL iterators to recursively traverse the css directory and find all css files |
||
162 | $directoryIterator = new RecursiveDirectoryIterator($themePath . '/css/', FilesystemIterator::SKIP_DOTS); |
||
163 | $iterator = new RecursiveIteratorIterator($directoryIterator, RecursiveIteratorIterator::SELF_FIRST); |
||
164 | |||
165 | // Always rewind an iterator before using it!!! See https://bugs.php.net/bug.php?id=62914 (it might save you a couple of hours debugging) |
||
166 | $iterator->rewind(); |
||
167 | while ($iterator->valid()) { |
||
168 | $fileName = $iterator->getFilename(); |
||
169 | if (!$iterator->isDir() && (strtolower($iterator->getExtension()) === 'css' || str_ends_with($fileName, '.css.php'))) { |
||
170 | $cssFiles[] = substr((string) $iterator->key(), strlen(BASE_PATH)); |
||
171 | } |
||
172 | $iterator->next(); |
||
173 | } |
||
174 | } |
||
175 | |||
176 | // Sort the array alphabetically before adding the css |
||
177 | sort($cssFiles); |
||
178 | |||
179 | return $cssFiles; |
||
180 | } |
||
181 | |||
182 | /** |
||
183 | * Returns the value that is assigned to a property by the active theme |
||
184 | * or null otherwise. |
||
185 | * Currently only implemented for JSON themes. |
||
186 | * |
||
187 | * @param mixed $propName |
||
188 | * |
||
189 | * @return string the value that the active theme has set for the property, |
||
190 | * or NULL |
||
191 | */ |
||
192 | public static function getThemeProperty($propName) { |
||
193 | $theme = Theming::getActiveTheme(); |
||
194 | if (!Theming::isJsonTheme($theme)) { |
||
195 | return false; |
||
196 | } |
||
197 | |||
198 | $props = Theming::getJsonThemeProps($theme); |
||
199 | if (!isset($props[$propName])) { |
||
200 | return false; |
||
201 | } |
||
202 | |||
203 | return $props[$propName]; |
||
204 | } |
||
205 | |||
206 | /** |
||
207 | * Returns the color that the active theme has set for the primary color |
||
208 | * of the icons. Currently only supported for JSON themes. |
||
209 | * Note: Only SVG icons of an iconset that has defined the primary color |
||
210 | * can be 'recolored'. |
||
211 | * |
||
212 | * @return string the color that the active theme has set for the primary |
||
213 | * color of the icons, or FALSE |
||
214 | */ |
||
215 | public static function getPrimaryIconColor() { |
||
216 | $val = Theming::getThemeProperty('icons-primary-color'); |
||
217 | |||
218 | return $val ?? false; |
||
219 | } |
||
220 | |||
221 | /** |
||
222 | * Returns the color that the active theme has set for the secondary color |
||
223 | * of the icons. Currently only supported for JSON themes. |
||
224 | * Note: Only SVG icons of an iconset that has defined the secondary color |
||
225 | * can be 'recolored'. |
||
226 | * |
||
227 | * @return string the color that the active theme has set for the secondary |
||
228 | * color of the icons, or FALSE |
||
229 | */ |
||
230 | public static function getSecondaryIconColor() { |
||
231 | $val = Theming::getThemeProperty('icons-secondary-color'); |
||
232 | |||
233 | return $val ?? false; |
||
234 | } |
||
235 | |||
236 | /** |
||
237 | * Checks if a theme is a JSON theme. (Basically this means that it checks if a |
||
238 | * directory with the theme name exists and if that directory contains a file |
||
239 | * called theme.json). |
||
240 | * |
||
241 | * @param string $theme The name of the theme to check |
||
242 | * |
||
243 | * @return bool True if the theme is a json theme, false otherwise |
||
244 | */ |
||
245 | public static function isJsonTheme($theme) { |
||
246 | if (empty($theme)) { |
||
247 | return false; |
||
248 | } |
||
249 | |||
250 | if (!isset(Theming::$isJsonThemeCache[$theme])) { |
||
251 | $themePathCoreThemes = BASE_PATH . constant('THEME_PATH_' . DEBUG_LOADER); |
||
252 | |||
253 | // First check if this is a core theme, and if it isn't, check if it is a theme plugin |
||
254 | if (is_dir($themePathCoreThemes . '/' . $theme)) { |
||
255 | // We don't have core json themes, so return false |
||
256 | Theming::$isJsonThemeCache[$theme] = false; |
||
257 | } |
||
258 | elseif (is_dir(BASE_PATH . PATH_PLUGIN_DIR . '/' . $theme) && is_file(BASE_PATH . PATH_PLUGIN_DIR . '/' . $theme . '/theme.json')) { |
||
259 | Theming::$isJsonThemeCache[$theme] = true; |
||
260 | } |
||
261 | else { |
||
262 | Theming::$isJsonThemeCache[$theme] = false; |
||
263 | } |
||
264 | } |
||
265 | |||
266 | return Theming::$isJsonThemeCache[$theme]; |
||
267 | } |
||
268 | |||
269 | /** |
||
270 | * Retrieves the properties set in the theme.json file of the theme. |
||
271 | * |
||
272 | * @param string $theme The theme for which the properties should be retrieved |
||
273 | * |
||
274 | * @return array The decoded array of properties defined in the theme.json file |
||
275 | */ |
||
276 | public static function getJsonThemeProps($theme) { |
||
277 | if (!Theming::isJsonTheme($theme)) { |
||
278 | return false; |
||
279 | } |
||
280 | |||
281 | // Check if we have the props in the cache before reading the file |
||
282 | if (!isset(Theming::$jsonThemePropsCache[$theme])) { |
||
283 | $json = file_get_contents(BASE_PATH . PATH_PLUGIN_DIR . '/' . $theme . '/theme.json'); |
||
284 | Theming::$jsonThemePropsCache[$theme] = json_decode($json, true); |
||
285 | |||
286 | if (json_last_error() !== JSON_ERROR_NONE) { |
||
287 | error_log("The theme '{$theme}' does not have a valid theme.json file. " . json_last_error_msg()); |
||
288 | Theming::$jsonThemePropsCache[$theme] = ''; |
||
289 | } |
||
290 | } |
||
291 | |||
292 | return Theming::$jsonThemePropsCache[$theme]; |
||
293 | } |
||
294 | |||
295 | /** |
||
296 | * Normalizes all defined colors in a JSON theme to valid hex colors. |
||
297 | * |
||
298 | * @param array $themeProps A hash with the properties defined a theme.json file |
||
299 | */ |
||
300 | private static function normalizeColors($themeProps) { |
||
316 | } |
||
317 | |||
318 | /** |
||
319 | * Utility function to fix relative urls in JSON themes. |
||
320 | * |
||
321 | * @param string $url the url to be fixed |
||
322 | * @param string $theme the name of the theme the url is part of |
||
323 | */ |
||
324 | private static function fixUrl($url, $theme) { |
||
325 | // the url is absolute we don't have to fix anything |
||
326 | if (preg_match('/^https?:\/\//', $url)) { |
||
327 | return $url; |
||
328 | } |
||
329 | |||
330 | return PATH_PLUGIN_DIR . '/' . $theme . '/' . $url; |
||
331 | } |
||
332 | |||
333 | /** |
||
334 | * Retrieves the styles that should be added to the page for the json theme. |
||
335 | * |
||
336 | * @param string $theme The theme for which the properties should be retrieved |
||
337 | * |
||
338 | * @return string The styles (between <style> tags) |
||
339 | */ |
||
340 | public static function getStyles($theme) { |
||
439 | } |
||
440 | |||
441 | /** |
||
442 | * The templates of the styles that a json theme can add to the page. |
||
443 | * |
||
444 | * @property |
||
445 | */ |
||
446 | private static $styles = [ |
||
447 | 'primary-color' => ' |
||
448 | /* The Sign in button of the login screen */ |
||
449 | body.login #form-container #submitbutton, |
||
450 | #loading-mask #form-container #submitbutton { |
||
451 | background: {{primary-color}}; |
||
452 | } |
||
453 | |||
454 | /* The top bar of the Welcome dialog */ |
||
455 | .zarafa-welcome-body > .x-panel-bwrap > .x-panel-body div.zarafa-welcome-title { |
||
456 | border-left: 1px solid {{primary-color}}; |
||
457 | border-right: 1px solid {{primary-color}}; |
||
458 | background: {{primary-color}}; |
||
459 | } |
||
460 | |||
461 | /* The border line under the top menu bar */ |
||
462 | body #zarafa-mainmenu { |
||
463 | border-color: {{primary-color}}; |
||
464 | } |
||
465 | /* The background color of the top menu bar */ |
||
466 | body #zarafa-mainmenu.zarafa-maintabbar > .x-toolbar-ct { |
||
467 | background-color: {{primary-color}}; |
||
468 | } |
||
469 | /* Unread items */ |
||
470 | .k-unreadborders .x-grid3-row.x-grid3-row-collapsed.mail_unread > table, |
||
471 | .k-unreadborders .x-grid3-row.x-grid3-row-expanded.mail_unread > table { |
||
472 | border-left: 4px solid {{primary-color}} !important; |
||
473 | } |
||
474 | ', |
||
475 | |||
476 | 'primary-color:hover' => ' |
||
477 | /* Hover state and active state of the Sign in button */ |
||
478 | body.login #form-container #submitbutton:hover, |
||
479 | #loading-mask #form-container #submitbutton:hover, |
||
480 | body.login #form-container #submitbutton:active, |
||
481 | #loading-mask #form-container #submitbutton:active { |
||
482 | background: {{primary-color:hover}}; |
||
483 | } |
||
484 | |||
485 | /* Background color of the hover state of the buttons in the top menu bar */ |
||
486 | body #zarafa-mainmenu.zarafa-maintabbar > .x-toolbar-ct .x-btn.x-btn-over, |
||
487 | /* Background color of the active state of the buttons (i.e. when the buttons get clicked) */ |
||
488 | body #zarafa-mainmenu.zarafa-maintabbar > .x-toolbar-ct .x-btn.x-btn-over.x-btn-click, |
||
489 | /* Background color of the selected button */ |
||
490 | body #zarafa-mainmenu.zarafa-maintabbar > .x-toolbar-ct .zarafa-maintabbar-maintab-active, |
||
491 | /* Background color of the hover state of selected button */ |
||
492 | body #zarafa-mainmenu.zarafa-maintabbar > .x-toolbar-ct .zarafa-maintabbar-maintab-active.x-btn-over, |
||
493 | /* Background color of the active state of selected button */ |
||
494 | body #zarafa-mainmenu.zarafa-maintabbar > .x-toolbar-ct .zarafa-maintabbar-maintab-active.x-btn-over.x-btn-click { |
||
495 | background-color: {{primary-color:hover}} !important; |
||
496 | } |
||
497 | ', |
||
498 | |||
499 | 'mainbar-text-color' => ' |
||
500 | body #zarafa-mainmenu.zarafa-maintabbar > .x-toolbar-ct, |
||
501 | /* Text color of the buttons in the top menu bar */ |
||
502 | body #zarafa-mainmenu.zarafa-maintabbar > .x-toolbar-ct .x-btn button.x-btn-text, |
||
503 | body #zarafa-mainmenu.zarafa-maintabbar > .x-toolbar-ct .x-btn-over button.x-btn-text, |
||
504 | body #zarafa-mainmenu.zarafa-maintabbar > .x-toolbar-ct .x-btn-over.x-btn-click button.x-btn-text, |
||
505 | /* Text color of the selected button in the top menu bar */ |
||
506 | body #zarafa-mainmenu.zarafa-maintabbar > .x-toolbar-ct .zarafa-maintabbar-maintab-active button.x-btn-text, |
||
507 | body #zarafa-mainmenu.zarafa-maintabbar > .x-toolbar-ct .zarafa-maintabbar-maintab-active.x-btn-over button.x-btn-text, |
||
508 | body #zarafa-mainmenu.zarafa-maintabbar > .x-toolbar-ct .zarafa-maintabbar-maintab-active.x-btn-over.x-btn-click button.x-btn-text { |
||
509 | color: {{mainbar-text-color}} !important; |
||
510 | } |
||
511 | ', |
||
512 | |||
513 | 'action-color' => ' |
||
514 | /**************************************************************************** |
||
515 | * Action color |
||
516 | * =============== |
||
517 | * Some elements have a different color than the default color of these elements |
||
518 | * to get extra attention, e.g. "call-to-action buttons", the current day |
||
519 | * in the calendar, etc. |
||
520 | ****************************************************************************/ |
||
521 | /* Buttons, normal state */ |
||
522 | .x-btn.zarafa-action .x-btn-small, |
||
523 | .x-btn.zarafa-action .x-btn-medium, |
||
524 | .x-btn.zarafa-action .x-btn-large, |
||
525 | /* Buttons, active state */ |
||
526 | .x-btn.zarafa-action.x-btn-over.x-btn-click .x-btn-small, |
||
527 | .x-btn.zarafa-action.x-btn-over.x-btn-click .x-btn-medium, |
||
528 | .x-btn.zarafa-action.x-btn-over.x-btn-click .x-btn-large, |
||
529 | .x-btn.zarafa-action.x-btn-click .x-btn-small, |
||
530 | .x-btn.zarafa-action.x-btn-click .x-btn-medium, |
||
531 | .x-btn.zarafa-action.x-btn-click .x-btn-large, |
||
532 | /* Special case: Popup, Windows, or Messageboxes (first button is by default styled as the action button) */ |
||
533 | .x-window .x-panel-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn:not(.zarafa-normal) .x-btn-small, |
||
534 | .x-window .x-panel-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn:not(.zarafa-normal) .x-btn-medium, |
||
535 | .x-window .x-panel-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn:not(.zarafa-normal) .x-btn-large, |
||
536 | .x-window .x-panel-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn:not(.zarafa-normal) .x-btn-small, |
||
537 | .x-window .x-panel-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn:not(.zarafa-normal) .x-btn-medium, |
||
538 | .x-window .x-panel-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn:not(.zarafa-normal) .x-btn-large, |
||
539 | .x-window .x-window-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn:not(.zarafa-normal) .x-btn-small, |
||
540 | .x-window .x-window-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn:not(.zarafa-normal) .x-btn-medium, |
||
541 | .x-window .x-window-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn:not(.zarafa-normal) .x-btn-large, |
||
542 | .x-window .x-window-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn:not(.zarafa-normal) .x-btn-small, |
||
543 | .x-window .x-window-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn:not(.zarafa-normal) .x-btn-medium, |
||
544 | .x-window .x-window-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn:not(.zarafa-normal) .x-btn-large, |
||
545 | .x-window .x-panel-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-click:not(.zarafa-normal) .x-btn-small, |
||
546 | .x-window .x-panel-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-click:not(.zarafa-normal) .x-btn-medium, |
||
547 | .x-window .x-panel-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-click:not(.zarafa-normal) .x-btn-large, |
||
548 | .x-window .x-panel-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-click:not(.zarafa-normal) .x-btn-small, |
||
549 | .x-window .x-panel-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-click:not(.zarafa-normal) .x-btn-medium, |
||
550 | .x-window .x-panel-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-click:not(.zarafa-normal) .x-btn-large, |
||
551 | .x-window .x-window-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-click:not(.zarafa-normal) .x-btn-small, |
||
552 | .x-window .x-window-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-click:not(.zarafa-normal) .x-btn-medium, |
||
553 | .x-window .x-window-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-click:not(.zarafa-normal) .x-btn-large, |
||
554 | .x-window .x-window-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-click:not(.zarafa-normal) .x-btn-small, |
||
555 | .x-window .x-window-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-click:not(.zarafa-normal) .x-btn-medium, |
||
556 | .x-window .x-window-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-click:not(.zarafa-normal) .x-btn-large, |
||
557 | .x-window .x-panel-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over.x-btn-click:not(.zarafa-normal) .x-btn-small, |
||
558 | .x-window .x-panel-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over.x-btn-click:not(.zarafa-normal) .x-btn-medium, |
||
559 | .x-window .x-panel-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over.x-btn-click:not(.zarafa-normal) .x-btn-large, |
||
560 | .x-window .x-panel-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over.x-btn-click:not(.zarafa-normal) .x-btn-small, |
||
561 | .x-window .x-panel-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over.x-btn-click:not(.zarafa-normal) .x-btn-medium, |
||
562 | .x-window .x-panel-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over.x-btn-click:not(.zarafa-normal) .x-btn-large, |
||
563 | .x-window .x-window-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over.x-btn-click:not(.zarafa-normal) .x-btn-small, |
||
564 | .x-window .x-window-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over.x-btn-click:not(.zarafa-normal) .x-btn-medium, |
||
565 | .x-window .x-window-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over.x-btn-click:not(.zarafa-normal) .x-btn-large, |
||
566 | .x-window .x-window-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over.x-btn-click:not(.zarafa-normal) .x-btn-small, |
||
567 | .x-window .x-window-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over.x-btn-click:not(.zarafa-normal) .x-btn-medium, |
||
568 | .x-window .x-window-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over.x-btn-click:not(.zarafa-normal) .x-btn-large, |
||
569 | /* action button in reminder popout */ |
||
570 | .k-reminderpanel .x-panel-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn:not(.zarafa-normal) .x-btn-small, |
||
571 | .k-reminderpanel .x-panel-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-click:not(.zarafa-normal) .x-btn-small, |
||
572 | .k-reminderpanel .x-panel-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over.x-btn-click:not(.zarafa-normal) .x-btn-small, |
||
573 | /* Current day in the calendar */ |
||
574 | .zarafa-freebusy-panel .x-freebusy-timeline-container .x-freebusy-header .x-freebusy-header-body .x-freebusy-timeline-day.x-freebusy-timeline-day-current, |
||
575 | .zarafa-freebusy-panel .x-freebusy-timeline-container .x-freebusy-header .x-freebusy-header-body .x-freebusy-timeline-day.x-freebusy-timeline-day-current table, |
||
576 | .zarafa-freebusy-panel .x-freebusy-timeline-container .x-freebusy-header .x-freebusy-header-body .x-freebusy-timeline-day.x-freebusy-timeline-day-current table tr.x-freebusy-timeline-day td, |
||
577 | /* The date pickers */ |
||
578 | .x-date-picker .x-date-inner td.x-date-today a, |
||
579 | .x-date-picker .x-date-mp table td.x-date-mp-sel a, |
||
580 | .x-date-picker .x-date-mp table tr.x-date-mp-btns td button.x-date-mp-ok { |
||
581 | background: {{action-color}} !important; |
||
582 | } |
||
583 | /* Focused Action button */ |
||
584 | .x-btn.zarafa-action.x-btn-focus .x-btn-small, .x-btn.zarafa-action.x-btn-focus .x-btn-medium, .x-btn.zarafa-action.x-btn-focus .x-btn-large { |
||
585 | background: {{action-color}} !important; |
||
586 | } |
||
587 | /* Selected calendar */ |
||
588 | .zarafa-calendar-tabarea-stroke.zarafa-calendar-tab-selected { |
||
589 | border-top-color: {{action-color}}; |
||
590 | } |
||
591 | .x-date-picker .x-date-inner td.x-date-weeknumber a, |
||
592 | .zarafa-hierarchy-node-total-count span.zarafa-hierarchy-node-counter, |
||
593 | .zarafa-hierarchy-node-unread-count span.zarafa-hierarchy-node-counter { |
||
594 | color: {{action-color}}; |
||
595 | } |
||
596 | .x-date-picker .x-date-inner td.x-date-today a { |
||
597 | border-color: {{action-color}}; |
||
598 | } |
||
599 | .zarafa-freebusy-panel .x-freebusy-timeline-container .x-freebusy-header .x-freebusy-header-body .x-freebusy-timeline-day.x-freebusy-timeline-day-current, |
||
600 | .zarafa-freebusy-panel .x-freebusy-timeline-container .x-freebusy-body .x-freebusy-background .x-freebusy-timeline-day.x-freebusy-timeline-day-current { |
||
601 | border-right-color: {{action-color}}; |
||
602 | } |
||
603 | .zarafa-freebusy-panel .x-freebusy-timeline-container .x-freebusy-header .x-freebusy-header-body .x-freebusy-timeline-day.x-freebusy-timeline-day-current table tr.x-freebusy-timeline-hour td:first-child, |
||
604 | .zarafa-freebusy-panel .x-freebusy-timeline-container .x-freebusy-body .x-freebusy-background .x-freebusy-timeline-day.x-freebusy-timeline-day-current td:first-child { |
||
605 | border-left-color: {{action-color}}; |
||
606 | } |
||
607 | ', |
||
608 | |||
609 | 'action-color:hover' => ' |
||
610 | /* Buttons, hover state */ |
||
611 | .x-btn.zarafa-action.x-btn-over .x-btn-small, |
||
612 | .x-btn.zarafa-action.x-btn-over .x-btn-medium, |
||
613 | .x-btn.zarafa-action.x-btn-over .x-btn-large, |
||
614 | /* Special case: Popup, Windows, or Messageboxes */ |
||
615 | .x-window .x-panel-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over:not(.zarafa-normal) .x-btn-small, |
||
616 | .x-window .x-panel-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over:not(.zarafa-normal) .x-btn-medium, |
||
617 | .x-window .x-panel-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over:not(.zarafa-normal) .x-btn-large, |
||
618 | .x-window .x-panel-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over:not(.zarafa-normal) .x-btn-small, |
||
619 | .x-window .x-panel-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over:not(.zarafa-normal) .x-btn-medium, |
||
620 | .x-window .x-panel-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over:not(.zarafa-normal) .x-btn-large, |
||
621 | .x-window .x-window-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over:not(.zarafa-normal) .x-btn-small, |
||
622 | .x-window .x-window-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over:not(.zarafa-normal) .x-btn-medium, |
||
623 | .x-window .x-window-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over:not(.zarafa-normal) .x-btn-large, |
||
624 | .x-window .x-window-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over:not(.zarafa-normal) .x-btn-small, |
||
625 | .x-window .x-window-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over:not(.zarafa-normal) .x-btn-medium, |
||
626 | .x-window .x-window-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over:not(.zarafa-normal) .x-btn-large, |
||
627 | /* action button in reminder popout */ |
||
628 | .k-reminderpanel .x-panel-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-over:not(.zarafa-normal) .x-btn-small, |
||
629 | /* The date pickers */ |
||
630 | .x-date-picker .x-date-mp table tr.x-date-mp-btns td button.x-date-mp-ok:hover { |
||
631 | background: {{action-color:hover}} !important; |
||
632 | } |
||
633 | ', |
||
634 | |||
635 | 'selection-color' => ' |
||
636 | /********************************************************************* |
||
637 | * Selected items in grids and trees |
||
638 | * ================================= |
||
639 | * The background color of the selected items in grids and trees can |
||
640 | * be changed to better suit the theme. |
||
641 | *********************************************************************/ |
||
642 | /* selected item in grids */ |
||
643 | .x-grid3-row.x-grid3-row-selected, |
||
644 | .x-grid3 .x-grid3-row-selected .zarafa-grid-button-container, |
||
645 | /* selected item in tree hierarchies */ |
||
646 | .x-tree-node .zarafa-hierarchy-node.x-tree-selected, |
||
647 | /* selected items in boxfields (e.g. the recipient fields) */ |
||
648 | .x-zarafa-boxfield ul .x-zarafa-boxfield-item-focus, |
||
649 | .x-zarafa-boxfield ul .x-zarafa-boxfield-recipient-item.x-zarafa-boxfield-item-focus, |
||
650 | /* selected items in card view of Contacts context */ |
||
651 | div.zarafa-contact-cardview-selected, |
||
652 | /* selected items in icon view of Notes context */ |
||
653 | .zarafa-note-iconview-selected, |
||
654 | /* selected category in the Settings context */ |
||
655 | #zarafa-mainpanel-contentpanel-settings .zarafa-settings-category-panel .zarafa-settings-category-tab-active, |
||
656 | /* selected date in date pickers */ |
||
657 | .x-date-picker .x-date-inner td.x-date-selected:not(.x-date-today) a, |
||
658 | .x-date-picker .x-date-inner td.x-date-selected:not(.x-date-today) a:hover { |
||
659 | background-color: {{selection-color}} !important; |
||
660 | border-color: {{selection-color}}; |
||
661 | } |
||
662 | |||
663 | /* Selected x-menu */ |
||
664 | .x-menu-item-selected { |
||
665 | background-color: {{selection-color}}; |
||
666 | } |
||
667 | |||
668 | /********************************************************************* |
||
669 | * Extra information about items |
||
670 | * ================================= |
||
671 | * Sometimes extra information is shown in opened items. (e.g. "You replied |
||
672 | * to this message etc"). This can be styled with the following rules. |
||
673 | *********************************************************************/ |
||
674 | .preview-header-extrainfobox, |
||
675 | .preview-header-extrainfobox-item, |
||
676 | .k-appointmentcreatetab .zarafa-calendar-appointment-extrainfo div, |
||
677 | .k-taskgeneraltab .zarafa-calendar-appointment-extrainfo div, |
||
678 | .zarafa-mailcreatepanel > .x-panel-bwrap > .x-panel-body .zarafa-mailcreatepanel-extrainfo div { |
||
679 | background: {{selection-color}} !important; |
||
680 | } |
||
681 | |||
682 | /* Selected mail item */ |
||
683 | .k-unreadborders .x-grid3-row.x-grid3-row-expanded.mail_read.x-grid3-row-selected > table { |
||
684 | border-left: 4px solid {{selection-color}} !important; |
||
685 | } |
||
686 | |||
687 | /* Hover selected item */ |
||
688 | .k-unreadborders .x-grid3-row.x-grid3-row-expanded.mail_read.x-grid3-row-selected.x-grid3-row-over > table, |
||
689 | .k-unreadborders .x-grid3-row.x-grid3-row-collapsed.mail_read.x-grid3-row-selected > table { |
||
690 | border-left: 4px solid {{selection-color}} !important; |
||
691 | } |
||
692 | ', |
||
693 | |||
694 | 'selection-text-color' => ' |
||
695 | /********************************************************************* |
||
696 | * Extra information about items |
||
697 | * ================================= |
||
698 | * Sometimes extra information is shown in opened items. (e.g. "You replied |
||
699 | * to this message etc"). This can be styled with the following rules. |
||
700 | *********************************************************************/ |
||
701 | .preview-header-extrainfobox, |
||
702 | .preview-header-extrainfobox-item, |
||
703 | .k-appointmentcreatetab .zarafa-calendar-appointment-extrainfo div, |
||
704 | .k-taskgeneraltab .zarafa-calendar-appointment-extrainfo div, |
||
705 | .zarafa-mailcreatepanel > .x-panel-bwrap > .x-panel-body .zarafa-mailcreatepanel-extrainfo div { |
||
706 | color: {{selection-text-color}}; |
||
707 | } |
||
708 | ', |
||
709 | |||
710 | 'focus-color' => ' |
||
711 | /********************************************************************* |
||
712 | * Focused items |
||
713 | * ================================= |
||
714 | *********************************************************************/ |
||
715 | /* Normal button */ |
||
716 | .x-window .x-window-footer .x-toolbar-left-row .x-toolbar-cell:not(.x-hide-offsets) ~ .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-focus:not(.zarafa-action):not(.x-btn-over):not(.x-btn-click) .x-btn-small, |
||
717 | .x-window .x-panel-footer .x-toolbar-right-row .x-toolbar-cell:not(.x-hide-offsets) ~ .x-toolbar-cell:not(.x-hide-offsets) .x-btn.x-btn-focus:not(.zarafa-action):not(.x-btn-over):not(.x-btn-click) .x-btn-small, |
||
718 | .x-btn.x-btn-focus:not(.zarafa-action):not(.x-btn-click) .x-btn-small, |
||
719 | .x-btn.x-btn-focus:not(.zarafa-action):not(.x-btn-click) .x-btn-medium, |
||
720 | .x-btn.x-btn-focus:not(.zarafa-action):not(.x-btn-click) .x-btn-large, |
||
721 | .x-toolbar .x-btn.x-btn-focus:not(.zarafa-action):not(.x-btn-noicon) .x-btn-small { |
||
722 | border: 1px solid {{focus-color}} !important; |
||
723 | } |
||
724 | /* Login */ |
||
725 | body.login #form-container input:focus, |
||
726 | #loading-mask #form-container input:focus { |
||
727 | border-color: {{focus-color}}; |
||
728 | } |
||
729 | input:focus { |
||
730 | border-color: {{focus-color}}; |
||
731 | } |
||
732 | /* Form elements */ |
||
733 | .x-form-text.x-form-focus:not(.x-trigger-noedit) { |
||
734 | border-color: {{focus-color}} !important; |
||
735 | } |
||
736 | .x-form-field-wrap.x-trigger-wrap-focus:not(.x-freebusy-userlist-container) { |
||
737 | border-color: {{focus-color}}; |
||
738 | } |
||
739 | input.x-form-text.x-form-field.x-form-focus { |
||
740 | border-color: {{focus-color}} !important; |
||
741 | } |
||
742 | .x-form-field-wrap.x-trigger-wrap-focus:not(.x-freebusy-userlist-container) input.x-form-text.x-form-field.x-form-focus { |
||
743 | border-color: {{focus-color}} !important; |
||
744 | } |
||
745 | ', |
||
746 | |||
747 | 'logo-large' => ' |
||
748 | /* The logo in the Login screen. Maximum size of the logo image is 220x60px. */ |
||
749 | body.login #form-container #logo, |
||
750 | #loading-mask #form-container #logo { |
||
751 | background: url({{logo-large}}) no-repeat right center; |
||
752 | background-size: contain; |
||
753 | } |
||
754 | ', |
||
755 | |||
756 | 'logo-small' => ' |
||
757 | /**************************************************************************** |
||
758 | * The logo (shown on the right below the top bar) |
||
759 | * =============================================== |
||
760 | * The maximum height of the image that can be shown is 45px. |
||
761 | ****************************************************************************/ |
||
762 | .zarafa-maintoolbar { |
||
763 | background-image: url({{logo-small}}); |
||
764 | background-size: auto 38px; |
||
765 | } |
||
766 | ', |
||
767 | |||
768 | 'background-image' => ' |
||
769 | /********************************************************************************************* |
||
770 | * The Login screen and the Welcome screen |
||
771 | * ======================================= |
||
772 | ********************************************************************************************/ |
||
773 | /* Background image of the login screen */ |
||
774 | body.login, |
||
775 | #loading-mask, |
||
776 | #bg, |
||
777 | /* Background image of the Welcome screen */ |
||
778 | body.zarafa-welcome { |
||
779 | background: url({{background-image}}) no-repeat center center; |
||
780 | background-size: cover; |
||
781 | } |
||
782 | ', |
||
783 | |||
784 | 'spinner-image' => ' |
||
785 | /* The spinner of the login/loading screen */ |
||
793 |