PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
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
← All changes | includes/admin/class-charitable-admin-pages.php +616 -80 1.6.7 → 1.8.13 View file →
@@ -2,13 +2,13 @@
2 2 /**
3 3 * This class is responsible for adding the Charitable admin pages.
4 4 *
5 5 * @package Charitable/Classes/Charitable_Admin_Pages
6 - * @author Eric Daams
7 - * @copyright Copyright (c) 2018, Studio 164a
6 + * @author David Bisset
7 + * @copyright Copyright (c) 2023, WP Charitable LLC
8 8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 9 * @since 1.0.0
10 - * @version 1.6.0
10 + * @version 1.8.1.12
11 11 */
12 12
13 13 // Exit if accessed directly.
14 14 if ( ! defined( 'ABSPATH' ) ) {
@@ -19,9 +19,9 @@
19 19
20 20 /**
21 21 * Charitable_Admin_Pages
22 22 *
23 - * @since 1.0.0
23 + * @since 1.0.0
24 24 */
25 25 final class Charitable_Admin_Pages {
26 26
27 27 /**
@@ -47,21 +47,31 @@
47 47
48 48 /**
49 49 * Create class object.
50 50 *
51 - * @since 1.0.0
51 + * @since 1.0.0
52 52 */
53 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 + */
54 61 $this->admin_menu_capability = apply_filters( 'charitable_admin_menu_capability', 'view_charitable_sensitive_data' );
55 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 );
56 66 }
57 67
58 68 /**
59 69 * Returns and/or create the single instance of this class.
60 70 *
61 - * @since 1.2.0
71 + * @since 1.2.0
62 72 *
63 - * @return Charitable_Admin_Pages
73 + * @return Charitable_Admin_Pages
64 74 */
65 75 public static function get_instance() {
66 76 if ( is_null( self::$instance ) ) {
67 77 self::$instance = new self();
@@ -70,19 +80,44 @@
70 80 return self::$instance;
71 81 }
72 82
73 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 + /**
74 109 * Add Settings menu item under the Campaign menu tab.
75 110 *
76 - * @since 1.0.0
111 + * @since 1.0.0
77 112 *
78 - * @return void
113 + * @return void
79 114 */
80 115 public function add_menu() {
81 116 add_menu_page(
82 117 'Charitable',
83 118 'Charitable',
84 - $this->admin_menu_capability,
119 + 'edit_campaigns', // phpcs:ignore
85 120 $this->admin_menu_parent_page,
86 121 array( $this, 'render_welcome_page' )
87 122 );
88 123
@@ -113,10 +148,11 @@
113 148 /**
114 149 * Returns an array with all the submenu pages.
115 150 *
116 151 * @since 1.0.0
152 + * @since 1.8.8
117 153 *
118 - * @return array
154 + * @return array
119 155 */
120 156 private function get_submenu_pages() {
121 157 $campaign_post_type = get_post_type_object( 'campaign' );
122 158 $donation_post_type = get_post_type_object( 'donation' );
@@ -124,60 +160,267 @@
124 160 /**
125 161 * Filter the list of submenu pages that come
126 162 * under the Charitable menu tab.
127 163 *
128 - * @since 1.0.0
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.
129 170 *
130 171 * @param array $pages Every page is an array with at least a page_title,
131 172 * menu_title and menu_slug set.
132 173 */
133 - return apply_filters( 'charitable_submenu_pages', array(
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(
134 282 array(
135 - 'page_title' => $campaign_post_type->labels->menu_name,
136 - 'menu_title' => $campaign_post_type->labels->menu_name,
137 - 'menu_slug' => 'edit.php?post_type=campaign',
138 - ),
139 - array(
140 - 'page_title' => $campaign_post_type->labels->add_new,
141 - 'menu_title' => $campaign_post_type->labels->add_new,
142 - 'menu_slug' => 'post-new.php?post_type=campaign',
143 - ),
144 - array(
145 - 'page_title' => $donation_post_type->labels->menu_name,
146 - 'menu_title' => $donation_post_type->labels->menu_name,
147 - 'menu_slug' => 'edit.php?post_type=donation',
148 - ),
149 - array(
150 - 'page_title' => __( 'Campaign Categories', 'charitable' ),
151 - 'menu_title' => __( 'Categories', 'charitable' ),
152 - 'menu_slug' => 'edit-tags.php?taxonomy=campaign_category&post_type=campaign',
153 - ),
154 - array(
155 - 'page_title' => __( 'Campaign Tags', 'charitable' ),
156 - 'menu_title' => __( 'Tags', 'charitable' ),
157 - 'menu_slug' => 'edit-tags.php?taxonomy=campaign_tag&post_type=campaign',
158 - ),
159 - array(
160 - 'page_title' => __( 'Customize', 'charitable' ),
161 - 'menu_title' => __( 'Customize', 'charitable' ),
162 - 'menu_slug' => 'customize.php?autofocus[panel]=charitable&url=' . $this->get_customizer_campaign_preview_url(),
163 - ),
164 - array(
165 - 'page_title' => __( 'Charitable Settings', 'charitable' ),
166 - 'menu_title' => __( 'Settings', 'charitable' ),
167 - 'menu_slug' => 'charitable-settings',
168 - 'function' => array( $this, 'render_settings_page' ),
169 - 'capability' => 'manage_charitable_settings',
170 - ),
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 + )
171 289 ) );
290 +
291 + return $pages;
172 292 }
173 293
174 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 + /**
175 418 * Set up the redirect to the welcome page.
176 419 *
177 - * @since 1.3.0
420 + * @since 1.3.0
178 421 *
179 - * @return void
422 + * @return void
180 423 */
181 424 public function setup_welcome_redirect() {
182 425 add_action( 'admin_init', array( self::get_instance(), 'redirect_to_welcome' ) );
183 426 }
@@ -184,11 +427,11 @@
184 427
185 428 /**
186 429 * Redirect to the welcome page.
187 430 *
188 - * @since 1.3.0
431 + * @since 1.3.0
189 432 *
190 - * @return void
433 + * @return void
191 434 */
192 435 public function redirect_to_welcome() {
193 436 wp_safe_redirect( admin_url( 'admin.php?page=charitable&install=true' ) );
194 437 exit;
@@ -194,13 +437,228 @@
194 437 exit;
195 438 }
196 439
197 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 + /**
198 656 * Display the Charitable settings page.
199 657 *
200 - * @since 1.0.0
658 + * @since 1.0.0
201 659 *
202 - * @return void
660 + * @return void
203 661 */
204 662 public function render_settings_page() {
205 663 charitable_admin_view( 'settings/settings' );
206 664 }
@@ -205,13 +663,24 @@
205 663 charitable_admin_view( 'settings/settings' );
206 664 }
207 665
208 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 + /**
209 678 * Display the Charitable donations page.
210 679 *
211 - * @since 1.0.0
680 + * @since 1.0.0
212 681 *
213 - * @return void
682 + * @return void
214 683 *
215 684 * @deprecated 1.4.0
216 685 */
217 686 public function render_donations_page() {
@@ -226,11 +695,11 @@
226 695
227 696 /**
228 697 * Display the Charitable welcome page.
229 698 *
230 - * @since 1.0.0
699 + * @since 1.0.0
231 700 *
232 - * @return void
701 + * @return void
233 702 */
234 703 public function render_welcome_page() {
235 704 charitable_admin_view( 'welcome-page/page' );
236 705 }
@@ -242,32 +711,37 @@
242 711 *
243 712 * @return string
244 713 */
245 714 private function get_customizer_campaign_preview_url() {
246 - $campaign = Charitable_Campaigns::query( array(
247 - 'posts_per_page' => 1,
248 - 'post_status' => 'publish',
249 - 'fields' => 'ids',
250 - 'meta_query' => array(
251 - 'relation' => 'OR',
252 - array(
253 - 'key' => '_campaign_end_date',
254 - 'value' => date( 'Y-m-d H:i:s' ),
255 - 'compare' => '>=',
256 - 'type' => 'datetime',
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 + ),
257 733 ),
258 - array(
259 - 'key' => '_campaign_end_date',
260 - 'value' => 0,
261 - 'compare' => '=',
262 - ),
263 - ),
264 - ) );
734 + )
735 + );
265 736
266 737 if ( $campaign->found_posts ) {
267 - $url = charitable_get_permalink( 'campaign_donation', array(
268 - 'campaign_id' => current( $campaign->posts ),
269 - ) );
738 + $url = charitable_get_permalink(
739 + 'campaign_donation',
740 + array(
741 + 'campaign_id' => current( $campaign->posts ),
742 + )
743 + );
270 744 }
271 745
272 746 if ( ! isset( $url ) || false === $url ) {
273 747 $url = home_url();
@@ -273,8 +747,70 @@
273 747 $url = home_url();
274 748 }
275 749
276 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
277 813 }
278 814 }
279 815
280 816 endif;