Passed
Push — master ( 1f93d4...067214 )
by Richard
03:37 queued 17s
created

Block::getCasts()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 2
rs 10
1
<?php
2
3
4
namespace Riclep\Storyblok;
5
6
use Illuminate\Support\Collection;
7
use Illuminate\Support\Str;
8
use Illuminate\View\View;
9
use Riclep\Storyblok\Exceptions\UnableToRenderException;
10
use Riclep\Storyblok\Fields\Asset;
11
use Riclep\Storyblok\Fields\Image;
12
use Riclep\Storyblok\Fields\MultiAsset;
13
use Riclep\Storyblok\Fields\RichText;
14
use Riclep\Storyblok\Fields\Table;
15
use Riclep\Storyblok\Traits\CssClasses;
16
use Riclep\Storyblok\Traits\HasChildClasses;
17
use Riclep\Storyblok\Traits\HasMeta;
18
19
class Block implements \IteratorAggregate
20
{
21
	use CssClasses;
22
	use HasChildClasses;
23
	use HasMeta;
24
25
	/**
26
	 * @var bool resolve UUID relations automatically
27
	 */
28
	public $_autoResolveRelations = false;
29
30
	/**
31
	 * @var array list of field names containing relations to resolve
32
	 */
33
	public $_resolveRelations = [];
34
35
	/**
36
	 * @var array the path of nested components
37
	 */
38
	public $_componentPath = [];
39
40
	/**
41
	 * @var array the path of nested components
42
	 */
43
	protected $_casts = [];
44
45
	/**
46
	 * @var Collection all the fields for the Block
47
	 */
48
	private $_fields;
49
50
	/**
51
	 * @var Page|Block reference to the parent Block or Page
52
	 */
53
	private $_parent;
54
55
	/**
56
	 * Takes the Block’s content and a reference to the parent
57
	 * @param $content
58
	 * @param $parent
59
	 */
60
	public function __construct($content, $parent = null)
61
	{
62
		$this->_parent = $parent;
63
64
		$this->preprocess($content);
65
66
		if ($parent) {
67
			$this->_componentPath = array_merge($parent->_componentPath, [Str::lower($this->meta()['component'])]);
68
		}
69
70
		$this->processFields();
71
72
		// run automatic traits - methods matching initTraitClassName()
73
		foreach (class_uses_recursive($this) as $trait) {
74
			if (method_exists($this, $method = 'init' . class_basename($trait))) {
75
				$this->{$method}();
76
			}
77
		}
78
	}
79
80
	/**
81
	 * Returns the containing every field of content
82
	 *
83
	 * @return Collection
84
	 */
85
	public function content() {
86
		return $this->_fields;
87
	}
88
89
	/**
90
	 * Checks if the fields contain the specified key
91
	 *
92
	 * @param $key
93
	 * @return bool
94
	 */
95
	public function has($key) {
96
		return $this->_fields->has($key);
97
	}
98
99
	/**
100
	 * Returns the parent Block
101
	 *
102
	 * @return Block
103
	 */
104
	public function parent() {
105
		return $this->_parent;
106
	}
107
108
	/**
109
	 * Returns the page this Block belongs to
110
	 *
111
	 * @return Block
112
	 */
113
	public function page() {
114
		if ($this->parent() instanceof Page) {
0 ignored issues
show
introduced by
$this->parent() is never a sub-type of Riclep\Storyblok\Page.
Loading history...
115
			return $this->parent();
116
		}
117
118
		return $this->parent()->page();
119
	}
120
121
	/**
122
	 * Returns the first matching view, passing it the fields
123
	 *
124
	 * @return View
125
	 * @throws UnableToRenderException
126
	 */
127
	public function render() {
128
		try {
129
			return view()->first($this->views(), ['block' => $this]);
130
		} catch (\Exception $exception) {
131
			throw new UnableToRenderException('None of the views in the given array exist.', $this);
132
		}
133
	}
134
135
	/**
136
	 * Returns an array of possible views for the current Block based on
137
	 * it’s $componentPath match the component prefixed by each of it’s
138
	 * ancestors in turn, starting with the closest, for example:
139
	 *
140
	 * $componentPath = ['page', 'parent', 'child', 'this_block'];
141
	 *
142
	 * Becomes a list of possible views like so:
143
	 * ['child.this_block', 'parent.this_block', 'page.this_block'];
144
	 *
145
	 * Override this method with your custom implementation for
146
	 * ultimate control
147
	 *
148
	 * @return array
149
	 */
150
	public function views() {
151
		$compontentPath = $this->_componentPath;
152
		array_pop($compontentPath);
153
154
		$views = array_map(function($path) {
155
			return config('storyblok.view_path') . 'blocks.' . $path . '.' . $this->component();
156
		}, $compontentPath);
157
158
		$views = array_reverse($views);
159
160
		$views[] = config('storyblok.view_path') . 'blocks.' . $this->component();
161
162
		return $views;
163
	}
164
165
	/**
166
	 * Returns a component X generations previous
167
	 *
168
	 * @param $generation int
169
	 * @return mixed
170
	 */
171
	public function ancestorComponentName($generation)
172
	{
173
		return $this->_componentPath[count($this->_componentPath) - ($generation + 1)];
174
	}
175
176
	/**
177
	 * Checks if the current component is a child of another
178
	 *
179
	 * @param $parent string
180
	 * @return bool
181
	 */
182
	public function isChildOf($parent)
183
	{
184
		return $this->_componentPath[count($this->_componentPath) - 2] === $parent;
185
	}
186
187
	/**
188
	 * Checks if the component is an ancestor of another
189
	 *
190
	 * @param $parent string
191
	 * @return bool
192
	 */
193
	public function isAncestorOf($parent)
194
	{
195
		return in_array($parent, $this->parent()->_componentPath);
196
	}
197
198
	/**
199
	 * Returns the current Block’s component name from Storyblok
200
	 *
201
	 * @return string
202
	 */
203
	public function component() {
204
		return $this->_meta['component'];
205
	}
206
207
208
	/**
209
	 * Returns the HTML comment required for making this Block clickable in
210
	 * Storyblok’s visual editor. Don’t forget to set comments to true in
211
	 * your Vue.js app configuration.
212
	 *
213
	 * @return string
214
	 */
215
	public function editorLink() {
216
		if (array_key_exists('_editable', $this->_meta) && config('storyblok.edit_mode')) {
217
			return $this->_meta['_editable'];
218
		}
219
220
		return '';
221
	}
222
223
224
	/**
225
	 * Magic accessor to pull content from the _fields collection. Works just like
226
	 * Laravel’s model accessors. Matches public methods with the follow naming
227
	 * convention getSomeFieldAttribute() - called via $block->some_field
228
	 *
229
	 * @param $key
230
	 * @return null|string
231
	 */
232
	public function __get($key) {
233
		$accessor = 'get' . Str::studly($key) . 'Attribute';
234
235
		if (method_exists($this, $accessor)) {
236
			return $this->$accessor();
237
		}
238
239
		if ($this->has($key)) {
240
			return $this->_fields[$key];
241
		}
242
243
		return null;
244
	}
245
246
	/**
247
	 * Loops over every field to get te ball rolling
248
	 */
249
	private function processFields() {
250
		$this->_fields->transform(function ($field, $key) {
251
			return $this->getFieldType($field, $key);
252
		});
253
	}
254
255
	/**
256
	 * Converts fields into Field Classes based on various properties of their content
257
	 *
258
	 * @param $field
259
	 * @param $key
260
	 * @return array|Collection|mixed|Asset|Image|MultiAsset|RichText|Table
261
	 * @throws \Storyblok\ApiException
262
	 */
263
	private function getFieldType($field, $key) {
264
		$factory = new FieldFactory();
265
		return $factory->build($this, $field, $key);
266
	}
267
268
	/**
269
	 * When the field is an array we need to do more processing
270
	 *
271
	 * @param $field
272
	 * @return Collection|mixed|Asset|Image|MultiAsset|RichText|Table
273
	 */
274
	private function arrayFieldTypes($field, $key) {
275
		$factory = new ArrayFieldFactory();
0 ignored issues
show
Bug introduced by
The type Riclep\Storyblok\ArrayFieldFactory was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
276
		return $factory->build($this, $field, $key);
277
	}
278
279
	/**
280
	 * Storyblok returns fields and other meta content at the same level so
281
	 * let’s do a little tidying up first
282
	 *
283
	 * @param $content
284
	 */
285
	private function preprocess($content) {
286
		$this->_fields = collect(array_diff_key($content, array_flip(['_editable', '_uid', 'component'])));
287
288
		// remove non-content keys
289
		$this->_meta = array_intersect_key($content, array_flip(['_editable', '_uid', 'component']));
290
	}
291
292
	/**
293
	 * Returns cotent of the field. In the visual editor it returns a VueJS template tag
294
	 *
295
	 * @param $field
296
	 * @return string
297
	 */
298
	public function liveField($field) {
299
		if (config('storyblok.edit_mode')) {
300
			return '{{ Object.keys(laravelStoryblokLive).length ? laravelStoryblokLive.uuid_' . str_replace('-', '_', $this->uuid()) . '.' . $field . ' : null }}';
301
		}
302
303
		return $this->{$field};
304
	}
305
306
	/**
307
	 * Flattens all the fields in an array keyed by their UUID to make linking the JS simple
308
	 */
309
	public function flatten() {
310
		$this->content()->each(function ($item, $key) {
311
312
			if ($item instanceof Collection) {
313
				$item->each(function ($item) {
314
					$item->flatten();
315
				});
316
			} elseif ($item instanceof Field) {
317
				$this->page()->liveContent['uuid_' . str_replace('-', '_', $this->uuid())][$key] = (string) $item;
0 ignored issues
show
Bug Best Practice introduced by
The property liveContent does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
318
			} else {
319
				$this->page()->liveContent['uuid_' . str_replace('-', '_', $this->uuid())][$key] = $item;
320
			}
321
		});
322
	}
323
324
	/**
325
	 * Let’s up loop over the fields in Blade without needing to
326
	 * delve deep into the content collection
327
	 *
328
	 * @return \Traversable
329
	 */
330
	public function getIterator() {
331
		return $this->_fields;
332
	}
333
334
	public function getRelation(RequestStory $request, $relation) {
335
		$response = $request->get($relation);
336
337
		$class = $this->getChildClassName('Block', $response['content']['component']);
338
		$relationClass = new $class($response['content'], $this);
339
340
		$relationClass->addMeta([
341
			'name' => $response['name'],
342
			'published_at' => $response['published_at'],
343
			'full_slug' => $response['full_slug'],
344
		]);
345
346
		return $relationClass;
347
	}
348
349
	public function getCasts() {
350
		return $this->_casts;
351
	}
352
}