diff --git a/Civi/Core/Themes.php b/Civi/Core/Themes.php index 05469ba93710..340740ca27e8 100644 --- a/Civi/Core/Themes.php +++ b/Civi/Core/Themes.php @@ -265,4 +265,11 @@ public function cssId($cssExt, $cssFile) { return ($cssExt === 'civicrm') ? $cssFile : "$cssExt-$cssFile"; } + /** + * Clear the cache + */ + public function clearCache() { + $this->cache->clear(); + } + } diff --git a/ext/riverlea/CRM/riverlea/BAO/RiverleaStream.php b/ext/riverlea/CRM/riverlea/BAO/RiverleaStream.php new file mode 100644 index 000000000000..5d06d0bdccc7 --- /dev/null +++ b/ext/riverlea/CRM/riverlea/BAO/RiverleaStream.php @@ -0,0 +1,21 @@ +action === 'create' && !$event->params['name']) { + $event->params['name'] = \CRM_Utils_String::munge($event->params['label']); + } + } + + public static function self_hook_civicrm_postCommit(PostEvent $event): void { + \Civi::service('themes')->clear(); + } + +} diff --git a/ext/riverlea/CRM/riverlea/DAO/RiverleaStream.php b/ext/riverlea/CRM/riverlea/DAO/RiverleaStream.php new file mode 100644 index 000000000000..0fa0d57b18d5 --- /dev/null +++ b/ext/riverlea/CRM/riverlea/DAO/RiverleaStream.php @@ -0,0 +1,20 @@ +createEntityTable('schema/upgrader/1000-RiverleaStream.entityType.php'); + return TRUE; + } + +} diff --git a/ext/riverlea/Civi/Api4/Action/RiverleaStream/GetWithFileContent.php b/ext/riverlea/Civi/Api4/Action/RiverleaStream/GetWithFileContent.php new file mode 100644 index 000000000000..bebb08b9b67d --- /dev/null +++ b/ext/riverlea/Civi/Api4/Action/RiverleaStream/GetWithFileContent.php @@ -0,0 +1,71 @@ +getPath($extension, $path); + + if (is_file($finalPath)) { + // File exists and is a file? Return it! + return file_get_contents($finalPath) ?? ''; + } + return ''; + } + +} diff --git a/ext/riverlea/Civi/Api4/Action/RiverleaStream/Render.php b/ext/riverlea/Civi/Api4/Action/RiverleaStream/Render.php new file mode 100644 index 000000000000..cbbdd3eed518 --- /dev/null +++ b/ext/riverlea/Civi/Api4/Action/RiverleaStream/Render.php @@ -0,0 +1,162 @@ +darkMode) { + $this->darkMode = $this->getDarkModeDefault($stream); + } + + $content[] = self::concatStreamCss($stream); + + switch ($this->darkMode ?? NULL) { + case 'light': + // tell OS we want light for system elements + $content[] = ":root { color-scheme: light; }"; + break; + + case 'dark': + // tell OS we want dark for system elements + $content[] = ":root { color-scheme: dark; }"; + // add stream dark unconditionally + $content[] = self::concatStreamCss($stream, TRUE); + break; + + case 'inherit': + default: + // tell OS we are happy with light or dark for system elements + $content[] = ":root { color-scheme: light dark; }"; + // add stream dark vars wrapped inside a media query + $content[] = '@media (prefers-color-scheme: dark) {'; + $content[] = self::concatStreamCss($stream, TRUE); + $content[] = '}'; + break; + } + + return [ + 'content' => implode("\n", $content), + ]; + } + + private function getDarkModeDefault(array $stream) { + if ($this->isFrontend) { + return $stream['dark_frontend'] ?? \Civi::settings()->get('riverlea_dark_mode_frontend'); + } + else { + return $stream['dark_backend'] ?? \Civi::settings()->get('riverlea_dark_mode_backend'); + } + } + + /** + * This replaces stream meta with its dark version, ready to pass + * to concatStreamCss / getStreamCssFromFile + */ + private static function getDarkStream(array $stream): array { + $stream['label'] = $stream['label'] . " Dark Styles"; + $stream['vars'] = $stream['vars_dark']; + $stream['css_file'] = $stream['css_file_dark']; + $stream['custom_css'] = $stream['custom_css_dark']; + + return $stream; + } + + /** + * @param array $stream the stream meta + * @param bool $darkMode whether to use the regular stream meta, or dark version + * + * @return string + */ + private static function concatStreamCss(array $stream, bool $darkMode = FALSE): string { + if ($darkMode) { + $stream = self::getDarkStream($stream); + } + + $content = []; + + if ($stream['css_file']) { + $content[] = "/* {$stream['label']} file: {$stream['css_file']} */"; + $content[] = self::getStreamCssFromFile($stream); + } + + if ($stream['vars']) { + $content[] = "/* {$stream['label']} vars */"; + + $content[] = ":root {"; + foreach ($stream['vars'] as $var => $value) { + $content[] = "{$var}: {$value};"; + } + $content[] = "}"; + } + + if ($stream['custom_css']) { + $content[] = "/* {$stream['label']} custom css */"; + $content[] = $stream['custom_css']; + } + + return implode("\n", $content); + } + + /** + * @return string + */ + private static function getStreamCssFromFile(array $stream): string { + return GetWithFileContent::getFileContent($stream['css_file'], $stream['extension'], $stream['file_prefix']); + } + +} diff --git a/ext/riverlea/Civi/Api4/RiverleaStream.php b/ext/riverlea/Civi/Api4/RiverleaStream.php new file mode 100644 index 000000000000..9cf285612fe9 --- /dev/null +++ b/ext/riverlea/Civi/Api4/RiverleaStream.php @@ -0,0 +1,15 @@ +get('riverlea_dark_mode_frontend') : - \Civi::settings()->get('riverlea_dark_mode_backend'); + public function getAvailableStreamMeta(): array { + $streams = \Civi::$statics['riverlea_streams'] ?? NULL; + + if (is_null($streams)) { + try { + $streams = (array) \Civi\Api4\RiverleaStream::get(FALSE) + ->addSelect('name', 'label', 'extension', 'file_prefix', 'parent_id', 'id', 'modified_date') + ->execute() + ->indexBy('name'); + + \Civi::$statics['riverlea_streams'] = $streams; + } + catch (\CRM_Core_Exception $e) { + \Civi::log()->warning('Error loading Riverlea stream meta'); + return []; + } + } + + return $streams; + } + + public function getCssParams(): array { + $stream = \Civi::service('themes')->getActiveThemeKey(); + + // we add the stream modified date to asset params as a cache buster + $streamMeta = self::getAvailableStreamMeta()[$stream] ?? []; + $streamModified = $streamMeta['modified_date'] ?? NULL; return [ - 'stream' => \Civi::service('themes')->getActiveThemeKey(), - 'dark' => $darkModeSetting, + 'stream' => $stream, + 'modified' => $streamModified, + 'is_frontend' => \CRM_Utils_System::isFrontendPage(), ]; } @@ -45,75 +69,19 @@ public static function getCssParams(): array { * @see CRM_Utils_hook::buildAsset() * @see \Civi\Core\AssetBuilder */ - public static function buildAssetCss($e) { + public function buildAssetCss($e) { if ($e->asset !== static::CSS_FILE) { return; } $e->mimeType = 'text/css'; - $params = $e->params; - - $stream = $params['stream'] ?? 'empty'; - - $content = []; - - // add base vars for the stream - $content[] = self::getCSSFromFile('_variables.css', $stream); - - switch ($params['dark'] ?? NULL) { - case 'light': - // tell OS we want light for system elements - $content[] = ":root { color-scheme: light; }"; - break; - - case 'dark': - // tell OS we want dark for system elements - $content[] = ":root { color-scheme: dark; }"; - // add stream dark vars unconditionally - $content[] = self::getCSSFromFile('_dark.css', $stream); - break; - - case 'inherit': - default: - // tell OS we are happy with light or dark for system elements - $content[] = ":root { color-scheme: light dark; }"; - // add stream dark vars wrapped inside a media query - $content[] = '@media (prefers-color-scheme: dark) {'; - $content[] = self::getCSSFromFile('_dark.css', $stream); - $content[] = '}'; - break; - } - - $e->content = implode("\n", $content); - } + $render = \Civi\Api4\RiverleaStream::render(FALSE) + ->addWhere('name', '=', $e->params['stream']) + ->setIsFrontend($e->params['is_frontend']) + ->execute() + ->first(); - /** - * Check file exists and return contents or empty string - * - * @param string $cssFileName The name of the css file (eg. _variables.css) - * @param string $stream The name of the riverlea stream (eg. walbrook) - * - * @return string - */ - private static function getCSSFromFile(string $cssFileName, string $stream): string { - $res = \Civi::resources(); - $theme = \Civi::service('themes')->get($stream); - $file = ''; - // For riverlea themes CSS should be located in stream/streamname/css - prefix="stream/streamname/" - if (isset($theme['prefix'])) { - $file .= $theme['prefix']; - } - // Append css dir and filename so we end up with stream/streamname/css/filename.css - $file .= 'css/' . $cssFileName; - $file = $res->filterMinify($theme['ext'], $file); - - // Now get the full path for the css file - $filePath = $res->getPath($theme['ext'], $file); - if (is_file($filePath)) { - // File exists and is a file? Return it! - return file_get_contents($filePath) ?? ''; - } - return ''; + $e->content = $render['content'] ?? ''; } } diff --git a/ext/riverlea/README.md b/ext/riverlea/README.md index d109bf61eabb..0756f07b95b3 100755 --- a/ext/riverlea/README.md +++ b/ext/riverlea/README.md @@ -1,15 +1,16 @@ # RiverLea Theme Framework -This Framework separates CiviCRM's visual/UI CSS from structural CSS, using CSS variables. Installing it provides you with four subthemes or 'Streams' which are entirely created with CSS variables (other than Thames, which uses a little bit of CSS as well): +This Framework separates CiviCRM's visual/UI CSS from structural CSS, using CSS variables. Installing it provides you with four new Riverlea Themes or "Streams" which are almost entirely created with CSS variables: - Minetta, named after the river that runs under Greenwich, NYC. It is based on Civi's default 'Greenwich' theme. - Walbrook, named after the river that runs under Shoreditch, London. It is based on Shoreditch/TheIsland theme. - Hackney, named after the river that runs under Finsbury Park, based on Finsbury Park theme. - Thames, named after the river that runs close to Artful Robot HQ, based on their Aah theme. - You can chose between these subthemes via Display Settings, where you can also set dark-mode preferences. - The extension is licensed under [AGPL-3.0](LICENSE.txt). +When you enable CiviCRM, you will see these themes in your usual CiviCRM theme options on the Display Settings page. - ## Use in Front-End CiviCRM +The extension is licensed under [AGPL-3.0](LICENSE.txt). + +## Use in Front-End CiviCRM **USE WITH CAUTION AND TESTING** While RiverLea has been widely tested in the backend of CiviCRM, be very careful to use in front-end. Given the wide number of themes and scenarios for front-end pages, for existing sites we recommend only applying it to an existing web front-end after comprehensive testing on a dev site. @@ -42,40 +43,23 @@ Please ignore previous numbering patterns. ## Installation -### With (CLI, Zip) - -Sysadmins and developers may download the `.zip` file for this extension and -install it with the command-line tool [cv](https://github.com/civicrm/cv). - -```bash -cd -wget https://lab.civicrm.org/extensions/riverlea/-/archive/main/riverlea-main.zip -unzip riverlea-main.zip -``` - -### With (CLI, Git) - -Sysadmins and developers may clone the [Git](https://en.wikipedia.org/wiki/Git) repo for this extension and -install it with the command-line tool [cv](https://github.com/civicrm/cv). - -```bash -git clone https://lab.civicrm.org/extensions/riverlea.git -cv en riverlea -``` +This extension is bundled with CiviCRM core from version 5.82 onwards. You can install it from the Manage Extensions page. ### After installation -After installing the extension, go to Nav menu > Administer > Customize Data and Screens > Display Preferences, and select which subtheme/stream you want. +After installing the extension, go to Nav menu > Administer > Customize Data and Screens > Display Preferences, and select the stream you want. + +You can also set your Dark Mode preference on this screen: either always use Light Mode, always use Dark Mode, or let the user's browser/OS decide. ## Extension Structure -### Core variables -A list of all base variables used on all streams is at `core/css/_variables.css`. +### Core directory - `core` + +The majority of the Riverlea extension is a layer of core CSS, which styles CiviCRM markup based on the value +of a number of CSS variables. -### Stream/subtheme directories -Each ‘stream’ or subtheme directory must contain a further directory `css` with a `_variables.css`file and a `_dark.css` if darkmode is supported. Variables in this `_variables.css` file will overrule any variables in the core list above. The subtheme can also include fonts, images and other CSS files, which can be loaded from the `_variables.css` file as an import. +The list of all CSS variables with their default values can be found at `core/css/_variables.css`. -### Core directory Contains CSS files in: - In the **core/css** directory are theme files marked with an underscore: - core/css/_base.css – resets, basic type, colours, links, positioning @@ -89,9 +73,24 @@ Contains CSS files in: - other files here without underscore (`admin.css`, `api4-explorer.css`, `contactSummary.css` etc) overrides civicrm's CSS core directory with files of the same name that are called by templates and only load in certain parts of Civi. E.g. `dashboard.css` loads on the CiviCRM main dashboard, and no-where else. - three directories: `org.civicrm.afform-ang` for Afform output, `org.civicrm.afform_admin-ang` for FormBuilder and `org.civicrm.search_kit-css` for SearchKit replace css files in core Civi extensions. +### Stream directories - `streams/[stream_name]` +Each stream in the extension has a subdirectory under `streams` which primarily contains CSS files for the stream: a `_variables.css`file and a `_dark.css` if darkmode is supported. Variables in this `_variables.css` file will overrule any variables in the core list above. + +A stream could also include fonts, images and other CSS files, which can be loaded from the `_variables.css` file as an import. + +Each stream also has a Managed Record file which is used to "install" the stream. These are found in the `managed` directory. + ## Customising -Adding a customiser is on the roadmap, with a working prototype, but until its issues are resolved customising can be done through one of the three following methods: +Riverlea is designed to allow users to customise their UI by tweaking variables or creating new streams. + +At this stage, small changes are still being made to the variable structure, so any customisations you make may require revisiting in a future upgrade. + +The `core/css/_variables.css` file will give you idea of variables which can be altered. + +A [Customiser tool](https://github.com/civicrm/civicrm-core/pull/32344) is also being developed to create and edit Streams more easily through the UI. + +Use of the [ThemeTest extension](https://lab.civicrm.org/extensions/themetest) is recommended to more quickly identify which CSS variables match which UI element, and test multiple variations for each. ### 1. Add CSS variables to your parent theme @@ -101,55 +100,51 @@ For instance, to give all contribution page buttons rounded corners, you could a --crm-btn-radius: 2rem; ``` -Exploring the _variables.css file will give you idea of how much can be overwritten. - -### 2. Create a subtheme 'stream' +### 2. Add a custom CSS snippet in an extension -1. Inside the `/streams/` directory is an example stream called `empty`. Duplicate this and rename it the name of your stream. -2. In riverlea.php add a theme array to the function `riverlea_civicrm_themes(&$themes)`. -3. Edit `/streams/[streamname]/css/_variables.css` with your custom css variables. You can link to other CSS files, fonts or images in this file - inside the stream. - -E.g. to add a stream called "Vimur", you would name the directory 'vimur', and add the following: +You can override variables by listening to `hook_civicrm_alterBundle`: ``` - function riverlea_civicrm_themes(&$themes) { - $themes['vimur'] = array( - 'ext' => 'riverlea', - 'title' => 'Riverlea: Vimur', - 'prefix' => 'streams/vimur/', - ); - $themes['minetta'] = array( - 'ext' => 'riverlea', - 'title' => 'Riverlea: Minetta (~Greenwich)', - 'prefix' => 'streams/minetta/', - ); - … - } +function my_ext_civicrm_alterBundle(CRM_Core_Resources_Bundle $bundle) { + if ($bundle->name === 'coreResources') { + $riverleaOverrides = <<addStyle($riverleaOverrides, ['weight' => 200]); + } +} ``` -Use of the [ThemeTest extension](https://lab.civicrm.org/extensions/themetest) is recommended to more quickly identify which CSS variables match which UI element, and test multiple variations for each. +### 3. Create a new stream (preview) -IMPORTANT NOTE: Every time you upgrade RiverLea you will need to add your Stream again. This is obviously less than ideal, so for produciton you may prefer option 3: +New streams can be created using CiviCRM's Managed Entity system. (This is the same mechanism as can be used for packaging SearchKits, CustomFields, ContactTypes etc). -### 3. Create a subtheme extension +The Customiser will provide tools to automate some of this process. -NB: this approach has had very limited testing +0. If not adding to an existing extension, [follow the docs to create one](https://docs.civicrm.org/dev/en/latest/extensions/civix/#generate-module). -1. Create a theme extension using Civix, following the [instructions in the CiviCRM Developer Guide](https://docs.civicrm.org/dev/en/latest/framework/theme/). -2. Create a subtheme of RiverLea using the instructions in **2. Create a subtheme 'stream'** above. -3. Copy the subtheme into the root of your new theme extenion. -4. Edit its main php file, enable both extensions and select your stream. -E.g. for a stream called 'styx', with a theme extension called 'ocean', then in ocean.php you would write: +1. Enable the `mgd-php` mixin in your extensions `info.xml` ``` -function ocean_civicrm_themes(&$themes) { - $themes['styx'] = array( - 'ext' => 'ocean', - 'title' => 'River Styx', - 'prefix' => 'styx/', - 'search_order' => array('_riverlea_core_', 'styx', '_fallback_'), - ); -} + + menu-xml@1.0.0 + mgd-php@1.0.0 + ... + +``` + +2. Copy the template `*.mgd.php` from the `docs` folder of this extension into your extension's `managed` folder (create the managed folder if it doesn't exist). Remove the `.template` part so the file extension is `*.mgd.php`. + +3. Update the `use CRM_riverlea_ExtensionUtil` line for the equivalent from your extension. + +4. You can add variable declarations directly to the `vars` and `vars_dark` keys in the `*.mgd.php` file. + +5. Or you can create `stream/main.css` and `stream/dark.css` files in your extension, and add your CSS to them. It's best to use variable declarations as much as possible to maintain compatibility with future versions +of CiviCRM. + ## Troubleshooting - Unless you really need it (e.g. applying an urgent fix, or running a test), delete the custom/ext version of RiverLea, once you are on CiviCRM 5.80 or later. diff --git a/ext/riverlea/docs/RiverleaStream_MyStream.mgd.php.template b/ext/riverlea/docs/RiverleaStream_MyStream.mgd.php.template new file mode 100644 index 000000000000..d58db63425d2 --- /dev/null +++ b/ext/riverlea/docs/RiverleaStream_MyStream.mgd.php.template @@ -0,0 +1,45 @@ + 'RiverleaStream_MyStream', +// 'entity' => 'RiverleaStream', +// 'update' => 'always', +// 'cleanup' => 'always', +// 'params' => [ +// 'version' => 4, +// 'values' => [ +// +// # Describe your stream: +// +// 'name' => 'my_unique_stream_name', +// 'label' => E::ts('My Stream'), +// 'description' => 'Describe your stream...', +// +// # Set stream variables directly here: +// +// 'vars' => [ +// '--crm-c-primary' => 'purple', +// ], +// 'vars_dark' => [ +// # Extra variables for dark mode +// '--crm-c-primary' => 'blue', +// ], +// +// # If you want to add static css files to your stream, then uncomment +// # the following four lines, and create your files at +// # - 'your_extension/stream/main.css' +// # - 'your_extension/stream/dark.css' +// +// 'extension' => 'my_extension_name', # or E::SHORT_NAME if you update the extensionutils above +// 'file_prefix' => 'stream/', +// 'css_file' => 'main.css', +// 'css_file_dark' => 'dark.css', +// ], +// 'match' => ['name'], +// ], +// ], +// ]; diff --git a/ext/riverlea/info.xml b/ext/riverlea/info.xml index f05fb3112769..8ad7ab5dd3d6 100755 --- a/ext/riverlea/info.xml +++ b/ext/riverlea/info.xml @@ -20,6 +20,7 @@ A cross-CMS CiviCRM theme framework. + CRM/Riverlea @@ -31,5 +32,7 @@ setting-admin@1.0.1 mgd-php@1.0.0 scan-classes@1.0.0 + entity-types-php@2.0.0 + CiviMix\Schema\Riverlea\AutomaticUpgrader diff --git a/ext/riverlea/managed/RiverleaStream_HackneyBrook.mgd.php b/ext/riverlea/managed/RiverleaStream_HackneyBrook.mgd.php new file mode 100644 index 000000000000..ebfb99f2eb53 --- /dev/null +++ b/ext/riverlea/managed/RiverleaStream_HackneyBrook.mgd.php @@ -0,0 +1,28 @@ + 'RiverleaStream_HackneyBrook', + 'entity' => 'RiverleaStream', + 'update' => 'always', + 'cleanup' => 'always', + 'params' => [ + 'version' => 4, + 'values' => [ + 'name' => 'hackneybrook', + 'label' => E::ts('Hackney Brook'), + 'description' => 'named after the Hackney Brook, a tributary of the River Lea that ran through Finsbury Park', + 'is_reserved' => TRUE, + 'extension' => E::SHORT_NAME, + 'file_prefix' => 'streams/hackneybrook/css/', + 'css_file' => '_variables.css', + 'css_file_dark' => '_dark.css', + 'vars' => [], + 'vars_dark' => [], + ], + 'match' => ['name'], + ], + ], +]; diff --git a/ext/riverlea/managed/RiverleaStream_Minetta.mgd.php b/ext/riverlea/managed/RiverleaStream_Minetta.mgd.php new file mode 100644 index 000000000000..f898a559dde1 --- /dev/null +++ b/ext/riverlea/managed/RiverleaStream_Minetta.mgd.php @@ -0,0 +1,29 @@ + 'RiverleaStream_Minetta', + 'entity' => 'RiverleaStream', + 'update' => 'always', + 'cleanup' => 'always', + 'params' => [ + 'version' => 4, + 'values' => [ + 'name' => 'minetta', + 'description' => 'Generic CiviCRM UI, somewhat familiar to users of CiviCRM since 2014. Named after Minetta Creek, which runs under Greenwich, New York', + 'label' => E::ts('Minetta'), + 'is_reserved' => TRUE, + 'extension' => E::SHORT_NAME, + 'file_prefix' => 'streams/minetta/css/', + 'css_file' => '_variables.css', + 'css_file_dark' => '_dark.css', + 'vars' => [ + '--crm-version' => "'Minetta, v' var(--crm-release)", + ], + ], + 'match' => ['name'], + ], + ], +]; diff --git a/ext/riverlea/managed/RiverleaStream_Thames.mgd.php b/ext/riverlea/managed/RiverleaStream_Thames.mgd.php new file mode 100644 index 000000000000..403a6b2d41bc --- /dev/null +++ b/ext/riverlea/managed/RiverleaStream_Thames.mgd.php @@ -0,0 +1,27 @@ + 'RiverleaStream_Thames', + 'entity' => 'RiverleaStream', + 'update' => 'always', + 'cleanup' => 'always', + 'params' => [ + 'version' => 4, + 'values' => [ + 'name' => 'thames', + 'description' => 'Aaaaah', + 'label' => E::ts('Thames'), + 'is_reserved' => TRUE, + 'extension' => E::SHORT_NAME, + 'file_prefix' => 'streams/thames/css/', + 'css_file' => '_variables.css', + 'css_file_dark' => '_dark.css', + 'vars' => [], + ], + 'match' => ['name'], + ], + ], +]; diff --git a/ext/riverlea/managed/RiverleaStream_Walbrook.mgd.php b/ext/riverlea/managed/RiverleaStream_Walbrook.mgd.php new file mode 100644 index 000000000000..67027d3b8c13 --- /dev/null +++ b/ext/riverlea/managed/RiverleaStream_Walbrook.mgd.php @@ -0,0 +1,27 @@ + 'RiverleaStream_Walbrook', + 'entity' => 'RiverleaStream', + 'update' => 'always', + 'cleanup' => 'always', + 'params' => [ + 'version' => 4, + 'values' => [ + 'name' => 'walbrook', + 'description' => 'Based on Shoreditch theme. Named after after River Walbrook, which runs under Shoreditch, London', + 'label' => E::ts('Walbrook'), + 'is_reserved' => TRUE, + 'extension' => E::SHORT_NAME, + 'file_prefix' => 'streams/walbrook/css/', + 'css_file' => '_variables.css', + 'css_file_dark' => '_dark.css', + 'vars' => [], + ], + 'match' => ['name'], + ], + ], +]; diff --git a/ext/riverlea/riverlea.civix.php b/ext/riverlea/riverlea.civix.php index 01dd89c91247..8fe8da11c003 100755 --- a/ext/riverlea/riverlea.civix.php +++ b/ext/riverlea/riverlea.civix.php @@ -75,10 +75,45 @@ public static function findClass($suffix) { return self::CLASS_PREFIX . '_' . str_replace('\\', '_', $suffix); } + /** + * @return \CiviMix\Schema\SchemaHelperInterface + */ + public static function schema() { + if (!isset($GLOBALS['CiviMixSchema'])) { + pathload()->loadPackage('civimix-schema@5', TRUE); + } + return $GLOBALS['CiviMixSchema']->getHelper(static::LONG_NAME); + } + } use CRM_riverlea_ExtensionUtil as E; +spl_autoload_register('_riverlea_civix_class_loader', TRUE, TRUE); + +function _riverlea_civix_class_loader($class) { + if ($class === 'CRM_riverlea_DAO_Base') { + if (version_compare(CRM_Utils_System::version(), '5.74.beta', '>=')) { + class_alias('CRM_Core_DAO_Base', 'CRM_riverlea_DAO_Base'); + // ^^ Materialize concrete names -- encourage IDE's to pick up on this association. + } + else { + $realClass = 'CiviMix\\Schema\\Riverlea\\DAO'; + class_alias($realClass, $class); + // ^^ Abstract names -- discourage IDE's from picking up on this association. + } + return; + } + + // This allows us to tap-in to the installation process (without incurring real file-reads on typical requests). + if (strpos($class, 'CiviMix\\Schema\\Riverlea\\') === 0) { + // civimix-schema@5 is designed for backported use in download/activation workflows, + // where new revisions may become dynamically available. + pathload()->loadPackage('civimix-schema@5', TRUE); + CiviMix\Schema\loadClass($class); + } +} + /** * (Delegated) Implements hook_civicrm_config(). * diff --git a/ext/riverlea/riverlea.php b/ext/riverlea/riverlea.php index 555aa4a7a9df..2ce580b33993 100755 --- a/ext/riverlea/riverlea.php +++ b/ext/riverlea/riverlea.php @@ -4,39 +4,55 @@ use CRM_riverlea_ExtensionUtil as E; /** - * Supports multiple theme variations/streams. + * Supply available streams to the theme hook + * + * Note: if this looks labour intensive, don't worry - the output + * is cached in \Civi\Core\Themes */ function riverlea_civicrm_themes(&$themes) { - $themes['minetta'] = array( - 'ext' => 'riverlea', - 'title' => 'Minetta (RiverLea ~Greenwich)', - 'prefix' => 'streams/minetta/', - 'search_order' => array('minetta', '_riverlea_core_', '_fallback_'), - ); - $themes['walbrook'] = array( - 'ext' => 'riverlea', - 'title' => 'Walbrook (RiverLea ~Shoreditch/Island)', - 'prefix' => 'streams/walbrook/', - 'search_order' => array('walbrook', '_riverlea_core_', '_fallback_'), - ); - $themes['hackneybrook'] = array( - 'ext' => 'riverlea', - 'title' => 'Hackney Brook (RiverLea ~Finsbury Park)', - 'prefix' => 'streams/hackneybrook/', - 'search_order' => array('hackneybrook', '_riverlea_core_', '_fallback_'), - ); - $themes['thames'] = array( - 'ext' => 'riverlea', - 'title' => 'Thames (RiverLea ~Aah)', - 'prefix' => 'streams/thames/', - 'search_order' => array('thames', '_riverlea_core_', '_fallback_'), - ); - $themes['_riverlea_core_'] = array( + // always add (hidden) Riverlea base theme + $themes['_riverlea_core_'] = [ 'ext' => 'riverlea', 'title' => 'Riverlea: base theme', 'prefix' => 'core/', - 'search_order' => array('_riverlea_core_', '_fallback_'), - ); + 'search_order' => ['_riverlea_core_', '_fallback_'], + ]; + + try { + $streams = \Civi::service('riverlea.dynamic_css')->getAvailableStreamMeta(); + } + catch (\CRM_Core_Exception $e) { + // dont crash the whole hook if Riverlea is broken + \CRM_Core_Session::setStatus('Error occured making Riverlea streams available to the theme engine: ' . $e->getMessage()); + return; + } + + $streamsById = array_column($streams, NULL, 'id'); + + foreach ($streams as $name => $stream) { + $themeMeta = [ + 'title' => $stream['label'], + 'search_order' => [], + ]; + + $extension = $stream['extension']; + + // we only add the stream itself to the search order if + // it has an extension (which indicates it may have its own + // file overrides) + if ($extension) { + $themeMeta['search_order'][] = $name; + + // used to resolve files from this stream + $themeMeta['ext'] = $extension; + $themeMeta['prefix'] = $stream['file_prefix'] ?? ''; + } + + $themeMeta['search_order'][] = '_riverlea_core_'; + $themeMeta['search_order'][] = '_fallback_'; + + $themes[$name] = $themeMeta; + } } /** @@ -77,7 +93,7 @@ function riverlea_civicrm_alterBundle(CRM_Core_Resources_Bundle $bundle) { // get DynamicCss asset $bundle->addStyleUrl(\Civi::service('asset_builder')->getUrl( \Civi\Riverlea\DynamicCss::CSS_FILE, - \Civi\Riverlea\DynamicCss::getCssParams() + \Civi::service('riverlea.dynamic_css')->getCssParams() )); } } diff --git a/ext/riverlea/schema/RiverleaStream.entityType.php b/ext/riverlea/schema/RiverleaStream.entityType.php new file mode 100644 index 000000000000..12b5c2fe2489 --- /dev/null +++ b/ext/riverlea/schema/RiverleaStream.entityType.php @@ -0,0 +1,137 @@ + 'RiverleaStream', + 'table' => 'civicrm_riverlea_stream', + 'class' => 'CRM_riverlea_DAO_RiverleaStream', + 'getInfo' => fn() => [ + 'title' => E::ts('RiverleaStream'), + 'title_plural' => E::ts('RiverleaStreams'), + 'description' => E::ts('Streams are configurable themes in the Riverlea Theme Framework'), + 'log' => TRUE, + ], + 'getIndices' => fn() => [ + 'index_stream_name' => [ + 'fields' => [ + 'name' => TRUE, + ], + 'unique' => TRUE, + 'add' => '6.1', + ], + ], + 'getPaths' => fn() => [ + // 'add' => 'civicrm/admin/riverlea/stream/create', + // 'update' => 'civicrm/admin/riverlea/stream/update#?RiverleaStream=[id]', + // 'delete' => 'civicrm/contact/view/delete?reset=1&delete=1&cid=[id]', + ], + 'getFields' => fn() => [ + 'id' => [ + 'title' => E::ts('ID'), + 'sql_type' => 'int unsigned', + 'input_type' => 'Number', + 'required' => TRUE, + 'description' => E::ts('Unique RiverleaStream ID'), + 'primary_key' => TRUE, + 'auto_increment' => TRUE, + ], + 'name' => [ + 'title' => ts('Machine-Name'), + 'sql_type' => 'varchar(255)', + 'input_type' => 'Text', + 'description' => ts('Machine-name for this stream.'), + ], + 'label' => [ + 'title' => ts('Label'), + 'sql_type' => 'varchar(512)', + 'input_type' => 'Text', + 'required' => TRUE, + 'description' => ts('User-facing name for this stream'), + ], + 'description' => [ + 'title' => E::ts('Description'), + 'sql_type' => 'text', + 'input_type' => 'TextArea', + 'description' => E::ts('Description of this stream'), + 'default' => NULL, + ], + 'is_reserved' => [ + 'title' => E::ts('Is Reserved?'), + 'description' => E::ts('Reserved streams are not editable through the UI'), + 'sql_type' => 'boolean', + 'input_type' => 'CheckBox', + 'required' => TRUE, + 'default' => FALSE, + ], + 'extension' => [ + 'title' => ts('Extension'), + 'sql_type' => 'varchar(255)', + 'input_type' => 'Select', + 'description' => ts('Extension that provides this stream.'), + 'default' => NULL, + // @todo why not work? + // 'entity_reference' => [ + // 'entity' => 'Extension', + // 'key' => 'file', + // 'on_delete' => 'CASCADE', + // ], + ], + 'file_prefix' => [ + 'title' => ts('Extension File Prefix'), + 'sql_type' => 'varchar(255)', + 'input_type' => 'Text', + 'description' => ts('File prefix to stream files within extension'), + 'default' => NULL, + ], + 'css_file' => [ + 'title' => ts('CSS File'), + 'description' => ts('A file containing stream css - path should be relative to the extension and file_prefix'), + 'sql_type' => 'varchar(512)', + 'input_type' => 'Text', + 'default' => NULL, + ], + 'css_file_dark' => [ + 'title' => ts('Dark-mode CSS File'), + 'description' => ts('A file containing stream css for darkmode - path should be relative to the extension and file_prefix'), + 'sql_type' => 'varchar(512)', + 'input_type' => 'Text', + 'default' => NULL, + ], + 'vars' => [ + 'title' => E::ts('Variable Settings'), + 'sql_type' => 'text', + 'description' => E::ts('Variable declarations for this stream'), + 'default' => NULL, + 'serialize' => CRM_Core_DAO::SERIALIZE_JSON, + ], + 'vars_dark' => [ + 'title' => E::ts('Dark-mode Variable Settings'), + 'sql_type' => 'text', + 'description' => E::ts('Variable declaration overrides for the dark mode of this stream'), + 'default' => NULL, + 'serialize' => CRM_Core_DAO::SERIALIZE_JSON, + ], + 'custom_css' => [ + 'title' => E::ts('Custom CSS'), + 'sql_type' => 'text', + 'input_type' => 'TextArea', + 'description' => E::ts('Custom css for this stream'), + 'default' => NULL, + ], + 'custom_css_dark' => [ + 'title' => E::ts('Dark-mode Custom CSS'), + 'sql_type' => 'text', + 'input_type' => 'TextArea', + 'description' => E::ts('Custom css for the darkmode of this stream'), + 'default' => NULL, + ], + 'modified_date' => [ + 'title' => ts('Modified Date'), + 'sql_type' => 'timestamp', + 'input_type' => 'Select Date', + 'readonly' => TRUE, + 'description' => ts('When the stream was last modified - helps with cache busting.'), + 'default' => 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP', + ], + ], +]; diff --git a/ext/riverlea/schema/upgrader/1000-RiverleaStream.entityType.php b/ext/riverlea/schema/upgrader/1000-RiverleaStream.entityType.php new file mode 100644 index 000000000000..12b5c2fe2489 --- /dev/null +++ b/ext/riverlea/schema/upgrader/1000-RiverleaStream.entityType.php @@ -0,0 +1,137 @@ + 'RiverleaStream', + 'table' => 'civicrm_riverlea_stream', + 'class' => 'CRM_riverlea_DAO_RiverleaStream', + 'getInfo' => fn() => [ + 'title' => E::ts('RiverleaStream'), + 'title_plural' => E::ts('RiverleaStreams'), + 'description' => E::ts('Streams are configurable themes in the Riverlea Theme Framework'), + 'log' => TRUE, + ], + 'getIndices' => fn() => [ + 'index_stream_name' => [ + 'fields' => [ + 'name' => TRUE, + ], + 'unique' => TRUE, + 'add' => '6.1', + ], + ], + 'getPaths' => fn() => [ + // 'add' => 'civicrm/admin/riverlea/stream/create', + // 'update' => 'civicrm/admin/riverlea/stream/update#?RiverleaStream=[id]', + // 'delete' => 'civicrm/contact/view/delete?reset=1&delete=1&cid=[id]', + ], + 'getFields' => fn() => [ + 'id' => [ + 'title' => E::ts('ID'), + 'sql_type' => 'int unsigned', + 'input_type' => 'Number', + 'required' => TRUE, + 'description' => E::ts('Unique RiverleaStream ID'), + 'primary_key' => TRUE, + 'auto_increment' => TRUE, + ], + 'name' => [ + 'title' => ts('Machine-Name'), + 'sql_type' => 'varchar(255)', + 'input_type' => 'Text', + 'description' => ts('Machine-name for this stream.'), + ], + 'label' => [ + 'title' => ts('Label'), + 'sql_type' => 'varchar(512)', + 'input_type' => 'Text', + 'required' => TRUE, + 'description' => ts('User-facing name for this stream'), + ], + 'description' => [ + 'title' => E::ts('Description'), + 'sql_type' => 'text', + 'input_type' => 'TextArea', + 'description' => E::ts('Description of this stream'), + 'default' => NULL, + ], + 'is_reserved' => [ + 'title' => E::ts('Is Reserved?'), + 'description' => E::ts('Reserved streams are not editable through the UI'), + 'sql_type' => 'boolean', + 'input_type' => 'CheckBox', + 'required' => TRUE, + 'default' => FALSE, + ], + 'extension' => [ + 'title' => ts('Extension'), + 'sql_type' => 'varchar(255)', + 'input_type' => 'Select', + 'description' => ts('Extension that provides this stream.'), + 'default' => NULL, + // @todo why not work? + // 'entity_reference' => [ + // 'entity' => 'Extension', + // 'key' => 'file', + // 'on_delete' => 'CASCADE', + // ], + ], + 'file_prefix' => [ + 'title' => ts('Extension File Prefix'), + 'sql_type' => 'varchar(255)', + 'input_type' => 'Text', + 'description' => ts('File prefix to stream files within extension'), + 'default' => NULL, + ], + 'css_file' => [ + 'title' => ts('CSS File'), + 'description' => ts('A file containing stream css - path should be relative to the extension and file_prefix'), + 'sql_type' => 'varchar(512)', + 'input_type' => 'Text', + 'default' => NULL, + ], + 'css_file_dark' => [ + 'title' => ts('Dark-mode CSS File'), + 'description' => ts('A file containing stream css for darkmode - path should be relative to the extension and file_prefix'), + 'sql_type' => 'varchar(512)', + 'input_type' => 'Text', + 'default' => NULL, + ], + 'vars' => [ + 'title' => E::ts('Variable Settings'), + 'sql_type' => 'text', + 'description' => E::ts('Variable declarations for this stream'), + 'default' => NULL, + 'serialize' => CRM_Core_DAO::SERIALIZE_JSON, + ], + 'vars_dark' => [ + 'title' => E::ts('Dark-mode Variable Settings'), + 'sql_type' => 'text', + 'description' => E::ts('Variable declaration overrides for the dark mode of this stream'), + 'default' => NULL, + 'serialize' => CRM_Core_DAO::SERIALIZE_JSON, + ], + 'custom_css' => [ + 'title' => E::ts('Custom CSS'), + 'sql_type' => 'text', + 'input_type' => 'TextArea', + 'description' => E::ts('Custom css for this stream'), + 'default' => NULL, + ], + 'custom_css_dark' => [ + 'title' => E::ts('Dark-mode Custom CSS'), + 'sql_type' => 'text', + 'input_type' => 'TextArea', + 'description' => E::ts('Custom css for the darkmode of this stream'), + 'default' => NULL, + ], + 'modified_date' => [ + 'title' => ts('Modified Date'), + 'sql_type' => 'timestamp', + 'input_type' => 'Select Date', + 'readonly' => TRUE, + 'description' => ts('When the stream was last modified - helps with cache busting.'), + 'default' => 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP', + ], + ], +]; diff --git a/ext/riverlea/streams/minetta/css/_variables.css b/ext/riverlea/streams/minetta/css/_variables.css index 64f8073ce95c..bceec0776c32 100644 --- a/ext/riverlea/streams/minetta/css/_variables.css +++ b/ext/riverlea/streams/minetta/css/_variables.css @@ -4,9 +4,6 @@ Riverlea version: 1.3.8; */ -:root { - --crm-version: 'Minetta, v' var(--crm-release); -} .contactCardRight:has(#crm-contact-thumbnail) .float-left { width: calc(100% - var(--crm-flex-gap) - var(--crm-dash-image-size)); }