From 8dbd7691a10621ae45b9113f9d79946cdd4fd2b5 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Wed, 5 Aug 2020 03:51:14 -0700 Subject: [PATCH] CollectionTrait - Create baseline trait based on CRM_Core_Region --- CRM/Core/Region.php | 143 ++------------------- CRM/Core/Resources/CollectionTrait.php | 168 +++++++++++++++++++++++++ 2 files changed, 178 insertions(+), 133 deletions(-) create mode 100644 CRM/Core/Resources/CollectionTrait.php diff --git a/CRM/Core/Region.php b/CRM/Core/Region.php index d87664019d..0d6cfc6330 100644 --- a/CRM/Core/Region.php +++ b/CRM/Core/Region.php @@ -20,6 +20,10 @@ class CRM_Core_Region { return Civi::$statics[__CLASS__][$name]; } + use CRM_Core_Resources_CollectionTrait { + CRM_Core_Resources_CollectionTrait::add as _add; + } + /** * Symbolic name of this region * @@ -27,28 +31,13 @@ class CRM_Core_Region { */ public $_name; - /** - * List of snippets to inject within region. - * - * e.g. $this->_snippets[3]['type'] = 'template'; - * - * @var array - */ - public $_snippets; - - /** - * Whether the snippets array has been sorted - * - * @var bool - */ - public $_isSorted; - /** * @param string $name */ public function __construct($name) { $this->_name = $name; - $this->_snippets = []; + $this->types = ['markup', 'template', 'callback', 'scriptUrl', 'script', 'jquery', 'settings', 'style', 'styleUrl']; + $this->defaults['region'] = $name; // Placeholder which represents any of the default content generated by the main Smarty template $this->add([ @@ -57,92 +46,6 @@ class CRM_Core_Region { 'markup' => '', 'weight' => 0, ]); - $this->_isSorted = TRUE; - } - - /** - * Add a snippet of content to a region. - * - * ``` - * CRM_Core_Region::instance('page-header')->add(array( - * 'markup' => '
Hello!
', - * )); - * CRM_Core_Region::instance('page-header')->add(array( - * 'script' => 'alert("Hello");', - * )); - * CRM_Core_Region::instance('page-header')->add(array( - * 'template' => 'CRM/Myextension/Extra.tpl', - * )); - * CRM_Core_Region::instance('page-header')->add(array( - * 'callback' => 'myextension_callback_function', - * )); - * ``` - * - * Note: This function does not perform any extra encoding of markup, script code, or etc. If - * you're passing in user-data, you must clean it yourself. - * - * @param array $snippet - * Array; keys:. - * - type: string (auto-detected for markup, template, callback, script, scriptUrl, jquery, style, styleUrl) - * - name: string, optional - * - weight: int, optional; default=1 - * - disabled: int, optional; default=0 - * - markup: string, HTML; required (for type==markup) - * - template: string, path; required (for type==template) - * - callback: mixed; required (for type==callback) - * - arguments: array, optional (for type==callback) - * - script: string, Javascript code - * - scriptUrl: string, URL of a Javascript file - * - jquery: string, Javascript code which runs inside a jQuery(function($){...}); block - * - settings: array, list of static values to convey. - * - style: string, CSS code - * - styleUrl: string, URL of a CSS file - * - * @return array - */ - public function add($snippet) { - static $types = ['markup', 'template', 'callback', 'scriptUrl', 'script', 'jquery', 'settings', 'style', 'styleUrl']; - $defaults = [ - 'region' => $this->_name, - 'weight' => 1, - 'disabled' => FALSE, - ]; - $snippet += $defaults; - if (!isset($snippet['type'])) { - foreach ($types as $type) { - // auto-detect - if (isset($snippet[$type])) { - $snippet['type'] = $type; - break; - } - } - } - if (!isset($snippet['name'])) { - $snippet['name'] = count($this->_snippets); - } - - $this->_snippets[$snippet['name']] = $snippet; - $this->_isSorted = FALSE; - return $snippet; - } - - /** - * @param string $name - * @param $snippet - */ - public function update($name, $snippet) { - $this->_snippets[$name] = array_merge($this->_snippets[$name], $snippet); - $this->_isSorted = FALSE; - } - - /** - * Get snippet. - * - * @param string $name - * @return array|NULL - */ - public function &get($name) { - return $this->_snippets[$name]; } /** @@ -156,20 +59,17 @@ class CRM_Core_Region { */ public function render($default, $allowCmsOverride = TRUE) { // $default is just another part of the region - if (is_array($this->_snippets['default'])) { - $this->_snippets['default']['markup'] = $default; + if (is_array($this->snippets['default'])) { + $this->snippets['default']['markup'] = $default; } // We hand as much of the work off to the CMS as possible $cms = CRM_Core_Config::singleton()->userSystem; - if (!$this->_isSorted) { - uasort($this->_snippets, ['CRM_Core_Region', '_cmpSnippet']); - $this->_isSorted = TRUE; - } + $this->sort(); $smarty = CRM_Core_Smarty::singleton(); $html = ''; - foreach ($this->_snippets as $snippet) { + foreach ($this->snippets as $snippet) { if ($snippet['disabled']) { continue; } @@ -233,27 +133,4 @@ class CRM_Core_Region { return $html; } - /** - * @param $a - * @param $b - * - * @return int - */ - public static function _cmpSnippet($a, $b) { - if ($a['weight'] < $b['weight']) { - return -1; - } - if ($a['weight'] > $b['weight']) { - return 1; - } - // fallback to name sort; don't really want to do this, but it makes results more stable - if ($a['name'] < $b['name']) { - return -1; - } - if ($a['name'] > $b['name']) { - return 1; - } - return 0; - } - } diff --git a/CRM/Core/Resources/CollectionTrait.php b/CRM/Core/Resources/CollectionTrait.php new file mode 100644 index 0000000000..3cb245ca17 --- /dev/null +++ b/CRM/Core/Resources/CollectionTrait.php @@ -0,0 +1,168 @@ + 1, 'disabled' => FALSE]; + + /** + * List of snippets to inject within region. + * + * e.g. $this->_snippets[3]['type'] = 'template'; + * + * @var array + */ + protected $snippets = []; + + /** + * Whether the snippets array has been sorted + * + * @var bool + */ + protected $isSorted = TRUE; + + /** + * Whitelist of supported types. + * + * @var array + */ + protected $types = []; + + /** + * Add an item to the collection. For example, when working with 'page-header' collection: + * + * ``` + * CRM_Core_Region::instance('page-header')->add(array( + * 'markup' => '
Hello!
', + * )); + * CRM_Core_Region::instance('page-header')->add(array( + * 'script' => 'alert("Hello");', + * )); + * CRM_Core_Region::instance('page-header')->add(array( + * 'template' => 'CRM/Myextension/Extra.tpl', + * )); + * CRM_Core_Region::instance('page-header')->add(array( + * 'callback' => 'myextension_callback_function', + * )); + * ``` + * + * Note: This function does not perform any extra encoding of markup, script code, or etc. If + * you're passing in user-data, you must clean it yourself. + * + * @param array $snippet + * Array; keys:. + * - type: string (auto-detected for markup, template, callback, script, scriptUrl, jquery, style, styleUrl) + * - name: string, optional + * - weight: int, optional; default=1 + * - disabled: int, optional; default=0 + * - markup: string, HTML; required (for type==markup) + * - template: string, path; required (for type==template) + * - callback: mixed; required (for type==callback) + * - arguments: array, optional (for type==callback) + * - script: string, Javascript code + * - scriptUrl: string, URL of a Javascript file + * - jquery: string, Javascript code which runs inside a jQuery(function($){...}); block + * - settings: array, list of static values to convey. + * - style: string, CSS code + * - styleUrl: string, URL of a CSS file + * + * @return array + * The full/computed snippet (with defaults applied). + */ + public function add($snippet) { + $snippet = array_merge($this->defaults, $snippet); + if (!isset($snippet['type'])) { + foreach ($this->types as $type) { + // auto-detect + if (isset($snippet[$type])) { + $snippet['type'] = $type; + break; + } + } + } + elseif (!in_array($snippet['type'], $this->types)) { + throw new \RuntimeException("Unsupported snippet type: " . $snippet['type']); + } + if (!isset($snippet['name'])) { + $snippet['name'] = count($this->snippets); + } + + $this->snippets[$snippet['name']] = $snippet; + $this->isSorted = FALSE; + return $snippet; + } + + /** + * @param string $name + * @param $snippet + */ + public function update($name, $snippet) { + $this->snippets[$name] = array_merge($this->snippets[$name], $snippet); + $this->isSorted = FALSE; + } + + /** + * Get snippet. + * + * @param string $name + * @return array|NULL + */ + public function &get($name) { + return $this->snippets[$name]; + } + + /** + * Ensure that the collection is sorted. + * + * @return static + */ + protected function sort() { + if (!$this->isSorted) { + uasort($this->snippets, [__CLASS__, '_cmpSnippet']); + $this->isSorted = TRUE; + } + return $this; + } + + /** + * @param $a + * @param $b + * + * @return int + */ + public static function _cmpSnippet($a, $b) { + if ($a['weight'] < $b['weight']) { + return -1; + } + if ($a['weight'] > $b['weight']) { + return 1; + } + // fallback to name sort; don't really want to do this, but it makes results more stable + if ($a['name'] < $b['name']) { + return -1; + } + if ($a['name'] > $b['name']) { + return 1; + } + return 0; + } + +} -- 2.25.1