PluginProbe
Plugin Groups / 2.0.6
Plugin Groups v2.0.6
trunk 1.0.3 1.1.0 1.2.1 1.2.2 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.9 3.0.0
plugin-groups / classes / class-plugin-groups.php

class-plugin-groups.php in Plugin Groups 2.0.6, at classes/class-plugin-groups.php

1,111 lines 27.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Core class for Plugin Groups.
4 *
5 * @package plugin_groups
6 */
7
8 namespace Plugin_Groups;
9
10 use WP_Admin_Bar;
11
12 /**
13 * Plugin_Groups Class.
14 */
15 class Plugin_Groups {
16
17 /**
18 * The single instance of the class.
19 *
20 * @var Plugin_Groups
21 */
22 protected static $instance = null;
23
24 /**
25 * Holds the version of the plugin.
26 *
27 * @var string
28 */
29 protected $version;
30
31 /**
32 * Holds the plugin name.
33 *
34 * @var string
35 */
36 protected $plugin_name;
37
38 /**
39 * Holds the plugin config.
40 *
41 * @var array
42 */
43 protected $config = array();
44
45 /**
46 * Holds the Groups.
47 *
48 * @var array
49 */
50 protected $groups = array();
51
52 /**
53 * Holds the menu slug.
54 *
55 * @var string
56 */
57 public static $slug = 'plugin-groups';
58
59 /**
60 * Holds the current group.
61 *
62 * @var string
63 */
64 protected $current_group;
65
66 /**
67 * Holds the current status.
68 *
69 * @var string
70 */
71 protected $current_status;
72
73 /**
74 * Holds a list of missing plugins.
75 *
76 * @var array
77 */
78 protected $missing = array();
79
80 /**
81 * Holds the current group and status path.
82 *
83 * @var string
84 */
85 protected $current_nav_path;
86
87 /**
88 * Hold the record of the plugins current version for upgrade.
89 *
90 * @var string
91 */
92 const VERSION_KEY = '_plugin_groups_version';
93
94 /**
95 * Hold the config storage key.
96 *
97 * @var string
98 */
99 const CONFIG_KEY = '_plugin_groups_config';
100
101 /**
102 * Initiate the plugin_groups object.
103 */
104 public function __construct() {
105
106 require_once ABSPATH . 'wp-admin/includes/plugin.php';
107 $plugin = get_plugin_data( PLGGRP_CORE );
108 $this->plugin_name = $plugin['Name'];
109 $this->version = $plugin['Version'];
110
111 spl_autoload_register( array( $this, 'autoload_class' ), true, false );
112
113 // Start hooks.
114 $this->setup_hooks();
115
116 // Init the bulk actions.
117 new Bulk_Actions( $this );
118 new Extras( $this );
119 new Rest( $this );
120 }
121
122 /**
123 * Setup and register WordPress hooks.
124 */
125 protected function setup_hooks() {
126
127 // Load plugin text domain
128 add_action( 'init', array( $this, 'plugin_groups_init' ), PHP_INT_MAX ); // Always the last thing to init.
129 add_action( 'admin_init', array( $this, 'admin_init' ) );
130 add_action( 'admin_menu', array( $this, 'admin_menu' ) );
131 add_action( 'network_admin_menu', array( $this, 'admin_menu' ) );
132 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
133 add_filter( 'views_plugins', array( $this, 'add_groups' ), PHP_INT_MAX );
134 add_filter( 'views_plugins-network', array( $this, 'add_groups' ), PHP_INT_MAX );
135 add_filter( 'all_plugins', array( $this, 'catch_selected_group' ) );
136 add_filter( 'show_advanced_plugins', array( $this, 'filter_shown_status' ), 10, 2 );
137 add_filter( 'site_transient_update_plugins', array( $this, 'alter_update_plugins' ) );
138 add_action( 'pre_current_active_plugins', array( $this, 'render_group_navigation' ) );
139 add_filter( 'bulk_actions-plugins', array( $this, 'bulk_actions' ) );
140 add_action( 'admin_bar_menu', array( $this, 'admin_bar_item' ), 100 );
141 add_filter( 'self_admin_url', array( $this, 'append_group_to_self' ), 10, 3 );
142 add_filter( 'plugin_action_links', array( $this, 'append_group_to_actions' ) );
143 }
144
145 /**
146 * Append the current group id to plugin action links to maintain selection on activation and deactivation of plugins.
147 *
148 * @param array $actions Array of action links.
149 *
150 * @return array
151 */
152 public function append_group_to_actions( $actions ) {
153
154 if ( isset( $this->current_group ) ) {
155 foreach ( $actions as &$tag ) {
156 $parts = shortcode_parse_atts( $tag );
157 if ( isset( $parts['href'] ) ) {
158 $url = html_entity_decode( $parts['href'] );
159 $url = add_query_arg( self::$slug, $this->current_group, $url );
160
161 $tag = str_replace( $parts['href'], htmlentities( $url ), $tag );
162 }
163 }
164 }
165
166 return $actions;
167 }
168
169 /**
170 * Append the group to redirect URLS after activation and deactivation of plugins.
171 *
172 * @param string $url The url to append to.
173 * @param string $path The path location.
174 *
175 * @return string
176 */
177 public function append_group_to_self( $url, $path ) {
178
179 if ( 'plugins.php' === wp_parse_url( $path, PHP_URL_PATH ) ) {
180 if ( ! isset( $this->current_group ) ) {
181 // Init the current group if not set yet.
182 $this->catch_selected_group();
183 }
184 // If we found a group, then append it to the self URL on the plugins page to maintain groups in nav.
185 if ( isset( $this->current_group ) ) {
186 $url = add_query_arg( self::$slug, $this->current_group, $url );
187 }
188 }
189
190 return $url;
191 }
192
193 /**
194 * Remove plugins from the update available.
195 *
196 * @param object $data The data object.
197 *
198 * @return object
199 */
200 public function alter_update_plugins( $data ) {
201
202 if ( $this->current_group ) {
203 $keys = array_keys( $this->groups[ $this->current_group ] );
204 foreach ( $data->response as $key => $plugin ) {
205 if ( ! in_array( $key, $keys, true ) ) {
206 unset( $data->response[ $key ] );
207 }
208 }
209 }
210
211 return $data;
212 }
213
214 /**
215 * Remove status that are not relevant.
216 *
217 * @param bool $show Flag to show.
218 * @param string $status The current status
219 *
220 * @return bool
221 */
222 public function filter_shown_status( $show, $status ) {
223
224 global $plugins;
225
226 static $removes = array(
227 'mustuse',
228 'dropins',
229 );
230 if ( $this->current_group && in_array( $status, $removes, true ) ) {
231 $show = false;
232 }
233 if ( true === $this->config['params']['legacyGrouping'] ) {
234 $plugins += $this->groups;
235 }
236
237 return $show;
238 }
239
240 /**
241 * Render the Group navigation.
242 */
243 public function render_group_navigation() {
244
245 // Use new group system.
246 if ( true !== $this->config['params']['legacyGrouping'] && 'groups-dropdown' !== $this->config['params']['navStyle'] ) {
247 $parts = array();
248 $parts[] = $this->make_all_tag();
249 foreach ( $this->groups as $key => $plugins ) {
250 $parts[] = $this->make_group_tag( $key );
251 }
252
253 $groups = implode( "", $parts );
254 $group_set = Utils::build_tag( 'ul', array( 'class' => array( $this->config['params']['navStyle'] ) ), $groups );
255 $html = Utils::build_tag( 'div', array( 'class' => self::$slug ), $group_set );
256 if ( 1 < count( $parts ) ) {
257 echo wp_kses( $html, wp_kses_allowed_html( 'post' ) );
258 }
259 }
260 }
261
262 /**
263 * Add dropdown navigation on bulk actions.
264 *
265 * @param array $actions Unchanged actions.
266 *
267 * @return array
268 */
269 public function bulk_actions( $actions ) {
270
271 if ( true !== $this->config['params']['legacyGrouping'] && 'groups-dropdown' === $this->config['params']['navStyle'] ) {
272 $this->dropdown_navigation();
273 }
274
275 return $actions;
276 }
277
278 /**
279 * Render a dropdown navigation.
280 *
281 * @param bool $echo Optional flag to echo the field or return the string.
282 *
283 * @return void|string
284 */
285 public function dropdown_navigation( $echo = true ) {
286
287 $html = array();
288 $html[] = '<label for="bulk-action-selector-groups" class="screen-reader-text">' . __( 'All groups', self::$slug ) . '</label>';
289 $html[] = '<select id="bulk-action-selector-groups" onChange="window.location = this.value">';
290 $html[] = '<option value="' . esc_url( $this->get_nav_url() ) . '" ' . ( ! empty( $this->current_group ) ? 'selected="selected"' : '' ) . '>' . __( 'All groups', self::$slug ) . '</option>';
291
292 foreach ( $this->groups as $key => $plugins ) {
293 $group = $this->config['groups'][ $key ];
294 $selected = '';
295 if ( $this->current_group === $key ) {
296 $selected = ' selected="selected"';
297 }
298
299 $html[] = "\t" . '<option value="' . esc_url( $this->get_nav_url( $key ) ) . '"' . $selected . '>' . $group['name'] . ' (' . count( $plugins ) . ')</option>' . "\n";
300 }
301
302 $html[] = "</select>\n";
303 $html = implode( $html );
304 if ( false === $echo ) {
305 return $html;
306 }
307 echo $html;
308 }
309
310 /**
311 * Catch the selected group.
312 *
313 * @param array $plugins List of plugins.
314 *
315 * @return array
316 */
317 public function catch_selected_group( $plugins = array() ) {
318
319 global $status;
320
321 $this->current_status = $status;
322 $selected_group = filter_input( INPUT_GET, self::$slug, FILTER_SANITIZE_STRING );
323 if ( $selected_group && isset( $this->groups[ $selected_group ] ) ) {
324 $this->current_group = $selected_group;
325 if ( ! empty( $plugins ) ) {
326 $plugins = $this->groups[ $selected_group ];
327
328 // Add selection path.
329 $this->current_nav_path .= ' | ' . $this->config['groups'][ $selected_group ]['name'];
330 }
331 }
332
333 // Add our styles if we have groups;
334 if ( ! empty( $this->groups ) ) {
335 wp_enqueue_style( self::$slug . '-navbar' );
336 }
337
338 return $plugins;
339 }
340
341 /**
342 * Get a url link for group navigation.
343 *
344 * @param null|string $id The group ID or null for all.
345 *
346 * @return string
347 */
348 protected function get_nav_url( $id = null ) {
349
350 $url = filter_input( INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_URL );
351 if ( ! $url || false === strpos( 'plugins.php', $url ) ) {
352 // Just in case.
353 $url = self_admin_url( 'plugins.php' );
354 }
355 if ( null === $id ) {
356 // Null id is for the 'All groups'.
357 return remove_query_arg( self::$slug, $url );
358 }
359
360 // If no plugins get a link back to admin.
361 if ( empty( $this->groups[ $id ] ) ) {
362 return add_query_arg( 'page', self::$slug, $url );
363 }
364
365 return add_query_arg(
366 array(
367 'plugin_status' => $this->current_status,
368 self::$slug => $id,
369
370 ),
371 $url
372 );
373 }
374
375 /**
376 * Get the current group.
377 *
378 * @return array|null
379 */
380 public function get_current_group() {
381
382 if ( ! isset( $this->current_group ) ) {
383 $this->catch_selected_group();
384 }
385
386 return $this->current_group ? $this->config['groups'][ $this->current_group ] : null;
387 }
388
389 /**
390 * Get the groups.
391 *
392 * @return array
393 */
394 public function get_groups() {
395
396 return array_values( $this->config['groups'] );
397 }
398
399 /**
400 * Make a group tag.
401 *
402 * @param string $id The Group ID.
403 *
404 * @return string
405 */
406 protected function make_group_tag( $id ) {
407
408 $group = $this->config['groups'][ $id ];
409 $total = count( $this->groups[ $id ] );
410 $counter = '';
411
412 if ( ! empty( $total ) ) {
413
414 $counter = Utils::build_tag( 'span', array( 'class' => 'count' ), " ({$total})" );
415 }
416 $li_atts = array(
417 'class' => array(
418 'group-link',
419 $group['id'],
420 ),
421 );
422 $link_atts = array(
423 'href' => $this->get_nav_url( $id ),
424 );
425 if ( $group['id'] === $this->current_group ) {
426 $li_atts['class'][] = 'current';
427 $link_atts['class'] = 'current';
428 $link_atts['aria-current'] = 'page';
429 }
430
431 $link = Utils::build_tag( 'a', $link_atts, $group['name'] . $counter );
432
433 return Utils::build_tag( 'li', $li_atts, $link );
434 }
435
436 /**
437 * Load presets into groups for built-in and filtered presets.
438 *
439 * @return array
440 */
441 protected function load_presets() {
442
443 $groups = array(
444 'WooCommerce' => array( 'WooCommerce' ),
445 'Easy Digital Downloads' => array( 'Easy Digital Downloads' ),
446 'Ninja Forms' => array( 'Ninja Forms' ),
447 'Gravity Forms' => array( 'Gravity Forms' ),
448 'WPForms' => array( 'WPForms' ),
449 'E-Commerce' => array(
450 'WooCommerce',
451 'Easy Digital Downloads',
452 ),
453 'Forms' => array(
454 'Ninja Forms',
455 'Gravity Forms',
456 'WPForms',
457 'Formidable Forms',
458 ),
459 'SEO' => array(
460 'All in One SEO',
461 'Yoast SEO',
462 ),
463 // @todo: Add more categories for presets.
464 );
465
466 /**
467 * Filter the presets (legacy)
468 *
469 * @deprecated 2.0.0
470 */
471 $groups = apply_filters( 'plugin-groups-get-presets', $groups );
472
473 /**
474 * Filter preset plugin groups.
475 *
476 * @hook get_preset_plugin_groups
477 * @since 2.0.0
478 *
479 * @param $groups {array} The preset groups.
480 *
481 * @return array
482 */
483 $presets = apply_filters( 'get_preset_plugin_groups', $groups );
484 $preset_groups = array();
485 foreach ( $presets as $name => $keywords ) {
486 $id = sanitize_title( $name );
487 $preset_groups[ $id ] = array(
488 'id' => $id,
489 'name' => $name,
490 'plugins' => array(),
491 'keywords' => $keywords,
492 );
493 }
494
495 return array(
496 'preset_groups' => $preset_groups,
497 'presets' => array_keys( $groups ),
498 );
499 }
500
501 /**
502 * Make all link tag.
503 *
504 * @return string
505 */
506 protected function make_all_tag() {
507
508 $link_atts = array(
509 'href' => $this->get_nav_url(),
510 );
511 if ( empty( $this->current_group ) ) {
512 $link_atts['class'] = 'current';
513 $link_atts['aria-current'] = 'page';
514 }
515 $link = Utils::build_tag( 'a', $link_atts, __( 'All Groups', self::$slug ) );
516
517 return Utils::build_tag( 'li', array( 'class' => array( 'group-link', '__allgroups' ) ), $link );
518 }
519
520 /**
521 * Add new filter view
522 *
523 * @param array $views The list of nav tags for plugin statuses.
524 *
525 * @return array
526 */
527 public function add_groups( $views ) {
528
529 foreach ( $views as &$tag ) {
530 if ( preg_match( '/<a([^>]*?)>{1}/', $tag, $found ) ) {
531 $atts = Utils::get_tag_attributes( $found[1] );
532 $url = add_query_arg( self::$slug, $this->current_group, $atts['href'] );
533 $tag = str_replace( $atts['href'], $url, $tag );
534 wp_parse_str( wp_parse_url( $atts['href'], PHP_URL_QUERY ), $query );
535 if ( isset( $query['plugin_status'] ) && $query['plugin_status'] === $this->current_status ) {
536 $names = explode( ' ', wp_kses( $tag, array() ) );
537 array_pop( $names );
538 $this->current_nav_path .= ' | ' . implode( ' ', $names );
539 }
540 }
541 }
542
543 // Legacy.
544 if ( true === $this->config['params']['legacyGrouping'] ) {
545 foreach ( $this->groups as $key => $plugins ) {
546 $views[ $key ] = $this->make_group_tag( $key );
547 }
548 }
549
550 // @todo: Make method.
551 if ( ! empty( $this->current_nav_path ) ) {
552 $append = array(
553 'name' => $this->current_nav_path,
554 );
555 wp_add_inline_script(
556 'wp-util',
557 'var appendHead = ' . wp_json_encode(
558 $append
559 ) . '; var pluginsHead = document.getElementsByClassName(\'wp-heading-inline\' );if( pluginsHead.length ) {pluginsHead[0].innerText += appendHead.name}'
560 );
561 }
562
563 return $views;
564 }
565
566 /**
567 * Save the current config.
568 *
569 * @param null|int $site_id The site ID to save for.
570 *
571 * @return bool
572 */
573 public function save_config( $site_id = null ) {
574
575 if ( is_multisite() ) {
576 if ( null === $site_id ) {
577 $site_id = get_current_blog_id();
578 }
579 $success = update_network_option( $site_id, self::CONFIG_KEY, $this->config );
580 } else {
581 $success = update_option( self::CONFIG_KEY, $this->config );
582 }
583
584 return $success;
585 }
586
587 /**
588 * Autoloader by Locating and finding classes via folder structure.
589 *
590 * @param string $class class name to be checked and autoloaded.
591 */
592
593 function autoload_class( $class ) {
594
595 $class_location = self::locate_class_file( $class );
596 if ( $class_location ) {
597 include_once $class_location;
598 }
599 }
600
601 /**
602 * Locates the path to a requested class name.
603 *
604 * @param string $class The class name to locate.
605 *
606 * @return string|null
607 */
608 static public function locate_class_file( $class ) {
609
610 $return = null;
611 $parts = explode( '\\', strtolower( str_replace( '_', '-', $class ) ) );
612 $core = array_shift( $parts );
613 $self = strtolower( str_replace( '_', '-', __CLASS__ ) );
614 if ( $core === self::$slug ) {
615 $name = 'class-' . strtolower( array_pop( $parts ) ) . '.php';
616 $parts[] = $name;
617 $path = PLGGRP_PATH . 'classes/' . implode( '/', $parts );
618 if ( file_exists( $path ) ) {
619 $return = $path;
620 }
621 }
622
623 return $return;
624 }
625
626 /**
627 * Get the plugin version
628 */
629 public function version() {
630
631 return $this->version;
632 }
633
634 /**
635 * Check plugin_groups version to allow 3rd party implementations to update or upgrade.
636 */
637 protected function check_version() {
638
639 $previous_version = get_option( self::VERSION_KEY, 0.0 );
640 $new_version = $this->version();
641 if ( version_compare( $previous_version, $new_version, '<' ) ) {
642 if ( version_compare( $previous_version, '2.0.0', '<' ) ) {
643 $data = get_option( 'plugin_groups_plugin_groups', array() );
644 if ( ! empty( $data ) ) {
645 $new_config = $this->convert_legacy_groups( $data );
646 update_option( self::CONFIG_KEY, $new_config );
647 }
648 }
649 // Allow for updating.
650 do_action( "_plugin_groups_version_upgrade", $previous_version, $new_version );
651 // Update version.
652 update_option( self::VERSION_KEY, $new_version, true );
653 }
654 }
655
656 /**
657 * Convert legacy configs.
658 *
659 * @param array $data Array of previous version config.
660 *
661 * @return array
662 */
663 protected function convert_legacy_groups( $data ) {
664
665 $groups = array();
666 foreach ( $data['group'] as $group ) {
667 $keywords = explode( "\n", $group['config']['keywords'] );
668 $new_group = array(
669 'id' => $group['_id'],
670 'name' => $group['config']['group_name'],
671 'plugins' => isset( $group['config']['plugins'] ) ? $group['config']['plugins'] : array(),
672 'keywords' => array_filter( $keywords ),
673 );
674 $groups[ $group['_id'] ] = $new_group;
675 }
676
677 // Set up the new config.
678 $config = $this->get_default_config();
679 $config['groups'] = $groups;
680 $config['selectedPresets'] = isset( $data['presets'] ) ? $data['presets'] : array();
681 $config['params']['legacyGrouping'] = true;
682 $config['params']['navStyle'] = 'subsubsub';
683
684 return $config;
685 }
686
687 /**
688 * Initialise plugin_groups.
689 */
690 public function plugin_groups_init() {
691
692 // Check version.
693 $this->check_version();
694
695 // Load config.
696 $config = $this->load_config();
697 $this->set_config( $config );
698
699 /**
700 * Init the settings system
701 *
702 * @param Plugin_Groups ${slug} The core object.
703 */
704 do_action( 'plugin_groups_init' );
705 }
706
707 /**
708 * Hook into admin_init.
709 */
710 public function admin_init() {
711
712 if ( ! empty( $_POST['plugin-group-config'] ) ) {
713 $data = stripslashes( $_POST['plugin-group-config'] );
714 $data = json_decode( $data, true );
715 update_option( Plugin_Groups::CONFIG_KEY, $data );
716 wp_safe_redirect( admin_url( 'plugins.php?page=plugin-groups' ) );
717 }
718 $asset = include PLGGRP_PATH . 'js/' . self::$slug . '.asset.php';
719 wp_register_script( self::$slug, PLGGRP_URL . 'js/' . self::$slug . '.js', $asset['dependencies'], $asset['version'], true );
720 wp_register_style( self::$slug, PLGGRP_URL . 'css/' . self::$slug . '.css', array(), $asset['version'] );
721 wp_register_style( self::$slug . '-navbar', PLGGRP_URL . 'css/' . self::$slug . '-navbar.css', array(), $asset['version'] );
722 }
723
724 /**
725 * Hook into the admin_menu.
726 */
727 public function admin_menu() {
728
729 if ( ! $this->network_active() || $this->site_enabled() || is_network_admin() ) {
730 add_submenu_page( 'plugins.php', __( 'Plugin Groups', self::$slug ), __( 'Plugin Groups', self::$slug ), 'manage_options', 'plugin-groups', array( $this, 'render_admin' ), 50 );
731 }
732 }
733
734 /**
735 * Check if the plugin is network activated.
736 *
737 * @return bool
738 */
739 protected function network_active() {
740
741 return is_plugin_active_for_network( PLGGRP_SLUG );
742 }
743
744 /**
745 * Check to see if the site is allowed to use this.
746 *
747 * @return bool
748 */
749 protected function site_enabled() {
750
751 $site_id = get_current_blog_id();
752 $main_config = get_network_option( get_main_site_id(), self::CONFIG_KEY, $this->get_default_config() );
753
754 return in_array( $site_id, $main_config['sitesEnabled'], true );
755 }
756
757 /**
758 * Add Groups to the admin bar.
759 *
760 * @param WP_Admin_Bar $admin_bar
761 */
762 public function admin_bar_item( $admin_bar ) {
763
764 if ( ! $this->config['params']['menuGroups'] || ! current_user_can( 'activate_plugins' ) ) {
765 return;
766 }
767
768 $groups = array(
769 'parent' => array(
770 'id' => self::$slug,
771 'title' => $this->plugin_name,
772 'href' => $this->get_nav_url( 'dashboard' ),
773 'meta' => array(
774 'title' => $this->plugin_name,
775 ),
776 ),
777 );
778
779 foreach ( $this->groups as $key => $plugins ) {
780 $group = $this->config['groups'][ $key ];
781 $groups[ $key ] = array(
782 'id' => $key,
783 'parent' => self::$slug,
784 'title' => $group['name'] . ' (' . count( $plugins ) . ')',
785 'href' => $this->get_nav_url( $key ),
786 );
787 }
788
789 foreach ( $groups as $option ) {
790 $admin_bar->add_menu( $option );
791 }
792 }
793
794 /**
795 * Enqueue assets where needed.
796 */
797 public function enqueue_assets() {
798
799 $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
800 if ( $page && self::$slug === $page ) {
801 wp_enqueue_script( self::$slug );
802 wp_enqueue_style( self::$slug );
803 wp_set_script_translations( self::$slug, self::$slug );
804 $this->prep_config();
805 }
806 }
807
808 /**
809 * Prepare the config data for output to the admin UI.
810 */
811 protected function prep_config() {
812
813 $data = $this->build_config_object();
814
815 // Add config data.
816 wp_add_inline_script( self::$slug, 'var plgData = ' . $data, 'before' );
817 }
818
819 /**
820 * Build the json config object.
821 *
822 * @param int|null $site_id The site to get config for, ir null for current.
823 *
824 * @return string|false
825 */
826 public function build_config_object( $site_id = null ) {
827
828 if ( null === $site_id ) {
829 $data = $this->config;
830 } else {
831 $data = $this->load_config( $site_id );
832 }
833
834 // Prep config data.
835 $data['saveURL'] = rest_url( self::$slug . '/save' );
836 $data['legacyURL'] = add_query_arg( 'reactivate-legacy', true, $this->get_nav_url( 'dashboard' ) );
837 $data['restNonce'] = wp_create_nonce( 'wp_rest' );
838 // Multisite.
839 if ( $this->network_active() && ( is_network_admin() || defined( 'REST_REQUEST' ) && true === REST_REQUEST ) ) {
840 $data['loadURL'] = rest_url( self::$slug . '/load' );
841 $data['sites'] = get_sites();
842 $data['mainSite'] = get_main_site_id();
843 }
844
845 // Add plugins.
846 $data['plugins'] = get_plugins();
847
848 // Remove presets from groups for admin.
849 $data['groups'] = array_diff_key( $data['groups'], $data['preset_groups'] );
850
851 // Remove ungrouped.
852 unset( $data['groups']['__ungrouped'] );
853
854 return wp_json_encode( $data );
855 }
856
857 /**
858 * Get the default config.
859 *
860 * @return array
861 */
862 protected function get_default_config() {
863
864 return array(
865 'groups' => array(),
866 'selectedPresets' => array(),
867 'params' => array(
868 'legacyGrouping' => false,
869 'navStyle' => 'subsubsub',
870 'menuGroups' => false,
871 ),
872
873 'sitesEnabled' => array(), // Used for multisite.
874 );
875 }
876
877 /**
878 * Load the UI config.
879 *
880 * @param int|null $site_id The site ID to load. Null for current site.
881 *
882 * @return array;
883 */
884 public function load_config( $site_id = null ) {
885
886 // Load the config.
887 if ( is_multisite() ) {
888 if ( ! $site_id ) {
889 $site_id = get_current_blog_id();
890 }
891 $config = get_network_option( $site_id, self::CONFIG_KEY, $this->get_default_config() );
892 $config['siteID'] = (int) $site_id;
893 } else {
894 $config = get_option( self::CONFIG_KEY, $this->get_default_config() );
895 }
896 $config['pluginName'] = $this->plugin_name;
897 $config['version'] = $this->version;
898 $config['slug'] = self::$slug;
899
900 // Flag if network admin vs main site.
901 if ( is_network_admin() && is_main_site() ) {
902 $config['networkAdmin'] = true;
903 }
904
905 // Load the presets.
906 $config += $this->load_presets();
907
908 return $config;
909 }
910
911 /**
912 * Set the config.
913 *
914 * @param array $config The config to set.
915 * @param int|null $site_id The site ID to load. Null for current site.
916 */
917 public function set_config( $config, $site_id = null ) {
918
919 $this->config = $config;
920 if ( ! empty( $this->config['params']['showUngrouped'] ) ) {
921 $ungrouped = $this->create_ungrouped();
922 if ( ! empty( $ungrouped['plugins'] ) ) {
923 $this->config['groups']['__ungrouped'] = $ungrouped;
924 }
925 }
926 // Populate groups with plugins.
927 array_map( array( $this, 'populate_plugins' ), $this->config['groups'] );
928 // register selected presets.
929 if ( ! empty( $this->config['selectedPresets'] ) ) {
930 array_map( array( $this, 'register_preset' ), $this->config['selectedPresets'] );
931 }
932 }
933
934 /**
935 * Create the Ungrouped, group.
936 *
937 * @return array
938 */
939 protected function create_ungrouped() {
940 $plugins = array_keys( get_plugins() );
941 $new_group = array(
942 'id' => '__ungrouped',
943 'name' => __( 'Ungrouped', self::$slug ),
944 'plugins' => array(),
945 );
946 $grouped = array();
947 foreach ( $this->config['groups'] as $group ) {
948 $grouped = array_merge( $grouped, $group['plugins'] );
949 }
950 $grouped = array_unique( $grouped );
951 $new_group['plugins'] = array_diff( $plugins, $grouped );
952
953 return $new_group;
954 }
955
956 /**
957 * Register a preset.
958 *
959 * @param string $preset The preset name to register.
960 */
961 protected function register_preset( $preset ) {
962
963 $key = sanitize_title( $preset );
964 if ( isset( $this->config['preset_groups'][ $key ] ) ) {
965 $this->config['groups'][ $key ] = $this->config['preset_groups'][ $key ];
966 }
967 }
968
969 /**
970 * Populate a group with selected plugins.
971 *
972 * @param array $group The group array.
973 */
974 protected function populate_plugins( $group ) {
975
976 static $plugins;
977 if ( ! $plugins ) {
978 $plugins = get_plugins();
979 }
980 foreach ( $group['plugins'] as $plugin ) {
981 if ( ! isset( $plugins[ $plugin ] ) ) {
982 $this->missing[ $plugin ] = array(
983 'Version' => '0.0',
984 );
985 continue;
986 }
987 $this->groups[ $group['id'] ][ $plugin ] = $plugins[ $plugin ];
988 }
989 // Populate keywords now to keep ordering.
990 $this->populate_keywords( $group['id'] );
991 }
992
993 /**
994 * Populate a group from keywords.
995 *
996 * @param string $id The group ID.
997 */
998 protected function populate_keywords( $id ) {
999
1000 if ( ! isset( $this->config['groups'][ $id ] ) || empty( $this->config['groups'][ $id ]['keywords'] ) ) {
1001 return;
1002 }
1003 $keywords = array_map( 'strtolower', $this->config['groups'][ $id ]['keywords'] );
1004 $plugins = get_plugins();
1005 foreach ( $plugins as $plugin_key => $plugin_data ) {
1006 if ( isset( $this->groups[ $id ][ $plugin_key ] ) ) {
1007 continue;
1008 }
1009 $plugin_string = strtolower( implode( ' ', $plugin_data ) );
1010 $matched = array_filter(
1011 $keywords,
1012 function ( $keyword ) use ( $plugin_string ) {
1013
1014 return false !== strpos( $plugin_string, $keyword );
1015 }
1016 );
1017 if ( ! empty( $matched ) ) {
1018 $this->groups[ $id ][ $plugin_key ] = $plugin_data;
1019 }
1020 }
1021 }
1022
1023 /**
1024 * Create a new Group.
1025 *
1026 * @param string $name The group name to create.
1027 * @param array $plugin_slugs Plugin slug or list of slugs to add to the group.
1028 *
1029 * @return string|bool
1030 */
1031 public function create_group( $name, array $plugin_slugs = array() ) {
1032
1033 $success = false;
1034 $new_id = Utils::generate_id();
1035 $group = array(
1036 'id' => $new_id,
1037 'keywords' => array(),
1038 'name' => trim( $name ),
1039 'open' => false,
1040 'plugins' => $plugin_slugs,
1041 );
1042 $this->config['groups'][ $new_id ] = $group;
1043 if ( $this->save_config() ) {
1044 $success = $new_id;
1045 }
1046
1047 return $success;
1048 }
1049
1050 /**
1051 * Add plugins to a group.
1052 *
1053 * @param string $group_id The group ID to add plugin to.
1054 * @param array $plugin_slugs Plugin slug or list of slugs to add to the group.
1055 *
1056 * @return bool
1057 */
1058 public function add_to_group( $group_id, array $plugin_slugs ) {
1059
1060 $success = false;
1061 if ( isset( $this->config['groups'][ $group_id ] ) ) {
1062 $new_plugins = array_merge( $this->config['groups'][ $group_id ]['plugins'], $plugin_slugs );
1063 $this->config['groups'][ $group_id ]['plugins'] = array_unique( $new_plugins );
1064 $success = $this->save_config();
1065 }
1066
1067 return $success;
1068 }
1069
1070 /**
1071 * Remove Plugins from a group.
1072 *
1073 * @param string $group_id The group ID to add plugin to.
1074 * @param array $plugin_slugs Plugin slug or list of slugs to add to the group.
1075 *
1076 * @return bool
1077 */
1078 public function remove_from_group( $group_id, array $plugin_slugs ) {
1079
1080 $success = false;
1081 if ( isset( $this->config['groups'][ $group_id ] ) ) {
1082 $this->config['groups'][ $group_id ]['plugins'] = array_diff( $this->config['groups'][ $group_id ]['plugins'], $plugin_slugs );
1083 $success = $this->save_config();
1084 }
1085
1086 return $success;
1087 }
1088
1089 /**
1090 * Render the admin page.
1091 */
1092 public function render_admin() {
1093
1094 include PLGGRP_PATH . 'includes/main.php';
1095 }
1096
1097 /**
1098 * Get the instance of the class.
1099 *
1100 * @return Plugin_Groups
1101 */
1102 public static function get_instance() {
1103
1104 if ( is_null( self::$instance ) ) {
1105 self::$instance = new self();
1106 }
1107
1108 return self::$instance;
1109 }
1110 }
1111