Passed
Push — master ( 061514...60c0d3 )
by Richard
04:17 queued 16s
created

Block::rewind()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
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 0
dl 0
loc 3
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
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
	private $iteratorIndex = 0;
31
32
	/**
33
	 * Converts Storyblok’s API response into something usable by us. Each block becomes a class
34
	 * with the Storyblok UUID, the component name and any content under it’s own content key
35
	 *
36
	 * @param $block
37
	 * @param $key
38
	 */
39
	public function __construct($block, $key)
40
	{
41
		$this->processStoryblokKeys($block);
42
43
		$this->content->transform(function($item, $key) {
44
			return $this->processBlock($item, $key);
45
		});
46
47
		$this->carboniseDates();
48
49
		if ($this->getMethods()->contains('transform')) {
50
			$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

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