PluginProbe
Widget Context / 1.4.0
Widget Context v1.4.0
1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.1.0 1.1.1 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 trunk 0.4.1 0.4.2 0.4.3 0.4.4 0.4.5 0.6 0.7 0.7.1 0.7.2 0.8 0.8.1 All 30 releases
widget-context / src / WidgetContext.php

WidgetContext.php in Widget Context 1.4.0, at src/WidgetContext.php

1,278 lines 35.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use Preseto\WidgetContext\UriRuleMatcher;
4 use Preseto\WidgetContext\UriRules;
5
6 /**
7 * Widget Context plugin core.
8 */
9 class WidgetContext {
10
11 /**
12 * Rule ID for the invert by URL.
13 *
14 * @var string
15 */
16 const RULE_KEY_URLS_INVERT = 'urls_invert';
17
18 /**
19 * Nonce action when saving the individual widget context settings.
20 *
21 * @var string
22 */
23 const SAVE_NONCE_ACTION = 'widget-context-update';
24
25 private $sidebars_widgets;
26 private $options_name = 'widget_logic_options'; // Context settings for widgets (visibility, etc)
27 private $settings_name = 'widget_context_settings'; // Widget Context global settings
28 private $sidebars_widgets_copy;
29
30 private $context_options = array(); // Store visibility settings
31 private $context_settings = array(); // Store admin settings
32 private $contexts = array();
33
34 /**
35 * Instance of the abstract plugin.
36 *
37 * @var Preseto\WidgetContext\Plugin
38 */
39 private $plugin;
40
41 /**
42 * Instance of the current class for legacy purposes.
43 *
44 * @var WidgetContext
45 */
46 protected static $instance;
47
48 /**
49 * Start the plugin.
50 *
51 * @param Preseto\WidgetContext\Plugin $path Instance of the abstract plugin.
52 */
53 public function __construct( $plugin ) {
54 $this->plugin = $plugin;
55
56 // Keep an instance for legacy purposes.
57 self::$instance = $this;
58 }
59
60 /**
61 * Legacy singleton instance getter.
62 *
63 * @return WidgetContext
64 */
65 public static function instance() {
66 return self::$instance;
67 }
68
69 /**
70 * Interface for registering modules.
71 *
72 * @param mixed $module Instance of the module.
73 *
74 * @return void
75 */
76 public function register_module( $module ) {
77 $module->init();
78 }
79
80 /**
81 * Hook into WP.
82 */
83 public function init() {
84 // Define available widget contexts
85 add_action( 'init', array( $this, 'define_widget_contexts' ), 5 );
86
87 // Load plugin settings and show/hide widgets by altering the
88 // $sidebars_widgets global variable
89 add_action( 'wp', array( $this, 'set_widget_contexts_frontend' ) );
90
91 // Append Widget Context settings to widget controls
92 add_action( 'in_widget_form', array( $this, 'widget_context_controls' ), 10 );
93
94 // Add admin menu for config
95 add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
96
97 // Save widget context settings, when in admin area
98 add_action( 'sidebar_admin_setup', array( $this, 'save_widget_context_settings' ) );
99
100 // Fix legacy context option naming
101 add_filter( 'widget_context_options', array( $this, 'fix_legacy_options' ) );
102
103 // Register admin settings menu
104 add_action( 'admin_menu', array( $this, 'widget_context_settings_menu' ) );
105
106 // Add quick links to the plugin list.
107 add_action(
108 'plugin_action_links_' . $this->plugin->basename(),
109 array( $this, 'plugin_action_links' )
110 );
111 }
112
113 function define_widget_contexts() {
114 register_setting( $this->settings_name, $this->settings_name );
115
116 $this->context_options = apply_filters(
117 'widget_context_options',
118 (array) get_option( $this->options_name, array() )
119 );
120
121 $this->context_settings = wp_parse_args(
122 (array) get_option( $this->settings_name, array() ),
123 array(
124 'contexts' => array(),
125 )
126 );
127
128 // Default context
129 $default_contexts = array(
130 'incexc' => array(
131 'label' => __( 'Widget Context', 'widget-context' ),
132 'description' => __( 'Set the default logic to show or hide.', 'widget-context' ),
133 'weight' => -100,
134 'type' => 'core',
135 ),
136 'location' => array(
137 'label' => __( 'Global Sections', 'widget-context' ),
138 'description' => __( 'Match using the standard WordPress template tags.', 'widget-context' ),
139 'weight' => 10,
140 ),
141 'url' => array(
142 'label' => __( 'Target by URL', 'widget-context' ),
143 'description' => __( 'Match using URL patterns.', 'widget-context' ),
144 'weight' => 20,
145 ),
146 self::RULE_KEY_URLS_INVERT => array(
147 'label' => __( 'Exclude by URL', 'widget-context' ),
148 'description' => __( 'Override other matches using URL patterns.', 'widget-context' ),
149 'weight' => 25,
150 ),
151 'admin_notes' => array(
152 'label' => __( 'Notes (invisible to public)', 'widget-context' ),
153 'description' => __( 'Keep private notes on widget context settings.', 'widget-context' ),
154 'weight' => 90,
155 ),
156 );
157
158 // Add default context controls and checks
159 foreach ( array_keys( $default_contexts ) as $context_name ) {
160 add_filter( 'widget_context_control-' . $context_name, array( $this, 'control_' . $context_name ), 10, 2 );
161 add_filter( 'widget_context_check-' . $context_name, array( $this, 'context_check_' . $context_name ), 10, 2 );
162 }
163
164 // Enable other plugins and themes to specify their own contexts
165 $this->contexts = apply_filters( 'widget_contexts', $default_contexts );
166
167 // Sort contexts by their weight
168 uasort( $this->contexts, array( $this, 'sort_context_by_weight' ) );
169
170 if ( $this->is_legacy_widgets_enabled() ) {
171 add_filter( 'gutenberg_use_widgets_block_editor', '__return_false' );
172 add_filter( 'use_widgets_block_editor', '__return_false' );
173 }
174 }
175
176
177 public function get_context_options( $widget_id = null ) {
178 if ( ! $widget_id ) {
179 return $this->context_options;
180 }
181
182 if ( isset( $this->context_options[ $widget_id ] ) ) {
183 return $this->context_options[ $widget_id ];
184 }
185
186 return null;
187 }
188
189
190 public function get_context_settings( $widget_id = null ) {
191 if ( ! $widget_id ) {
192 return $this->context_settings;
193 }
194
195 if ( isset( $this->context_settings[ $widget_id ] ) ) {
196 return $this->context_settings[ $widget_id ];
197 }
198
199 return null;
200 }
201
202
203 public function get_contexts() {
204 return $this->contexts;
205 }
206
207
208 function sort_context_by_weight( $a, $b ) {
209 if ( ! isset( $a['weight'] ) ) {
210 $a['weight'] = 10;
211 }
212
213 if ( ! isset( $b['weight'] ) ) {
214 $b['weight'] = 10;
215 }
216
217 return ( $a['weight'] < $b['weight'] ) ? -1 : 1;
218 }
219
220
221 /**
222 * Get the state of the PRO nag.
223 *
224 * @return boolean
225 */
226 public function pro_nag_enabled() {
227 return (bool) apply_filters( 'widget_context_pro_nag', true );
228 }
229
230 /**
231 * Add a link to the plugin settings in the plugin list.
232 *
233 * @return array List of links.
234 */
235 public function plugin_action_links( $links ) {
236 $links[] = sprintf(
237 '<a href="%s">%s</a>',
238 esc_url( $this->plugin_settings_admin_url() ),
239 esc_html__( 'Settings', 'widget-context' )
240 );
241
242 $links[] = sprintf(
243 '<a href="%s">%s</a>',
244 esc_url( $this->customize_widgets_admin_url() ),
245 esc_html__( 'Configure Widgets', 'widget-context' )
246 );
247
248 if ( $this->pro_nag_enabled() ) {
249 $links[] = sprintf(
250 '<a href="%s" target="_blank">PRO 🚀</a>',
251 esc_url( 'https://widgetcontext.com/pro' )
252 );
253 }
254
255 return $links;
256 }
257
258 function set_widget_contexts_frontend() {
259 // Hide/show widgets for is_active_sidebar() to work
260 add_filter( 'sidebars_widgets', array( $this, 'maybe_unset_widgets_by_context' ), 10 );
261 }
262
263
264 function admin_scripts( $page ) {
265 // Enqueue only on widgets and customizer view
266 if ( ! in_array( $page, array( 'widgets.php', 'appearance_page_widget_context_settings' ), true ) ) {
267 return;
268 }
269
270 wp_enqueue_style(
271 'widget-context-css',
272 $this->plugin->asset_url( 'assets/css/admin.css' ),
273 null,
274 $this->plugin->asset_version()
275 );
276
277 wp_enqueue_script(
278 'widget-context-js',
279 $this->plugin->asset_url( 'assets/js/widget-context.js' ),
280 array( 'jquery' ),
281 $this->plugin->asset_version()
282 );
283 }
284
285
286 function widget_context_controls( $widget ) {
287 echo $this->display_widget_context( $widget->id );
288 }
289
290
291 function save_widget_context_settings() {
292 if ( ! current_user_can( 'edit_theme_options' ) || empty( $_POST['wl'] ) || ! is_array( $_POST['wl'] ) ) {
293 return;
294 }
295
296 // Add and update.
297 foreach ( $_POST['wl'] as $widget_id => $widget_context_input ) {
298 $update_nonce = $this->get_widget_nonce_action( $widget_id );
299
300 if ( ! empty( $_POST[ $update_nonce ] ) && wp_verify_nonce( $_POST[ $update_nonce ], self::SAVE_NONCE_ACTION ) ) {
301 if ( ! isset( $this->context_options[ $widget_id ] ) ) {
302 $this->context_options[ $widget_id ] = array();
303 }
304
305 if ( ! empty( $_POST['delete_widget'] ) ) { // Delete.
306 unset( $this->context_options[ $widget_id ] );
307 } else { // Update.
308 $this->context_options[ $widget_id ] = $widget_context_input;
309 }
310 }
311 }
312
313 // Get a list of all widget IDs.
314 $all_widget_ids = array();
315 foreach ( wp_get_sidebars_widgets() as $widget_area => $widgets ) {
316 $all_widget_ids = array_merge( $all_widget_ids, array_values( $widgets ) );
317 }
318
319 // Cleanup non-existant widget contexts from the settings.
320 foreach ( $this->context_options as $widget_id => $widget_context ) {
321 if ( ! in_array( $widget_id, $all_widget_ids, true ) ) {
322 unset( $this->context_options[ $widget_id ] );
323 }
324 }
325
326 update_option( $this->options_name, $this->context_options );
327 }
328
329
330 function maybe_unset_widgets_by_context( $sidebars_widgets ) {
331 // Don't run this at the backend or before
332 // post query has been run
333 if ( is_admin() ) {
334 return $sidebars_widgets;
335 }
336
337 // Return from cache if we have done the context checks already
338 if ( ! empty( $this->sidebars_widgets ) ) {
339 return $this->sidebars_widgets;
340 }
341
342 // Store a local copy of the original widget location
343 $this->sidebars_widgets_copy = $sidebars_widgets;
344
345 foreach ( $sidebars_widgets as $widget_area => $widget_list ) {
346
347 if ( 'wp_inactive_widgets' === $widget_area || empty( $widget_list ) ) {
348 continue;
349 }
350
351 foreach ( $widget_list as $pos => $widget_id ) {
352
353 if ( ! $this->check_widget_visibility( $widget_id ) ) {
354 unset( $sidebars_widgets[ $widget_area ][ $pos ] );
355 }
356 }
357 }
358
359 // Store in class cache
360 $this->sidebars_widgets = $sidebars_widgets;
361
362 return $sidebars_widgets;
363 }
364
365 /**
366 * Determine widget visibility according to the current global context.
367 *
368 * @param string $widget_id Widget ID.
369 *
370 * @return boolean
371 */
372 public function check_widget_visibility( $widget_id ) {
373 // Check if this widget even has context set.
374 if ( ! isset( $this->context_options[ $widget_id ] ) ) {
375 return true;
376 }
377
378 // Get the match rule for this widget (show/hide/selected/notselected).
379 $match_rule = $this->context_options[ $widget_id ]['incexc']['condition'];
380
381 // Force show or hide the widget!
382 if ( 'show' === $match_rule ) {
383 return true;
384 } elseif ( 'hide' === $match_rule ) {
385 return false;
386 }
387
388 // Show or hide on match.
389 $condition = ( 'selected' === $match_rule );
390
391 if ( $this->context_matches_condition_for_widget_id( $widget_id ) ) {
392 return $condition;
393 }
394
395 return ! $condition;
396 }
397
398 /**
399 * Check if widget visibility rules match the current context.
400 *
401 * @param string $widget_id Widget ID.
402 *
403 * @return boolean
404 */
405 public function context_matches_condition_for_widget_id( $widget_id ) {
406 $matches = $this->context_matches_for_widget_id( $widget_id );
407
408 // Inverted rules can only override another positive match.
409 return ( in_array( true, $matches, true ) && ! in_array( false, $matches, true ) );
410 }
411
412 /**
413 * Get context rule matches for a widget ID.
414 *
415 * @param string $widget_id Widget ID.
416 *
417 * @return array
418 */
419 public function context_matches_for_widget_id( $widget_id ) {
420 $matches = array();
421
422 foreach ( $this->get_contexts() as $context_id => $context_settings ) {
423 // This context check has been disabled in the plugin settings
424 if ( isset( $this->context_settings['contexts'][ $context_id ] ) && ! $this->context_settings['contexts'][ $context_id ] ) {
425 continue;
426 }
427
428 $widget_context_args = array();
429
430 // Make sure that context settings for this widget are defined
431 if ( ! empty( $this->context_options[ $widget_id ][ $context_id ] ) ) {
432 $widget_context_args = $this->context_options[ $widget_id ][ $context_id ];
433 }
434
435 $matches[ $context_id ] = apply_filters(
436 'widget_context_check-' . $context_id,
437 null,
438 $widget_context_args
439 );
440 }
441
442 return $matches;
443 }
444
445
446 /**
447 * Default context checks
448 */
449
450 function context_check_incexc( $check, $settings ) {
451 return $check;
452 }
453
454
455 function context_check_location( $check, $settings ) {
456 $status = array(
457 'is_front_page' => is_front_page(),
458 'is_home' => is_home(),
459 'is_singular' => is_singular(),
460 'is_single' => is_singular( 'post' ),
461 'is_page' => ( is_page() && ! is_front_page() ),
462 'is_attachment' => is_attachment(),
463 'is_search' => is_search(),
464 'is_404' => is_404(),
465 'is_archive' => is_archive(),
466 'is_date' => is_date(),
467 'is_day' => is_day(),
468 'is_month' => is_month(),
469 'is_year' => is_year(),
470 'is_category' => is_category(),
471 'is_tag' => is_tag(),
472 'is_author' => is_author(),
473 );
474
475 $matched = array_intersect_assoc( $settings, $status );
476
477 if ( ! empty( $matched ) ) {
478 return true;
479 }
480
481 return $check;
482 }
483
484 /**
485 * Fetch a setting value for the context setting as a string.
486 *
487 * @param array $settings List of all settings by setting key.
488 * @param string $key Setting key to check.
489 *
490 * @return string
491 */
492 protected function get_setting_as_string( $settings, $key ) {
493 if ( ! is_array( $settings ) ) {
494 $settings = array();
495 }
496
497 $settings = wp_parse_args(
498 $settings,
499 array(
500 $key => null,
501 )
502 );
503
504 return trim( (string) $settings[ $key ] );
505 }
506
507 /**
508 * Check if a set of URL paths match the current request.
509 *
510 * @param bool $check Current visibility state.
511 * @param array $settings Visibility settings.
512 *
513 * @return bool
514 */
515 public function context_check_url( $check, $settings ) {
516 $path = $this->get_request_path();
517 $urls = $this->get_setting_as_string( $settings, 'urls' );
518
519 if ( ! empty( $urls ) && $this->match_path( $path, $urls ) ) {
520 return true;
521 }
522
523 return $check;
524 }
525
526 /**
527 * Check if a set of URL paths match the current request.
528 *
529 * @param bool $check Current visibility state.
530 * @param array $settings Visibility settings.
531 *
532 * @return bool
533 */
534 public function context_check_urls_invert( $check, $settings ) {
535 $path = $this->get_request_path();
536 $urls = $this->get_setting_as_string( $settings, self::RULE_KEY_URLS_INVERT );
537
538 if ( ! empty( $urls ) && $this->match_path( $path, $urls ) ) {
539 return false; // Override any positive matches.
540 }
541
542 return $check;
543 }
544
545 /**
546 * Fetch the request path for the current request.
547 *
548 * @return string
549 */
550 protected function get_request_path() {
551 static $path;
552
553 if ( ! isset( $path ) ) {
554 $path = $this->path_from_uri( $_SERVER['REQUEST_URI'] );
555 }
556
557 return $path;
558 }
559
560 /**
561 * Return the path relative to the root of the hostname. We always remove
562 * the leading and trailing slashes around the URI path.
563 *
564 * @param string $uri Current request URI.
565 *
566 * @return string
567 */
568 public function path_from_uri( $uri ) {
569 $parts = wp_parse_args(
570 wp_parse_url( $uri ),
571 array(
572 'path' => '',
573 )
574 );
575
576 $path = trim( $parts['path'], '/' );
577
578 if ( ! empty( $parts['query'] ) ) {
579 $path .= '?' . $parts['query'];
580 }
581
582 return $path;
583 }
584
585 /**
586 * Parse a text blob of URI fragments into URI rules.
587 *
588 * @param string $paths String of URI paths seperated by line breaks.
589 *
590 * @return array List of formatted URI paths.
591 */
592 protected function uri_rules_from_paths( $paths ) {
593 $patterns = explode( "\n", $paths );
594
595 $patterns = array_map(
596 function ( $pattern ) {
597 // Resolve rule paths the same way as the request URI.
598 return $this->path_from_uri( trim( $pattern ) );
599 },
600 $patterns
601 );
602
603 return array_filter( $patterns );
604 }
605
606 /**
607 * Check if the current request matches path rules.
608 *
609 * @param string $path Current request relative to the root of the hostname.
610 * @param string $rules A list of path patterns seperated by new line.
611 *
612 * @return bool|null Return `null` if no rules to match against.
613 */
614 public function match_path( $path, $rules ) {
615 $uri_rules = new UriRules( $this->uri_rules_from_paths( $rules ) );
616 $uri_rules_paths = $uri_rules->rules();
617
618 /**
619 * Ignore query parameters in path unless any of the rules actually use them.
620 * Defaults to matching paths with any query parameters.
621 */
622 if ( ! $uri_rules->has_rules_with_query_strings() ) {
623 $path = strtok( $path, '?' );
624 }
625
626 if ( ! empty( $uri_rules_paths ) ) {
627 $matcher = new UriRuleMatcher();
628
629 return $matcher->uri_matches_rules( $path, $uri_rules_paths );
630 }
631
632 return null;
633 }
634
635
636 // Dummy function
637 function context_check_admin_notes( $check, $widget_id ) {}
638
639
640 // Dummy function
641 function context_check_general( $check, $widget_id ) {}
642
643
644 /*
645 Widget Controls
646 */
647
648 function display_widget_context( $widget_id = null ) {
649 $controls = array();
650 $controls_disabled = array();
651 $controls_core = array();
652
653 foreach ( $this->contexts as $context_name => $context_settings ) {
654 $context_classes = array(
655 'context-group',
656 sprintf( 'context-group-%s', esc_attr( $context_name ) ),
657 );
658
659 // Hide this context from the admin UX. We can't remove them
660 // because settings will get lost if this page is submitted.
661 if ( isset( $this->context_settings['contexts'][ $context_name ] ) && ! $this->context_settings['contexts'][ $context_name ] ) {
662 $context_classes[] = 'context-inactive';
663 $controls_disabled[] = $context_name;
664 }
665
666 // Store core controls
667 if ( isset( $context_settings['type'] ) && 'core' === $context_settings['type'] ) {
668 $controls_core[] = $context_name;
669 }
670
671 $control_args = array(
672 'name' => $context_name,
673 'input_prefix' => 'wl' . $this->get_field_name( array( $widget_id, $context_name ) ),
674 'settings' => $this->get_field_value( array( $widget_id, $context_name ) ),
675 'widget_id' => $widget_id,
676 );
677
678 $context_controls = apply_filters( 'widget_context_control-' . $context_name, $control_args );
679 $context_classes = apply_filters( 'widget_context_classes-' . $context_name, $context_classes, $control_args );
680
681 if ( ! empty( $context_controls ) && is_string( $context_controls ) ) {
682 $controls[ $context_name ] = sprintf(
683 '<div class="%s">
684 <h4 class="context-toggle">%s</h4>
685 <div class="context-group-wrap">
686 %s
687 </div>
688 </div>',
689 esc_attr( implode( ' ', $context_classes ) ),
690 esc_html( $context_settings['label'] ),
691 $context_controls
692 );
693 }
694 }
695
696 // Non-core controls that should be visible if enabled
697 $controls_not_core = array_diff( array_keys( $controls ), $controls_core );
698
699 // Check if any non-core context controls have been enabled
700 $has_controls = array_diff( $controls_not_core, $controls_disabled );
701
702 if ( empty( $controls ) || empty( $has_controls ) ) {
703
704 if ( current_user_can( 'edit_theme_options' ) ) {
705 $controls = array(
706 sprintf(
707 '<p class="error">%s</p>',
708 sprintf(
709 /* translators: %s is a URL to the settings page. */
710 __( 'No widget controls enabled. You can enable them in <a href="%s">Widget Context settings</a>.', 'widget-context' ),
711 $this->plugin_settings_admin_url()
712 )
713 ),
714 );
715 } else {
716 $controls = array(
717 sprintf(
718 '<p class="error">%s</p>',
719 __( 'No widget controls enabled.', 'widget-context' )
720 ),
721 );
722 }
723 }
724
725 $settings_link = array();
726
727 if ( current_user_can( 'edit_theme_options' ) ) {
728 $settings_link[] = sprintf(
729 '<a href="%s" title="%s" target="_blank">%s</a>',
730 esc_url( $this->plugin_settings_admin_url() ),
731 esc_attr__( 'Widget Context Settings', 'widget-context' ),
732 esc_html__( 'Settings', 'widget-context' )
733 );
734
735 if ( $this->pro_nag_enabled() ) {
736 $settings_link[] = sprintf(
737 '<a href="%s" target="_blank">PRO 🚀</a>',
738 esc_url( 'https://widgetcontext.com/pro' )
739 );
740 }
741 }
742
743 $controls[] = wp_nonce_field( self::SAVE_NONCE_ACTION, $this->get_widget_nonce_action( $widget_id ), false, false );
744
745 return sprintf(
746 '<div class="widget-context">
747 <div class="widget-context-header">
748 <h3>%s</h3>
749 <span class="widget-context-settings-link">%s</span>
750 </div>
751 <div class="widget-context-inside" id="widget-context-%s" data-widget-id="%s">
752 %s
753 </div>
754 </div>',
755 __( 'Widget Context', 'widget-context' ),
756 implode( ' | ', $settings_link ),
757 // Inslide classes
758 esc_attr( $widget_id ),
759 esc_attr( $widget_id ),
760 // Controls
761 implode( '', $controls )
762 );
763 }
764
765 /**
766 * Get the nonce action for widget context settings.
767 *
768 * @param string $widget_id Widget ID.
769 *
770 * @return string
771 */
772 private function get_widget_nonce_action( $widget_id ) {
773 return 'widget-context--' . $widget_id;
774 }
775
776
777 function control_incexc( $control_args ) {
778 $options = array(
779 'show' => __( 'Show widget everywhere', 'widget-context' ),
780 'selected' => __( 'Show widget on selected', 'widget-context' ),
781 'notselected' => __( 'Hide widget on selected', 'widget-context' ),
782 'hide' => __( 'Hide widget everywhere', 'widget-context' ),
783 );
784
785 return $this->make_simple_dropdown( $control_args, 'condition', $options );
786 }
787
788
789 function control_location( $control_args ) {
790 $options = array(
791 'is_front_page' => __( 'Front page', 'widget-context' ),
792 'is_home' => __( 'Blog page', 'widget-context' ),
793 'is_singular' => __( 'All posts, pages and custom post types', 'widget-context' ),
794 'is_single' => __( 'All posts', 'widget-context' ),
795 'is_page' => __( 'All pages', 'widget-context' ),
796 'is_attachment' => __( 'All attachments', 'widget-context' ),
797 'is_search' => __( 'Search results', 'widget-context' ),
798 'is_404' => __( '404 error page', 'widget-context' ),
799 'is_archive' => __( 'All archives', 'widget-context' ),
800 'is_date' => __( 'All date archives', 'widget-context' ),
801 'is_day' => __( 'Daily archives', 'widget-context' ),
802 'is_month' => __( 'Monthly archives', 'widget-context' ),
803 'is_year' => __( 'Yearly archives', 'widget-context' ),
804 'is_category' => __( 'All category archives', 'widget-context' ),
805 'is_tag' => __( 'All tag archives', 'widget-context' ),
806 'is_author' => __( 'All author archives', 'widget-context' ),
807 );
808
809 foreach ( $options as $option => $label ) {
810 $out[] = $this->make_simple_checkbox( $control_args, $option, $label );
811 }
812
813 return implode( '', $out );
814 }
815
816
817 function control_url( $control_args ) {
818 return sprintf(
819 '<div>%s</div>
820 <p class="help">%s</p>',
821 $this->make_simple_textarea( $control_args, 'urls' ),
822 __( 'Enter one location fragment per line. Use <strong>*</strong> character as a wildcard. Example: <code>page/example</code> to target a specific page or <code>page/*</code> to target all children of a page.', 'widget-context' )
823 );
824 }
825
826
827 function control_urls_invert( $control_args ) {
828 return sprintf(
829 '<div>%s</div>
830 <p class="help">%s</p>',
831 $this->make_simple_textarea( $control_args, self::RULE_KEY_URLS_INVERT ),
832 __( 'Specify URLs to override the Target by URLs settings. Useful for excluding specific URLs when using wildcards in Target by URL.', 'widget-context' )
833 );
834 }
835
836
837 function control_admin_notes( $control_args ) {
838 return sprintf(
839 '<div>%s</div>',
840 $this->make_simple_textarea( $control_args, 'notes' )
841 );
842 }
843
844
845
846 /**
847 * Widget control helpers
848 */
849
850
851 function make_simple_checkbox( $control_args, $option, $label ) {
852 $value = false;
853
854 if ( isset( $control_args['settings'][ $option ] ) && $control_args['settings'][ $option ] ) {
855 $value = true;
856 }
857
858 return sprintf(
859 '<label class="wc-field-checkbox-%s" data-widget-id="%s">
860 <input type="hidden" value="0" name="%s[%s]" />
861 <input type="checkbox" value="1" name="%s[%s]" %s />&nbsp;%s
862 </label>',
863 $this->get_field_classname( $option ),
864 esc_attr( $control_args['widget_id'] ),
865 // Input hidden
866 $control_args['input_prefix'],
867 esc_attr( $option ),
868 // Input value
869 $control_args['input_prefix'],
870 esc_attr( $option ),
871 checked( $value, true, false ),
872 // Label
873 esc_html( $label )
874 );
875 }
876
877
878 function make_simple_textarea( $control_args, $option, $label = null ) {
879 $value = '';
880
881 if ( isset( $control_args['settings'][ $option ] ) ) {
882 $value = esc_textarea( $control_args['settings'][ $option ] );
883 }
884
885 return sprintf(
886 '<label class="wc-field-textarea-%s" data-widget-id="%s">
887 <strong>%s</strong>
888 <textarea name="%s[%s]">%s</textarea>
889 </label>',
890 $this->get_field_classname( $option ),
891 esc_attr( $control_args['widget_id'] ),
892 // Label
893 esc_html( $label ),
894 // Input
895 $control_args['input_prefix'],
896 $option,
897 $value
898 );
899 }
900
901
902 function make_simple_textfield( $control_args, $option, $label_before = null, $label_after = null ) {
903 $value = false;
904
905 if ( isset( $control_args['settings'][ $option ] ) ) {
906 $value = esc_attr( $control_args['settings'][ $option ] );
907 }
908
909 return sprintf(
910 '<label class="wc-field-text-%s" data-widget-id="%s">
911 %s
912 <input type="text" name="%s[%s]" value="%s" />
913 %s
914 </label>',
915 $this->get_field_classname( $option ),
916 esc_attr( $control_args['widget_id'] ),
917 // Before
918 $label_before,
919 // Input
920 $control_args['input_prefix'],
921 $option,
922 esc_attr( $value ),
923 // After
924 esc_html( $label_after )
925 );
926 }
927
928
929 function make_simple_dropdown( $control_args, $option, $selection = array(), $label_before = null, $label_after = null ) {
930 $options = array();
931 $value = false;
932
933 if ( isset( $control_args['settings'][ $option ] ) ) {
934 $value = $control_args['settings'][ $option ];
935 }
936
937 if ( empty( $selection ) ) {
938 $options[] = sprintf(
939 '<option value="">%s</option>',
940 esc_html__( 'No options available', 'widget-context' )
941 );
942 }
943
944 foreach ( $selection as $sid => $svalue ) {
945 $options[] = sprintf(
946 '<option value="%s" %s>%s</option>',
947 esc_attr( $sid ),
948 selected( $value, $sid, false ),
949 esc_html( $svalue )
950 );
951 }
952
953 return sprintf(
954 '<label class="wc-field-select-%s" data-widget-id="%s">
955 %s
956 <select name="%s[%s]">
957 %s
958 </select>
959 %s
960 </label>',
961 $this->get_field_classname( $option ),
962 esc_attr( $control_args['widget_id'] ),
963 // Before
964 $label_before,
965 // Input
966 $control_args['input_prefix'],
967 $option,
968 implode( '', $options ),
969 // After
970 $label_after
971 );
972 }
973
974
975 /**
976 * Returns [part1][part2][partN] from array( 'part1', 'part2', 'part3' )
977 *
978 * @param array $parts i.e. array( 'part1', 'part2', 'part3' )
979 * @return string i.e. [part1][part2][partN]
980 */
981 function get_field_name( $parts ) {
982 return esc_attr( sprintf( '[%s]', implode( '][', $parts ) ) );
983 }
984
985 function get_field_classname( $name ) {
986 if ( is_array( $name ) ) {
987 $name = end( $name );
988 }
989
990 return sanitize_html_class( str_replace( '_', '-', $name ) );
991 }
992
993
994 /**
995 * Given option keys return its value
996 *
997 * @param array $parts i.e. array( 'part1', 'part2', 'part3' )
998 * @param array $options i.e. array( 'part1' => array( 'part2' => array( 'part3' => 'VALUE' ) ) )
999 * @return string Returns option value
1000 */
1001 function get_field_value( $parts, $options = null ) {
1002 if ( null === $options ) {
1003 $options = $this->context_options;
1004 }
1005
1006 $value = false;
1007
1008 if ( empty( $parts ) || ! is_array( $parts ) ) {
1009 return false;
1010 }
1011
1012 $part = array_shift( $parts );
1013
1014 if ( ! empty( $parts ) && isset( $options[ $part ] ) && is_array( $options[ $part ] ) ) {
1015 $value = $this->get_field_value( $parts, $options[ $part ] );
1016 } elseif ( isset( $options[ $part ] ) ) {
1017 return $options[ $part ];
1018 }
1019
1020 return $value;
1021 }
1022
1023
1024 function fix_legacy_options( $options ) {
1025 if ( empty( $options ) || ! is_array( $options ) ) {
1026 return $options;
1027 }
1028
1029 foreach ( $options as $widget_id => $option ) {
1030 // This doesn't have an include/exclude rule defined
1031 if ( ! isset( $option['incexc'] ) ) {
1032 unset( $options[ $widget_id ] );
1033 }
1034
1035 // We moved from [incexc] = 1/0 to [incexc][condition] = 1/0
1036 if ( isset( $option['incexc'] ) && ! is_array( $option['incexc'] ) ) {
1037 $options[ $widget_id ]['incexc'] = array( 'condition' => $option['incexc'] );
1038 }
1039
1040 // Move notes from "general" group to "admin_notes"
1041 if ( isset( $option['general']['notes'] ) ) {
1042 $options[ $widget_id ]['admin_notes']['notes'] = $option['general']['notes'];
1043 unset( $option['general']['notes'] );
1044 }
1045
1046 // We moved word count out of location context group
1047 if ( isset( $option['location']['check_wordcount'] ) ) {
1048 $options[ $widget_id ]['word_count'] = array(
1049 'check_wordcount' => true,
1050 'check_wordcount_type' => $option['location']['check_wordcount_type'],
1051 'word_count' => $option['location']['word_count'],
1052 );
1053 }
1054 }
1055
1056 return $options;
1057 }
1058
1059
1060
1061 /**
1062 * Admin Settings
1063 */
1064
1065
1066 function widget_context_settings_menu() {
1067 add_theme_page(
1068 __( 'Widget Context Settings', 'widget-context' ),
1069 __( 'Widget Context', 'widget-context' ),
1070 'manage_options',
1071 $this->settings_name,
1072 array( $this, 'widget_context_admin_view' ),
1073 3 // Try to place it right under the Widgets.
1074 );
1075 }
1076
1077 /**
1078 * Return a link to the Customize Widgets admin page.
1079 *
1080 * @return string
1081 */
1082 public function customize_widgets_admin_url() {
1083 return admin_url( 'customize.php?autofocus[panel]=widgets' );
1084 }
1085
1086
1087 /**
1088 * Get the URL to the plugin settings page.
1089 *
1090 * @return string
1091 */
1092 public function plugin_settings_admin_url() {
1093 return admin_url( 'themes.php?page=widget_context_settings' );
1094 }
1095
1096 /**
1097 * If the legacy widgets interface is enabled in the plugin settings.
1098 *
1099 * @return bool
1100 */
1101 public function is_legacy_widgets_enabled() {
1102 return ! empty( $this->context_settings['enable-legacy-widgets'] );
1103 }
1104
1105
1106 function widget_context_admin_view() {
1107 $context_controls = array();
1108
1109 foreach ( $this->get_contexts() as $context_id => $context_args ) {
1110 // Hide core modules from being disabled
1111 if ( isset( $context_args['type'] ) && 'core' === $context_args['type'] ) {
1112 continue;
1113 }
1114
1115 if ( ! empty( $context_args['description'] ) ) {
1116 $context_description = sprintf(
1117 '<p class="description">%s</p>',
1118 esc_html( $context_args['description'] )
1119 );
1120 } else {
1121 $context_description = null;
1122 }
1123
1124 // Enable new modules by default
1125 if ( ! isset( $this->context_settings['contexts'][ $context_id ] ) ) {
1126 $this->context_settings['contexts'][ $context_id ] = 1;
1127 }
1128
1129 $context_controls[] = sprintf(
1130 '<li class="enabled-contexts-item context-%s">
1131 <label>
1132 <input type="hidden" name="%s[contexts][%s]" value="0" />
1133 <input type="checkbox" name="%s[contexts][%s]" value="1" %s /> %s
1134 </label>
1135 %s
1136 </li>',
1137 esc_attr( $context_id ),
1138 $this->settings_name,
1139 esc_attr( $context_id ),
1140 $this->settings_name,
1141 esc_attr( $context_id ),
1142 checked( $this->context_settings['contexts'][ $context_id ], 1, false ),
1143 esc_html( $context_args['label'] ),
1144 $context_description
1145 );
1146 }
1147
1148 ?>
1149 <div class="wrap wrap-widget-context">
1150 <h2><?php esc_html_e( 'Widget Context Settings', 'widget-context' ); ?></h2>
1151
1152 <div class="widget-context-settings-wrap">
1153
1154 <div class="widget-context-form">
1155 <form method="post" action="options.php">
1156 <?php
1157 settings_fields( $this->settings_name );
1158 do_settings_sections( $this->settings_name );
1159 ?>
1160
1161 <table class="form-table" role="presentation">
1162 <tr id="widget-context-pro">
1163 <th scrope="row">
1164 <?php esc_html_e( 'Support', 'widget-context' ); ?>
1165 </th>
1166 <td>
1167 <p>
1168 <a href="https://widgetcontext.com/pro">Subscribe to get premium support</a> and the 🚀 PRO version of the plugin for free when it's launched!
1169 Your support enables consistent maintenance and new feature development, and is greatly appreciated.
1170 </p>
1171 </td>
1172 </tr>
1173 <tr>
1174 <th scrope="row">
1175 <?php esc_html_e( 'Widget Interface', 'widget-context' ); ?>
1176 </th>
1177 <td>
1178 <label>
1179 <input type="hidden" name="<?php echo esc_attr( $this->settings_name ); ?>[enable-legacy-widgets]" value="0" />
1180 <input type="checkbox" name="<?php echo esc_attr( $this->settings_name ); ?>[enable-legacy-widgets]" value="1" <?php checked( $this->context_settings['enable-legacy-widgets'], 1 ); ?> />
1181 <?php esc_html_e( 'Enable legacy widget interface', 'widget-context' ); ?>
1182 </label>
1183 <p class="description">
1184 <?php esc_html_e( 'Enable the legacy (non-block) widget interface under "Appearance → Widgets" that was disabled in WordPress 5.8.', 'widget-context' ); ?>
1185 </p>
1186 </td>
1187 </tr>
1188 <tr>
1189 <th scrope="row">
1190 <?php esc_html_e( 'Configure Widgets', 'widget-context' ); ?>
1191 </th>
1192 <td>
1193 <p>
1194 <a class="button button-primary" href="<?php echo esc_url( $this->customize_widgets_admin_url() ); ?>"><?php esc_html_e( 'Configure Widgets', 'widget-context' ); ?></a>
1195 </p>
1196 <p class="description">
1197 <?php esc_html_e( 'Configure widget context using the WordPress Customizer (with preview) or using the widget settings under "Appearance → Widgets".', 'widget-context' ); ?>
1198 </p>
1199 </td>
1200 </tr>
1201 <tr>
1202 <th scrope="row">
1203 <?php esc_html_e( 'Enabled Contexts', 'widget-context' ); ?>
1204 </th>
1205 <td>
1206 <p>
1207 <?php esc_html_e( 'Select the context rules available for all widgets and hide the unused ones:', 'widget-context' ); ?>
1208 </p>
1209 <?php printf( '<ul>%s</ul>', implode( '', $context_controls ) ); ?>
1210 </td>
1211 </tr>
1212 </table>
1213
1214 <?php
1215 submit_button();
1216 ?>
1217 </form>
1218 </div>
1219
1220 <div class="widget-context-sidebar">
1221 <div class="wc-sidebar-in">
1222
1223 <div class="wc-sidebar-section wc-sidebar-credits">
1224 <p>
1225 <img src="https://gravatar.com/avatar/661eb21385c25c01ad64ab9e13b37331?s=120" alt="Kaspars Dambis" width="60" height="60" />
1226 <?php
1227 printf(
1228 // translators: %s: link with an anchor text.
1229 esc_html__( 'Widget Context is created and maintained by %s.', 'widget-context' ),
1230 '<a href="https://widgetcontext.com/about">Kaspars Dambis</a>'
1231 );
1232 ?>
1233 </p>
1234 </div>
1235
1236 <div class="wc-sidebar-section wc-sidebar-newsletter">
1237 <h3><?php esc_html_e( 'News & Updates', 'widget-context' ); ?></h3>
1238 <p><?php esc_html_e( 'Subscribe to receive news and updates about the plugin.', 'widget-context' ); ?></p>
1239 <form action="//osc.us2.list-manage.com/subscribe/post?u=e8d173fc54c0fc4286a2b52e8&amp;id=8afe96c5a3" method="post" target="_blank">
1240 <?php $user = wp_get_current_user(); ?>
1241 <p><label><?php _e( 'Your Name', 'widget-context' ); ?>: <input type="text" name="NAME" value="<?php echo esc_attr( sprintf( '%s %s', $user->first_name, $user->last_name ) ); ?>" /></label></p>
1242 <p><label><?php _e( 'Your Email', 'widget-context' ); ?>: <input type="text" name="EMAIL" value="<?php echo esc_attr( $user->user_email ); ?>" /></label></p>
1243 <p><input class="button" name="subscribe" type="submit" value="<?php esc_attr_e( 'Subscribe', 'widget-context' ); ?>" /></p>
1244 </form>
1245 <h3>
1246 <?php esc_html_e( 'Suggested Plugins', 'widget-context' ); ?>
1247 </h3>
1248 <p>
1249 <?php esc_html_e( 'Here are some of my other plugins:', 'widget-context' ); ?>
1250 </p>
1251 <ul>
1252 <li>
1253 <strong><small>NEW:</small></strong>
1254 <a href="https://blockcontext.com?utm_source=wc">Block Context</a> for showing or hiding Gutenberg blocks in context.
1255 </li>
1256 <li>
1257 <a href="https://preseto.com/go/cf7-storage?utm_source=wc">Storage for Contact Form 7</a> saves all Contact Form 7 submissions (including attachments) in your WordPress database.
1258 </li>
1259 <li>
1260 <a href="https://formcontrols.com/?utm_source=wc">Contact Form 7 Controls</a> adds a simple interface for managing Contact Form 7 form settings.
1261 </li>
1262 </ul>
1263 </div>
1264
1265 </div>
1266 </div>
1267
1268 </div>
1269 </div>
1270 <?php
1271 }
1272
1273
1274 public function get_sidebars_widgets_copy() {
1275 return $this->sidebars_widgets_copy;
1276 }
1277 }
1278