Модуль SHS для анонимов

Главные вкладки

Аватар пользователя NicD NicD 14 июля 2019 в 0:48

Здравствуйте,
Есть хороший модуль https://www.drupal.org/project/shs , который позволяет ВЫБИРАТЬ иерархически категории при добавлении контента.
Вот патч #39 https://www.drupal.org/project/shs/issues/2712115,
который позволяет ДОБАВЛЯТЬ иерархически категории, если необходимая категория не сущетвует. Я попробовал на Drupal 8.7, все работает, только одна маленькая ошибка, которая меня интересует.
Ошибка следующая; можно создавать иерархические категории только как зарегистрированный пользователь, или админ и нельзя как анонимный пользователь.
Все разрешения для контента и категории открыты, но когда пробуешь создать категорию как анонимный пользователь то ничего не происходит, а в лог сообщениях пишет “access denied”.
Не могу понять, почему запрещено для анонимов.
Вот как выглядит патч #39

<?php
diff 
--git a/js/models/WidgetModel.js b/js/models/WidgetModel.js
index d62b4f4
..2c299d9 100644
--- a/js/models/WidgetModel.js
+++ b/js/models/WidgetModel.js
@@ -31,+31,11 @@
        */
       
defaultValue'_none',
 
+      
/**
+       * The new item that was created.
+       */
+      createValuenull,
+
       
/**
        * Position of widget in app.
        *
diff --git a/js/views/AppView.js b/js/views/AppView.js
index 8693214..dfed10a 100644
--- a/js/views/AppView.js
+++ b/js/views/AppView.js
@@ -65,9 +65,6 @@
           parents: parents
         }));
       });
-//      $.each(app.getConfig('parents'), function (index, item) {
-//        // Add WidgetModel for each parent.
-//      });
 
       app.collection.trigger('initialize:shs');
 
@@ -127,6 +124,19 @@
           // Use value of parent widget (which is the id of the model ;)).
           value = widgetModel.get('id');
         }
+        else if (widgetModel.get('createValue')) {
+          // Add the created item to the original select item.
+          var options = $("option", app.$el).map(function () {
+            return $(this).val();
+          }).get();
+          if ($.inArray(value, options) === -1) {
+            var item = widgetModel.get('createValue');
+            app.$el.append($("<option/>").val(item.tid).text(item.name));
+            // We can now reset our widget model to the new tid.
+              widgetModel.set('createValue', null);
+            widgetModel.set('defaultValue', item.tid);
+          }
+        }
       }
       // Set the updated value.
       app.$el.val(value).trigger({
diff --git a/js/views/ContainerView.js b/js/views/ContainerView.js
index 2ec8218..8e6a25c 100644
--- a/js/views/ContainerView.js
+++ b/js/views/ContainerView.js
@@ -42,7 +42,7 @@
       }
 
       this.collection = new Drupal.shs.WidgetCollection({
-        url: Drupal.url(this.app.getConfig('baseUrl') + '/' + this.app.getConfig('fieldName') + '/' + this.app.getConfig('bundle'))
+        url: Drupal.url.toAbsolute(this.app.getConfig('baseUrl') + '/' + this.app.getConfig('fieldName') + '/' + this.app.getConfig('bundle'))
       });
       this.collection.reset();
 
@@ -119,13 +119,21 @@
       container.collection.remove(models);
 
       var anyValue = container.app.getSetting('anyValue');
-      if (value !== anyValue) {
+      var createValue = container.app.getSetting('createValue');
+
+      if (value === createValue && widgetModel.get('createValue')) {
+        var item = widgetModel.get('createValue');
+        value = item.tid;
+      }
+
+      if ((value !== anyValue) && (value !== createValue)) {
         // Add new model with current selection.
         container.collection.add(new Drupal.shs.classes[container.app.getConfig('fieldName')].models.widget({
           id: value,
           level: widgetModel.get('level') + 1
         }));
       }
+
       if (value === anyValue && widgetModel.get('level') > 0) {
         // Use value of parent widget (which is the id of the model ;)).
         value = widgetModel.get('id');
diff --git a/js/views/WidgetView.js b/js/views/WidgetView.js
index da6ac0a..364551f 100644
--- a/js/views/WidgetView.js
+++ b/js/views/WidgetView.js
@@ -62,10 +62,10 @@
     render: function () {
       var widget = this;
       widget.$el.prop('id', widget.container.app.$el.prop('id') + '-shs-' + widget.container.model.get('delta') + '-' + widget.model.get('level'))
-              .addClass('shs-select')
-              // Add core class to apply default styles to the element.
-              .addClass('form-select')
-              .hide();
+          .addClass('shs-select')
+          // Add core class to apply default styles to the element.
+          .addClass('form-select')
+          .hide();
       if (widget.model.get('dataLoaded')) {
         widget.$el.show();
       }
@@ -120,9 +120,9 @@
         var label = false;
         if (labels.hasOwnProperty(widget.model.get('level')) && (label = labels[widget.model.get('level')]) !== false) {
           $('<label>')
-                  .prop('for', widget.$el.prop('id'))
-                  .text(label)
-                  .appendTo($container);
+              .prop('for', widget.$el.prop('id'))
+              .text(label)
+              .appendTo($container);
         }
       }
 
@@ -138,6 +138,71 @@
         $container.append(widget.$el.fadeIn(widget.container.app.getConfig('display.animationSpeed')));
       }
 
+      var createValue2 = widget.model.get('createValue');
+      if (widget.$el.val() === createValue2 || widget.$el.val() === "_none") {
+        var create_item_input_id = 'shs-widget-create-new-item-' + widget.container.app.getConfig('fieldName') + '-delta-' + widget.container.model.get('delta');
+        $container.append($('<input>')
+            .on('keyup', function (e) {
+              if (e.keyCode === 13) {
+                // Do something
+                $('#' + create_item_input_id + '_button').trigger('click');
+                e.preventDefault();
+                return false;
+              }
+            })
+            .attr('type', 'text')
+            .attr('id', create_item_input_id)
+            .attr('name', 'create_new_item')
+            .attr('class', 'text-full form-text')
+            .attr('placeholder', Drupal.t("New term"))
+        ).append($('<button>')
+            .attr('id', create_item_input_id + '_button')
+            .attr('class', 'button')
+            .html(Drupal.t('Add'))
+            .on('click', function (e) {
+              var value = $('#' + create_item_input_id).val();
+
+              // Get the langcode value if present.
+              var $language = $('select[name="langcode[0][value]"]');
+              var langcode = 'und';
+              if ($language) {
+                langcode = $language.val();
+              }
+
+              $.ajax({
+                url: Drupal.url.toAbsolute(widget.container.app.getConfig('createUrl')),
+                type: 'POST',
+                dataType: 'json',
+                async: false,
+                contentType: 'application/json',
+                Accept: 'application/json',
+                data: JSON.stringify({
+                  arguments: {
+                    value: value,
+                    bundle: widget.container.app.getConfig('bundle'),
+                    entity_id: widget.model.get('id'),
+                    langcode: langcode
+                  }
+                })
+              }).then(function (data) {
+                if (data !== 'null') {
+                  // Force a reload.
+                  widget.model.set('dataLoaded', false);
+
+                  // Update create value of attached model.
+                  widget.model.set('createValue', data);
+
+                  // Fire events.
+                  widget.container.collection.trigger('update:selection', widget.model, widget.model.get('defaultValue'), widget);
+                }
+              });
+
+              e.preventDefault();
+              return false;
+            })
+        );
+      }
+
       widget.model.set('dataLoaded', true);
       // Return self for chaining.
       return widget;
diff --git a/shs.routing.yml b/shs.routing.yml
index cf93fd9..f4e602e 100644
--- a/shs.routing.yml
+++ b/shs.routing.yml
@@ -6,3 +6,11 @@ shs.term_data:
     entity_id: null
   requirements:
     _permission: 'access content'
+
+shs.create_term:
+  path: '/shs-create-term'
+  defaults:
+    _controller: '\Drupal\shs\Controller\ShsController::createTerm'
+  requirements:
+    _csrf_token: 'TRUE'
+    _custom_access:  '\Drupal\shs\Controller\ShsController::createTermAccess'
diff --git a/src/Controller/ShsController.php b/src/Controller/ShsController.php
index e9e4616..f204f88 100644
--- a/src/Controller/ShsController.php
+++ b/src/Controller/ShsController.php
@@ -2,11 +2,15 @@
 
 namespace Drupal\shs\Controller;
 
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Session\AccountInterface;
 use Drupal\shs\Cache\ShsCacheableJsonResponse;
 use Drupal\shs\Cache\ShsTermCacheDependency;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Controller for getting taxonomy terms.
@@ -98,4 +102,93 @@ class ShsController extends ControllerBase {
     return $response;
   }
 
+  /**
+   * Create term data.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *
+   * @return JsonResponse
+   *   Json response.
+   */
+  public function createTerm(Request $request) {
+
+    
$result NULL;
+
+    
// Obtain the data from the json.
+    $data json_decode($request->getContent());
+    
$value $data->arguments->value;
+    
$bundle $data->arguments->bundle;
+    
$entity_id $data->arguments->entity_id;
+    
$langcode $data->arguments->langcode;
+
+    if (!
$langcode) {
+      
$langcode = \Drupal::languageManager()->getCurrentLanguage()->getId();
+    }
+
+    
$entity_manager = \Drupal::getContainer()->get('entity.manager');
+    
$storage $entity_manager->getStorage('taxonomy_term');
+
+    
// Try to find the term.
+    $found NULL;
+    
$terms $storage->loadTree($bundle$entity_id1TRUE);
+    foreach (
$terms as $term) {
+      if (
$term->hasTranslation($langcode)) {
+        
$term $term->getTranslation($langcode);
+      }
+      else {
+        
$langcode $term->default_langcode;
+      }
+
+      if (
strcasecmp($term->getName(), $value) === 0) {
+        
$found $term;
+        break;
+      }
+    }
+
+    if (!
$found) {
+      
$term $storage->create([
+        
'vid' => $bundle,
+        
'langcode' => $langcode,
+        
'name' => $value,
+        
'parent' => [$entity_id],
+      ]);
+      
$term->save();
+    }
+    else {
+      
$term $found;
+    }
+
+    
$result = (object) [
+      
'tid' => $term->id(),
+      
'name' => $term->getName(),
+      
'description__value' => $term->getDescription(),
+      
'langcode' => $langcode,
+      
'hasChildren' => shs_term_has_children($term->id()),
+    ];
+
+    
$response = new JsonResponse();
+    
$response->setData($result);
+
+    return 
$response;
+  }
+
+  
/**
+   * Checks access for a specific request.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   Run access checks for this account.
+   *
+   * @return bool
+   */
+  public function createTermAccess(AccountInterface $account) {
+    
$request = \Drupal::request();
+    
$data json_decode($request->getContent());
+    
$bundle $data->arguments->bundle;
+
+    return 
AccessResult::allowedIfHasPermissions($account, [
+      
"edit terms in {$bundle}",
+      
'administer taxonomy',
+    ], 
'OR');
+  }
+
 }
diff --git a/src/Plugin/Field/FieldWidget/OptionsShsWidget.php b/src/Plugin/Field/FieldWidget/OptionsShsWidget.php
index 3bb51cf
..f75f7cf 100644
--- a/src/Plugin/Field/FieldWidget/OptionsShsWidget.php
+++ b/src/Plugin/Field/FieldWidget/OptionsShsWidget.php
@@ -3,11 +3,14 @@
 namespace 
Drupal\shs\Plugin\Field\FieldWidget;
 
 use 
Drupal\Component\Utility\Html;
+use 
Drupal\Core\Entity\FieldableEntityInterface;
 use 
Drupal\Core\Field\FieldDefinitionInterface;
 use 
Drupal\Core\Field\FieldItemListInterface;
 use 
Drupal\Core\Field\Plugin\Field\FieldWidget\OptionsSelectWidget;
 use 
Drupal\Core\Form\FormStateInterface;
 use 
Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use 
Drupal\Core\Render\BubbleableMetadata;
+use 
Drupal\Core\Url;
 use 
Drupal\shs\WidgetDefaults;
 use 
Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -
91,+94,@@ class OptionsShsWidget extends OptionsSelectWidget implements ContainerFactoryPl
       
'#title' => t('Allow creating new items'),
       
'#default_value' => $this->getSetting('create_new_items'),
       
'#description' => t('Allow users to create new items of the source bundle.'),
-      
'#disabled' => TRUE,
     ];
     
$element['create_new_levels'] = [
       
'#type' => 'checkbox',
@@ -
103,+105,@@ class OptionsShsWidget extends OptionsSelectWidget implements ContainerFactoryPl
           
':input[name="fields[' $field_name '][settings_edit_form][settings][create_new_items]"]' => ['checked' => TRUE],
         ],
       ],
-      
'#disabled' => TRUE,
     ];
     
$element['force_deepest'] = [
       
'#type' => 'checkbox',
@@ -
177,10 +178,23 @@ class OptionsShsWidget extends OptionsSelectWidget implements ContainerFactoryPl
       $parents 
$this->widgetDefaults->getParentDefaults($default_value$settings_additional['anyValue'], $this->fieldDefinition->getItemDefinition()->getSetting('target_type'), $cardinality);
     }
 
+    
// Generate a token for our ajax url.
+    // We need to run this through a render function because Drupal.url generates
+    // a placeholder token.
+    $urlBubbleable Url::fromRoute('shs.create_term')->toString(TRUE);
+    
$urlRender = array(
+      
'#markup' => $urlBubbleable->getGeneratedUrl(),
+    );
+    
BubbleableMetadata::createFromRenderArray($urlRender)
+      ->
merge($urlBubbleable)->applyTo($urlRender);
+    
$url = (string) \Drupal::service('renderer')->renderPlain($urlRender);
+
+
     
$settings_shs = [
       
'settings' => $this->getSettings() + $settings_additional,
       
'bundle' => $bundle,
       
'baseUrl' => 'shs-term-data',
+      
'createUrl' => $url,
       
'cardinality' => $cardinality,
       
'parents' => $parents,
       
'defaultValue' => $default_value,
@@ -
319,+333,35 @@ class OptionsShsWidget extends OptionsSelectWidget implements ContainerFactoryPl
     
return $options[$value];
   }
 
+  
/**
+   * Returns the array of options for the widget.
+   *
+   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
+   *   The entity for which to return options.
+   *
+   * @return array
+   *   The array of options for the widget.
+   */
+  protected function getOptions(FieldableEntityInterface $entity) {
+    if (!isset(
$this->options)) {
+      
$options parent::getOptions($entity);
+
+      
// Add a create option if the widget needs one.
+      $new_label $this->getCreateLabel();
+      if (
$new_label) {
+        
$options['_create'] = $new_label;
+      }
+
+      
$this->options $options;
+    }
+    return 
$this->options;
+  }
+
+  
/**
+   * Returns the label for creating a new term.
+   * @return string
+   */
+  public function getCreateLabel() {
+    return (string) 
t('Create...');
+  }
 }
?>

Комментарии