ResolveConfig   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 264
Duplicated Lines 0 %

Importance

Changes 6
Bugs 1 Features 2
Metric Value
wmc 31
eloc 97
c 6
b 1
f 2
dl 0
loc 264
rs 9.92

11 Methods

Rating   Name   Duplication   Size   Complexity  
A forFieldGroup() 0 12 1
A forField() 0 3 1
B forEntity() 0 47 7
A forLayout() 0 3 1
B forNestedEntities() 0 29 7
A forConditionalLogic() 0 21 4
A mapLocation() 0 3 1
A validateConfig() 0 11 3
A pushSingleOrMultiple() 0 10 3
A isAssoc() 0 6 2
A forLocation() 0 3 1
1
<?php
2
3
namespace ACFComposer;
4
5
use Exception;
6
7
class ResolveConfig
8
{
9
    /**
10
     * Validates and resolves a configuration for a local field group.
11
     *
12
     * @param array $config Configuration array for the local field group.
13
     *
14
     * @return array Resolved field group configuration.
15
     */
16
    public static function forFieldGroup($config)
17
    {
18
        $output = self::validateConfig($config, ['name', 'title', 'fields', 'location']);
19
        $keySuffix = $output['name'];
20
        $output['key'] = "group_{$keySuffix}";
21
        $output['fields'] = array_reduce($config['fields'], function ($carry, $fieldConfig) use ($keySuffix) {
22
            $fields = self::forField($fieldConfig, [$keySuffix]);
23
            self::pushSingleOrMultiple($carry, $fields);
24
            return $carry;
25
        }, []);
26
        $output['location'] = array_map([self::class, 'mapLocation'], $output['location']);
27
        return $output;
28
    }
29
30
    /**
31
     * Validates a location the configuration for a field group location.
32
     *
33
     * @param array $config Configuration array for a location of a field group.
34
     *
35
     * @return array Valid config.
36
     */
37
    public static function forLocation($config)
38
    {
39
        return self::validateConfig($config, ['param', 'operator', 'value']);
40
    }
41
42
    /**
43
     * Validates and resolves a field configuration.
44
     *
45
     * @param array|string $config Configuration array for a any kind of field.
46
     * @param array $parentKeys Previously used keys of all parent fields.
47
     *
48
     * @return array Resolved config for a field.
49
     */
50
    public static function forField($config, $parentKeys = [])
51
    {
52
        return self::forEntity($config, ['name', 'label', 'type'], $parentKeys);
53
    }
54
55
    /**
56
     * Validates and resolves a layout configuration of a flexible content field.
57
     *
58
     * @param array|string $config Configuration array for the local field group.
59
     * @param array $parentKeys Previously used keys of all parent fields.
60
     *
61
     * @return array Resolved config for a layout of a flexible content field.
62
     */
63
    public static function forLayout($config, $parentKeys = [])
64
    {
65
        return self::forEntity($config, ['name', 'label'], $parentKeys);
66
    }
67
68
69
    /**
70
     * Validates and resolves configuration for a field, subfield, or layout. Applies prefix through filter arguments.
71
     *
72
     * @param array|string $config Configuration array for the nested entity.
73
     * @param array $requiredAttributes Required attributes.
74
     * @param array $parentKeys Previously used keys of all parent fields.
75
     * @param string $prefix Optional prefix for named field based on filter arguments.
76
     *
77
     * @return array Resolved config.
78
     */
79
    protected static function forEntity($config, $requiredAttributes, $parentKeys = [], $prefix = null)
80
    {
81
        if (is_string($config)) {
82
            $filterName = $config;
83
            $filterParts = explode('#', $filterName);
84
            if (isset($filterParts[1])) {
85
                $prefix = $filterParts[1];
86
                $config = apply_filters($filterParts[0], null, $prefix);
87
                if (!self::isAssoc($config)) {
0 ignored issues
show
Bug introduced by
It seems like $config can also be of type null; however, parameter $arr of ACFComposer\ResolveConfig::isAssoc() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

87
                if (!self::isAssoc(/** @scrutinizer ignore-type */ $config)) {
Loading history...
88
                    $config = array_map(function ($singleConfig) use ($prefix) {
89
                        $singleConfig['name'] = $prefix . '_' . $singleConfig['name'];
90
                        return $singleConfig;
91
                    }, $config);
0 ignored issues
show
Bug introduced by
It seems like $config can also be of type null; however, parameter $array of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

91
                    }, /** @scrutinizer ignore-type */ $config);
Loading history...
92
                } else {
93
                    $config['name'] = $prefix . '_' . $config['name'];
94
                }
95
            } else {
96
                $config = apply_filters($filterName, null);
97
            }
98
99
100
            if (is_null($config)) {
101
                trigger_error("ACFComposer: Filter {$filterName} does not exist!", E_USER_WARNING);
102
                return [];
103
            }
104
        }
105
        if (!self::isAssoc($config)) {
0 ignored issues
show
Bug introduced by
It seems like $config can also be of type null and string; however, parameter $arr of ACFComposer\ResolveConfig::isAssoc() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

105
        if (!self::isAssoc(/** @scrutinizer ignore-type */ $config)) {
Loading history...
106
            return array_map(function ($singleConfig) use ($requiredAttributes, $parentKeys, $prefix) {
107
                return self::forEntity($singleConfig, $requiredAttributes, $parentKeys, $prefix);
108
            }, $config);
0 ignored issues
show
Bug introduced by
It seems like $config can also be of type null and string; however, parameter $array of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

108
            }, /** @scrutinizer ignore-type */ $config);
Loading history...
109
        }
110
111
        $output = self::validateConfig($config, $requiredAttributes);
0 ignored issues
show
Bug introduced by
It seems like $config can also be of type string; however, parameter $config of ACFComposer\ResolveConfig::validateConfig() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

111
        $output = self::validateConfig(/** @scrutinizer ignore-type */ $config, $requiredAttributes);
Loading history...
112
113
        $parentKeysIncludingPrefix = isset($prefix) ? array_merge($parentKeys, [$prefix]) : $parentKeys;
114
        $output = self::forConditionalLogic($output, $parentKeysIncludingPrefix);
115
116
        array_push($parentKeys, $output['name']);
117
118
        $keySuffix = implode('_', $parentKeys);
119
        $output['key'] = "field_{$keySuffix}";
120
121
        $output = apply_filters('ACFComposer/resolveEntity', $output);
122
        $output = apply_filters("ACFComposer/resolveEntity?name={$output['name']}", $output);
123
        $output = apply_filters("ACFComposer/resolveEntity?key={$output['key']}", $output);
124
        $output = self::forNestedEntities($output, $parentKeys);
125
        return $output;
126
    }
127
128
    /**
129
     * Validates and resolves configuration for subfields and layouts.
130
     *
131
     * @param array $config Configuration array for the nested entity.
132
     * @param array $parentKeys Previously used keys of all parent fields.
133
     *
134
     * @return array Resolved config.
135
     */
136
    protected static function forNestedEntities($config, $parentKeys)
137
    {
138
        if (array_key_exists('sub_fields', $config)) {
139
            $config['sub_fields'] = array_reduce($config['sub_fields'], function ($output, $subField) use ($parentKeys) {
140
                $fields = self::forField($subField, $parentKeys);
141
                if (!self::isAssoc($fields)) {
142
                    foreach ($fields as $field) {
143
                        array_push($output, $field);
144
                    }
145
                } else {
146
                    array_push($output, $fields);
147
                }
148
                return $output;
149
            }, []);
150
        }
151
        if (array_key_exists('layouts', $config)) {
152
            $config['layouts'] = array_reduce($config['layouts'], function ($output, $layout) use ($parentKeys) {
153
                $subLayouts = self::forLayout($layout, $parentKeys);
154
                if (!self::isAssoc($subLayouts)) {
155
                    foreach ($subLayouts as $subLayout) {
156
                        array_push($output, $subLayout);
157
                    }
158
                } else {
159
                    array_push($output, $subLayouts);
160
                }
161
                return $output;
162
            }, []);
163
        }
164
        return $config;
165
    }
166
167
    /**
168
     * Validates a configuration array based on given required attributes.
169
     *
170
     * Usually the field key has to be provided for conditional logic to work. Since all keys are generated automatically by this plugin, you can instead provide a 'relative path' to a field by it's name.
171
     *
172
     * @param array $config Configuration array.
173
     * @param array $requiredAttributes Required Attributes.
174
     *
175
     * @throws Exception if a required attribute is not present.
176
     * @throws Exception if the `key` attribute is not present.
177
     *
178
     * @return array Given $config.
179
     */
180
    protected static function validateConfig($config, $requiredAttributes = [])
181
    {
182
        array_walk($requiredAttributes, function ($key) use ($config) {
183
            if (!array_key_exists($key, $config)) {
184
                throw new Exception("Field config needs to contain a \'{$key}\' property.");
185
            }
186
        });
187
        if (array_key_exists('key', $config)) {
188
            throw new Exception('Field config must not contain a \'key\' property.');
189
        }
190
        return $config;
191
    }
192
193
    /**
194
     * Maps location configurations to their resolved config arrays.
195
     *
196
     * @param array $locationArray All locations for a field group.
197
     *
198
     * @return array Resolved locations array.
199
     */
200
    protected static function mapLocation($locationArray)
201
    {
202
        return array_map([self::class, 'forLocation'], $locationArray);
203
    }
204
205
    /**
206
     * Resolves a field's conditional logic attribute.
207
     *
208
     * Usually the field key has to be provided for conditional logic to work. Since all keys are generated automatically by this plugin, you can instead provide a 'relative path' to a field by it's name.
209
     *
210
     * @param array $config Configuration array for the conditional logic attribute.
211
     * @param array $parentKeys Previously used keys of all parent fields.
212
     *
213
     * @return array Resolved conditional logic attribute.
214
     */
215
    protected static function forConditionalLogic($config, $parentKeys)
216
    {
217
        if (array_key_exists('conditional_logic', $config)) {
218
            $config['conditional_logic'] = array_map(function ($conditionGroup) use ($parentKeys) {
219
                return array_map(function ($condition) use ($parentKeys) {
220
                    if (array_key_exists('fieldPath', $condition)) {
221
                        $conditionalField = $condition['fieldPath'];
222
                        while (substr($conditionalField, 0, 3) === '../') {
223
                            $conditionalField = substr($conditionalField, 3);
224
                            array_pop($parentKeys);
225
                        }
226
                        array_push($parentKeys, $conditionalField);
227
                        $keySuffix = implode('_', $parentKeys);
228
                        $condition['field'] = "field_{$keySuffix}";
229
                        unset($condition['fieldPath']);
230
                    }
231
                    return $condition;
232
                }, $conditionGroup);
233
            }, $config['conditional_logic']);
234
        }
235
        return $config;
236
    }
237
238
    /**
239
     * Checks whether or not a given array is associative.
240
     *
241
     * @param array $arr Array to check.
242
     *
243
     * @return boolean
244
     */
245
    protected static function isAssoc(array $arr)
246
    {
247
        if (array() === $arr) {
248
            return false;
249
        }
250
        return array_keys($arr) !== range(0, count($arr) - 1);
251
    }
252
253
    /**
254
     * Adds a single or multiple elements to an array.
255
     *
256
     * @param array &$arr Array to add to.
257
     * @param array $fields Single or multiple associative arrays to add to $arr.
258
     *
259
     * @return boolean
260
     */
261
    protected static function pushSingleOrMultiple(array &$carry, array $fields)
262
    {
263
        if (!self::isAssoc($fields)) {
264
            foreach ($fields as $field) {
265
                self::pushSingleOrMultiple($carry, $field);
266
            }
267
        } else {
268
            array_push($carry, $fields);
269
        }
270
        return $carry;
271
    }
272
}
273