Completed
Push — feature/linting ( 5eac33...52e8dc )
by Christopher
02:13
created

FieldValueManager::getAttributeName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
143
    $entity->setNewRevision($new_revision);
144
145
    if (isset($langcode) && $entity->get('langcode') != $langcode) {
146
      if ($entity->hasTranslation($langcode)) {
147
        $entity = $entity->getTranslation($langcode);
148
      }
149
      else {
150
        $entity->set('langcode', $langcode);
151
      }
152
    }
153
154
    $entity->setNeedsSave(TRUE);
155
156
    return $entity;
157
  }
158
159
  /**
160
   * {@inheritdoc}
161
   */
162
  public function setItems(EntityReferenceRevisionsFieldItemList $items, array $entities, $new_revision = FALSE, $langcode = NULL) {
163
    $values = [];
164
    $delta = 0;
165
    foreach ($entities as $entity) {
166
      $entity = $this->prepareEntityForSave($entity, $new_revision, $langcode);
167
      $values[$delta]['entity'] = $entity;
168
      $values[$delta]['target_id'] = $entity->id();
169
      $values[$delta]['target_revision_id'] = $entity->getRevisionId();
170
      $delta++;
171
    }
172
173
    $items->setValue($values);
174
    $items->filterEmptyItems();
175
    return $items;
176
  }
177
178
  /**
179
   * {@inheritdoc}
180
   */
181
  public function getTextBundles(array $allowed_bundles = []) {
182
183
    if (!empty($allowed_bundles)) {
184
      $results = $this->bundleStorage->getQuery()->execute();
185
      if (is_array($results)) {
186
        foreach ($results as $name) {
187
          $allowed_bundles[$name] = [
188
            'label' => $this->bundleStorage->load($name)->label(),
189
          ];
190
        }
191
      }
192
    }
193
194
    $bundles = [];
195
    foreach ($allowed_bundles as $name => $type) {
196
      $text_fields = $this->getTextFields($name);
197
      if (count($text_fields) == 1) {
198
        $bundles[$name] = [
199
          'label' => $type['label'],
200
          'text_field' => reset($text_fields),
201
        ];
202
      }
203
    }
204
    return $bundles;
205
  }
206
207
  /**
208
   * {@inheritdoc}
209
   */
210
  public function isParagraphsField(FieldDefinitionInterface $field_definition) {
211
    $field_definition = $this->ensureFieldConfig($field_definition);
212
213
    if ($field_definition->getType() != 'entity_reference_revisions') {
214
      return FALSE;
215
    }
216
217
    if ($field_definition->getFieldStorageDefinition()->getSetting('target_type') != 'paragraph') {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return !($field_definiti...type') != 'paragraph');.
Loading history...
218
      return FALSE;
219
    }
220
221
    return TRUE;
222
  }
223
224
  /**
225
   * {@inheritdoc}
226
   */
227
  public function isParagraphsEditorField(FieldDefinitionInterface $field_definition) {
228
    $field_definition = $this->ensureFieldConfig($field_definition);
229
230
    if (!$this->isParagraphsField($field_definition)) {
231
      return FALSE;
232
    }
233
234
    // We only every allow this widget to be applied to fields that have
235
    // unlimited cardinality. Otherwise we'd have to deal with keeping track of
236
    // how many paragraphs are in the Editor instance.
237
    $cardinality = $field_definition->getFieldStorageDefinition()->getCardinality();
238
    if ($cardinality != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
239
      return FALSE;
240
    }
241
242
    // Make sure it is a pragraphs editor enabled field.
243
    $settings = $field_definition->getThirdPartySettings('paragraphs_editor');
244
    if (empty($settings['enabled'])) {
245
      return FALSE;
246
    }
247
248
    // Make sure the bundle for storing text is valid.
249
    $text_bundle = $field_definition->getThirdPartySetting('paragraphs_editor', 'text_bundle');
0 ignored issues
show
Unused Code introduced by
$text_bundle is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
250
    $text_field = $field_definition->getThirdPartySetting('paragraphs_editor', 'text_field');
0 ignored issues
show
Unused Code introduced by
$text_field is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
251
252
    return TRUE;
253
  }
254
255
  /**
256
   * {@inheritdoc}
257
   */
258
  public function getTextFields($bundle_name) {
259
    $matches = [];
260
    $field_definitions = $this->entityFieldManager->getFieldDefinitions('paragraph', $bundle_name);
261
    foreach ($field_definitions as $field_definition) {
262
      if ($this->isTextField($field_definition)) {
263
        $matches[] = $field_definition->getName();
264
      }
265
    }
266
    return $matches;
267
  }
268
269
  /**
270
   * {@inheritdoc}
271
   */
272
  public function getElement($element_name) {
273
    return isset($this->elements[$element_name]) ? $this->elements[$element_name] : NULL;
274
  }
275
276
  /**
277
   * {@inheritdoc}
278
   */
279
  public function getAttributeName($element_name, $attribute_name) {
280
    $element = $this->getElement($element_name);
281
    if (!empty($element['attributes'])) {
282
      $map = array_flip($element['attributes']);
283
      $key = !empty($map[$attribute_name]) ? $map[$attribute_name] : NULL;
284
      return $key;
285
    }
286
    else {
287
      return NULL;
288
    }
289
  }
290
291
  /**
292
   * {@inheritdoc}
293
   */
294
  public function getSelector($element_name) {
295
    $element = $this->getElement($element_name);
296
    $selector = !empty($element['tag']) ? $element['tag'] : '';
297
    if (!empty($element['attributes']['class'])) {
298
      $classes = explode(' ', $element['attributes']['class']);
299
      $selector .= '.' . implode('.', $classes);
300
    }
301
    return $selector;
302
  }
303
304
  /**
305
   * Helper function to check if a field is a text field.
306
   *
307
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_config
308
   *   The field to check.
309
   *
310
   * @return bool
311
   *   TRUE if it's a paragraphs editor approved text field, FALSE otherwise.
312
   */
313
  protected function isTextField(FieldDefinitionInterface $field_config) {
314
    return $field_config->getType() == 'text_long';
315
  }
316
317
  /**
318
   * Enforces that an entity is a paragraph entity.
319
   *
320
   * @return \Drupal\paragraphs\ParagraphInterface
321
   *   The entity, or NULL if it was not a paragraph.
322
   */
323
  protected function ensureParagraph(EntityInterface $entity) {
324
    if (!$entity instanceof ParagraphInterface) {
325
      throw new \Exception('Not a paragraph.');
326
    }
327
    return $entity;
328
  }
329
330
  /**
331
   * Enforces that an entity is a field config entity.
332
   *
333
   * @return \Drupal\Core\Field\FieldConfigInterface
334
   *   The entity, or NULL if it was not a field config instance.
335
   */
336
  protected function ensureFieldConfig(FieldDefinitionInterface $entity) {
337
    if (!$entity instanceof FieldConfigInterface) {
338
      throw new \Exception('Not a field config.');
339
    }
340
    return $entity;
341
  }
342
343
}
344