Skip to content

Commit

Permalink
Merge pull request #315 from totten/setting-mixin
Browse files Browse the repository at this point in the history
Add setting-admin@1. Use some extra patches to achieve 5.27+ compatibility
  • Loading branch information
totten authored Nov 17, 2023
2 parents 84dd21b + 9519d46 commit b1cdfb9
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 0 deletions.
8 changes: 8 additions & 0 deletions mixin-backports.php
Original file line number Diff line number Diff line change
Expand Up @@ -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/[email protected]',
'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',
Expand Down
10 changes: 10 additions & 0 deletions mixin-mods/README.md
Original file line number Diff line number Diff line change
@@ -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`.
226 changes: 226 additions & 0 deletions mixin-mods/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
<?php

/**
* The "setting-admin" mixin defines a standard idiom for managing extension settings:
*
* 1. Create a permission "administer {myext}" ("Administer {My Extension}").
* 2. Create a page "civicrm/admin/setting/{myext}" (via `CRM_Admin_Form_Generic`)
* 3. Assign all settings from "{myext}" to appear on the page.
* 4. Create a link "Administer > System Settings" to "{My Extension} Settings"
*
* (The values of "{myext}" and "{My Extension}" come from info.xml's `<file>` and `<name>`.)
*
* 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.)
*
* <civix><setting-page-title>My Custom Title</setting-page-title></civix>
*
* #####################################################################
* NOTE: [email protected] 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 [email protected]+.
* #####################################################################
*
* @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);

};

0 comments on commit b1cdfb9

Please sign in to comment.