Completed
Push — feature/linting ( bbbf28...a4917f )
by Christopher
17:38
created

FieldValueManager::ensureParagraph()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
rs 9.4285
1
<?php
2
3
namespace Drupal\paragraphs_editor\EditorFieldValue;
4
5
use Drupal\Core\Entity\EntityFieldManagerInterface;
6
use Drupal\Core\Entity\EntityTypeManagerInterface;
7
use Drupal\Core\Field\FieldDefinitionInterface;
8
use Drupal\Core\Field\FieldStorageDefinitionInterface;
9
use Drupal\entity_reference_revisions\EntityReferenceRevisionsFieldItemList;
10
use Drupal\paragraphs\ParagraphInterface;
11
use Drupal\paragraphs_editor\Utility\TypeUtility;
12
13
/**
14
 * Manages the paragraphs editor field values.
15
 */
16
class FieldValueManager implements FieldValueManagerInterface {
17
18
  /**
19
   * The storage plugin for the paragraph entity type.
20
   *
21
   * @var \Drupal\Core\Entity\EntityStorageInterface
22
   */
23
  protected $storage;
24
25
  /**
26
   * The storage plugin for the paragraph type config entity type.
27
   *
28
   * @var \Drupal\Core\Entity\EntityStorageInterface
29
   */
30
  protected $bundleStorage;
31
32
  /**
33
   * The field value manager service for collecting field information.
34
   *
35
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
36
   */
37
  protected $entityFieldManager;
38
39
  /**
40
   * Element definitions for custom elements that can occur in an editor field.
41
   *
42
   * @var array
43
   */
44
  protected $elements;
45
46
  /**
47
   * A static cache of paragraph revisions.
48
   *
49
   * @var \Drupal\paragraphs\ParagraphInterface[]
50
   */
51
  protected $revisionCache = [];
52
53
  /**
54
   * Creates a field value manager object.
55
   *
56
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
57
   *   The field manager service.
58
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
59
   *   The entity type manager service.
60
   * @param array $elements
61
   *   An array of widget binder element definitions.
62
   */
63 18
  public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, array $elements) {
64 18
    $this->entityFieldManager = $entity_field_manager;
65 18
    $this->storage = $entity_type_manager->getStorage('paragraph');
66 18
    $this->bundleStorage = $entity_type_manager->getStorage('paragraphs_type');
67 18
    $this->elements = $elements;
68 18
  }
69
70
  /**
71
   * {@inheritdoc}
72
   */
73 1
  public function getReferencedEntities(EntityReferenceRevisionsFieldItemList $items) {
74 1
    $entities = [];
75 1
    foreach ($items as $item) {
76 1
      $value = $item->getValue();
77 1
      if (!empty($value['entity']) && $value['entity'] instanceof ParagraphInterface) {
78 1
        $entities[] = $item->entity;
79
      }
80 1
      elseif ($item->target_revision_id !== NULL) {
81 1
        if (!empty($this->revisionCache[$item->target_revision_id])) {
82
          $entities[] = $this->revisionCache[$item->target_revision_id];
83
        }
84
        else {
85 1
          $entity = $this->storage->loadRevision($item->target_revision_id);
86 1
          $entity = TypeUtility::ensureParagraph($entity);
87
          $this->revisionCache[$item->target_revision_id] = $entity;
88 1
          $entities[] = $entity;
89
        }
90
      }
91
    }
92 1
    return $entities;
93
  }
94
95
  /**
96
   * {@inheritdoc}
97
   */
98 4
  public function wrapItems(EntityReferenceRevisionsFieldItemList $items) {
99 4
    $field_definition = $items->getFieldDefinition();
100 4
    if (!$this->isParagraphsEditorField($field_definition)) {
101
      throw new \Exception('Attempt to wrap non-paragraphs editor field.');
102
    }
103
104
    // Build a list of refrenced entities and filter out the text entities.
105
    $settings = $field_definition->getThirdPartySettings('paragraphs_editor');
106
    $markup = '';
107
    $entities = [];
108
    $text_entity = NULL;
109
110
    foreach ($this->getReferencedEntities($items) as $entity) {
111
      if ($entity->bundle() == $settings['text_bundle']) {
112
        $markup .= $entity->{$settings['text_field']}->value;
113
        if (!$text_entity) {
114
          $text_entity = $entity;
115
        }
116
      }
117
      else {
118
        $entities[$entity->uuid()] = $entity;
119
      }
120
    }
121
122
    // If there is no text entity we need to create one.
123
    if (!$text_entity) {
124
      $text_entity = TypeUtility::ensureParagraph($this->storage->create([
125
        'type' => $settings['text_bundle'],
126
      ]));
127
    }
128
129
    // Reset the text entity markup in case we merged multiple text entities.
130
    $text_entity->{$settings['text_field']}->value = $markup;
131
    if (empty($text_entity->{$settings['text_field']}->format) && !empty($settings['filter_format'])) {
132
      $text_entity->{$settings['text_field']}->format = $settings['filter_format'];
133
    }
134
135
    return new FieldValueWrapper($field_definition, $text_entity, $entities);
0 ignored issues
show
Compatibility introduced by
$field_definition of type object<Drupal\Core\Field...eldDefinitionInterface> is not a sub-type of object<Drupal\Core\Field\FieldConfigInterface>. It seems like you assume a child interface of the interface Drupal\Core\Field\FieldDefinitionInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
136
  }
137
138
  /**
139
   * {@inheritdoc}
140
   */
141 2
  public function prepareEntityForSave($entity, $new_revision, $langcode) {
142 2
    $entity->setNewRevision($new_revision);
143
144 2
    if (isset($langcode) && $entity->get('langcode') != $langcode) {
145 1
      if ($entity->hasTranslation($langcode)) {
146
        $entity = $entity->getTranslation($langcode);
147
      }
148
      else {
149 1
        $entity->set('langcode', $langcode);
150
      }
151
    }
152
153 2
    $entity->setNeedsSave(TRUE);
154
155 2
    return $entity;
156
  }
157
158
  /**
159
   * {@inheritdoc}
160
   */
161 1
  public function setItems(EntityReferenceRevisionsFieldItemList $items, array $entities, $new_revision = FALSE, $langcode = NULL) {
162 1
    $values = [];
163 1
    $delta = 0;
164 1
    foreach ($entities as $entity) {
165 1
      $entity = $this->prepareEntityForSave($entity, $new_revision, $langcode);
166 1
      $values[$delta]['entity'] = $entity;
167 1
      $values[$delta]['target_id'] = $entity->id();
168 1
      $values[$delta]['target_revision_id'] = $entity->getRevisionId();
169 1
      $delta++;
170
    }
171
172 1
    $items->setValue($values);
173 1
    $items->filterEmptyItems();
174 1
    return $items;
175
  }
176
177
  /**
178
   * {@inheritdoc}
179
   */
180 1
  public function getTextBundles(array $allowed_bundles = []) {
181
182 1
    if (!empty($allowed_bundles)) {
183
      $results = $this->bundleStorage->getQuery()->execute();
184
      if (is_array($results)) {
185
        foreach ($results as $name) {
186
          $allowed_bundles[$name] = [
187
            'label' => $this->bundleStorage->load($name)->label(),
188
          ];
189
        }
190
      }
191
    }
192
193 1
    $bundles = [];
194 1
    foreach ($allowed_bundles as $name => $type) {
195
      $text_fields = $this->getTextFields($name);
196
      if (count($text_fields) == 1) {
197
        $bundles[$name] = [
198
          'label' => $type['label'],
199
          'text_field' => reset($text_fields),
200
        ];
201
      }
202
    }
203 1
    return $bundles;
204
  }
205
206
  /**
207
   * {@inheritdoc}
208
   */
209 3
  public function isParagraphsField(FieldDefinitionInterface $field_definition) {
210 3
    $field_definition = TypeUtility::ensureFieldConfig($field_definition);
0 ignored issues
show
Documentation introduced by
$field_definition is of type object<Drupal\Core\Field...eldDefinitionInterface>, but the function expects a null|object<Drupal\parag...eldDefinitionInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
211
212
    if ($field_definition->getType() != 'entity_reference_revisions') {
213
      return FALSE;
214
    }
215
216
    $target_type = $field_definition->getFieldStorageDefinition()->getSetting('target_type');
217
    return $target_type == 'paragraph';
218
  }
219
220
  /**
221
   * {@inheritdoc}
222
   */
223 7
  public function isParagraphsEditorField(FieldDefinitionInterface $field_definition) {
224 7
    $field_definition = TypeUtility::ensureFieldConfig($field_definition);
0 ignored issues
show
Documentation introduced by
$field_definition is of type object<Drupal\Core\Field...eldDefinitionInterface>, but the function expects a null|object<Drupal\parag...eldDefinitionInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
225
226
    if (!$this->isParagraphsField($field_definition)) {
0 ignored issues
show
Bug introduced by
It seems like $field_definition defined by \Drupal\paragraphs_edito...nfig($field_definition) on line 224 can be null; however, Drupal\paragraphs_editor...er::isParagraphsField() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
227
      return FALSE;
228
    }
229
230
    // We only every allow this widget to be applied to fields that have
231
    // unlimited cardinality. Otherwise we'd have to deal with keeping track of
232
    // how many paragraphs are in the Editor instance.
233
    $cardinality = $field_definition->getFieldStorageDefinition()->getCardinality();
234
    if ($cardinality != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
235
      return FALSE;
236
    }
237
238
    // Make sure it is a pragraphs editor enabled field.
239
    $settings = $field_definition->getThirdPartySettings('paragraphs_editor');
240
    return !empty($settings['enabled']);
241
  }
242
243
  /**
244
   * {@inheritdoc}
245
   */
246
  public function getTextFields($bundle_name) {
247
    $matches = [];
248
    $field_definitions = $this->entityFieldManager->getFieldDefinitions('paragraph', $bundle_name);
249
    foreach ($field_definitions as $field_definition) {
250
      if ($this->isTextField($field_definition)) {
251
        $matches[] = $field_definition->getName();
252
      }
253
    }
254
    return $matches;
255
  }
256
257
  /**
258
   * {@inheritdoc}
259
   */
260 4
  public function getElement($element_name) {
261 4
    return isset($this->elements[$element_name]) ? $this->elements[$element_name] : NULL;
262
  }
263
264
  /**
265
   * {@inheritdoc}
266
   */
267 1
  public function getAttributeName($element_name, $attribute_name) {
268 1
    $element = $this->getElement($element_name);
269 1
    if (!empty($element['attributes'])) {
270 1
      $map = array_flip($element['attributes']);
271 1
      $key = !empty($map[$attribute_name]) ? $map[$attribute_name] : NULL;
272 1
      return $key;
273
    }
274
    else {
275 1
      return NULL;
276
    }
277
  }
278
279
  /**
280
   * {@inheritdoc}
281
   */
282 1
  public function getSelector($element_name) {
283 1
    $element = $this->getElement($element_name);
284 1
    $selector = !empty($element['tag']) ? $element['tag'] : '';
285 1
    if (!empty($element['attributes']['class'])) {
286 1
      $classes = explode(' ', $element['attributes']['class']);
287 1
      $selector .= '.' . implode('.', $classes);
288
    }
289 1
    return $selector;
290
  }
291
292
  /**
293
   * Helper function to check if a field is a text field.
294
   *
295
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_config
296
   *   The field to check.
297
   *
298
   * @return bool
299
   *   TRUE if it's a paragraphs editor approved text field, FALSE otherwise.
300
   */
301
  protected function isTextField(FieldDefinitionInterface $field_config) {
302
    return $field_config->getType() == 'text_long';
303
  }
304
305
}
306