Passed
Branch develop (7ed98a)
by Richard
04:39
created

Block::preprocess()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
4
namespace Riclep\Storyblok;
5
6
use Exception;
7
use Illuminate\Support\Str;
8
use Riclep\Storyblok\Fields\Asset;
9
use Riclep\Storyblok\Fields\MultiAsset;
10
use Riclep\Storyblok\Fields\RichText;
11
use Riclep\Storyblok\Fields\Table;
12
use Storyblok\Client;
13
14
class Block
15
{
16
	public $_autoResolveRelations = false;
17
	public $_componentPath = [];
18
	private $_fields;
19
	private $_meta;
20
	private $_parent;
21
22
	public function __construct($content, $parent)
23
	{
24
		$this->_parent = $parent;
25
26
		$this->preprocess($content);
27
		$this->_componentPath = array_merge($parent->_componentPath, [Str::lower($this->meta()['component'])]);
28
29
		$this->processFields();
30
	}
31
32
	public function content() {
33
		return $this->_fields;
34
	}
35
36
	public function meta() {
37
		return $this->_meta;
38
	}
39
40
	public function addMeta($fields) {
41
		$this->_meta = array_merge($this->_meta, $fields);
42
	}
43
44
	public function has($key) {
45
		return $this->_fields->has($key);
46
	}
47
48
	public function parent() {
49
		return $this->_parent;
50
	}
51
52
	public function page() {
53
		if ($this->parent() instanceof Page) {
54
			return $this->parent();
55
		}
56
57
		return $this->parent()->page();
58
	}
59
60
	/**
61
	 * Returns a component X generations previous
62
	 *
63
	 * @param $generation
64
	 * @return mixed
65
	 */
66
	public function ancestorComponentName($generation)
67
	{
68
		return $this->_componentPath[count($this->_componentPath) - ($generation + 1)];
69
	}
70
71
	/**
72
	 * Checks if the current component is a child of another
73
	 *
74
	 * @param $parent
75
	 * @return bool
76
	 */
77
	public function isChildOf($parent)
78
	{
79
		return $this->_componentPath[count($this->_componentPath) - 2] === $parent;
80
	}
81
82
	/**
83
	 * Checks if the component is an ancestor of another
84
	 *
85
	 * @param $parent
86
	 * @return bool
87
	 */
88
	public function isAncestorOf($parent)
89
	{
90
		return in_array($parent, $this->parent()->_componentPath);
91
	}
92
93
	public function __get($key) {
94
		$accessor = 'get' . Str::studly($key) . 'Attribute';
95
96
		if (method_exists($this, $accessor)) {
97
			return $this->$accessor();
98
		}
99
100
		try {
101
			if ($this->has($key)) {
102
				return $this->_fields[$key];
103
			}
104
105
			return false;
106
		} catch (Exception $e) {
107
			return 'Caught exception: ' .  $e->getMessage();
108
		}
109
	}
110
111
	private function processFields() {
112
		$this->_fields->transform(function ($field, $key) {
113
			return $this->getFieldType($field, $key);
114
		});
115
	}
116
117
	private function getFieldType($field, $key) {
118
		// does the block assign any $casts?
119
		if (property_exists($this, 'casts') && array_key_exists($key, $this->casts)) {
0 ignored issues
show
Bug Best Practice introduced by
The property casts does not exist on Riclep\Storyblok\Block. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug introduced by
It seems like $this->casts can also be of type false and string; however, parameter $search of array_key_exists() 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

119
		if (property_exists($this, 'casts') && array_key_exists($key, /** @scrutinizer ignore-type */ $this->casts)) {
Loading history...
120
			return new $this->casts[$key]($field);
121
		}
122
123
		// auto-match Field classes
124
		if ($class = $this->getFieldClass($key)) {
125
			return new $class($field);
126
		}
127
128
		// complex fields
129
		if (is_array($field) && !empty($field)) {
130
			return $this->arrayFieldTypes($field);
131
		}
132
133
		// strings or anything else - do nothing
134
		return $field;
135
	}
136
137
	// TODO process old asset fields
138
	// TODO option to convert all text fields to a class - single or multiline?
139
	private function arrayFieldTypes($field) {
140
		if (array_key_exists('linktype', $field)) {
141
			$class = 'Riclep\Storyblok\Fields\\' . Str::studly($field['linktype']) . 'Link';
142
143
			return new $class($field);
144
		}
145
146
		if (array_key_exists('type', $field) && $field['type'] === 'doc') {
147
			return new RichText($field);
148
		}
149
150
		if (array_key_exists('fieldtype', $field) && $field['fieldtype'] === 'asset') {
151
			return new Asset($field);
152
		}
153
154
		if (array_key_exists('fieldtype', $field) && $field['fieldtype'] === 'table') {
155
			return new Table($field);
156
		}
157
158
		if (Str::isUuid($field[0])) {
159
			if ($this->_autoResolveRelations) {
160
				return collect($field)->transform(function ($relation) {
161
					$request = new RequestStory();
162
					$response = $request->get($relation);
163
164
					$class = $this->getBlockClass($response['content']);
165
					$relationClass = new $class($response['content'], $this);
166
167
					$relationClass->addMeta([
168
						'published_at' => $response['published_at'],
169
						'full_slug' => $response['full_slug'],
170
					]);
171
172
					return $relationClass;
173
				});
174
			}
175
		}
176
177
		// had child items
178
		if (is_array($field[0])) {
179
			// resolved relationships - entire story is returned, we just want the content and a few meta items
180
			if (array_key_exists('content', $field[0])) {
181
				return collect($field)->transform(function ($relation) {
182
					$class = $this->getBlockClass($relation['content']);
183
					$relationClass = new $class($relation['content'], $this);
184
185
					$relationClass->addMeta([
186
						'published_at' => $relation['published_at'],
187
						'full_slug' => $relation['full_slug'],
188
					]);
189
190
					return $relationClass;
191
				});
192
			}
193
194
			// this field holds blocks!
195
			if (array_key_exists('component', $field[0])) {
196
				return collect($field)->transform(function ($block) {
197
					$class = $this->getBlockClass($block);
198
199
					return new $class($block, $this);
200
				});
201
			}
202
203
			// multi assets
204
			if (array_key_exists('filename', $field[0])) {
205
				return new MultiAsset($field);
206
			}
207
		}
208
209
		// just return the array
210
		return $field;
211
	}
212
213
	private function preprocess($content) {
214
		$this->_fields = collect(array_diff_key($content, array_flip(['_editable', '_uid', 'component'])));
215
216
		// remove non-content keys
217
		$this->_meta = array_intersect_key($content, array_flip(['_editable', '_uid', 'component']));
218
	}
219
220
	private function getBlockClass($content) {
221
		$component = $content['component'];
222
223
		if (class_exists(config('storyblok.component_class_namespace') . 'Blocks\\' . Str::studly($component))) {
224
			return config('storyblok.component_class_namespace') . 'Blocks\\' . Str::studly($component);
225
		}
226
227
		return config('storyblok.component_class_namespace') . 'Block';
228
	}
229
230
	private function getFieldClass($key) {
231
		if (class_exists(config('storyblok.component_class_namespace') . 'Fields\\' . Str::studly($key))) {
232
			return config('storyblok.component_class_namespace') . 'Fields\\' . Str::studly($key);
233
		}
234
235
		return false;
236
	}
237
}