CollectionTrait - Create baseline trait based on CRM_Core_Region
authorTim Otten <totten@civicrm.org>
Wed, 5 Aug 2020 10:51:14 +0000 (03:51 -0700)
committerSeamus Lee <seamuslee001@gmail.com>
Thu, 3 Sep 2020 22:01:59 +0000 (08:01 +1000)
CRM/Core/Region.php
CRM/Core/Resources/CollectionTrait.php [new file with mode: 0644]

index d87664019dc926d48b9dfb10674f16e61c2f87f2..0d6cfc63301dbe75ec8006c2df71e05de343fcaf 100644 (file)
@@ -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' => '<div style="color:red">Hello!</div>',
-   * ));
-   * 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 (file)
index 0000000..3cb245c
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved.                        |
+ |                                                                    |
+ | This work is published under the GNU AGPLv3 license with some      |
+ | permitted exceptions and without any warranty. For full license    |
+ | and copyright information, see https://civicrm.org/licensing       |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Class CRM_Core_Resources_CollectionTrait
+ *
+ * This is a building-block for creating classes which maintain a list of resources.
+ */
+trait CRM_Core_Resources_CollectionTrait {
+
+  /**
+   * Static defaults - a list of options to apply to any new snippets.
+   *
+   * @var array
+   */
+  protected $defaults = ['weight' => 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' => '<div style="color:red">Hello!</div>',
+   * ));
+   * 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;
+  }
+
+}