-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathConfig.php
464 lines (407 loc) · 14.1 KB
/
Config.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
<?php // phpcs:ignore WordPress.NamingConventions
/**
* The Web Solver WordPress Admin Onboarding Wizard Configuration.
*
* @todo Set the config namespace.
* @todo Check all todo tags and make approriate changes where needed.
*
* @package TheWebSolver\Core\Admin\Onboarding\Class
*
* -----------------------------------
* DEVELOPED-MAINTAINED-SUPPPORTED BY
* -----------------------------------
* ███║ ███╗ ████████████████
* ███║ ███║ ═════════██████╗
* ███║ ███║ ╔══█████═╝
* ████████████║ ╚═█████
* ███║═════███║ █████╗
* ███║ ███║ █████═╝
* ███║ ███║ ████████████████╗
* ╚═╝ ╚═╝ ═══════════════╝
*/
/**
* Onboarding namespace.
*
* @todo MUST REPLACE AND USE OWN NAMESPACE.
*/
namespace My_Plugin\My_Feature;
use stdClass;
use WP_Error;
use Exception;
use TheWebSolver\Core\Admin\Onboarding\Wizard;
use TheWebSolver\Core\Admin\Onboarding\Form;
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;
/**
* Configuration class.
*/
final class Config {
/**
* The onboarding prefixer.
*
* @var string
*
* @since 1.0
*/
private $prefix = 'thewebsolver';
/**
* The user capability who can access onboarding.
*
* @var string Default is `manage_options` i.e. Admin Capability.
*
* @since 1.0
*/
private $capability = 'manage_options';
/**
* The onboarding wizard child-class file path.
*
* @var string
*
* @since 1.0
*/
private $child_file;
/**
* The onboarding wizard child-class name.
*
* @var string
*
* @since 1.0
*/
private $child_name;
/**
* Onboarding form handler.
*
* @var Form
*
* @since 1.1
*/
public $form;
/**
* Onboarding instance.
*
* @var \TheWebSolver\Core\Admin\Onboarding\Wizard
*
* @since 1.1
*/
public $onboarding;
/**
* Initializes onboarding wizard.
*
* @throws Exception If `Config.php` and `Includes/Wizard.php`
* file namespace didn't match.
* @since 1.0
* @since 1.1 Throws Exception and WP dies.
* @since 1.1 Sets onboarding property instead of returning it.
*/
public function onboarding() {
// Prepare and instantiate external child-class, if valid.
$class = new stdClass();
if ( file_exists( $this->child_file ) && 0 < strlen( $this->child_name ) ) {
// Include the child-class file.
include_once $this->child_file;
// Prepare classname using the same namespace as this (config) file.
$child = '\\' . __NAMESPACE__ . '\\' . $this->child_name;
$class = class_exists( $child ) ? new $child() : $class;
}
// Create onboarding wizard from external child-class, if instance of abstract class "Wizard".
if ( $class instanceof Wizard ) {
/**
* External onboarding wizard child-class.
*
* @var \TheWebSolver\Core\Admin\Onboarding\Wizard
* */
$onboarding = $class;
$onboarding->set_config( $this );
} else {
// New shiny wizard creation from internal child-class.
include_once __DIR__ . '/Includes/Wizard.php';
try {
if ( class_exists( __NAMESPACE__ . '\\Onboarding_Wizard' ) ) {
$onboarding = new Onboarding_Wizard();
$onboarding->set_config( $this );
} else {
throw new Exception( '<p>Namespace of <b>Config</b> and <b>Wizard</b> class does not match.</p><hr>Set same namespace used on <b>Config.php</b> file in <b>Includes/Wizard.php file</b> to instantiate <code><b><em>Onboarding_Wizard</em></b></code> class.' );
}
} catch ( Exception $e ) {
wp_die( wp_kses_post( $e->getMessage() ), 'Namespace Mismatch' );
}
}
$onboarding->init();
$this->onboarding = $onboarding;
}
/**
* Determines whether plugin onboarding should run or not after plugin activation.
*
* NOTE: WITHOUT USING THIS, ONBOARDING WIZARD WILL NOT REDIRECT AFTER PLUGIN ACTIVATION.
*
* By default a filter is created to enable/disable onboarding.
* Onboarding can then be turned off using this filter.\
* FILTER IS USEFUL FOR END-USERS TO ENABLE/DISABLE ONBOARDING WIZARD.
*
* Additional checks must be made as required.
* For eg. Saving an option during installation and checking whether that option exists.
* This way it will make sure that it's a clean install before enabling onboarding.
*
* @param string[] $check Validation before enabling onboarding during plugin activation.
* Must have all values as `true` *(in string, not bool)* to pass the check.
*
* @see Onboarding::activate()
* @link https://developer.wordpress.org/reference/functions/register_activation_hook/
* @since 1.0
* @todo Use this with function registered at activation hook.
* For more info: {@see register_activation_hook()).
*/
public function enable_onboarding( array $check ) {
/**
* WPHOOK: Filter -> enable/disable onboarding redirect after plugin activation.
*
* @param bool $redirect Whether to redirect or not.
* @var bool
* @since 1.0
* @example usage
* ```
* // Disable redirection after plugin activation.
* add_filter( 'hzfex_enable_onboarding_redirect', 'no_redirect', 10, 2 );
* function no_redirect( $enable, $prefix ) {
* // Bail if not our onboarding wizard.
* if ( 'my-prefix' !== $prefix ) {
* return $enable;
* }
*
* return false;
* }
* ```
*/
$redirect = apply_filters( 'hzfex_enable_onboarding_redirect', true, $this->get_prefix() );
if ( $redirect && ! in_array( 'false', $check, true ) ) {
set_transient( $this->get_prefix() . '_onboarding_redirect', 'yes', 30 );
/**
* Use this option to conditionally show/hide admin notice (or anything else).
* It's will be updated to `complete` only at the last step of the onboarding wizard.
*/
update_option( $this->get_prefix() . '_onboarding_steps_status', 'pending' );
}
}
/**
* Instantiates Onboarding Wizard class.
*
* This is hooked to WordPress `init` action.
*
* @since 1.0
* @since 1.1 Added form handler class.
*/
public function start_onboarding() {
// Only run on WordPress Admin.
if ( ! is_admin() ) {
return;
}
$this->onboarding();
include_once __DIR__ . '/Includes/Source/Form.php';
$this->form = new Form( $this->prefix, $this->get_path() . 'templates/' );
/**
* WPHOOK: Filter -> enable/disable onboarding redirect after plugin activation.
*
* Same filter used during activation, for preventing any redirection bypass.
*
* @param bool $redirect Whether to redirect or not.
* @var bool
* @see {@method `Wizard::enable_onboarding()`}
* @since 1.0
*/
$redirect = apply_filters( 'hzfex_enable_onboarding_redirect', true, $this->get_prefix() );
// Start onboarding wizard if everything seems OKAYYYY!!!
if (
'yes' === get_transient( $this->get_prefix() . '_onboarding_redirect' ) &&
true === current_user_can( $this->get_capability() ) &&
true === $redirect
) {
add_action( 'admin_init', array( $this, 'init' ) );
}
}
/**
* Starts onboarding.
*
* @see {@method `Config::start_onboarding()`}
* @since 1.0
*/
public function init() {
$get = wp_unslash( $_GET ); // phpcs:disable WordPress.Security.NonceVerification.Recommended
$current_page = isset( $get['page'] ) ? $get['page'] : false;
$multi_activated = isset( $get['activate-multi'] );
// Bail early on these events.
if ( wp_doing_ajax() || is_network_admin() ) {
return;
}
// Bail if on onboarding page or multiple-plugins activated at once.
if ( $this->get_page() === $current_page || $multi_activated ) {
delete_transient( $this->get_prefix() . '_onboarding_redirect' );
return;
}
// Once redirected, that's enough. Don't do it ever again.
delete_transient( $this->get_prefix() . '_onboarding_redirect' );
// Voila!!! We are now at onboarding intro page.
wp_safe_redirect( admin_url( 'admin.php?page=' . $this->get_page() ) );
exit;
}
/**
* Gets onboarding wizard prefix.
*
* @return string
*
* @since 1.0
*/
public function get_prefix() {
return $this->prefix;
}
/**
* Gets user capability to run onboarding wizard.
*
* @return string
*
* @since 1.0
*/
public function get_capability() {
return $this->capability;
}
/**
* Creates and gets onboarding wizard page slug.
*
* @return string
*
* @since 1.0
* @example usage
*
* ```
* // To point to onboarding page, create URL like this.
* admin_url( 'admin.php?page=' . Config::get_page() );
* ```
*/
public function get_page() {
return $this->get_prefix() . '-onboarding-setup';
}
/**
* Gets onboarding root URL.
*
* @return string
*
* @since 1.0
*/
public function get_url() {
return plugin_dir_url( __FILE__ );
}
/**
* Gets onboarding root path.
*
* @return string
*
* @since 1.0
*/
public function get_path() {
return plugin_dir_path( __FILE__ );
}
/**
* Instantiates config if namespace matches.
*
* Singleton config class in this namespace.
*
* If `$src` and `$name` is given, then onboarding will be instantiated from that classname, if valid.
* {@see @method `Config::onboarding()`}.
*
* @param string $prefix Prefix for onboarding wizard. Only change once set if you know the consequences.
* It will be used for WordPress Hooks, Options, Transients, etc.
* {@todo MUST BE A UNIQUE PREFIX FOR YOUR PLUGIN}.
* @param string $capability The current user capability who can manage onboarding.
* {@todo CHANGE CAPABILITY ONLY IF ABSOLUTELY NECESSARY}.
* For e.g. If will be using WooCommerce later, or maybe installing WooCommerce
* as dependency plugin from within intro page of onboarding wizard,
* then maybe set it as `manage_woocommerce` (although not necessary).
* This filters the user cap and apply `manage_woocommerce` capability to `admin`
* even if `WooCommerce` not installed yet.
* {@see @method `TheWebSolver\Core\Admin\Onboarding\Wizard::init()`}.
* @param string $src (Optional) The child-class file source path.
* @param string $name (optional) The onboarding wizard child-class extending abstract class.
*
* @return Config|void Config instance in this namespace, die with WP_Error msg if namespace not declared or did't match.
*
* @since 1.0
* @static
*/
public static function get( string $prefix, string $capability = 'manage_options', $src = '', string $name = '' ) {
static $config = false;
$namespace = self::validate( $capability, $prefix );
if ( is_wp_error( $namespace ) ) {
wp_die(
wp_kses_post( $namespace->get_error_message() ),
wp_kses_post( $namespace->get_error_data() )
);
}
if ( ! is_a( $config, get_class() ) ) {
$config = new self();
// Set onboarding prefix.
$config->prefix = $prefix;
// Set onboarding capability.
$config->capability = $capability;
// Set external child-class file.
$config->child_file = $src;
// Set external child-class name.
$config->child_name = $name;
// Include the web solver API abstraction class.
include_once __DIR__ . '/thewebsolver.php';
// Include the main onboarding abstract class.
include_once __DIR__ . '/Includes/Source/Onboarding.php';
// WordPress Hook to start onboarding.
add_action( 'init', array( $config, 'start_onboarding' ) );
}
return $config;
}
/**
* Validates namespace and prefix.
*
* @param string $cap The current user capability.
* @param string $prefix The onboarding wizard prefix.
*
* @return string|WP_Error Namespace if valid, `WP_Error` otherwise.
*
* @since 1.0
* @since 1.1 Removed translation support (info was only for developers).
* @static
*/
private static function validate( string $cap, string $prefix ) {
// Trim beginning slashes from namespace, if any, to exact match namespace.
$ns = __NAMESPACE__;
$dir = ltrim( dirname( __FILE__ ), ABSPATH ) . '/';
$located = '';
$default = 'My_Plugin\\My_Feature';
if ( ! function_exists( 'wp_get_current_user' ) ) {
include_once ABSPATH . 'wp-includes/pluggable.php';
}
$user_caps = wp_get_current_user()->allcaps;
// Only show directory information if user has given capability.
if ( isset( $user_caps[ $cap ] ) && $user_caps[ $cap ] ) {
$located = sprintf( 'Files are located inside directory: <code><b><em>%1$s</em></b></code>', $dir );
}
if ( 'thewebsolver' === $prefix || '' === $prefix ) {
// Prefix errors.
$prefix_title = 'Onboarding class prefix error';
$prefix_msg = sprintf( '<h1>%1$s</h1><p>Use your plugin\'s unique prefix for <code><b><em>Config::get()</em></b></code> to get the config instance.</p><p>Default prefix <b><em>"thewebsolver"</em></b> is being used.</p><p>%2$s</p>', $prefix_title, $located );
return new WP_Error( 'prefix_mismatch', $prefix_msg, $prefix_title );
}
$note = 'Set unique namespace to instantiate <code><b><em>Config::get()</em></b></code> and declare the same namespace at the top of the <code><b><em>Config.php</em></b></code> and <code><b><em>Includes/Wizard.php</em></b></code> files.';
// Case where namespace not declared.
if ( 0 === strlen( __NAMESPACE__ ) ) {
$notitle = 'Namespace not declared';
$nons = 'Onboarding Config was instantiated without namespace.';
return new WP_Error( 'namespace_not_declared', sprintf( '<h1>%1$s</h1><p>%2$s</p><p>%3$s</p><p>%4$s</p>', $notitle, $nons, $note, $located ), $notitle );
}
// Case where default namespace is being used.
if ( __NAMESPACE__ === $default ) {
$title = 'Namespace Not Unique';
return new WP_Error( 'namespace_no_match', sprintf( '<h1>%1$s</h1><p>Onboarding Config was instantiated with default namespace.</p><p>%2$s</p><p>%3$s</p><hr><p>Change this default namespace: <code><b><em>%4$s</em></b></code></p>', $title, $note, $located, $default ), $title );
}
return $ns;
}
/**
* Private constructor to prevent direct instantiation.
*/
private function __construct() {}
}