From 4d99fcc687a31c4c10b681051080cadff2acb1fc Mon Sep 17 00:00:00 2001
From: Rosie Le Faive <lefaive@gmail.com>
Date: Wed, 30 Nov 2022 00:36:03 -0400
Subject: [PATCH 1/6] Solr filter for entity relationship that has term.

---
 .../processor/EntityReferenceWithUri.php      | 195 ++++++++++++++++++
 .../EntityReferenceWithUriProperty.php        |  62 ++++++
 2 files changed, 257 insertions(+)
 create mode 100644 src/Plugin/search_api/processor/EntityReferenceWithUri.php
 create mode 100644 src/Plugin/search_api/processor/Property/EntityReferenceWithUriProperty.php

diff --git a/src/Plugin/search_api/processor/EntityReferenceWithUri.php b/src/Plugin/search_api/processor/EntityReferenceWithUri.php
new file mode 100644
index 00000000..996c857d
--- /dev/null
+++ b/src/Plugin/search_api/processor/EntityReferenceWithUri.php
@@ -0,0 +1,195 @@
+<?php
+
+namespace Drupal\islandora\Plugin\search_api\processor;
+
+use Drupal\islandora\Plugin\search_api\processor\Property\EntityReferenceWithUriProperty;
+use Drupal\search_api\Datasource\DatasourceInterface;
+use Drupal\search_api\Item\ItemInterface;
+use Drupal\search_api\Processor\ProcessorPluginBase;
+use Drupal\islandora\IslandoraUtils;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Add a filterable search api field for entity reference fields.
+ *
+ * @SearchApiProcessor(
+ *   id = "entity_reference_with_term",
+ *   label = @Translation("Entity Reference, where the target has a given taxonomy term"),
+ *   description = @Translation("Indexes the titles of Entity Reference field targets, where the targets have a taxonomy term or terms. Does not apply to Taxonomy Reference fields."),
+ *   stages = {
+ *     "add_properties" = 0,
+ *   },
+ *   locked = false,
+ *   hidden = false,
+ * )
+ */
+class EntityReferenceWithUri extends ProcessorPluginBase {
+
+  /**
+   * Islandora utils.
+   *
+   * @var \Drupal\islandora\IslandoraUtils
+   */
+  protected IslandoraUtils $utils;
+
+  /**
+   * Constructor.
+   *
+   * @param array $configuration
+   *   The plugin configuration, i.e. an array with configuration values keyed
+   *   by configuration option name. The special key 'context' may be used to
+   *   initialize the defined contexts by setting it to an array of context
+   *   values keyed by context names.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\islandora\IslandoraUtils $utils
+   *   Islandora utils.
+   */
+  public function __construct(
+    array $configuration,
+          $plugin_id,
+          $plugin_definition,
+    IslandoraUtils $utils
+  ) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->utils = $utils;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('islandora.utils'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPropertyDefinitions(DatasourceInterface $datasource = NULL) {
+    $properties = [];
+    if (!$datasource || !$datasource->getEntityTypeId()) {
+      return $properties;
+    }
+
+    $entity_type = $datasource->getEntityTypeId();
+
+    // Get all configured Entity Relation fields for this entity type.
+    $fields = \Drupal::entityTypeManager()->getStorage('field_config')->loadByProperties([
+      'entity_type' => $entity_type,
+      'field_type' => 'entity_reference',
+    ]);
+
+    foreach ($fields as $field) {
+      // Skip over fields that point to taxonomy term fields themselves.
+      if ($field->getSetting('target_type') == 'taxonomy_term') {
+        continue;
+      }
+
+      $definition = [
+        'label' => $this->t('@label (by term URI) [@bundle]', [
+          '@label' => $field->label(),
+          '@bundle' => $field->getTargetBundle(),
+        ]),
+        'description' => $this->t('Index the target entity, but only if the target entity has a taxonomy term.'),
+        'type' => 'string',
+        'processor_id' => $this->getPluginId(),
+        'settings' => [
+          'filter_terms' => [],
+          'target_type' => $field->getSetting('target_type'),
+        ],
+        'is_list' => TRUE,
+      ];
+      $fieldname = 'entity_reference_with_term__' . str_replace('.', '__', $field->id());
+      $properties[$fieldname] = new EntityReferenceWithUriProperty($definition);
+    }
+    return $properties;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addFieldValues(ItemInterface $item) {
+    // Skip if no Entity Reference with URI fields are configured.
+    $relevant_search_api_fields = [];
+    $search_api_fields = $item->getFields(FALSE);
+    foreach ($search_api_fields as $field) {
+      if (substr($field->getPropertyPath(), 0, 28) == 'entity_reference_with_term__') {
+        $relevant_search_api_fields[] = $field;
+      }
+    }
+    if (empty($relevant_search_api_fields)) {
+      return;
+    }
+
+    $content_entity = $item->getOriginalObject()->getValue();
+    $type_and_bundle_prefix = $content_entity->getEntityTypeId() . '__' . $content_entity->bundle() . '__';
+    foreach ($relevant_search_api_fields as $search_api_field) {
+      $entity_type = $search_api_field->getConfiguration()['target_type'];
+      $filter_terms = $search_api_field->getConfiguration()['filter_terms'];
+      $filter_tids = array_map(function ($element) {
+        return $element['target_id'];
+      }, $filter_terms);
+      $require_all = $search_api_field->getConfiguration()['require_all'];
+      $field_name = substr($search_api_field->getPropertyPath(), 28);
+      $field_name = str_replace($type_and_bundle_prefix, '', $field_name);
+      if ($content_entity->hasField($field_name)) {
+        $referenced_terms = [];
+        foreach ($content_entity->get($field_name)->getValue() as $values) {
+          foreach ($values as $value) {
+            // Load the entity stored in our field.
+            $target_entity = \Drupal::entityTypeManager()
+              ->getStorage($entity_type)
+              ->load($value);
+            // Load the taxonomy terms on the entity stored in our field.
+            $referenced_terms = array_merge($referenced_terms, array_filter($target_entity->referencedEntities(), function ($entity) {
+              if ($entity->getEntityTypeId() != 'taxonomy_term') {
+                return FALSE;
+              }
+              else {
+                return TRUE;
+              }
+            }));
+          }
+
+          $referenced_tids = array_map(function ($element) {
+            return $element->id();
+          },
+            $referenced_terms
+          );
+          if ($require_all) {
+            if (count(array_intersect($referenced_tids, $filter_tids)) == count($filter_tids)) {
+              // "All" and all the terms specified are present.
+              $label = $target_entity->label();
+              $search_api_field->addValue($label);
+            }
+          }
+          else {
+            if (count(array_intersect($referenced_tids, $filter_tids)) > 0) {
+              // "Any" and at least one term specified is present.
+              $label = $target_entity->label();
+              $search_api_field->addValue($label);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function requiresReindexing(array $old_settings = NULL, array $new_settings = NULL) {
+    if ($new_settings != $old_settings) {
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+}
diff --git a/src/Plugin/search_api/processor/Property/EntityReferenceWithUriProperty.php b/src/Plugin/search_api/processor/Property/EntityReferenceWithUriProperty.php
new file mode 100644
index 00000000..3a084953
--- /dev/null
+++ b/src/Plugin/search_api/processor/Property/EntityReferenceWithUriProperty.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\islandora\Plugin\search_api\processor\Property;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\search_api\Item\FieldInterface;
+use Drupal\search_api\Processor\ConfigurablePropertyBase;
+use Drupal\taxonomy\Entity\Term;
+
+/**
+ * Defines a "Entity Reference with URI" search property.
+ *
+ * @see \Drupal\islandora\Plugin\search_api\processor\EntityReferenceWithUri
+ */
+class EntityReferenceWithUriProperty extends ConfigurablePropertyBase {
+
+  use StringTranslationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return [
+      'filter_terms' => [],
+      'require_all' => TRUE,
+      'target_type' => '',
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(FieldInterface $field, array $form, FormStateInterface $form_state) {
+    $configuration = $field->getConfiguration();
+    $logger = \Drupal::logger('islandora');
+    $logger->info('<pre>' . print_r($configuration['filter_terms'], TRUE) . '</pre>');
+    $form['filter_terms'] = [
+      '#type' => 'entity_autocomplete',
+      '#target_type' => 'taxonomy_term',
+      '#title' => $this->t('Related entities must have this URI to be included.'),
+      '#tags' => TRUE,
+      '#default_value' => array_map(function ($element) {
+        return Term::load($element['target_id']);
+      }, array_values($configuration['filter_terms'])),
+      '#required' => TRUE,
+    ];
+    $form['require_all'] = [
+      '#type' => 'checkbox',
+      '#title' => 'Require all terms',
+      '#description' => 'Only index related entities that have all the listed terms.',
+      '#default_value' => $configuration['require_all'],
+    ];
+    // Store the target type of the field, it's a pain to get it when indexing.
+    $form['target_type'] = [
+      '#type' => 'hidden',
+      '#value' => $field->getDataDefinition()->getSetting('target_type'),
+    ];
+    return $form;
+  }
+
+}

From 1a940a885fe98e0a4b30582e12b52ce0c0ae2d1d Mon Sep 17 00:00:00 2001
From: Rosie Le Faive <lefaive@gmail.com>
Date: Sat, 3 Dec 2022 21:56:02 -0400
Subject: [PATCH 2/6] Add missing t() function.

---
 .../.EntityReferenceWithUriProperty.php.un~   | Bin 0 -> 2144 bytes
 .../EntityReferenceWithUriProperty.php        |   4 +-
 .../EntityReferenceWithUriProperty.php~       |  62 ++++++++++++++++++
 3 files changed, 64 insertions(+), 2 deletions(-)
 create mode 100644 src/Plugin/search_api/processor/Property/.EntityReferenceWithUriProperty.php.un~
 create mode 100644 src/Plugin/search_api/processor/Property/EntityReferenceWithUriProperty.php~

diff --git a/src/Plugin/search_api/processor/Property/.EntityReferenceWithUriProperty.php.un~ b/src/Plugin/search_api/processor/Property/.EntityReferenceWithUriProperty.php.un~
new file mode 100644
index 0000000000000000000000000000000000000000..f6c2b1325767e68e7880cf5e85246540fc3ee19c
GIT binary patch
literal 2144
zcmWH`%$*;a=aT=Ffhp)hVOXV1)s&gpYFocQy?%8uuX4lh24f!a7X?S>^2eQKU|_HV
z;xGj;P*+Y#Elw`VEGWs$&r?^hwNp^{&&#P)$jnPgtxzaR%}FdtO;JeAE6FU$Of6O@
z$w(|w$Ve<pRY=Uq0rFB6ax#lSDoRp|a*OrUb$})t1F-}UGXgOS5Q6}ShGB-}?th#t
zAX!F+_zNIeAYg(}U=k!{1jIrh-hUtf8wKKw0vd!sa`)dq;OJmr_zH?QLm&plAt;rA
zqCyRpQh;e7Cl!)Lf>H}hGmD_902KA=I;M@_$OP$N24WZhMJy;X^*{onkqHU~S^{9+
zV#F7j`k0ZaQj(EbtZP@Ip^iINL9qzS%OK~2Vi=asM`INfA~XfSSY<|zRZxa90b*Dd
zrBPuH&C<x(8RSh+LI6d69EdZTr9q)UO8|^~4rp}=E}zjOKOUB)(Q9%<#-^xBH*I{r
F3IO)Dw8j7c

literal 0
HcmV?d00001

diff --git a/src/Plugin/search_api/processor/Property/EntityReferenceWithUriProperty.php b/src/Plugin/search_api/processor/Property/EntityReferenceWithUriProperty.php
index 3a084953..5197fc94 100644
--- a/src/Plugin/search_api/processor/Property/EntityReferenceWithUriProperty.php
+++ b/src/Plugin/search_api/processor/Property/EntityReferenceWithUriProperty.php
@@ -47,8 +47,8 @@ class EntityReferenceWithUriProperty extends ConfigurablePropertyBase {
     ];
     $form['require_all'] = [
       '#type' => 'checkbox',
-      '#title' => 'Require all terms',
-      '#description' => 'Only index related entities that have all the listed terms.',
+      '#title' => $this->t('Require all terms'),
+      '#description' => $this->t('Only index related entities that have all the listed terms.'),
       '#default_value' => $configuration['require_all'],
     ];
     // Store the target type of the field, it's a pain to get it when indexing.
diff --git a/src/Plugin/search_api/processor/Property/EntityReferenceWithUriProperty.php~ b/src/Plugin/search_api/processor/Property/EntityReferenceWithUriProperty.php~
new file mode 100644
index 00000000..3a084953
--- /dev/null
+++ b/src/Plugin/search_api/processor/Property/EntityReferenceWithUriProperty.php~
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\islandora\Plugin\search_api\processor\Property;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\search_api\Item\FieldInterface;
+use Drupal\search_api\Processor\ConfigurablePropertyBase;
+use Drupal\taxonomy\Entity\Term;
+
+/**
+ * Defines a "Entity Reference with URI" search property.
+ *
+ * @see \Drupal\islandora\Plugin\search_api\processor\EntityReferenceWithUri
+ */
+class EntityReferenceWithUriProperty extends ConfigurablePropertyBase {
+
+  use StringTranslationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return [
+      'filter_terms' => [],
+      'require_all' => TRUE,
+      'target_type' => '',
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(FieldInterface $field, array $form, FormStateInterface $form_state) {
+    $configuration = $field->getConfiguration();
+    $logger = \Drupal::logger('islandora');
+    $logger->info('<pre>' . print_r($configuration['filter_terms'], TRUE) . '</pre>');
+    $form['filter_terms'] = [
+      '#type' => 'entity_autocomplete',
+      '#target_type' => 'taxonomy_term',
+      '#title' => $this->t('Related entities must have this URI to be included.'),
+      '#tags' => TRUE,
+      '#default_value' => array_map(function ($element) {
+        return Term::load($element['target_id']);
+      }, array_values($configuration['filter_terms'])),
+      '#required' => TRUE,
+    ];
+    $form['require_all'] = [
+      '#type' => 'checkbox',
+      '#title' => 'Require all terms',
+      '#description' => 'Only index related entities that have all the listed terms.',
+      '#default_value' => $configuration['require_all'],
+    ];
+    // Store the target type of the field, it's a pain to get it when indexing.
+    $form['target_type'] = [
+      '#type' => 'hidden',
+      '#value' => $field->getDataDefinition()->getSetting('target_type'),
+    ];
+    return $form;
+  }
+
+}

From 387210973f145493def31a85e19e15c13a4d4d91 Mon Sep 17 00:00:00 2001
From: Rosie Le Faive <lefaive@gmail.com>
Date: Wed, 22 Feb 2023 16:03:07 -0400
Subject: [PATCH 3/6] Attempt at re-doing DI and creating a schema.

---
 .../processor/EntityReferenceWithUri.php      | 32 +++++++++++++++----
 1 file changed, 26 insertions(+), 6 deletions(-)

diff --git a/src/Plugin/search_api/processor/EntityReferenceWithUri.php b/src/Plugin/search_api/processor/EntityReferenceWithUri.php
index 996c857d..a2ed86a6 100644
--- a/src/Plugin/search_api/processor/EntityReferenceWithUri.php
+++ b/src/Plugin/search_api/processor/EntityReferenceWithUri.php
@@ -2,6 +2,9 @@
 
 namespace Drupal\islandora\Plugin\search_api\processor;
 
+use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
+use Drupal\Component\Plugin\Exception\PluginNotFoundException;
+use Drupal\Core\Entity\EntityTypeManager;
 use Drupal\islandora\Plugin\search_api\processor\Property\EntityReferenceWithUriProperty;
 use Drupal\search_api\Datasource\DatasourceInterface;
 use Drupal\search_api\Item\ItemInterface;
@@ -32,6 +35,13 @@ class EntityReferenceWithUri extends ProcessorPluginBase {
    */
   protected IslandoraUtils $utils;
 
+  /**
+   * Entity Type Manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManager
+   */
+  protected EntityTypeManager $entityTypeManager;
+
   /**
    * Constructor.
    *
@@ -46,15 +56,19 @@ class EntityReferenceWithUri extends ProcessorPluginBase {
    *   The plugin implementation definition.
    * @param \Drupal\islandora\IslandoraUtils $utils
    *   Islandora utils.
+   * @param \Drupal\Core\Entity\EntityTypeManager $entityTypeManager
+   *   Drupal Entity Type Manager.
    */
   public function __construct(
     array $configuration,
           $plugin_id,
           $plugin_definition,
-    IslandoraUtils $utils
+    IslandoraUtils $utils,
+    EntityTypeManager $entityTypeManager
   ) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     $this->utils = $utils;
+    $this->entityTypeManager = $entityTypeManager;
   }
 
   /**
@@ -66,6 +80,7 @@ class EntityReferenceWithUri extends ProcessorPluginBase {
       $plugin_id,
       $plugin_definition,
       $container->get('islandora.utils'),
+      $container->get('entity_type.manager'),
     );
   }
 
@@ -81,7 +96,7 @@ class EntityReferenceWithUri extends ProcessorPluginBase {
     $entity_type = $datasource->getEntityTypeId();
 
     // Get all configured Entity Relation fields for this entity type.
-    $fields = \Drupal::entityTypeManager()->getStorage('field_config')->loadByProperties([
+    $fields = $this->entityTypeManager->getStorage('field_config')->loadByProperties([
       'entity_type' => $entity_type,
       'field_type' => 'entity_reference',
     ]);
@@ -97,7 +112,7 @@ class EntityReferenceWithUri extends ProcessorPluginBase {
           '@label' => $field->label(),
           '@bundle' => $field->getTargetBundle(),
         ]),
-        'description' => $this->t('Index the target entity, but only if the target entity has a taxonomy term.'),
+        'description' => $this->t('Index the related entity, but only if the target entity has a taxonomy term.'),
         'type' => 'string',
         'processor_id' => $this->getPluginId(),
         'settings' => [
@@ -114,6 +129,7 @@ class EntityReferenceWithUri extends ProcessorPluginBase {
 
   /**
    * {@inheritdoc}
+   * @throws \Drupal\search_api\SearchApiException
    */
   public function addFieldValues(ItemInterface $item) {
     // Skip if no Entity Reference with URI fields are configured.
@@ -144,9 +160,13 @@ class EntityReferenceWithUri extends ProcessorPluginBase {
         foreach ($content_entity->get($field_name)->getValue() as $values) {
           foreach ($values as $value) {
             // Load the entity stored in our field.
-            $target_entity = \Drupal::entityTypeManager()
-              ->getStorage($entity_type)
-              ->load($value);
+            try {
+              $target_entity = $this->entityTypeManager
+                ->getStorage($entity_type)
+                ->load($value);
+            } catch (InvalidPluginDefinitionException $e) {
+            } catch (PluginNotFoundException $e) {
+            }
             // Load the taxonomy terms on the entity stored in our field.
             $referenced_terms = array_merge($referenced_terms, array_filter($target_entity->referencedEntities(), function ($entity) {
               if ($entity->getEntityTypeId() != 'taxonomy_term') {

From 3889a9564ac33ef624d019cb9273ea97528840c6 Mon Sep 17 00:00:00 2001
From: Rosie Le Faive <lefaive@gmail.com>
Date: Wed, 22 Feb 2023 16:03:37 -0400
Subject: [PATCH 4/6] first attempt at a schema.

---
 config/schema/islandora.schema.yml | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/config/schema/islandora.schema.yml b/config/schema/islandora.schema.yml
index 49de998b..062bb78a 100644
--- a/config/schema/islandora.schema.yml
+++ b/config/schema/islandora.schema.yml
@@ -231,6 +231,21 @@ reaction.plugin.delete:
 reaction.plugin.derivative:
   type: islandora.reaction.actions
 
+search_api.property_configuration.entity_reference_with_term:
+  type: mapping
+  label: 'Entity reference filtered by term configuration'
+  mapping:
+    filter_terms:
+      type: sequence
+      label: 'Filter terms to find on related entities'
+      orderby: value
+    require_all:
+      type: boolean
+      label: 'Whether to require all terms, or any term.'
+    target_type:
+      type: string
+      label: 'The target entity type of this field.'
+
 field.widget.settings.media_track:
   type: field.widget.settings.file_generic
 

From 07ae7b084dfa6db00da79924897821850a29cb92 Mon Sep 17 00:00:00 2001
From: Rosie Le Faive <lefaive@gmail.com>
Date: Tue, 28 Feb 2023 15:45:26 -0400
Subject: [PATCH 5/6] phpcs.

---
 src/Plugin/search_api/processor/EntityReferenceWithUri.php | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/Plugin/search_api/processor/EntityReferenceWithUri.php b/src/Plugin/search_api/processor/EntityReferenceWithUri.php
index a2ed86a6..0867b19f 100644
--- a/src/Plugin/search_api/processor/EntityReferenceWithUri.php
+++ b/src/Plugin/search_api/processor/EntityReferenceWithUri.php
@@ -129,7 +129,6 @@ class EntityReferenceWithUri extends ProcessorPluginBase {
 
   /**
    * {@inheritdoc}
-   * @throws \Drupal\search_api\SearchApiException
    */
   public function addFieldValues(ItemInterface $item) {
     // Skip if no Entity Reference with URI fields are configured.
@@ -164,8 +163,10 @@ class EntityReferenceWithUri extends ProcessorPluginBase {
               $target_entity = $this->entityTypeManager
                 ->getStorage($entity_type)
                 ->load($value);
-            } catch (InvalidPluginDefinitionException $e) {
-            } catch (PluginNotFoundException $e) {
+            }
+            catch (InvalidPluginDefinitionException $e) {
+            }
+            catch (PluginNotFoundException $e) {
             }
             // Load the taxonomy terms on the entity stored in our field.
             $referenced_terms = array_merge($referenced_terms, array_filter($target_entity->referencedEntities(), function ($entity) {

From 3957896d4017cdd186f57aa84221dd6be8c8c2f9 Mon Sep 17 00:00:00 2001
From: Rosie Le Faive <lefaive@gmail.com>
Date: Wed, 10 Jan 2024 10:28:54 -0400
Subject: [PATCH 6/6] Remove extra file.

---
 .../.EntityReferenceWithUriProperty.php.un~      | Bin 2144 -> 0 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 src/Plugin/search_api/processor/Property/.EntityReferenceWithUriProperty.php.un~

diff --git a/src/Plugin/search_api/processor/Property/.EntityReferenceWithUriProperty.php.un~ b/src/Plugin/search_api/processor/Property/.EntityReferenceWithUriProperty.php.un~
deleted file mode 100644
index f6c2b1325767e68e7880cf5e85246540fc3ee19c..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 2144
zcmWH`%$*;a=aT=Ffhp)hVOXV1)s&gpYFocQy?%8uuX4lh24f!a7X?S>^2eQKU|_HV
z;xGj;P*+Y#Elw`VEGWs$&r?^hwNp^{&&#P)$jnPgtxzaR%}FdtO;JeAE6FU$Of6O@
z$w(|w$Ve<pRY=Uq0rFB6ax#lSDoRp|a*OrUb$})t1F-}UGXgOS5Q6}ShGB-}?th#t
zAX!F+_zNIeAYg(}U=k!{1jIrh-hUtf8wKKw0vd!sa`)dq;OJmr_zH?QLm&plAt;rA
zqCyRpQh;e7Cl!)Lf>H}hGmD_902KA=I;M@_$OP$N24WZhMJy;X^*{onkqHU~S^{9+
zV#F7j`k0ZaQj(EbtZP@Ip^iINL9qzS%OK~2Vi=asM`INfA~XfSSY<|zRZxa90b*Dd
zrBPuH&C<x(8RSh+LI6d69EdZTr9q)UO8|^~4rp}=E}zjOKOUB)(Q9%<#-^xBH*I{r
F3IO)Dw8j7c