PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.11.1
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.11.1
1.8.13.1 1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 All 212 releases
charitable / includes / admin / class-charitable-admin-pages.php

class-charitable-admin-pages.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.11.1, at includes/admin/class-charitable-admin-pages.php

817 lines 21.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * This class is responsible for adding the Charitable admin pages.
4 *
5 * @package Charitable/Classes/Charitable_Admin_Pages
6 * @author David Bisset
7 * @copyright Copyright (c) 2023, WP Charitable LLC
8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 * @since 1.0.0
10 * @version 1.8.1.12
11 */
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 if ( ! class_exists( 'Charitable_Admin_Pages' ) ) :
19
20 /**
21 * Charitable_Admin_Pages
22 *
23 * @since 1.0.0
24 */
25 final class Charitable_Admin_Pages {
26
27 /**
28 * The single instance of this class.
29 *
30 * @var Charitable_Admin_Pages|null
31 */
32 private static $instance = null;
33
34 /**
35 * The page to use when registering sections and fields.
36 *
37 * @var string
38 */
39 private $admin_menu_parent_page;
40
41 /**
42 * The capability required to view the admin menu.
43 *
44 * @var string
45 */
46 private $admin_menu_capability;
47
48 /**
49 * Create class object.
50 *
51 * @since 1.0.0
52 */
53 private function __construct() {
54 /**
55 * The default capability required to view Charitable pages.
56 *
57 * @since 1.0.0
58 *
59 * @param string $cap The capability required.
60 */
61 $this->admin_menu_capability = apply_filters( 'charitable_admin_menu_capability', 'view_charitable_sensitive_data' );
62 $this->admin_menu_parent_page = 'charitable';
63
64 // Hook into plugin installation to set timestamps for rotating menu
65 add_action( 'upgrader_process_complete', array( $this, 'handle_plugin_installation' ), 10, 2 );
66 }
67
68 /**
69 * Returns and/or create the single instance of this class.
70 *
71 * @since 1.2.0
72 *
73 * @return Charitable_Admin_Pages
74 */
75 public static function get_instance() {
76 if ( is_null( self::$instance ) ) {
77 self::$instance = new self();
78 }
79
80 return self::$instance;
81 }
82
83 /**
84 * This forces the charitable menu to be open for category and tag pages.
85 *
86 * @since 1.7.0
87 *
88 * @param string $parent_file The parent file.
89 *
90 * @return string
91 */
92 public function menu_highlight( $parent_file ) {
93 global $current_screen;
94
95 $taxonomy = isset( $current_screen->taxonomy ) ? $current_screen->taxonomy : false;
96 if ( false !== $taxonomy && ( 'campaign_category' === $taxonomy || 'campaign_tag' === $taxonomy ) ) {
97 $parent_file = 'charitable';
98 }
99
100 $post_type = isset( $current_screen->post_type ) ? $current_screen->post_type : false;
101 if ( false !== $post_type && ( 'campaign' === $post_type || 'donation' === $post_type ) ) {
102 $parent_file = 'charitable';
103 }
104
105 return $parent_file;
106 }
107
108 /**
109 * Add Settings menu item under the Campaign menu tab.
110 *
111 * @since 1.0.0
112 *
113 * @return void
114 */
115 public function add_menu() {
116 add_menu_page(
117 'Charitable',
118 'Charitable',
119 'edit_campaigns', // phpcs:ignore
120 $this->admin_menu_parent_page,
121 array( $this, 'render_welcome_page' )
122 );
123
124 foreach ( $this->get_submenu_pages() as $page ) {
125 if ( ! isset( $page['page_title'] )
126 || ! isset( $page['menu_title'] )
127 || ! isset( $page['menu_slug'] ) ) {
128 continue;
129 }
130
131 $page_title = $page['page_title'];
132 $menu_title = $page['menu_title'];
133 $capability = isset( $page['capability'] ) ? $page['capability'] : $this->admin_menu_capability;
134 $menu_slug = $page['menu_slug'];
135 $function = isset( $page['function'] ) ? $page['function'] : '';
136
137 add_submenu_page(
138 $this->admin_menu_parent_page,
139 $page_title,
140 $menu_title,
141 $capability,
142 $menu_slug,
143 $function
144 );
145 }
146 }
147
148 /**
149 * Returns an array with all the submenu pages.
150 *
151 * @since 1.0.0
152 * @since 1.8.8
153 *
154 * @return array
155 */
156 private function get_submenu_pages() {
157 $campaign_post_type = get_post_type_object( 'campaign' );
158 $donation_post_type = get_post_type_object( 'donation' );
159
160 /**
161 * Filter the list of submenu pages that come
162 * under the Charitable menu tab.
163 *
164 * @since 1.0.0
165 * @version 1.8.1 Added dashboard and reports.
166 * @version 1.8.1.6 Added tools.
167 * @version 1.8.1.8 Added SMTP.
168 * @version 1.8.2 Remove categories, tags, and customize.
169 * @version 1.8.5.1 Added donors.
170 *
171 * @param array $pages Every page is an array with at least a page_title,
172 * menu_title and menu_slug set.
173 */
174 // Build dashboard pages array based on legacy dashboard setting
175 $dashboard_pages = array();
176
177 if ( charitable_use_legacy_dashboard() ) {
178 // Show legacy dashboard
179 $dashboard_pages[] = array(
180 'page_title' => __( 'Dashboard', 'charitable' ),
181 'menu_title' => __( 'Dashboard', 'charitable' ),
182 'menu_slug' => 'charitable-dashboard',
183 'function' => array( $this, 'render_dashboard_new_page' ),
184 'capability' => 'manage_charitable_settings',
185 );
186 } else {
187 // Show new dashboard (formerly beta)
188 $dashboard_pages[] = array(
189 'page_title' => __( 'Dashboard', 'charitable' ),
190 'menu_title' => __( 'Dashboard', 'charitable' ),
191 'menu_slug' => 'charitable-dashboard',
192 'function' => array( $this, 'render_dashboard_page' ),
193 'capability' => 'manage_charitable_settings',
194 );
195 }
196
197 $pages = apply_filters(
198 'charitable_submenu_pages',
199 array_merge(
200 $dashboard_pages,
201 array(
202 array(
203 'page_title' => $campaign_post_type->labels->menu_name,
204 'menu_title' => $campaign_post_type->labels->menu_name,
205 'menu_slug' => 'edit.php?post_type=campaign',
206 'capability' => 'edit_campaigns',
207 ),
208 array(
209 'page_title' => $campaign_post_type->labels->add_new,
210 'menu_title' => $campaign_post_type->labels->add_new,
211 'menu_slug' => 'post-new.php?post_type=campaign',
212 'capability' => 'edit_campaigns',
213 ),
214 array(
215 'page_title' => $donation_post_type->labels->menu_name,
216 'menu_title' => $donation_post_type->labels->menu_name,
217 'menu_slug' => 'edit.php?post_type=donation',
218 'capability' => 'edit_donations',
219 ),
220 array(
221 'page_title' => __( 'Charitable Donors', 'charitable' ),
222 'menu_title' => __( 'Donors', 'charitable' ) . ' <span class="charitable-menu-new-indicator">&nbsp;' . esc_html__( 'NEW', 'charitable' ) . '!</span>',
223 'menu_slug' => 'charitable-donors',
224 'function' => array( $this, 'render_donors_page' ),
225 'capability' => 'manage_charitable_settings',
226 ),
227 array(
228 'page_title' => __( 'Charitable Reports', 'charitable' ),
229 'menu_title' => __( 'Reports', 'charitable' ),
230 'menu_slug' => 'charitable-reports',
231 'function' => array( $this, 'render_reports_page' ),
232 'capability' => 'manage_charitable_settings',
233 ),
234 array(
235 'page_title' => __( 'Charitable Tools', 'charitable' ),
236 'menu_title' => __( 'Tools', 'charitable' ),
237 'menu_slug' => 'charitable-tools',
238 'function' => array( $this, 'render_tools_page' ),
239 'capability' => 'manage_charitable_settings',
240 ),
241 array(
242 'page_title' => __( 'Charitable Settings', 'charitable' ),
243 'menu_title' => __( 'Settings', 'charitable' ),
244 'menu_slug' => 'charitable-settings',
245 'function' => array( $this, 'render_settings_page' ),
246 'capability' => 'manage_charitable_settings',
247 ),
248 array(
249 'page_title' => __( 'Charitable Addons', 'charitable' ),
250 'menu_title' => '<span style="color:#f18500">' . __( 'Addons', 'charitable' ) . '</span>',
251 'menu_slug' => 'charitable-addons',
252 'function' => array( $this, 'render_addons_directory_page' ),
253 'capability' => 'manage_charitable_settings',
254 ),
255 array(
256 'page_title' => __( 'SMTP', 'charitable' ),
257 'menu_title' => __( 'SMTP', 'charitable' ),
258 'menu_slug' => 'charitable-smtp',
259 'function' => array( $this, 'render_smtp_page' ),
260 'capability' => 'manage_charitable_settings',
261 ),
262 )
263 )
264 );
265
266 // Rotating submenu: Privacy Compliance / SMTP.
267 $rotation = $this->get_rotating_marketing_submenu();
268
269 if ( $rotation ) {
270 $pages = array_merge( $pages, array(
271 array(
272 'page_title' => $rotation['page_title'],
273 'menu_title' => $rotation['menu_title'],
274 'menu_slug' => $rotation['menu_slug'],
275 'function' => array( $this, 'admin_page' ),
276 'capability' => 'manage_charitable_settings',
277 )
278 ) );
279 }
280
281 $pages = array_merge( $pages, array(
282 array(
283 'page_title' => __( 'About Charitable', 'charitable' ),
284 'menu_title' => __( 'About Us', 'charitable' ),
285 'menu_slug' => 'charitable-about',
286 'function' => array( $this, 'render_about_page' ),
287 'capability' => 'view_charitable_sensitive_data',
288 )
289 ) );
290
291 return $pages;
292 }
293
294 /**
295 * Determine which marketing submenu item to show (rotation).
296 *
297 * Current behavior:
298 * - Show "Privacy Compliance" until the WPConsent plugin has been activated for 7 or more days.
299 * - Once 7+ days have passed since WPConsent first activation, show "SMTP".
300 * - This can be extended to more items later and to use per-item activation timestamps.
301 *
302 * @since 1.8.8
303 *
304 * @return array|null { menu_title, page_title, menu_slug } or null to show none.
305 */
306 private function get_rotating_marketing_submenu(): ?array {
307
308 $items = $this->get_marketing_rotation_items();
309 if ( empty( $items ) ) {
310 return null;
311 }
312
313 $now = time();
314
315 // Find the first item that should still be displayed.
316 foreach ( $items as $item ) {
317 $label = $item['label'] ?? '';
318 $menu_slug = $item['menu_slug'] ?? '';
319 $option_name = $item['option_name'] ?? '';
320 $option_key = $item['option_key'] ?? '';
321 $plugin_file = $item['plugin_file'] ?? '';
322 $period_days = (int) ( $item['period_days'] ?? 7 );
323
324 if ( empty( $label ) || empty( $menu_slug ) ) {
325 continue; // Skip misconfigured entries.
326 }
327
328 // If a reference plugin is provided and it's not active, keep showing this item.
329 if ( ! empty( $plugin_file ) ) {
330 if ( ! function_exists( 'is_plugin_active' ) ) {
331 require_once ABSPATH . 'wp-admin/includes/plugin.php';
332 }
333 if ( function_exists( 'is_plugin_active' ) && ! is_plugin_active( $plugin_file ) ) {
334 return [
335 'menu_title' => $label,
336 'page_title' => $label,
337 'menu_slug' => $menu_slug,
338 ];
339 }
340 }
341
342 $ts = 0;
343 if ( ! empty( $option_name ) ) {
344 $opt = get_option( $option_name );
345 if ( is_array( $opt ) ) {
346 $ts = isset( $opt[ $option_key ] ) && is_numeric( $opt[ $option_key ] ) ? (int) $opt[ $option_key ] : 0;
347 } elseif ( is_numeric( $opt ) ) {
348 $ts = (int) $opt;
349 }
350 }
351
352 $threshold = max( 0, $period_days ) * DAY_IN_SECONDS;
353 $within = $ts === 0 || ( $now - $ts ) < $threshold;
354
355 if ( $within ) {
356 return [
357 'menu_title' => $label,
358 'page_title' => $label,
359 'menu_slug' => $menu_slug,
360 ];
361 }
362 }
363
364 // If all items are considered "complete", return the first one (cycle back).
365 $first = $items[0];
366 return [
367 'menu_title' => $first['label'],
368 'page_title' => $first['label'],
369 'menu_slug' => $first['menu_slug'],
370 ];
371 }
372
373 /**
374 * Editable list of rotating marketing submenu items.
375 *
376 * @since 1.8.8
377 *
378 * @return array
379 */
380 private function get_marketing_rotation_items(): array {
381 return [
382 [
383 'label' => esc_html__( 'Privacy Compliance', 'charitable' ),
384 'menu_slug' => 'charitable-privacy-compliance',
385 'option_name' => 'wpconsent_activated',
386 'option_key' => 'wpconsent',
387 'plugin_file' => 'wpconsent-cookies-banner-privacy-suite/wpconsent.php',
388 'period_days' => 7,
389 ],
390 [
391 'label' => esc_html__( 'Backups', 'charitable' ),
392 'menu_slug' => 'charitable-backups',
393 'option_name' => 'duplicator_activated',
394 'option_key' => 'duplicator',
395 'plugin_file' => 'duplicator/duplicator.php',
396 'period_days' => 7,
397 ],
398 [
399 'label' => esc_html__( 'SEO', 'charitable' ),
400 'menu_slug' => 'charitable-seo',
401 'option_name' => 'all-in-one-seo-pack_activated',
402 'option_key' => 'all-in-one-seo-pack',
403 'plugin_file' => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
404 'period_days' => 7,
405 ],
406 [
407 'label' => esc_html__( 'Automation', 'charitable' ),
408 'menu_slug' => 'charitable-automation',
409 'option_name' => 'uncanny-automator_activated',
410 'option_key' => 'uncanny_automator',
411 'plugin_file' => 'uncanny-automator/uncanny-automator.php',
412 'period_days' => 7,
413 ],
414 ];
415 }
416
417 /**
418 * Set up the redirect to the welcome page.
419 *
420 * @since 1.3.0
421 *
422 * @return void
423 */
424 public function setup_welcome_redirect() {
425 add_action( 'admin_init', array( self::get_instance(), 'redirect_to_welcome' ) );
426 }
427
428 /**
429 * Redirect to the welcome page.
430 *
431 * @since 1.3.0
432 *
433 * @return void
434 */
435 public function redirect_to_welcome() {
436 wp_safe_redirect( admin_url( 'admin.php?page=charitable&install=true' ) );
437 exit;
438 }
439
440 /**
441 * Display the Charitable donors page.
442 *
443 * @since 1.8.5
444 *
445 * @return void
446 */
447 public function render_donors_page() {
448 charitable_admin_view( 'donors/donors' );
449 }
450
451 /**
452 * Display the Charitable reports page.
453 *
454 * @since 1.8.1
455 *
456 * @return void
457 */
458 public function render_reports_page() {
459 charitable_admin_view( 'reports/reports' );
460 }
461
462 /**
463 * Display the Charitable tools page.
464 *
465 * @since 1.8.1.6
466 *
467 * @return void
468 */
469 public function render_tools_page() {
470 charitable_admin_view( 'tools/tools' );
471 }
472
473 /**
474 * Display the Charitable SMTP page.
475 *
476 * @since 1.8.1.8
477 *
478 * @return void
479 */
480 public function render_smtp_page() {
481 charitable_admin_view( 'smtp/smtp' );
482 }
483
484 /**
485 * Display the Charitable About page.
486 *
487 * @since 1.8.7.6
488 *
489 * @return void
490 */
491 public function render_about_page() {
492 charitable_admin_view( 'about/about' );
493 }
494
495 /**
496 * Display the Charitable Growth Tools page.
497 *
498 * @since 1.8.1.6
499 *
500 * @return void
501 */
502 public function render_growth_tools_page() {
503 charitable_admin_view( 'growth-tools/growth-tools' );
504 }
505
506 /**
507 * Display the Charitable Privacy Compliance page.
508 *
509 * @since 1.8.8
510 *
511 * @return void
512 */
513 public function render_privacy_compliance_page() {
514 charitable_admin_view( 'privacy-compliance/privacy-compliance' );
515 }
516
517 /**
518 * Display the Charitable Backups page.
519 *
520 * @since 1.8.8
521 *
522 * @return void
523 */
524 public function render_backups_page() {
525 charitable_admin_view( 'backups/backups' );
526 }
527
528 /**
529 * Display the Charitable SEO page.
530 *
531 * @since 1.8.8
532 *
533 * @return void
534 */
535 public function render_seo_page() {
536 charitable_admin_view( 'seo/seo' );
537 }
538
539 /**
540 * Display the Charitable Automation page.
541 *
542 * @since 1.8.8
543 *
544 * @return void
545 */
546 public function render_automation_page() {
547 charitable_admin_view( 'automation/automation' );
548 }
549
550 /**
551 * Dynamic admin page renderer for rotating marketing submenu items.
552 *
553 * @since 1.8.8
554 *
555 * @return void
556 */
557 public function admin_page() {
558 $rotation = $this->get_rotating_marketing_submenu();
559 if ( ! $rotation ) {
560 return;
561 }
562
563 $menu_slug = $rotation['menu_slug'];
564
565 switch ( $menu_slug ) {
566 case 'charitable-privacy-compliance':
567 $this->render_privacy_compliance_page();
568 break;
569 case 'charitable-backups':
570 $this->render_backups_page();
571 break;
572 case 'charitable-seo':
573 $this->render_seo_page();
574 break;
575 case 'charitable-automation':
576 $this->render_automation_page();
577 break;
578 default:
579 // Fallback to privacy compliance if unknown
580 $this->render_privacy_compliance_page();
581 break;
582 }
583 }
584
585 /**
586 * Display the Charitable dashboard page.
587 *
588 * @since 1.8.1
589 *
590 * @return void
591 */
592 public function render_dashboard_page() {
593 $this->enqueue_dashboard_spinner_script();
594 charitable_admin_view( 'dashboard/dashboard' );
595 }
596
597 /**
598 * Display the Charitable dashboard new page.
599 *
600 * @since 1.8.1
601 *
602 * @return void
603 */
604 public function render_dashboard_new_page() {
605 $this->enqueue_dashboard_spinner_script();
606 charitable_admin_view( 'dashboard/dashboard-legacy' );
607 }
608
609 /**
610 * Enqueue dashboard spinner script
611 *
612 * @since 1.8.7.5
613 *
614 * @return void
615 */
616 private function enqueue_dashboard_spinner_script() {
617 // Add inline script to hide spinner when dashboard content loads
618 wp_add_inline_script( 'charitable-admin-dashboard', '
619 (function() {
620 // Hide spinners immediately when dashboard content is detected
621 function hideDashboardSpinners() {
622 document.body.classList.add("charitable-dashboard-loaded");
623 var dashboardV2 = document.getElementById("charitable-dashboard-v2");
624 if (dashboardV2) {
625 dashboardV2.classList.add("charitable-dashboard-loaded");
626 }
627 }
628
629 // Hide spinners on DOM ready
630 if (document.readyState === "loading") {
631 document.addEventListener("DOMContentLoaded", hideDashboardSpinners);
632 } else {
633 hideDashboardSpinners();
634 }
635
636 // Also hide spinners when dashboard content is detected
637 var checkForContent = function() {
638 var hasContent = document.querySelector("#charitable-dashboard-v2 .charitable-dashboard-v2-content") ||
639 document.querySelector("#charitable-dashboard-report-container") ||
640 document.querySelector(".charitable-dashboard-report");
641
642 if (hasContent) {
643 hideDashboardSpinners();
644 } else {
645 setTimeout(checkForContent, 100);
646 }
647 };
648
649 // Start checking for content after a short delay
650 setTimeout(checkForContent, 200);
651 })();
652 ' );
653 }
654
655 /**
656 * Display the Charitable settings page.
657 *
658 * @since 1.0.0
659 *
660 * @return void
661 */
662 public function render_settings_page() {
663 charitable_admin_view( 'settings/settings' );
664 }
665
666 /**
667 * Display the Charitable addons page.
668 *
669 * @since 1.7.0.4
670 *
671 * @return void
672 */
673 public function render_addons_directory_page() {
674 charitable_admin_view( 'addons-directory/addons-directory' );
675 }
676
677 /**
678 * Display the Charitable donations page.
679 *
680 * @since 1.0.0
681 *
682 * @return void
683 *
684 * @deprecated 1.4.0
685 */
686 public function render_donations_page() {
687 charitable_get_deprecated()->deprecated_function(
688 __METHOD__,
689 '1.4.0',
690 __( 'Donations page now rendered by WordPress default manage_edit-donation_columns', 'charitable' )
691 );
692
693 charitable_admin_view( 'donations-page/page' );
694 }
695
696 /**
697 * Display the Charitable welcome page.
698 *
699 * @since 1.0.0
700 *
701 * @return void
702 */
703 public function render_welcome_page() {
704 charitable_admin_view( 'welcome-page/page' );
705 }
706
707 /**
708 * Return a preview URL for the customizer.
709 *
710 * @since 1.6.0
711 *
712 * @return string
713 */
714 private function get_customizer_campaign_preview_url() {
715 $campaign = Charitable_Campaigns::query(
716 array(
717 'posts_per_page' => 1,
718 'post_status' => 'publish',
719 'fields' => 'ids',
720 'meta_query' => array(
721 'relation' => 'OR',
722 array(
723 'key' => '_campaign_end_date',
724 'value' => date( 'Y-m-d H:i:s' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
725 'compare' => '>=',
726 'type' => 'datetime',
727 ),
728 array(
729 'key' => '_campaign_end_date',
730 'value' => 0,
731 'compare' => '=',
732 ),
733 ),
734 )
735 );
736
737 if ( $campaign->found_posts ) {
738 $url = charitable_get_permalink(
739 'campaign_donation',
740 array(
741 'campaign_id' => current( $campaign->posts ),
742 )
743 );
744 }
745
746 if ( ! isset( $url ) || false === $url ) {
747 $url = home_url();
748 }
749
750 return urlencode( $url );
751 }
752
753 /**
754 * Handle plugin installation to set timestamps for rotating menu items.
755 *
756 * @since 1.8.8
757 *
758 * @param WP_Upgrader $upgrader The upgrader instance.
759 * @param array $hook_extra Extra arguments passed to hooked filters.
760 * @return void
761 */
762 public function handle_plugin_installation( $upgrader, $hook_extra ) {
763 // Only process plugin installations, not updates
764 if ( ! isset( $hook_extra['type'] ) || 'plugin' !== $hook_extra['type'] ) {
765 return;
766 }
767
768 // Only process installations, not updates
769 if ( ! isset( $hook_extra['action'] ) || 'install' !== $hook_extra['action'] ) {
770 return;
771 }
772
773 $items = $this->get_marketing_rotation_items();
774 $current_time = time();
775
776 foreach ( $items as $item ) {
777 $option_name = $item['option_name'] ?? '';
778 $option_key = $item['option_key'] ?? '';
779 $plugin_file = $item['plugin_file'] ?? '';
780
781 if ( empty( $option_name ) || empty( $plugin_file ) ) {
782 continue;
783 }
784
785 // Check if the installed plugin matches our rotating menu items
786 $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_file;
787 if ( file_exists( $plugin_path ) ) {
788 // Set timestamp for this plugin installation
789 $existing_option = get_option( $option_name );
790
791 if ( is_array( $existing_option ) ) {
792 $existing_option[ $option_key ] = $current_time;
793 update_option( $option_name, $existing_option );
794 } else {
795 update_option( $option_name, array( $option_key => $current_time ) );
796 }
797 }
798 }
799 }
800
801 /**
802 * Handle plugin activation to set timestamps for rotating menu items.
803 * (Kept for reference but not used in current implementation)
804 *
805 * @since 1.8.8
806 *
807 * @param string $plugin The plugin file that was activated.
808 * @return void
809 */
810 public function handle_plugin_activation( $plugin ) {
811 // This method is kept for reference but not used in current implementation
812 // The rotating menu now tracks installations instead of activations
813 }
814 }
815
816 endif;
817