Test Failed
Push — master ( 9d7f3f...33a504 )
by Alain
01:51
created

ShortcodeManager::instantiate()   B

Complexity

Conditions 6
Paths 20

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
cc 6
nc 20
nop 3
dl 0
loc 30
ccs 0
cts 0
cp 0
crap 42
rs 8.8177
c 0
b 0
f 0
1
<?php
2
/**
3
 * Bright Nucleus Shortcode Component.
4
 *
5
 * @package   BrightNucleus\Shortcode
6
 * @author    Alain Schlesser <[email protected]>
7
 * @license   MIT
8
 * @link      http://www.brightnucleus.com/
9
 * @copyright 2015-2016 Alain Schlesser, Bright Nucleus
10
 */
11
12
namespace BrightNucleus\Shortcode;
13
14
use BrightNucleus\Config\ConfigInterface;
15
use BrightNucleus\Config\ConfigTrait;
16
use BrightNucleus\Dependency\DependencyManagerInterface as DependencyManager;
17
use BrightNucleus\Exception\RuntimeException;
18
use BrightNucleus\Invoker\InstantiatorTrait;
19
use BrightNucleus\Shortcode\Exception\FailedToInstantiateObject;
20
use BrightNucleus\View\ViewBuilder;
21
use Exception;
22
23
/**
24
 * Shortcode Manager.
25
 *
26
 * This class manages all the shortcodes that it gets passed within a
27
 * ConfigInterface.
28
 *
29
 * @since   0.1.0
30
 *
31
 * @package BrightNucleus\Shortcode
32
 * @author  Alain Schlesser <[email protected]>
33
 */
34
class ShortcodeManager implements ShortcodeManagerInterface {
35
36
	use ConfigTrait;
37
	use InstantiatorTrait;
38
39
	/*
40
	 * The delimiter that is used to express key-subkey relations in the config.
41
	 */
42
	const CONFIG_SEPARATOR = '/';
43
44
	/*
45
	 * Default classes that are used when omitted from the config.
46
	 */
47
	const DEFAULT_SHORTCODE             = 'BrightNucleus\Shortcode\Shortcode';
48
	const DEFAULT_SHORTCODE_ATTS_PARSER = 'BrightNucleus\Shortcode\ShortcodeAttsParser';
49
	const DEFAULT_SHORTCODE_UI          = 'BrightNucleus\Shortcode\ShortcodeUI';
50
51
	/*
52
	 * The names of the configuration keys.
53
	 */
54
	const KEY_CUSTOM_ATTS_PARSER = 'custom_atts_parser';
55
	const KEY_CUSTOM_CLASS       = 'custom_class';
56
	const KEY_CUSTOM_UI          = 'custom_ui';
57
	const KEY_UI                 = 'ui';
58
	/**
59
	 * Collection of ShortcodeInterface objects.
60
	 *
61
	 * @since 0.1.0
62
	 *
63
	 * @var ShortcodeInterface[]
64
	 */
65
	protected $shortcodes = [];
66
67
	/**
68
	 * DependencyManagerInterface implementation.
69
	 *
70
	 * @since 0.1.0
71
	 *
72
	 * @var DependencyManager
73
	 */
74
	protected $dependencies;
75
76
	/**
77
	 * View builder instance to use for rendering views.
78
	 *
79
	 * @since 0.4.0
80
	 *
81
	 * @var ViewBuilder
82
	 */
83
	protected $view_builder;
84
85
	/**
86
	 * Collection of ShortcodeUIInterface objects.
87
	 *
88
	 * @since 0.1.0
89
	 *
90
	 * @var ShortcodeUIInterface[]
91
	 */
92
	protected $shortcode_uis = [];
93
94
	/**
95
	 * External injector to use.
96
	 *
97
	 * @var object
98
	 */
99
	protected $injector;
100
101
	/**
102
	 * Instantiate a ShortcodeManager object.
103
	 *
104
	 * @since 0.1.0
105
	 * @since 0.4.0 Add optional $viewBuilder argument.
106
	 *
107
	 * @param ConfigInterface        $config       Configuration to set up the
108
	 *                                             shortcodes.
109
	 * @param DependencyManager|null $dependencies Optional. Dependencies that
110
	 *                                             are needed by the
111
	 *                                             shortcodes.
112
	 * @param ViewBuilder|null       $view_builder Optional. View builder
113
	 *                                             instance to use for
114
	 *                                             rendering views.
115
	 *
116
	 * @throws RuntimeException If the config could not be processed.
117
	 */
118
	public function __construct(
119
		ConfigInterface $config,
120
		DependencyManager $dependencies = null,
121
		ViewBuilder $view_builder = null
122
	) {
123
		$this->processConfig( $config );
124
		$this->dependencies = $dependencies;
125
		$this->view_builder = $view_builder ?? Views::getViewBuilder();
126
	}
127
128
	/**
129
	 * Use an external injector to instantiate the different classes.
130
	 *
131
	 * The injector will
132
	 * @param object $injector Injector to use.
133
	 */
134
	public function with_injector( $injector ) {
135
		if ( ! method_exists( $injector, 'make' ) ) {
136
			throw new RuntimeException(
137
				'Invalid injector provided, it does not have a make() method.'
138
			);
139
		}
140
141
		$this->injector = $injector;
142
	}
143
144
	/**
145
	 * Initialize the Shortcode Manager.
146
	 *
147
	 * @since 0.1.0
148
	 */
149
	protected function init_shortcodes() {
150
		foreach ( $this->getConfigKeys() as $tag ) {
151
			$this->init_shortcode( $tag );
152
		}
153
	}
154
155
	/**
156
	 * Initialize a single shortcode.
157
	 *
158
	 * @since 0.1.0
159
	 *
160
	 * @param string $tag The tag of the shortcode to register.
161
	 *
162
	 * @throws FailedToInstantiateObject If the Shortcode Atts Parser object
163
	 *                                   could not be instantiated.
164
	 * @throws FailedToInstantiateObject If the Shortcode object could not be
165
	 *                                   instantiated.
166
	 */
167
	protected function init_shortcode( $tag ) {
168
		$shortcode_class       = $this->get_shortcode_class( $tag );
169
		$shortcode_atts_parser = $this->get_shortcode_atts_parser_class( $tag );
170
171
		$atts_parser = $this->instantiate(
172
			ShortcodeAttsParserInterface::class,
173
			$shortcode_atts_parser,
174
			[ 'config' => $this->config->getSubConfig( $tag ) ]
175
		);
176
177
		$this->shortcodes[] = $this->instantiate(
178
			ShortcodeInterface::class,
179
			$shortcode_class,
180
			[
181
				'shortcode_tag' => $tag,
182
				'config'        => $this->config->getSubConfig( $tag ),
183
				'atts_parser'   => $atts_parser,
184
				'dependencies'  => $this->dependencies,
185
				'view_builder'  => $this->view_builder,
186
			]
187
		);
188
189
		if ( $this->hasConfigKey( $tag, self::KEY_UI ) ) {
190
			$this->init_shortcode_ui( $tag );
191
		}
192
	}
193
194
	/**
195
	 * Get the class name of an implementation of the ShortcodeInterface.
196
	 *
197
	 * @since 0.1.0
198
	 *
199
	 * @param string $tag Shortcode tag to get the class for.
200
	 *
201
	 * @return string Class name of the Shortcode.
202
	 */
203
	protected function get_shortcode_class( $tag ) {
204
		$shortcode_class = $this->hasConfigKey( $tag, self::KEY_CUSTOM_CLASS )
205
			? $this->getConfigKey( $tag, self::KEY_CUSTOM_CLASS )
206
			: self::DEFAULT_SHORTCODE;
207
208
		return $shortcode_class;
209
	}
210
211
	/**
212
	 * Get the class name of an implementation of the
213
	 * ShortcodeAttsParsersInterface.
214
	 *
215
	 * @since 0.1.0
216
	 *
217
	 * @param string $tag Shortcode tag to get the class for.
218
	 *
219
	 * @return string Class name of the ShortcodeAttsParser.
220
	 */
221
	protected function get_shortcode_atts_parser_class( $tag ) {
222
		$atts_parser = $this->hasConfigKey( $tag, self::KEY_CUSTOM_ATTS_PARSER )
223
			? $this->getConfigKey( $tag, self::KEY_CUSTOM_ATTS_PARSER )
224
			: self::DEFAULT_SHORTCODE_ATTS_PARSER;
225
226
		return $atts_parser;
227
	}
228
229
	/**
230
	 * Initialize the Shortcode UI for a single shortcode.
231
	 *
232
	 * @since 0.1.0
233
	 *
234
	 * @param string $tag                The tag of the shortcode to register
235
	 *                                   the UI for.
236
	 *
237
	 * @throws FailedToInstantiateObject If the Shortcode UI object could not
238
	 *                                   be instantiated.
239
	 */
240
	protected function init_shortcode_ui( $tag ) {
241
		$shortcode_ui_class = $this->get_shortcode_ui_class( $tag );
242
243
		$this->shortcode_uis[] = $this->instantiate(
244
			ShortcodeUIInterface::class,
245
			$shortcode_ui_class,
246
			[
247
				'shortcode_tag' => $tag,
248
				'config'        => $this->config->getSubConfig( $tag, self::KEY_UI ),
0 ignored issues
show
Unused Code introduced by
The call to ConfigInterface::getSubConfig() has too many arguments starting with self::KEY_UI.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
249
				'dependencies'  => $this->dependencies,
250
			]
251
		);
252
	}
253
254
	/**
255
	 * Get the class name of an implementation of the ShortcodeUIInterface.
256
	 *
257
	 * @since 0.1.0
258
	 *
259
	 * @param string $tag Configuration settings.
260
	 *
261
	 * @return string Class name of the ShortcodeUI.
262
	 */
263
	protected function get_shortcode_ui_class( $tag ) {
264
		$ui_class = $this->hasConfigKey( $tag, self::KEY_CUSTOM_UI )
265
			? $this->getConfigKey( $tag, self::KEY_CUSTOM_UI )
266
			: self::DEFAULT_SHORTCODE_UI;
267
268
		return $ui_class;
269
	}
270
271
	/**
272
	 * Register all of the shortcode handlers.
273
	 *
274
	 * @since 0.1.0
275
	 *
276
	 * @param mixed $context Optional. Context information to pass to shortcode.
277
	 *
278
	 * @return void
279
	 */
280
	public function register( $context = null ) {
281
		$this->init_shortcodes();
282
283
		$context                  = $this->validate_context( $context );
284
		$context['page_template'] = $this->get_page_template();
285
286
		array_walk( $this->shortcodes,
287
			function ( ShortcodeInterface $shortcode ) use ( $context ) {
288
				$shortcode->register( $context );
289
			} );
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 8 spaces, but found 12.
Loading history...
290
291
		// This hook only gets fired when Shortcode UI plugin is active.
292
		\add_action(
293
			'register_shortcode_ui',
294
			[ $this, 'register_shortcode_ui', ]
295
		);
296
	}
297
298
	/**
299
	 * Validate the context to make sure it is an array.
300
	 *
301
	 * @since 0.2.3
302
	 *
303
	 * @param mixed $context The context as passed in by WordPress.
304
	 *
305
	 * @return array Validated context.
306
	 */
307
	protected function validate_context( $context ) {
308
		if ( is_string( $context ) ) {
309
			return [ 'wp_context' => $context ];
310
		}
311
312
		return (array) $context;
313
	}
314
315
	/**
316
	 * Get the name of the page template.
317
	 *
318
	 * @since 0.1.0
319
	 *
320
	 * @return string Name of the page template.
321
	 */
322
	protected function get_page_template() {
323
		$template = str_replace(
324
			\trailingslashit( \get_stylesheet_directory() ),
325
			'',
326
			\get_page_template()
327
		);
328
329
		return $template;
330
	}
331
332
	/**
333
	 * Register the shortcode UI handlers.
334
	 *
335
	 * @since 0.1.0
336
	 */
337
	public function register_shortcode_ui() {
338
		$template = $this->get_page_template();
339
		$context  = [ 'page_template' => $template ];
340
341
		array_walk( $this->shortcode_uis,
342
			function ( ShortcodeUIInterface $shortcode_ui ) use ( $context ) {
343
				$shortcode_ui->register( $context );
344
			}
345
		);
346
	}
347
348
	/**
349
	 * Execute a specific shortcode directly from code.
350
	 *
351
	 * @since 0.2.4
352
	 *
353
	 * @param string $tag     Tag of the shortcode to execute.
354
	 * @param array  $atts    Array of attributes to pass to the shortcode.
355
	 * @param null   $content Inner content to pass to the shortcode.
356
	 *
357
	 * @return string|false Rendered HTML.
358
	 */
359
	public function do_tag( $tag, array $atts = [], $content = null ) {
360
		return \BrightNucleus\Shortcode\do_tag( $tag, $atts, $content );
361
	}
362
363
	/**
364
	 * Instantiate a new object through either a class name or a factory method.
365
	 *
366
	 * @since 0.3.0
367
	 *
368
	 * @param string          $interface Interface the object needs to
369
	 *                                   implement.
370
	 * @param callable|string $class     Fully qualified class name or factory
371
	 *                                   method.
372
	 * @param array           $args      Arguments passed to the constructor or
373
	 *                                   factory method.
374
	 *
375
	 * @return object Object that implements the interface.
376
	 * @throws FailedToInstantiateObject If no valid object could be
377
	 *                                   instantiated.
378
	 */
379
	protected function instantiate( $interface, $class, array $args ) {
380
		try {
381
			if ( is_callable( $class ) ) {
382
				$class = call_user_func_array( $class, $args );
383
			}
384
385
			if ( is_string( $class ) ) {
386
				if ( null !== $this->injector ) {
387
					$class = $this->injector->make( $class, $args );
388
				} else {
389
					$class = $this->instantiateClass( $class, $args );
390
				}
391
			}
392
		} catch ( Exception $exception ) {
393
			throw FailedToInstantiateObject::fromFactory(
394
				$class,
395
				$interface,
396
				$exception
397
			);
398
		}
399
400
		if ( ! is_subclass_of( $class, $interface ) ) {
401
			throw FailedToInstantiateObject::fromInvalidObject(
402
				$class,
403
				$interface
404
			);
405
		}
406
407
		return $class;
408
	}
409
}
410