Completed
Push — master ( 7473a9...a1417a )
by Richard
09:24 queued 11s
created

Block::random()   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\ConvertsMarkdown;
20
use Riclep\Storyblok\Traits\ProcessesBlocks;
21
use Riclep\Storyblok\Traits\RequestsStories;
22
23
abstract class Block implements \JsonSerializable, \Iterator, \ArrayAccess, \Countable
24
{
25
	use ProcessesBlocks;
26
	use RequestsStories;
27
	use ConvertsMarkdown;
28
29
	protected $_uid;
30
	protected $component;
31
	protected $content;
32
	public $meta;
33
	private $iteratorIndex = 0;
34
35
	/**
36
	 * Converts Storyblok’s API response into something usable by us. Each block becomes a class
37
	 * with the Storyblok UUID, the component name and any content under it’s own content key
38
	 *
39
	 * @param $block
40
	 */
41
	public function __construct($block)
42
	{
43
		if (array_key_exists('content', $block)) {
44
			// child story so go straight to the contents but store a few useful meta items from the Story
45
			$this->processStoryblokKeys($block['content']);
46
			$this->meta = array_intersect_key($block, array_flip(['name', 'created_at', 'published_at', 'slug', 'full_slug']));
47
		} else {
48
			$this->processStoryblokKeys($block);
49
		}
50
51
		$this->content->transform(function($item, $key) {
52
			return $this->processBlock($item, $key);
53
		});
54
55
		$this->carboniseDates();
56
57
		$this->convertMarkdown();
58
59
		if ($this->getMethods()->contains('transform')) {
60
			$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

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