-
Notifications
You must be signed in to change notification settings - Fork 23
/
wds-required-plugins.php
722 lines (624 loc) · 18.7 KB
/
wds-required-plugins.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
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
<?php // @codingStandardsIgnoreLine: Filename okay here.
/**
* Plugin Name: WDS Required Plugins
* Plugin URI: http://webdevstudios.com
* Description: Forcefully require specific plugins to be activated.
* Author: WebDevStudios
* Author URI: http://webdevstudios.com
* Version: 1.3.0
* Text Domain: wds-required-plugins
* Domain Path: /languages
* License: GPLv2
* Props: 1.0.0 - Patrick Garman, Justin Sternberg, Brad Parbs
*
* @package WDS_Required_Plugins
* @since 0.1.4
*
* Required: true
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Required plugins class
*
* @package WordPress
*
* @subpackage Project
* @since Unknown
*/
final class WDS_Required_Plugins {
/**
* Instance of this class.
*
* @author Justin Sternberg
* @since Unknown
*
* @var WDS_Required_Plugins object
*/
public static $instance = null;
/**
* Whether text-domain has been registered.
*
* @var bool
*
* @author Justin Sternberg
* @since Unknown
*/
private static $l10n_done = false;
/**
* Text/markup for required text.
*
* @see self::required_text_markup() This will set the default value, but we
* can't here because we want to translate it.
*
* @var string
*
* @author Justin Sternberg
* @since Unknown
*/
private $required_text = '';
/**
* Required Text Code.
*
* @author Aubrey Portwood
* @since 1.0.0
*
* @var string
*/
private $required_text_code = '<span style="color: #888">%s</span>';
/**
* Logged incompatibilities.
*
* @author Aubrey Portwood
* @since 1.0.0
*
* @var array
*/
public $incompatibilities = [];
/**
* Creates or returns an instance of this class.
*
* @since 0.1.0
* @author Justin Sternberg
*
* @return WDS_Required_Plugins A single instance of this class.
*/
public static function init() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Initiate our hooks
*
* @since 0.1.0
* @author Unknown
*
*/
private function __construct() {
if ( $this->incompatible() ) {
return;
}
// Attempt activation + load text domain in the admin.
add_action( 'admin_init', [ $this, 'activate_if_not' ] );
add_action( 'admin_init', [ $this, 'required_text_markup' ] );
add_filter( 'extra_plugin_headers', [ $this, 'add_required_plugin_header' ] );
// Filter plugin links to remove deactivate option.
add_filter( 'plugin_action_links', [ $this, 'filter_plugin_links' ], 10, 2 );
add_filter( 'network_admin_plugin_action_links', [ $this, 'filter_plugin_links' ], 10, 2 );
// Remove plugins from the plugins.
add_filter( 'all_plugins', [ $this, 'maybe_remove_plugins_from_list' ] );
// Load text domain.
add_action( 'init', [ $this, 'l10n' ] );
}
/**
* Are we currently incompatible with something?
*
* @author Aubrey Portwood
* @since 1.0.0
*
* @return bool True if we are incompatible with something, false if not.
*/
public function incompatible() {
// Our tests.
$this->incompatibilities = [
/*
* WP Migrate DB Pro is performing an AJAX migration.
*/
(bool) $this->is_wpmdb(),
];
/**
* Add or filter your incompatibility tests here.
*
* Note, the entire array needs to be false for
* there to not be any incompatibilities.
*
* @author Aubrey Portwood
*
* @since 1.0.0
* @param array $incom A list of tests that determine incompatibilities.
*/
$filter = apply_filters( 'wds_required_plugins_incompatibilities', $this->incompatibilities );
if ( is_array( $filter ) ) {
// The filter might have added more tests, use those.
$this->incompatibilities = $filter;
}
// If the array has any incompatibility, we are incompatible.
return in_array( true, $this->incompatibilities, true );
}
/**
* Is WP Migrate DB Pro doing something?
*
* @author Aubrey Portwood
* @since 1.0.0
*
* @return bool True if we find wpmdb set as the action.
*/
public function is_wpmdb() {
// @codingStandardsIgnoreLine: Nonce validation not necessary here.
return wp_doing_ajax() && stristr( isset( $_POST['action'] ) && is_string( $_POST['action'] ) ? $_POST['action'] : '', 'wpmdb_' );
}
/**
* Activate required plugins if they are not.
*
* @since 0.1.1
* @author Unknown
*/
public function activate_if_not() {
// If we're installing multisite, then disable our plugins and bail out.
if ( defined( 'WP_INSTALLING_NETWORK' ) && WP_INSTALLING_NETWORK ) {
add_filter( 'pre_option_active_plugins', '__return_empty_array' );
add_filter( 'pre_site_option_active_sitewide_plugins', '__return_empty_array' );
return;
}
// Loop through each plugin we have set as required.
foreach ( $this->get_required_plugins() as $plugin ) {
$this->maybe_activate_plugin( $plugin );
}
// If we're multisite, attempt to network activate our plugins.
if ( is_multisite() && function_exists( 'is_plugin_active_for_network' ) ) {
// Loop through each network required plugin.
foreach ( $this->get_network_required_plugins() as $plugin ) {
$this->maybe_activate_plugin( $plugin, true );
}
}
}
/**
* Activates a required plugin if it's found, and auto-activation is enabled.
*
* @since 0.1.4
* @author Unknown
*
* @author Aubrey Portwood <[email protected]>
* @since 1.0.1 Added Exception if plugin not found.
*
* @param string $plugin The plugin to activate.
* @param bool $network Whether we are activating a network-required plugin.
*
* @throws Exception If we can't activate a required plugin.
*/
public function maybe_activate_plugin( $plugin, $network = false ) {
/**
* Filter if you don't want the required plugin to auto-activate. `true` by default.
*
* If the plugin you are making required is not active, this will
* not force it to be activated.
*
* @author Justin Sternberg
* @since Unknown
*
* @param bool $auto_activate Should we auto-activate the plugin, true by default.
* @param string $plugin The plugin being activated.
* @param string $network On what network?
*/
$auto_activate = apply_filters( 'wds_required_plugin_auto_activate', true, $plugin, $network );
if ( ! $auto_activate ) {
// Don't auto-activate.
return;
}
/**
* Is this plugin supposed to be activated network wide?
*
* @author Justin Sternberg
* @since Unknown
*
* @param bool $is_multisite The value of is_multisite().
* @param string $plugin The plugin being activated.
* @param string $network The network.
*/
$is_multisite = apply_filters( 'wds_required_plugin_network_activate', is_multisite(), $plugin, $network );
// Filter if you don't want the required plugin to network-activate by default.
$network_wide = $network ? true : $is_multisite;
// Where is the plugin file?
$abs_plugin = trailingslashit( WP_PLUGIN_DIR ) . $plugin;
// Only if the plugin file exists, if it doesn't it needs to fail below.
if ( file_exists( $abs_plugin ) ) {
// Don't activate if already active.
if ( is_plugin_active( $plugin ) ) {
return;
}
// Don't activate if already network-active.
if ( $network && is_plugin_active_for_network( $plugin ) ) {
return;
}
}
// Activate the plugin.
$result = activate_plugin( $plugin, null, $network_wide );
// If we activated correctly, than return results of that.
if ( ! is_wp_error( $result ) ) {
return;
}
/**
* Filter if a plugin is not found (that's required).
*
* For instance to disable all logging you could:
*
* add_filter( 'wds_required_plugin_log_if_not_found', '__return_false' );
*
* Or, you could do it on a case-by-case basis with the $plugin being sent.
*
* @author Justin Sternberg
* @since Unknown
*
* @param bool $log_not_found Whether the plugin is indeed found or not,
* default to true in the normal case. Set to false
* if you would like to override that and not log it,
* for instance, if it's intentional.
*/
$log_not_found = apply_filters( 'wds_required_plugin_log_if_not_found', true, $plugin, $result, $network );
if ( ! $log_not_found ) {
return;
}
// translators: %1 and %2 are explained below. Set default log text.
$default_log_text = __( 'Required Plugin auto-activation failed for: %1$s, with message: %2$s', 'wds-required-plugins' );
// Filter the logging message format/text.
$log_msg_format = apply_filters( 'wds_required_plugins_error_log_text', $default_log_text, $plugin, $result, $network );
// Get our error message.
$error_message = method_exists( $result, 'get_error_message' ) ? $result->get_error_message() : '';
// The message.
$s_message = sprintf( esc_attr( $log_msg_format ), esc_attr( $plugin ), esc_attr( $error_message ) );
/**
* Filter whether we should stop if a plugin is not found.
*
* @since 1.1.0
* @author Aubrey Portwood <[email protected]>
*
* @param bool $stop_not_found Set to false to not halt execution if a plugin is not found.
*/
$stop_not_found = apply_filters( 'wds_required_plugin_stop_if_not_found', false, $plugin, $result, $network );
if ( $stop_not_found ) {
throw new Exception( $s_message );
} else {
// @codingStandardsIgnoreLine: Throw the right kind of error.
trigger_error( $s_message );
}
}
/**
* The required plugin label text.
*
* @since 0.1.0
* @author Unknown
*/
public function required_text_markup() {
$default = sprintf( $this->required_text_code, __( 'Required Plugin', 'wds-required-plugins' ) );
/**
* Set the value for what shows when a plugin is required.
*
* E.g. by default it's Required, but you could change it to
* "Cannot Deactivate" if you wanted to.
*
* @author Justin Sternberg
* @since Unknown
*
* @param string $default The default value that you can change.
*/
$filtered = apply_filters( 'wds_required_plugins_text', $default );
// The property on this object we'll set for use later.
if ( is_string( $filtered ) ) {
$this->required_text = $filtered;
} else {
$this->required_text = $default;
}
}
/**
* Remove the deactivation link for all custom/required plugins
*
* @since 0.1.0
*
* @param array $actions Array of actions avaible.
* @param string $plugin Slug of plugin.
*
* @author Justin Sternberg
* @author Brad Parbs
* @author Aubrey Portwood Added documentation for filters.
*
* @return array
*/
public function filter_plugin_links( $actions, $plugin ) {
$actions = (array) $actions;
// Get our required plugins for network + normal.
$required_plugins = array_unique( array_merge( $this->get_required_plugins(), $this->get_network_required_plugins() ) );
// Replace these action keys with what we have set for required text.
$action_keys = [
'deactivate',
'network_active',
];
foreach ( $action_keys as $key ) {
// Remove deactivate link for required plugins.
if ( array_key_exists( $key, $actions ) && in_array( $plugin, $required_plugins, true ) ) {
/**
* Should we remove the deactivated text for this plugin?
*
* @author Brad Parbs
* @since Unknown
*
* @param bool $remove Should we remove it? Default to true.
* @param string $plugin What plugin we're talking about.
*/
$wds_required_plugin_network_activate = apply_filters( 'wds_required_plugin_network_activate', true, $plugin );
// Filter if you don't want the required plugin to be network-required by default.
if ( $wds_required_plugin_network_activate ) {
$actions[ $key ] = $this->required_text;
}
}
}
return $actions;
}
/**
* Remove required plugins from the plugins list, if enabled.
*
* Must be enabled using the wds_required_plugin_remove_from_list filter.
* When enabled, all the plugins that end up being WDS Required
* also do not show in the plugins list.
*
* @since 0.1.5
* @author Brad Parbs
* @author Aubrey Portwood Made it so mu-plugins are also unseen.
*
* @param array $plugins Array of plugins.
* @return array Array of plugins.
*/
public function maybe_remove_plugins_from_list( $plugins ) {
/**
* Set to true to skip removing plugins from the list.
*
* Default to false (disabled).
*
* E.g.:
*
* add_filter( 'wds_required_plugin_remove_from_list', '__return_true' );
*
* @author Brad Parbs
* @since Unknown
*
* @param array $enabled Whether or not removing all plugins from the list is enabled.
*/
$enabled = apply_filters( 'wds_required_plugin_remove_from_list', false );
// Allow for removing all plugins from the plugins list.
if ( false === $enabled ) {
// Do not remove any plugins.
return $plugins;
}
// Loop through each of our required plugins.
foreach ( array_merge( $this->get_required_plugins(), $this->get_network_required_plugins() ) as $required_plugin ) {
// Remove from the all plugins list.
unset( $plugins[ $required_plugin ] );
}
// Send it back.
return $plugins;
}
/**
* Get the plugins that are required for the project. Plugins will be registered by the wds_required_plugins filter
*
* @author Justin Sternberg
* @author Aubrey Portwood Added filter documentation.
* @since 0.1.0
*
* @return array
*/
public function get_required_plugins() {
/**
* Set single site required plugins.
*
* Example:
*
* function wds_required_plugins_add( $required ) {
* $required = array_merge( $required, array(
* 'akismet/akismet.php',
* 'wordpress-importer/wordpress-importer.php',
* ) );
*
* return $required;
* }
* add_filter( 'wds_network_required_plugins', 'wds_required_plugins_add' );
*
* @author Brad Parbs
* @author Aubrey Portwood
*
* @since Unknown
*
* @var array
*/
$required_plugins = apply_filters( 'wds_required_plugins', [] );
if ( ! is_array( $required_plugins ) ) {
// The person who filtered this broke it.
return [];
}
$required_plugins = array_merge( $required_plugins, $this->get_header_required_plugins() );
return $required_plugins;
}
/**
* Get the network plugins that are required for the project. Plugins will be registered by the wds_network_required_plugins filter
*
* @since 0.1.3
* @author Patrick Garman
*
* @since 1.0.0 Cleanup and rewrite.
* @author Aubrey Portwood <[email protected]>
*
* @return array
*/
public function get_network_required_plugins() {
/**
* Set multisite site required plugins.
*
* Example:
*
* function wds_required_plugins_add( $required ) {
* $required = array_merge( $required, array(
* 'akismet/akismet.php',
* 'wordpress-importer/wordpress-importer.php',
* ) );
*
* return $required;
* }
* add_filter( 'wds_network_required_plugins', 'wds_required_plugins_add' );
*
* @author Brad Parbs
* @author Aubrey Portwood
*
* @since Unknown
*
* @var array
*/
$required_plugins = apply_filters( 'wds_network_required_plugins', [] );
if ( ! is_array( $required_plugins ) ) {
// The person who filtered this broke it.
return [];
}
return $required_plugins;
}
/**
* Load this library's text domain.
*
* @author Justin Sternberg
* @author Brad Parbs
* @since 0.1.1
*
*/
public function l10n() {
// Only do this one time.
if ( self::$l10n_done ) {
return;
}
// Bail on ajax requests.
if ( wp_doing_ajax() ) {
return;
}
// Bail if we're not in the admin.
if ( ! is_admin() ) {
return;
}
// Don't do anything if the user isn't permitted, or its an ajax request.
if ( ! current_user_can( 'activate_plugins' ) ) {
return;
}
$languages_path = dirname( plugin_basename( __FILE__ ) ) . DIRECTORY_SEPARATOR . 'languages';
// Try to load mu-plugin textdomain.
if ( load_muplugin_textdomain( 'wds-required-plugins', $languages_path ) ) {
self::$l10n_done = true;
return;
}
// If we didn't load, load as a plugin.
if ( load_plugin_textdomain( 'wds-required-plugins', false, $languages_path ) ) {
self::$l10n_done = true;
return;
}
// If we didn't load yet, load as a theme.
if ( load_theme_textdomain( 'wds-required-plugins', dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'languages' ) ) {
self::$l10n_done = true;
return;
}
// If we still didn't load, assume our text domain is right where we are.
$locale = apply_filters( 'plugin_locale', get_locale(), 'wds-required-plugins' );
$mofile = __DIR__ . '/languages/wds-required-plugins-' . $locale . '.mo';
load_textdomain( 'wds-required-plugins', $mofile );
self::$l10n_done = true;
}
/**
* Adds a header field for required plugins when WordPress reads plugin data.
*
* @since 1.2.0
* @author Zach Owen
*
* @param array $extra_headers Extra headers filtered in WP core.
* @return array
*/
public function add_required_plugin_header( $extra_headers ) {
$required_header = $this->get_required_header();
if ( in_array( $required_header, $extra_headers, true ) ) {
return $extra_headers;
}
$extra_headers[] = $required_header;
return $extra_headers;
}
/**
* Return a list of plugins with the required header set.
*
* @since 1.2.0
* @author Zach Owen
*
* @return array
*/
public function get_header_required_plugins() {
$all_plugins = apply_filters( 'all_plugins', get_plugins() );
if ( empty( $all_plugins ) ) {
return;
}
$required_header = $this->get_required_header();
$plugins = [];
/**
* Filter the value for the header that would indicate the plugin as required.
*
* @author Aubrey Portwood <[email protected]>
* @since 1.2.0
*
* @var array
*/
$values = apply_filters( 'wds_required_plugins_required_header_values', [
'true',
'yes',
'1',
'on',
'required',
'require',
] );
foreach ( $all_plugins as $file => $headers ) {
if ( ! in_array( $headers[ $required_header ], $values, true ) ) {
continue;
}
$plugins[] = $file;
}
return $plugins;
}
/**
* Get the key to use for the required plugin header identifier.
*
* @author Zach Owen
* @since 1.2.0
*
* @return string
*/
private function get_required_header() {
$header_text = 'Required';
/**
* Filter the text used as the identifier for the plugin being
* required.
*
* @author Zach Owen
* @since 1.2.0
*
* @param string $header The string to use as the identifier.
*/
$header = apply_filters( 'wds_required_plugin_header', $header_text );
if ( ! is_string( $header ) || empty( $header ) ) {
return $header_text;
}
return $header;
}
}
// Init.
WDS_Required_Plugins::init();