Passed
Push — master ( bcc5fb...a0f044 )
by Alain
03:34
created

ShortcodeManager::instantiate()   B

Complexity

Conditions 5
Paths 14

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 12
nc 14
nop 3
dl 0
loc 20
rs 8.8571
c 0
b 0
f 0
ccs 0
cts 10
cp 0
crap 30
1
<?php
2
/**
3
 * Shortcode Manager.
4
 *
5
 * @package   BrightNucleus\Shortcode
6
 * @author    Alain Schlesser <[email protected]>
7
 * @license   GPL-2.0+
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\Shortcode\Exception\FailedToInstantiateObject;
19
use Exception;
20
21
/**
22
 * Shortcode Manager.
23
 *
24
 * This class manages all the shortcodes that it gets passed within a
25
 * ConfigInterface.
26
 *
27
 * @since   0.1.0
28
 *
29
 * @package BrightNucleus\Shortcode
30
 * @author  Alain Schlesser <[email protected]>
31
 */
32
class ShortcodeManager implements ShortcodeManagerInterface {
33
34
	use ConfigTrait;
35
36
	/*
37
	 * The delimiter that is used to express key-subkey relations in the config.
38
	 */
39
	const CONFIG_SEPARATOR = '/';
40
41
	/*
42
	 * Default classes that are used when omitted from the config.
43
	 */
44
	const DEFAULT_SHORTCODE             = 'BrightNucleus\Shortcode\Shortcode';
45
	const DEFAULT_SHORTCODE_ATTS_PARSER = 'BrightNucleus\Shortcode\ShortcodeAttsParser';
46
	const DEFAULT_SHORTCODE_UI          = 'BrightNucleus\Shortcode\ShortcodeUI';
47
48
	/*
49
	 * The names of the configuration keys.
50
	 */
51
	const KEY_CUSTOM_ATTS_PARSER = 'custom_atts_parser';
52
	const KEY_CUSTOM_CLASS       = 'custom_class';
53
	const KEY_CUSTOM_UI          = 'custom_ui';
54
	const KEY_UI                 = 'ui';
55
	/**
56
	 * Collection of ShortcodeInterface objects.
57
	 *
58
	 * @since 0.1.0
59
	 *
60
	 * @var ShortcodeInterface[]
61
	 */
62
	protected $shortcodes = [];
63
64
	/**
65
	 * DependencyManagerInterface implementation.
66
	 *
67
	 * @since 0.1.0
68
	 *
69
	 * @var DependencyManager
70
	 */
71
	protected $dependencies;
72
73
	/**
74
	 * Collection of ShortcodeUIInterface objects.
75
	 *
76
	 * @since 0.1.0
77
	 *
78
	 * @var ShortcodeUIInterface[]
79
	 */
80
	protected $shortcode_uis = [];
81
82
	/**
83
	 * Instantiate a ShortcodeManager object.
84
	 *
85
	 * @since 0.1.0
86
	 *
87
	 * @param ConfigInterface        $config       Configuration to set up the
88
	 *                                             shortcodes.
89
	 * @param DependencyManager|null $dependencies Optional. Dependencies that
90
	 *                                             are needed by the shortcodes.
91
	 * @throws RuntimeException If the config could not be processed.
92
	 */
93
	public function __construct(
94
		ConfigInterface $config,
95
		DependencyManager $dependencies = null
96
	) {
97
		$this->processConfig( $config );
98
		$this->dependencies = $dependencies;
99
100
		$this->init_shortcodes();
101
	}
102
103
	/**
104
	 * Initialize the Shortcode Manager.
105
	 *
106
	 * @since 0.1.0
107
	 */
108
	protected function init_shortcodes() {
109
		foreach ( $this->getConfigKeys() as $tag ) {
110
			$this->init_shortcode( $tag );
111
		}
112
	}
113
114
	/**
115
	 * Initialize a single shortcode.
116
	 *
117
	 * @since 0.1.0
118
	 *
119
	 * @param string $tag                The tag of the shortcode to register.
120
	 * @throws FailedToInstantiateObject If the Shortcode Atts Parser object
121
	 *                                   could not be instantiated.
122
	 * @throws FailedToInstantiateObject If the Shortcode object could not be
123
	 *                                   instantiated.
124
	 */
125
	protected function init_shortcode( $tag ) {
126
		$shortcode_class       = $this->get_shortcode_class( $tag );
127
		$shortcode_atts_parser = $this->get_shortcode_atts_parser_class( $tag );
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $shortcode_atts_parser exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
128
129
		$atts_parser = $this->instantiate(
130
			ShortcodeAttsParserInterface::class,
131
			$shortcode_atts_parser,
132
			[ $this->config->getSubConfig( $tag ) ]
133
		);
134
135
		$this->shortcodes[] = $this->instantiate(
136
			ShortcodeInterface::class,
137
			$shortcode_class,
138
			[
139
				$tag,
140
				$this->config->getSubConfig( $tag ),
141
				$atts_parser,
142
				$this->dependencies,
143
			]
144
		);
145
146
		if ( $this->hasConfigKey( $tag, self::KEY_UI ) ) {
147
			$this->init_shortcode_ui( $tag );
148
		}
149
	}
150
151
	/**
152
	 * Get the class name of an implementation of the ShortcodeInterface.
153
	 *
154
	 * @since 0.1.0
155
	 *
156
	 * @param string $tag Shortcode tag to get the class for.
157
	 * @return string Class name of the Shortcode.
158
	 */
159
	protected function get_shortcode_class( $tag ) {
160
		$shortcode_class = $this->hasConfigKey( $tag, self::KEY_CUSTOM_CLASS )
161
			? $this->getConfigKey( $tag, self::KEY_CUSTOM_CLASS )
162
			: self::DEFAULT_SHORTCODE;
163
		return $shortcode_class;
164
	}
165
166
	/**
167
	 * Get the class name of an implementation of the
168
	 * ShortcodeAttsParsersInterface.
169
	 *
170
	 * @since 0.1.0
171
	 *
172
	 * @param string $tag Shortcode tag to get the class for.
173
	 * @return string Class name of the ShortcodeAttsParser.
174
	 */
175
	protected function get_shortcode_atts_parser_class( $tag ) {
176
		$atts_parser = $this->hasConfigKey( $tag, self::KEY_CUSTOM_ATTS_PARSER )
177
			? $this->getConfigKey( $tag, self::KEY_CUSTOM_ATTS_PARSER )
178
			: self::DEFAULT_SHORTCODE_ATTS_PARSER;
179
		return $atts_parser;
180
	}
181
182
	/**
183
	 * Initialize the Shortcode UI for a single shortcode.
184
	 *
185
	 * @since 0.1.0
186
	 *
187
	 * @param string $tag                The tag of the shortcode to register
188
	 *                                   the UI for.
189
	 * @throws FailedToInstantiateObject If the Shortcode UI object could not
190
	 *                                   be instantiated.
191
	 */
192
	protected function init_shortcode_ui( $tag ) {
193
		$shortcode_ui_class = $this->get_shortcode_ui_class( $tag );
194
195
		$this->shortcode_uis[] = $this->instantiate(
196
			ShortcodeUIInterface::class,
197
			$shortcode_ui_class,
198
			[
199
				$tag,
200
				$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...
201
				$this->dependencies,
202
			]
203
		);
204
	}
205
206
	/**
207
	 * Get the class name of an implementation of the ShortcodeUIInterface.
208
	 *
209
	 * @since 0.1.0
210
	 *
211
	 * @param string $tag Configuration settings.
212
	 * @return string Class name of the ShortcodeUI.
213
	 */
214
	protected function get_shortcode_ui_class( $tag ) {
215
		$ui_class = $this->hasConfigKey( $tag, self::KEY_CUSTOM_UI )
216
			? $this->getConfigKey( $tag, self::KEY_CUSTOM_UI )
217
			: self::DEFAULT_SHORTCODE_UI;
218
		return $ui_class;
219
	}
220
221
	/**
222
	 * Register all of the shortcode handlers.
223
	 *
224
	 * @since 0.1.0
225
	 *
226
	 * @param mixed $context Optional. Context information to pass to shortcode.
227
	 * @return void
228
	 */
229
	public function register( $context = null ) {
230
		$context                  = $this->validate_context( $context );
231
		$context['page_template'] = $this->get_page_template();
232
233
		array_walk( $this->shortcodes,
234
			function ( ShortcodeInterface $shortcode ) use ( $context ) {
235
				$shortcode->register( $context );
236
			} );
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...
237
238
		// This hook only gets fired when Shortcode UI plugin is active.
239
		\add_action(
240
			'register_shortcode_ui',
241
			[ $this, 'register_shortcode_ui', ]
242
		);
243
	}
244
245
	/**
246
	 * Validate the context to make sure it is an array.
247
	 *
248
	 * @since 0.2.3
249
	 *
250
	 * @param mixed $context The context as passed in by WordPress.
251
	 * @return array Validated context.
252
	 */
253
	protected function validate_context( $context ) {
254
		if ( is_string( $context ) ) {
255
			return [ 'wp_context' => $context ];
256
		}
257
		return (array) $context;
258
	}
259
260
	/**
261
	 * Get the name of the page template.
262
	 *
263
	 * @since 0.1.0
264
	 *
265
	 * @return string Name of the page template.
266
	 */
267
	protected function get_page_template() {
268
		$template = str_replace(
269
			\trailingslashit( \get_stylesheet_directory() ),
270
			'',
271
			\get_page_template()
272
		);
273
		return $template;
274
	}
275
276
	/**
277
	 * Register the shortcode UI handlers.
278
	 *
279
	 * @since 0.1.0
280
	 */
281
	public function register_shortcode_ui() {
282
		$template = $this->get_page_template();
283
		$context  = [ 'page_template' => $template ];
284
285
		array_walk( $this->shortcode_uis,
286
			function ( ShortcodeUIInterface $shortcode_ui ) use ( $context ) {
287
				$shortcode_ui->register( $context );
288
			}
289
		);
290
	}
291
292
	/**
293
	 * Execute a specific shortcode directly from code.
294
	 *
295
	 * @since 0.2.4
296
	 *
297
	 * @param string $tag     Tag of the shortcode to execute.
298
	 * @param array  $atts    Array of attributes to pass to the shortcode.
299
	 * @param null   $content Inner content to pass to the shortcode.
300
	 * @return string|false Rendered HTML.
0 ignored issues
show
Documentation introduced by
Should the return type not be string|false|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
301
	 */
302
	public function do_tag( $tag, array $atts = [], $content = null ) {
303
		\BrightNucleus\Shortcode\do_tag( $tag, $atts, $content );
304
	}
305
306
	/**
307
	 * Instantiate a new object through either a class name or a factory method.
308
	 *
309
	 * @since 0.3.0
310
	 *
311
	 * @param string          $interface Interface the object needs to
312
	 *                                   implement.
313
	 * @param callable|string $class     Fully qualified class name or factory
314
	 *                                   method.
315
	 * @param array           $args      Arguments passed to the constructor or
316
	 *                                   factory method.
317
	 * @return object Object that implements the interface.
318
	 * @throws FailedToInstantiateObject If no valid object could be
319
	 *                                   instantiated.
320
	 */
321
	protected function instantiate( $interface, $class, array $args ) {
322
		try {
323
			if ( is_callable( $class ) ) {
324
				$class = call_user_func_array( $class, $args );
325
			}
326
327
			if ( is_string( $class ) ) {
328
				$class = call_user_func_array( $class, $args );
329
			}
330
		} catch ( Exception $exception ) {
331
			throw FailedToInstantiateObject::fromFactory( $class, $interface );
332
		}
333
334
		if ( ! $class instanceof $interface ) {
335
			throw FailedToInstantiateObject::fromInvalidObject( $class,
336
				$interface );
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 12 spaces, but found 16.
Loading history...
337
		}
338
339
		return $class;
340
	}
341
}
342