From 9519d46113c7a980c0acc516fc86809f2e7db582 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Thu, 16 Nov 2023 12:17:16 -0800 Subject: [PATCH] Add setting-admin@1. Use some extra patches to achieve 5.27+ compatibility. --- mixin-backports.php | 8 + mixin-mods/README.md | 10 + mixin-mods/setting-admin@1.0.1.mixin.php | 226 +++++++++++++++++++++++ 3 files changed, 244 insertions(+) create mode 100644 mixin-mods/README.md create mode 100644 mixin-mods/setting-admin@1.0.1.mixin.php diff --git a/mixin-backports.php b/mixin-backports.php index b95e0df4..c05627d0 100644 --- a/mixin-backports.php +++ b/mixin-backports.php @@ -69,6 +69,14 @@ 'provided-by' => '5.51.beta2', 'minimum' => '5.51', /* No point in deploying no systems that lack civicrm-core:693067e365915ce280217047009c9e87d70d0719 */ ], + 'setting-admin@1' => [ + 'version' => '1.0.1', + 'sha256' => 'b7b5209c88d07a483886f73f0f709fa510c5c35dc1a3e415cda56eb3ac9b8bf8', + 'remote' => 'file://' . __DIR__ . '/mixin-mods/setting-admin@1.0.1.mixin.php', + 'local' => 'extern/mixin/setting-admin@1/mixin.php', + 'provided-by' => '5.68.beta1', + 'minimum' => '5.27', /* civix ships a special backport from `./mixin-mods/` to provide broader compatibility. Compat may go back further; haven't tested. Mainline has higher requirements. */ + ], 'setting-php@1' => [ 'version' => '1.0.0', 'sha256' => '5ce236c1a1a63637ce5f0f4fe5bf7f21eaa06c750ca16c0fbf4dd792da0d23c9', diff --git a/mixin-mods/README.md b/mixin-mods/README.md new file mode 100644 index 00000000..a626b273 --- /dev/null +++ b/mixin-mods/README.md @@ -0,0 +1,10 @@ +# mixin-mods + +This folder contains civix-specific forks of some mixins. Guidelines: + +* Always use a lower number than the current published. +* Keep the semantics/behavior in close alignment with same-version upstream. +* If you can't provide the same contract, then don't bother trying to backport. +* Only make changes for backward compatibility (Civi APIs, PHP APIs). +* To make a new variant, make a new copy. Keep old variants around for reference. +* You still need to maintain `mixin-backports.php`. diff --git a/mixin-mods/setting-admin@1.0.1.mixin.php b/mixin-mods/setting-admin@1.0.1.mixin.php new file mode 100644 index 00000000..f0325e0a --- /dev/null +++ b/mixin-mods/setting-admin@1.0.1.mixin.php @@ -0,0 +1,226 @@ + System Settings" to "{My Extension} Settings" + * + * (The values of "{myext}" and "{My Extension}" come from info.xml's `` and ``.) + * + * If you don't like the defaults, then there are a few override points: + * + * - If you manually create permission "administer {myext}", then your label/description takes precedence. + * - If you manually register route "civicrm/admin/setting/{myext}", then your definition takes precedence. + * - If you manually configure a setting with `settings_page`, then that setting will move to the other page. + * (To make a hidden setting, specify `settings_page => []`.) + * - If you manually add "civicrm/admin/setting/{myext}" to the menu, then your link takes precedence. + * + * Additionally, there is experimental support for overrides in info.xml. (Respected by v1.0.0 but not guaranteed future.) + * + * My Custom Title + * + * ##################################################################### + * NOTE: setting-admin@1.0 is a civix-only backport revised for broader compatibility. Differences: + * + * - Don't register hooks with the `&` prefix. + * - Don't call `_ts()`. Use `$ts()` trick ourselves. + * - Omit some type-hints. + * - Use separate namespace for EVERY PATCH-VERSION. Prevent conflicts when using polyfill-based loading. + * - Downstream cannot rely on namespace-names/class-names. That's fine. These particular classes are internal helpers. + * + * The mainline release is setting-admin@1.1+. + * ##################################################################### + * + * @mixinName setting-admin + * @mixinVersion 1.0.1 + * @since 5.68 + */ + +namespace Civi\Mixin\SettingAdminV1_0_1; + +use Civi; + +class About { + + /** + * @var \CRM_Extension_MixInfo + */ + private $mixInfo; + + /** + * @var \CRM_Extension_Info + */ + private $info; + + /** + * @param \CRM_Extension_MixInfo $mixInfo + */ + public static function instance($mixInfo): About { + $about = new About(); + $about->mixInfo = $mixInfo; + $about->info = \CRM_Extension_System::singleton()->getMapper()->keyToInfo($mixInfo->longName); + return $about; + } + + public function getPath(): string { + return 'civicrm/admin/setting/' . $this->mixInfo->shortName; + } + + public function getPerm(): string { + return 'administer ' . $this->mixInfo->shortName; + } + + public function getLabel(): string { + return $this->info->label ? \ts($this->info->label, ['domain' => $this->info->key]) : $this->info->key; + } + + public function getPageTitle(): string { + // Changing the title any other way is slightly annoying because you have to override both route+nav. + // It might be nice if one (route or menu) reliably inherited its title from the other... + if (!empty($this->info->civix['setting-page-title'])) { + return $this->info->civix['setting-page-title']; + // Could call _ts(..., [domain=>...]), but translation appears to happen at another level, + // and double-translation might confuse multilingual. + } + return ts('%1 Settings', [1 => $this->getLabel()]); + } + + public function getRoute(): array { + return [ + 'title' => $this->getPageTitle(), + 'page_callback' => 'CRM_Admin_Form_Generic', + 'access_arguments' => [['administer CiviCRM', $this->getPerm()], 'or'], + 'adminGroup' => 'System Settings', + 'desc' => \ts($this->info->description ?: ''), + ]; + } + + public function getNavigation(): array { + return [ + 'label' => $this->getPageTitle(), + 'name' => sprintf('%s_setting_admin', $this->mixInfo->shortName), + 'url' => $this->getPath() . '?reset=1', + // 'icon' => 'crm-i fa-wrench', // None of the other "System Settings" have icons, so we don't. + // 'permission' => ['administer CiviCRM', $this->getPerm()], + 'permission' => "administer CiviCRM,{$this->getPerm()}", + 'permission_operator' => 'OR', + ]; + } + +} + +class Nav { + + /** + * Visit all items in the nav-tree. + * + * @param array $items + * @param callable $callback + * function(array &$item): mixed + * To short-circuit execution, the callback should return a non-null value. + * @return string|null + * Return NULL by default. If the walk was short-circuited, then return that value. + */ + public static function walk(&$items, callable $callback) { + foreach ($items as &$item) { + $result = $callback($item); + if ($result !== NULL) { + return $result; + } + if (!empty($item['child'])) { + $result = static::walk($item['child'], $callback); + if ($result !== NULL) { + return $result; + } + } + } + return NULL; + } + +} + +/** + * @param \CRM_Extension_MixInfo $mixInfo + * @param \CRM_Extension_BootCache $bootCache + */ +return function ($mixInfo, $bootCache) { + + // Register the setting page ("civicrm/admin/setting/{myext}"). + Civi::dispatcher()->addListener('hook_civicrm_alterMenu', function ($e) use ($mixInfo) { + if (!$mixInfo->isActive()) { + return; + } + + $about = About::instance($mixInfo); + if (!isset($e->items[$about->getPath()])) { + $e->items[$about->getPath()] = $about->getRoute(); + } + }, -1000); + + // Define a permission "administer {myext}" + Civi::dispatcher()->addListener('hook_civicrm_permission', function ($e) use ($mixInfo) { + if (!$mixInfo->isActive()) { + return; + } + + $about = About::instance($mixInfo); + $perm = 'administer ' . $mixInfo->shortName; + if (!isset($e->permissions[$perm])) { + $e->permissions[$perm] = ts('%1: Administer settings', [1 => $about->getLabel()]); + } + }, -1000); + + // Any settings with "group=={myext}" should be added to our setting page (unless overridden). + // By default, 'weight' is based on the order-of-declaration (spaced out with increments of 10). + Civi::dispatcher()->addListener('hook_civicrm_alterSettingsMetaData', function($e) use ($mixInfo) { + if (!$mixInfo->isActive()) { + return; + } + + $weight = 1000; + $weightInterval = 10; + + foreach ($e->settingsMetaData as &$setting) { + if (($setting['group'] ?? '') === $mixInfo->shortName) { + if (!array_key_exists('settings_pages', $setting)) { + $setting['settings_pages'][$mixInfo->shortName] = [ + 'weight' => $weight, + ]; + $weight += $weightInterval; + } + } + } + }, -1000); + + // Add navigation-item ('civicrm/admin/setting/{myext}') unless you've already done so. + Civi::dispatcher()->addListener('hook_civicrm_navigationMenu', function ($e) use ($mixInfo) { + if (!$mixInfo->isActive()) { + return; + } + + $about = About::instance($mixInfo); + $newItem = $about->getNavigation() + ['active' => 1]; + + // Skip if we're already in the menu. (Ignore optional suffix `?reset=1`) + $found = Nav::walk($e->menu, function(&$item) use ($about) { + if (!isset($item['attributes']['url'])) { + return NULL; + } + return strpos($item['attributes']['url'], $about->getPath()) === 0 ? 'found' : NULL; + }); + if ($found) { + return; + } + + Nav::walk($e->menu, function(&$item) use ($newItem) { + if ($item['attributes']['name'] === 'System Settings') { + $item['child'][] = ['attributes' => $newItem]; + return 'done'; + } + }); + }, -1000); + +};