Passed
Push — master ( 3295fe...37d5c2 )
by Richard
04:14 queued 14s
created

Block::__construct()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 15
c 1
b 0
f 0
nc 8
nop 1
dl 0
loc 26
rs 9.7666
1
<?php
2
3
// TODO - add defaults / null object
4
// TODO - date casting to Carbon
5
6
/////// blocks might be keyed with numbers from storyblok.
7
/// we might need to be able to access specific ones - reordering content will change the number
8
/// we either need a method to find a specific child (by component name)
9
/// or a Block Trait to key content by the child component name
10
11
namespace Riclep\Storyblok;
12
13
14
use Carbon\Carbon;
15
use Exception;
16
use Illuminate\Support\Collection;
17
use Illuminate\Support\Str;
18
use ReflectionClass;
19
use ReflectionMethod;
20
use Riclep\Storyblok\Traits\AutoParagraphs;
21
use Riclep\Storyblok\Traits\ConvertsMarkdown;
22
use Riclep\Storyblok\Traits\ConvertsRichtext;
23
use Riclep\Storyblok\Traits\ProcessesBlocks;
24
use Riclep\Storyblok\Traits\RequestsStories;
25
26
abstract class Block implements \JsonSerializable, \Iterator, \ArrayAccess, \Countable
27
{
28
	use ProcessesBlocks;
29
	use RequestsStories;
30
	use ConvertsMarkdown;
31
	use ConvertsRichtext;
32
	use AutoParagraphs;
33
34
	protected $_uid;
35
	protected $component;
36
	protected $content;
37
	protected $_componentPath = [];
38
	public $_meta;
39
	private $iteratorIndex = 0;
40
41
	/**
42
	 * Converts Storyblok’s API response into something usable by us. Each block becomes a class
43
	 * with the Storyblok UUID, the component name and any content under it’s own content key
44
	 *
45
	 * @param $block
46
	 */
47
	public function __construct($block)
48
	{
49
		if (array_key_exists('content', $block)) {
50
			// child story so go straight to the contents but store a few useful meta items from the Story
51
			$this->processStoryblokKeys($block['content']);
52
			$this->_meta = array_intersect_key($block, array_flip(['name', 'created_at', 'published_at', 'slug', 'full_slug']));
53
		} else {
54
			$this->processStoryblokKeys($block);
55
		}
56
57
		$this->content->transform(function($item, $key) {
58
			return $this->processBlock($item, $key);
59
		});
60
61
		$this->carboniseDates();
62
63
		$this->convertMarkdown();
64
		$this->convertRichtext();
65
		$this->autoParagraphs();
66
67
		if ($this->getMethods()->contains('transform')) {
68
			$this->transform();
0 ignored issues
show
Bug introduced by
The method transform() does not exist on Riclep\Storyblok\Block. ( Ignorable by Annotation )

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

68
			$this->/** @scrutinizer ignore-call */ 
69
          transform();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
69
		}
70
71
		if (method_exists($this, 'init')) {
72
			$this->init();
73
		}
74
	}
75
76
	private function processStoryblokKeys($block) {
77
		$this->_uid = $block['_uid'] ?? null;
78
		$this->component = $block['component'] ?? null;
79
		$this->fieldtype = $block['fieldtype'] ?? null;
0 ignored issues
show
Bug Best Practice introduced by
The property fieldtype does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
80
		$this->content = collect(array_diff_key($block, array_flip(['_editable', '_uid', 'component', 'plugin', 'fieldtype'])));
81
	}
82
83
	/**
84
	 * @return mixed
85
	 */
86
	public function editableBridge()
87
	{
88
		return $this->_editable;
0 ignored issues
show
Bug Best Practice introduced by
The property _editable does not exist on Riclep\Storyblok\Block. Since you implemented __get, consider adding a @property annotation.
Loading history...
89
	}
90
91
	/**
92
	 * @return mixed
93
	 */
94
	public function random()
95
	{
96
		return $this->content->random();
97
	}
98
99
	/**
100
	 * @return mixed
101
	 */
102
	public function content()
103
	{
104
		return $this->content;
105
	}
106
107
	/**
108
	 * @return mixed
109
	 */
110
	public function component()
111
	{
112
		return $this->component;
113
	}
114
115
	public function makeComponentPath($componentPath)
116
	{
117
		$componentPath[] = $this->component();
118
119
		$this->_componentPath = $componentPath;
120
121
		// loop over all child classes, pass down current component list
122
		$this->content->each(function($block) use ($componentPath) {
123
			if ($block instanceof Block || $block instanceof Asset) {
124
				$block->makeComponentPath($componentPath);
125
			}
126
		});
127
	}
128
129
	public function filterComponent($componentName) {
130
		return $this->content->filter(function ($block) use ($componentName) {
131
			return $block->component === $componentName;
132
		});
133
	}
134
135
	public function componentPath()
136
	{
137
		return $this->_componentPath;
138
	}
139
140
	public function getAncestorComponent($generation)
141
	{
142
		return $this->_componentPath[count($this->_componentPath) - ($generation + 1)];
143
	}
144
145
	public function isChildOf($parent)
146
	{
147
		return $this->_componentPath[count($this->_componentPath) - 2] === $parent;
148
	}
149
150
	public function isAncestorOf($parent)
151
	{
152
		return in_array($parent, $this->_componentPath);
153
	}
154
155
	/**
156
	 * Checks if the content contains the specified item
157
	 *
158
	 * @param string $key
159
	 * @return mixed
160
	 */
161
	public function has($key) {
162
		return $this->content->has($key);
163
	}
164
165
	/**
166
	 * @return mixed
167
	 */
168
	public function uuid()
169
	{
170
		return $this->_uid;
171
	}
172
173
	/**
174
	 * Returns a rendered version of the block’s view
175
	 *
176
	 * @return string
177
	 */
178
	public function __toString()
179
	{
180
		return $this->content()->toJson(JSON_PRETTY_PRINT);
181
	}
182
183
	/**
184
	 * Determines how this object is converted to JSON
185
	 *
186
	 * @return mixed
187
	 */
188
	public function jsonSerialize()
189
	{
190
		if (property_exists($this, 'excluded')) {
191
			$content = $this->content->except($this->excluded);
0 ignored issues
show
Bug Best Practice introduced by
The property excluded does not exist on Riclep\Storyblok\Block. Since you implemented __get, consider adding a @property annotation.
Loading history...
192
		} else {
193
			$content = $this->content;
194
		}
195
196
		$attributes = [];
197
198
		// get the appended attributes
199
		if (property_exists($this, 'appends')) {
200
			foreach ($this->appends as $attribute) {
0 ignored issues
show
Bug Best Practice introduced by
The property appends does not exist on Riclep\Storyblok\Block. Since you implemented __get, consider adding a @property annotation.
Loading history...
201
				$attributes[$attribute] = $this->{$attribute};
202
			}
203
		}
204
205
		return $content->merge(collect($attributes));
206
	}
207
208
209
	/**
210
	 * As all content sits under the content property we can ease access to these with a magic getter
211
	 * it looks inside the content collection for a matching key and returns it.
212
	 *
213
	 * If an accessor has been created for an existing or ‘new’ content item it will be returned.
214
	 *
215
	 * @param $name
216
	 * @return mixed
217
	 * @throws \ReflectionException
218
	 */
219
	public function __get($name) {
220
		$accessor = 'get' . Str::studly($name) . 'Attribute';
221
222
		if ($this->getMethods()->contains($accessor)) {
223
			return $this->$accessor();
224
		}
225
226
		try {
227
			if ($this->has($name)) {
228
				return $this->content[$name];
229
			}
230
231
			return false;
232
		} catch (Exception $e) {
233
			return 'Caught exception: ' .  $e->getMessage();
234
		}
235
	}
236
237
	/**
238
	 * Gets all the public methods for a class and it’s descendants
239
	 *
240
	 * @return Collection
241
	 * @throws \ReflectionException
242
	 */
243
	public function getMethods() {
244
		$class = new ReflectionClass($this);
245
		return collect($class->getMethods(ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED))->transform(function($item) {
246
			return $item->name;
247
		});
248
	}
249
250
	protected function carboniseDates() {
251
		$properties = get_object_vars($this);
252
253
		if (array_key_exists('dates', $properties)) {
254
			foreach ($properties['dates'] as $date) {
255
				if ($this->content->has($date)) {
256
					$this->content[$date] = Carbon::parse($this->content[$date]) ?: $this->content[$date];
257
				}
258
			}
259
		}
260
	}
261
262
	public function isEmpty() {
263
		return $this->content->isEmpty();
264
	}
265
266
	/*
267
	 * Methods for Iterator trait allowing us to foreach over a collection of
268
	 * Blocks and return their content. This makes accessing child content
269
	 * in Blade much cleaner
270
	 * */
271
	public function current()
272
	{
273
		return $this->content[$this->iteratorIndex];
274
	}
275
276
	public function next()
277
	{
278
		$this->iteratorIndex++;
279
	}
280
281
	public function rewind()
282
	{
283
		$this->iteratorIndex = 0;
284
	}
285
286
	public function key()
287
	{
288
		return $this->iteratorIndex;
289
	}
290
291
	public function valid()
292
	{
293
		return isset($this->content[$this->iteratorIndex]);
294
	}
295
296
	/*
297
	 * Methods for ArrayAccess Trait - allows us to dig straight down to the content collection
298
	 * when calling a key on the Object
299
	 * */
300
	public function offsetSet($offset, $value) {
301
		if (is_null($offset)) {
302
			$this->content[] = $value;
303
		} else {
304
			$this->content[$offset] = $value;
305
		}
306
	}
307
308
	public function offsetExists($offset) {
309
		return isset($this->content[$offset]);
310
	}
311
312
	public function offsetUnset($offset) {
313
		unset($this->content[$offset]);
314
	}
315
316
	public function offsetGet($offset) {
317
		return isset($this->content[$offset]) ? $this->content[$offset] : null;
318
	}
319
320
321
	/*
322
	 * Countable trait
323
	 * */
324
	public function count()
325
	{
326
		return $this->content->count();
327
	}
328
329
}