diff --git a/CRM/Core/Resources/Bundle.php b/CRM/Core/Resources/Bundle.php index 62c1a0426958..3c1124f54cfc 100644 --- a/CRM/Core/Resources/Bundle.php +++ b/CRM/Core/Resources/Bundle.php @@ -32,10 +32,46 @@ class CRM_Core_Resources_Bundle implements CRM_Core_Resources_CollectionInterfac * @param string|NULL $name * @param string[]|NULL $types * List of resource-types to permit in this bundle. NULL for a default list. + * Ex: ['styleFile', 'styleUrl'] + * The following aliases are allowed: '*all*', '*default*', '*script*', '*style*' */ public function __construct($name = NULL, $types = NULL) { $this->name = $name; - $this->types = $types ?: ['script', 'scriptFile', 'scriptUrl', 'settings', 'style', 'styleFile', 'styleUrl']; + + $typeAliases = [ + '*all*' => ['script', 'scriptFile', 'scriptUrl', 'settings', 'style', 'styleFile', 'styleUrl', 'markup', 'template', 'callback'], + '*default*' => ['script', 'scriptFile', 'scriptUrl', 'settings', 'style', 'styleFile', 'styleUrl'], + '*style*' => ['style', 'styleFile', 'styleUrl'], + '*script*' => ['script', 'scriptFile', 'scriptUrl'], + ]; + $mapType = function ($t) use ($typeAliases) { + return $typeAliases[$t] ?? [$t]; + }; + $types = $types ?: ['*default*']; + $this->types = array_unique(array_merge(...array_map($mapType, (array) $types))); + } + + /** + * Fill in default values for the 'region' property. + * + * @return static + */ + public function fillDefaults() { + $this->filter(function ($s) { + if (!isset($s['region'])) { + if ($s['type'] === 'settings') { + $s['region'] = NULL; + } + elseif (preg_match(';^(markup|template|callback);', $s['type'])) { + $s['region'] = 'page-header'; + } + else { + $s['region'] = CRM_Core_Resources_Common::REGION; + } + } + return $s; + }); + return $this; } } diff --git a/CRM/Core/Resources/Common.php b/CRM/Core/Resources/Common.php index c85abeaec0a1..13b86a233dfe 100644 --- a/CRM/Core/Resources/Common.php +++ b/CRM/Core/Resources/Common.php @@ -16,6 +16,36 @@ class CRM_Core_Resources_Common { const REGION = 'html-header'; + /** + * Create a "basic" (generic) bundle. + * + * The bundle goes through some lifecycle events (like `hook_alterBundle`). + * + * To define default content for a basic bundle, you may either give an + * `$init` function or subscribe to `hook_alterBundle`. + * + * @param string $name + * Symbolic name of the bundle. + * @param callable|NULL $init + * Optional initialization function. Populate default resources. + * Signature: `function($bundle): void` + * Example: `function myinit($b) { $b->addScriptFile(...)->addStyleFile(...); }` + * @param string|string[] $types + * List of resource-types to permit in this bundle. NULL for a default list. + * Example: ['styleFile', 'styleUrl'] + * The following aliases are allowed: '*all*', '*default*', '*script*', '*style*' + * @return CRM_Core_Resources_Bundle + */ + public static function createBasicBundle($name, $init = NULL, $types = NULL) { + $bundle = new CRM_Core_Resources_Bundle($name, $types); + if ($init !== NULL) { + $init($bundle); + } + CRM_Utils_Hook::alterBundle($bundle); + $bundle->fillDefaults(); + return $bundle; + } + /** * The 'bundle.bootstrap3' service is a collection of resources which are * loaded when a page needs to support Boostrap CSS v3. @@ -47,7 +77,7 @@ public static function createBootstrap3Bundle($name) { ); CRM_Utils_Hook::alterBundle($bundle); - self::useRegion($bundle, self::REGION); + $bundle->fillDefaults(); return $bundle; } @@ -76,7 +106,7 @@ public static function createStyleBundle($name) { $bundle->addStyleFile('civicrm', 'css/crm-i.css', -101); CRM_Utils_Hook::alterBundle($bundle); - self::useRegion($bundle, self::REGION); + $bundle->fillDefaults(); return $bundle; } @@ -133,7 +163,7 @@ public static function createFullBundle($name) { ]); CRM_Utils_Hook::alterBundle($bundle); - self::useRegion($bundle, self::REGION); + $bundle->fillDefaults(); return $bundle; } @@ -273,21 +303,4 @@ protected static function coreResourceList($region) { return $items; } - /** - * Ensure that all elements of the bundle are in the same region. - * - * @param CRM_Core_Resources_Bundle $bundle - * @param string $region - * @return CRM_Core_Resources_Bundle - */ - protected static function useRegion($bundle, $region) { - $bundle->filter(function ($s) use ($region) { - if ($s['type'] !== 'settings' && !isset($s['region'])) { - $s['region'] = $region; - } - return $s; - }); - return $bundle; - } - } diff --git a/tests/phpunit/CRM/Core/Resources/BundleTest.php b/tests/phpunit/CRM/Core/Resources/BundleTest.php index 87630bf0214f..08490b979471 100644 --- a/tests/phpunit/CRM/Core/Resources/BundleTest.php +++ b/tests/phpunit/CRM/Core/Resources/BundleTest.php @@ -56,4 +56,20 @@ public function testMergeIntoRegion() { $this->assertEquals('http://example.com/region.css', $region->get('http://example.com/region.css')['styleUrl']); } + /** + * Add some resources - sometimes forgetting to set a 'region'. Fill in missing regions. + */ + public function testFillDefaults() { + $bundle = new CRM_Core_Resources_Bundle(__FUNCTION__, ['scriptUrl', 'styleUrl', 'markup']); + $bundle->addScriptUrl('http://example.com/myscript.js'); + $bundle->addStyleUrl('http://example.com/yonder-style.css', ['region' => 'yonder']); + $bundle->addMarkup('Cheese', ['name' => 'cheese']); + + $bundle->fillDefaults(); + + $this->assertEquals('html-header', $bundle->get('http://example.com/myscript.js')['region']); + $this->assertEquals('yonder', $bundle->get('http://example.com/yonder-style.css')['region']); + $this->assertEquals('page-header', $bundle->get('cheese')['region']); + } + } diff --git a/tests/phpunit/CRM/Core/ResourcesTest.php b/tests/phpunit/CRM/Core/ResourcesTest.php index 93596d0c0fdc..4adb1aa1ab41 100644 --- a/tests/phpunit/CRM/Core/ResourcesTest.php +++ b/tests/phpunit/CRM/Core/ResourcesTest.php @@ -60,6 +60,25 @@ public function tearDown() { $_GET = $this->originalGet; } + public function testCreateBasicBundle() { + $hits = []; + + $init = function(CRM_Core_Resources_Bundle $b) use (&$hits) { + $hits[] = 'init_' . $b->name; + $b->addScript('doStuff();'); + }; + $alter = function ($e) use (&$hits) { + $hits[] = 'alter_' . $e->bundle->name; + $e->bundle->addScript('alert();'); + }; + + Civi::dispatcher()->addListener('hook_civicrm_alterBundle', $alter); + $b = CRM_Core_Resources_Common::createBasicBundle('cheese', $init); + $this->assertEquals('cheese', $b->name); + $this->assertEquals(['init_cheese', 'alter_cheese'], $hits); + $this->assertEquals(['doStuff();', 'alert();'], array_values(CRM_Utils_Array::collect('script', $b->getAll()))); + } + /** * Make two bundles (multi-regional). Add them to CRM_Core_Resources. * Ensure that the resources land in the right regions.