Completed
Branch feature/linting (1538c5)
by Christopher
02:15
created

FieldValueManager::ensureFieldConfig()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 6
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
        $entity = $item->entity;
80
      }
81
      elseif ($item->target_revision_id !== NULL) {
82
        if (!empty($this->revisionCache[$item->target_revision_id])) {
83
          $entity = $this->revisionCache[$item->target_revision_id];
84
        }
85
        else {
86
          $entity = $this->storage->loadRevision($item->target_revision_id);
87
          $this->revisionCache[$item->target_revision_id] = $entity;
88
        }
89
      }
90
      $entities[] = $entity;
0 ignored issues
show
Bug introduced by
The variable $entity does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
91
    }
92
    return $entities;
93
  }
94
95
  /**
96
   * {@inheritdoc}
97
   */
98
  public function wrapItems(EntityReferenceRevisionsFieldItemList $items) {
99
    $field_definition = $items->getFieldDefinition();
100
    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 = $this->ensureParagraphEntity($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
  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...
142
    $entity->setNewRevision($new_revision);
143
144
    if (isset($langcode) && $entity->get('langcode') != $langcode) {
145
      if ($entity->hasTranslation($langcode)) {
146
        $entity = $entity->getTranslation($langcode);
147
      }
148
      else {
149
        $entity->set('langcode', $langcode);
150
      }
151
    }
152
153
    $entity->setNeedsSave(TRUE);
154
155
    return $entity;
156
  }
157
158
  /**
159
   * {@inheritdoc}
160
   */
161
  public function setItems(EntityReferenceRevisionsFieldItemList $items, array $entities, $new_revision = FALSE, $langcode = NULL) {
162
    $values = [];
163
    $delta = 0;
164
    foreach ($entities as $entity) {
165
      $entity = $this->prepareEntityForSave($entity, $new_revision, $langcode);
166
      $values[$delta]['entity'] = $entity;
167
      $values[$delta]['target_id'] = $entity->id();
168
      $values[$delta]['target_revision_id'] = $entity->getRevisionId();
169
      $delta++;
170
    }
171
172
    $items->setValue($values);
173
    $items->filterEmptyItems();
174
    return $items;
175
  }
176
177
  /**
178
   * {@inheritdoc}
179
   */
180
  public function getTextBundles(array $allowed_bundles = []) {
181
182
    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
    $bundles = [];
194
    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
    return $bundles;
204
  }
205
206
  /**
207
   * {@inheritdoc}
208
   */
209
  public function isParagraphsField(FieldDefinitionInterface $field_definition) {
210
    $field_definition = $this->ensureFieldConfig($field_definition);
211
212
    if ($field_definition->getType() != 'entity_reference_revisions') {
213
      return FALSE;
214
    }
215
216
    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...
217
      return FALSE;
218
    }
219
220
    return TRUE;
221
  }
222
223
  /**
224
   * {@inheritdoc}
225
   */
226
  public function isParagraphsEditorField(FieldDefinitionInterface $field_definition) {
227
    if (!$this->isParagraphsField($field_definition)) {
228
      return FALSE;
229
    }
230
231
    // We only every allow this widget to be applied to fields that have
232
    // unlimited cardinality. Otherwise we'd have to deal with keeping track of
233
    // how many paragraphs are in the Editor instance.
234
    $cardinality = $field_definition->getFieldStorageDefinition()->getCardinality();
235
    if ($cardinality != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
236
      return FALSE;
237
    }
238
239
    // Make sure it is a pragraphs editor enabled field.
240
    $settings = $field_definition->getThirdPartySettings('paragraphs_editor');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Drupal\Core\Field\FieldDefinitionInterface as the method getThirdPartySettings() does only exist in the following implementations of said interface: Drupal\Core\Field\Entity\BaseFieldOverride, Drupal\Core\Field\FieldConfigBase, Drupal\field\Entity\FieldConfig.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
241
    if (empty($settings['enabled'])) {
242
      return FALSE;
243
    }
244
245
    // Make sure the bundle for storing text is valid.
246
    $text_bundle = $field_definition->getThirdPartySetting('paragraphs_editor', 'text_bundle');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Drupal\Core\Field\FieldDefinitionInterface as the method getThirdPartySetting() does only exist in the following implementations of said interface: Drupal\Core\Field\Entity\BaseFieldOverride, Drupal\Core\Field\FieldConfigBase, Drupal\field\Entity\FieldConfig.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
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...
247
    $text_field = $field_definition->getThirdPartySetting('paragraphs_editor', 'text_field');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Drupal\Core\Field\FieldDefinitionInterface as the method getThirdPartySetting() does only exist in the following implementations of said interface: Drupal\Core\Field\Entity\BaseFieldOverride, Drupal\Core\Field\FieldConfigBase, Drupal\field\Entity\FieldConfig.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

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