Passed
Push — master ( 2e0e22...399417 )
by Richard
02:44 queued 10s
created

Block::has()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 2
rs 10
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
use Carbon\Carbon;
14
use Exception;
15
use Illuminate\Support\Collection;
16
use Illuminate\Support\Str;
17
use ReflectionClass;
18
use ReflectionMethod;
19
use Riclep\Storyblok\Traits\ProcessesBlocks;
20
use Riclep\Storyblok\Traits\RequestsStories;
21
22
abstract class Block implements \JsonSerializable, \Iterator, \ArrayAccess, \Countable
23
{
24
	use ProcessesBlocks;
25
	use RequestsStories; // TODO cab we dynamically add this just for blocks that make requests
26
27
	protected $_uid;
28
	protected $component;
29
	protected $content;
30
	public $meta;
31
	private $iteratorIndex = 0;
32
33
	/**
34
	 * Converts Storyblok’s API response into something usable by us. Each block becomes a class
35
	 * with the Storyblok UUID, the component name and any content under it’s own content key
36
	 *
37
	 * @param $block
38
	 */
39
	public function __construct($block)
40
	{
41
		if (array_key_exists('content', $block)) {
42
			// child story so go straight to the contents but store a few useful meta items from the Story
43
			$this->processStoryblokKeys($block['content']);
44
			$this->meta = array_intersect_key($block, array_flip(['name', 'created_at', 'published_at', 'slug', 'full_slug']));
45
		} else {
46
			$this->processStoryblokKeys($block);
47
		}
48
49
		$this->content->transform(function($item, $key) {
50
			return $this->processBlock($item, $key);
51
		});
52
53
		$this->carboniseDates();
54
55
		if ($this->getMethods()->contains('transform')) {
56
			$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

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