PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.1.0
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.1.0
1.6.0 1.5.1 1.5.0 1.4.0 1.3.0 trunk 0.0.1 1.0.0 1.1.0 1.1.1 1.1.2 1.2.0
suredonation / inc / admin / admin.php

admin.php in SureDonation – Donation Forms, Fundraising Campaigns & Donor Management 1.1.0, at inc/admin/admin.php

505 lines 16.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Admin Interface
4 *
5 * @package SureDonation
6 */
7
8 namespace SureDonation\Inc\Admin;
9
10 use SureDonation\Inc\Helper;
11 use SureDonation\Inc\Onboarding;
12 use SureDonation\Inc\Pdf\Pdf_Utils;
13 use SureDonation\Inc\Payments\Payment_Helper;
14 use SureDonation\Inc\Payments\PayPal\PayPal_Helper;
15 use SureDonation\Inc\Traits\Get_Instance;
16
17 // Exit if accessed directly.
18 if ( ! defined( 'ABSPATH' ) ) {
19 exit;
20 }
21
22 /**
23 * Admin class.
24 *
25 * @since 0.0.1
26 */
27 class Admin {
28 use Get_Instance;
29
30 /**
31 * Constructor.
32 *
33 * @since 0.0.1
34 */
35 public function __construct() {
36 add_action( 'admin_menu', [ $this, 'register_menu' ] );
37 add_action( 'admin_menu', [ $this, 'hide_forms_submenu' ], 999 );
38 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_scripts' ] );
39 add_action( 'admin_init', [ $this, 'redirect_cpt_listings' ] );
40 }
41
42 /**
43 * Redirect the default CPT list screens for suredonation_form and
44 * suredonation_cmpgn to their corresponding plugin pages.
45 *
46 * For the form listing, attempt to resolve the originating campaign
47 * from the referer (the "back" arrow on the form editor sends the
48 * user here) and redirect to that campaign's single view.
49 *
50 * @return void
51 * @since 1.0.0
52 */
53 public function redirect_cpt_listings() {
54 global $pagenow;
55
56 if ( 'edit.php' !== $pagenow ) {
57 return;
58 }
59
60 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading post_type from query, no state mutation.
61 $post_type = isset( $_GET['post_type'] ) ? sanitize_key( wp_unslash( $_GET['post_type'] ) ) : '';
62
63 if ( 'suredonation_form' === $post_type ) {
64 $campaign_id = $this->resolve_campaign_id_from_referer();
65 $target = $campaign_id
66 ? admin_url( 'admin.php?page=suredonation#/campaigns/' . $campaign_id )
67 : admin_url( 'admin.php?page=suredonation#/campaigns' );
68
69 wp_safe_redirect( $target );
70 exit;
71 }
72
73 if ( 'suredonation_cmpgn' === $post_type ) {
74 wp_safe_redirect( admin_url( 'admin.php?page=suredonation#/campaigns' ) );
75 exit;
76 }
77 }
78
79 /**
80 * Resolve the campaign ID from the referer when the form list page
81 * is reached from the form editor's "back" button.
82 *
83 * @return int Campaign ID, or 0 when it cannot be determined.
84 * @since 1.0.0
85 */
86 private function resolve_campaign_id_from_referer() {
87 $referer = wp_get_referer();
88 if ( ! $referer ) {
89 return 0;
90 }
91
92 $parts = wp_parse_url( $referer );
93 if ( empty( $parts['query'] ) ) {
94 return 0;
95 }
96
97 $query = [];
98 parse_str( $parts['query'], $query );
99
100 if ( empty( $query['post'] ) || ! is_numeric( $query['post'] ) ) {
101 return 0;
102 }
103
104 $form_id = absint( $query['post'] );
105 if ( 'suredonation_form' !== get_post_type( $form_id ) ) {
106 return 0;
107 }
108
109 $campaign_id = get_post_meta( $form_id, '_suredonation_campaign_id', true );
110 if ( ! is_numeric( $campaign_id ) ) {
111 return 0;
112 }
113 return absint( $campaign_id );
114 }
115
116 /**
117 * Enqueue admin scripts and styles.
118 *
119 * @param string $hook Current admin page hook.
120 * @return void
121 * @since 0.0.1
122 */
123 public function enqueue_admin_scripts( $hook ) {
124 // Only load on our plugin pages.
125 if ( strpos( $hook, 'suredonation' ) === false ) {
126 return;
127 }
128
129 // Onboarding gets its own lean bundle — bail before enqueuing the
130 // main admin app on that page.
131 if ( false !== strpos( $hook, Onboarding::PAGE_SLUG ) ) {
132 $this->enqueue_onboarding_assets();
133 return;
134 }
135
136 $asset_file = SUREDONATION_DIR . 'assets/build/admin.asset.php';
137
138 if ( ! file_exists( $asset_file ) ) {
139 return;
140 }
141
142 $asset = include $asset_file;
143
144 // Enqueue WordPress editor for TinyMCE support.
145 wp_enqueue_editor();
146
147 // Enqueue the media library so the campaign image picker can open it.
148 wp_enqueue_media();
149
150 // Enqueue editor buttons style (needed for TinyMCE icons).
151 wp_enqueue_style( 'editor-buttons' );
152
153 // Enqueue admin app script.
154 wp_enqueue_script(
155 'suredonation-admin',
156 SUREDONATION_URL . 'assets/build/admin.js',
157 $asset['dependencies'],
158 $asset['version'],
159 true
160 );
161
162 // Enqueue admin app styles.
163 wp_enqueue_style(
164 'suredonation-admin',
165 SUREDONATION_URL . 'assets/build/admin.css',
166 [],
167 $asset['version']
168 );
169
170 // The campaign drawer sits at a very high z-index. Lift the media library
171 // above it, and keep the media modal above its OWN backdrop so the picker
172 // is not dimmed by either overlay.
173 wp_add_inline_style(
174 'suredonation-admin',
175 '.media-modal-backdrop{z-index:2147483646 !important;}.media-modal{z-index:2147483647 !important;}'
176 );
177
178 // Load JS translations for the admin app.
179 wp_set_script_translations( 'suredonation-admin', 'suredonation' );
180
181 // Localize script with plugin data.
182 wp_localize_script(
183 'suredonation-admin',
184 'suredonation_admin',
185 [
186 'version' => SUREDONATION_VER,
187 'apiUrl' => rest_url( 'suredonation/v1' ),
188 'nonce' => wp_create_nonce( 'wp_rest' ),
189 'docsURL' => 'https://suredonation.com/docs/',
190 'plugin_url' => SUREDONATION_URL,
191 'site_url' => site_url(),
192 'admin_url' => admin_url(),
193 'is_first_campaign_created' => $this->has_campaigns(),
194 'is_onboarding_completed' => Onboarding::get_instance()->is_completed(),
195 'rotating_plugin_banner' => $this->get_rotating_plugin_banner(),
196 'smart_tags' => Helper::get_smart_tags(),
197 'is_pro_active' => defined( 'SUREDONATION_PRO_VER' ),
198 'pro_plugin_version' => defined( 'SUREDONATION_PRO_VER' ) ? SUREDONATION_PRO_VER : '',
199 'pro_plugin_name' => defined( 'SUREDONATION_PRO_PRODUCT' ) ? SUREDONATION_PRO_PRODUCT : 'SureDonation Pro',
200 'is_license_active' => defined( 'SUREDONATION_PRO_VER' ) && class_exists( 'SureDonationPro\Inc\Licensing\Licensing' ) ? \SureDonationPro\Inc\Licensing\Licensing::is_license_active() : false,
201 'pdf_library_exists' => Pdf_Utils::check_if_library_exists(),
202 'php_version_compatible' => Pdf_Utils::is_php_compatible(),
203 'pdf_nonce' => wp_create_nonce( 'suredonation_pdf_nonce' ),
204 'ajax_url' => admin_url( 'admin-ajax.php' ),
205 'plugin_installer_nonce' => wp_create_nonce( 'updates' ),
206 'plugin_manager_nonce' => wp_create_nonce( 'suredonation_plugin_manager' ),
207 'has_abilities_api' => class_exists( 'WP_Ability' ),
208 'current_user_login' => wp_get_current_user()->user_login,
209 // Bootstrap settings data so the General currency list and the
210 // PayPal connection state render synchronously (no post-mount
211 // fetch flicker). PayPal data is the non-sensitive subset only.
212 'payments' => [
213 'currencies' => Payment_Helper::get_currencies_list(),
214 'paypal' => PayPal_Helper::get_connection_state(),
215 ],
216 ]
217 );
218 }
219
220 /**
221 * Register admin menu.
222 *
223 * @return void
224 * @since 0.0.1
225 */
226 public function register_menu() {
227 $menu_slug = 'suredonation';
228
229 // SureDonation mark for the admin menu — single path with evenodd
230 // fill so the inner heart cuts a hole that reveals the sidebar
231 // background (mirrors the pattern used by SureForms).
232 $menu_icon_svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M0 3.64405C0 1.6315 1.62712 0 3.63425 0H20.2517C22.2588 0 23.886 1.6315 23.886 3.64405V20.3063C23.886 22.3188 22.2588 23.9503 20.2517 23.9503H3.63425C1.62712 23.9503 0 22.3188 0 20.3063V3.64405ZM12.6516 5.79655C14.4525 3.99075 17.3723 3.99075 19.1734 5.79655C20.9742 7.60234 20.9742 10.5301 19.1734 12.3359L17.5686 13.9451C17.2776 14.2368 16.8057 14.2368 16.5147 13.9451C16.2237 13.6533 16.2237 13.1802 16.5147 12.8884L18.1196 11.2793C19.3385 10.0571 19.3385 8.07537 18.1196 6.85312C16.9007 5.6309 14.9243 5.63092 13.7054 6.85312L11.3607 9.20415C11.0701 9.49552 11.0701 9.96791 11.3607 10.2593C11.6513 10.5506 12.1224 10.5506 12.413 10.2593L13.4659 9.20346C13.7569 8.91169 14.2288 8.91169 14.5198 9.20346C14.8107 9.49523 14.8107 9.96828 14.5198 10.26L13.4667 11.3159C12.5942 12.1909 11.1794 12.1909 10.3069 11.3159C9.43429 10.441 9.43429 9.02249 10.3069 8.14757L10.8879 7.56486L10.1781 6.85312C8.95914 5.63089 6.98277 5.63089 5.76382 6.85312C4.54486 8.07537 4.54488 10.0571 5.76382 11.2793L12.6985 18.2326C12.9895 18.5244 12.9895 18.9974 12.6985 19.2892C12.4075 19.581 11.9358 19.581 11.6448 19.2892L4.71008 12.3359C2.90915 10.5301 2.90913 7.60233 4.71008 5.79655C6.51102 3.99075 9.43091 3.99075 11.2318 5.79655L11.9417 6.50827L12.6516 5.79655ZM11.8605 14.9781C12.1515 14.6863 12.6233 14.6863 12.9143 14.9781L14.5397 16.6079C14.8307 16.8996 14.8307 17.3727 14.5397 17.6645C14.2487 17.9562 13.7769 17.9562 13.4859 17.6645L11.8605 16.0347C11.5695 15.7429 11.5695 15.2699 11.8605 14.9781ZM13.6482 13.1856C13.9392 12.8939 14.4109 12.8939 14.7019 13.1856L16.3273 14.8154C16.6183 15.1072 16.6183 15.5802 16.3273 15.872C16.0363 16.1638 15.5645 16.1638 15.2735 15.872L13.6482 14.2422C13.3572 13.9504 13.3572 13.4774 13.6482 13.1856Z" fill="#D1D5DB"/></svg>';
233
234 // Main menu page.
235 add_menu_page(
236 __( 'SureDonation', 'suredonation' ),
237 __( 'SureDonation', 'suredonation' ),
238 'manage_options',
239 $menu_slug,
240 [ $this, 'render_dashboard' ],
241 'data:image/svg+xml;base64,' . base64_encode( $menu_icon_svg ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Building data URI for menu icon.
242 25
243 );
244
245 // Register submenus.
246 $this->register_submenus( $menu_slug );
247 }
248
249 /**
250 * Render dashboard page.
251 *
252 * @return void
253 * @since 0.0.1
254 */
255 public function render_dashboard() {
256 ?>
257 <div id="suredonation-admin-root"></div>
258 <?php
259 }
260
261 /**
262 * Hide the "All Forms" submenu item.
263 *
264 * WordPress automatically adds a submenu for the suredonation_form CPT.
265 * We hide it since forms are managed through campaigns.
266 *
267 * @return void
268 * @since 0.0.1
269 */
270 public function hide_forms_submenu() {
271 remove_submenu_page( 'suredonation', 'edit.php?post_type=suredonation_form' );
272 }
273
274 /**
275 * Check if any campaigns have been created.
276 *
277 * @return bool True if at least one campaign exists.
278 * @since 0.0.1
279 */
280 private function has_campaigns() {
281 $campaigns = get_posts(
282 [
283 'post_type' => 'suredonation_cmpgn',
284 'posts_per_page' => 1,
285 'post_status' => [ 'publish', 'draft', 'private' ],
286 'fields' => 'ids',
287 ]
288 );
289
290 return ! empty( $campaigns );
291 }
292
293 /**
294 * Get rotating plugin banner data.
295 *
296 * @return array<string,mixed>|null Plugin banner data, or null when the plugin is already active.
297 * @since 0.0.1
298 */
299 private function get_rotating_plugin_banner() {
300 // Match SureForms: never nudge a plugin that is already active, so the
301 // dashboard card is hidden once SureRank is activated.
302 $status = $this->get_plugin_status( 'surerank/surerank.php' );
303 if ( 'Activated' === $status ) {
304 return null;
305 }
306
307 // Get SVG logo as base64.
308 $logo_svg = file_get_contents( SUREDONATION_DIR . 'images/surerank.svg' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local file read.
309 $logo_data = $logo_svg ? 'data:image/svg+xml;base64,' . base64_encode( $logo_svg ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
310
311 // SureRank plugin data.
312 return [
313 'slug' => 'surerank',
314 'path' => 'surerank/surerank.php',
315 'title' => 'SureRank',
316 'singleLineDescription' => __( 'SEO Made Easy for Your Website', 'suredonation' ),
317 'subtitle' => __( 'Beautiful pages, persuasive content, and custom code in seconds. The possibilities are endless!', 'suredonation' ),
318 'logo' => $logo_data,
319 'status' => $status,
320 ];
321 }
322
323 /**
324 * Get plugin installation status.
325 *
326 * @param string $plugin_file Plugin file path (e.g., 'plugin-folder/plugin-file.php').
327 * @return string Plugin status: 'Activated', 'Installed', or 'Install'.
328 * @since 0.0.1
329 */
330 private function get_plugin_status( $plugin_file ) {
331 if ( ! function_exists( 'is_plugin_active' ) ) {
332 include_once ABSPATH . 'wp-admin/includes/plugin.php';
333 }
334
335 if ( is_plugin_active( $plugin_file ) ) {
336 return 'Activated';
337 }
338
339 $all_plugins = get_plugins();
340 if ( isset( $all_plugins[ $plugin_file ] ) ) {
341 return 'Installed';
342 }
343
344 return 'Install';
345 }
346
347 /**
348 * Register submenus.
349 *
350 * @param string $menu_slug Menu slug.
351 * @return void
352 * @since 0.0.1
353 */
354 private function register_submenus( $menu_slug ) {
355 $capability = 'manage_options';
356
357 $submenus = [
358 [
359 'id' => $menu_slug,
360 'page_title' => __( 'Dashboard', 'suredonation' ),
361 ],
362 [
363 'id' => 'suredonation#/campaigns',
364 'page_title' => __( 'Campaigns', 'suredonation' ),
365 ],
366 [
367 'id' => 'suredonation#/donations',
368 'page_title' => __( 'Donations', 'suredonation' ),
369 ],
370 [
371 'id' => 'suredonation#/donors',
372 'page_title' => __( 'Donors', 'suredonation' ),
373 ],
374 [
375 'id' => 'suredonation#/reports',
376 'page_title' => __( 'Reports', 'suredonation' ),
377 ],
378 [
379 'id' => 'suredonation#/settings',
380 'page_title' => __( 'Settings', 'suredonation' ),
381 ],
382 ];
383
384 // Register the submenus.
385 foreach ( $submenus as $submenu ) {
386 add_submenu_page(
387 $menu_slug,
388 $submenu['page_title'],
389 $submenu['page_title'],
390 $capability,
391 $submenu['id'],
392 [ $this, 'render_dashboard' ]
393 );
394 }
395
396 // Hidden onboarding page (no menu parent so it never shows in
397 // the sidebar but is still routable via admin.php?page=...).
398 add_submenu_page(
399 '',
400 __( 'SureDonation Setup', 'suredonation' ),
401 __( 'SureDonation Setup', 'suredonation' ),
402 $capability,
403 Onboarding::PAGE_SLUG,
404 [ $this, 'render_onboarding' ]
405 );
406 }
407
408 /**
409 * Render onboarding root container.
410 *
411 * @return void
412 * @since 1.0.0
413 */
414 public function render_onboarding() {
415 ?>
416 <div id="suredonation-onboarding-root"></div>
417 <?php
418 }
419
420 /**
421 * Enqueue the dedicated onboarding bundle.
422 *
423 * Loaded only on the onboarding admin page so the main admin app
424 * doesn't pay the cost on activation redirects.
425 *
426 * @return void
427 * @since 1.0.0
428 */
429 private function enqueue_onboarding_assets() {
430 $asset_file = SUREDONATION_DIR . 'assets/build/admin/onboarding/index.asset.php';
431
432 if ( ! file_exists( $asset_file ) ) {
433 return;
434 }
435
436 $asset = include $asset_file;
437
438 wp_enqueue_script(
439 'suredonation-onboarding',
440 SUREDONATION_URL . 'assets/build/admin/onboarding/index.js',
441 $asset['dependencies'],
442 $asset['version'],
443 true
444 );
445
446 wp_enqueue_style(
447 'suredonation-onboarding',
448 SUREDONATION_URL . 'assets/build/admin/onboarding/index.css',
449 [],
450 $asset['version']
451 );
452
453 wp_set_script_translations( 'suredonation-onboarding', 'suredonation' );
454
455 $current_user = wp_get_current_user();
456 $is_pro = defined( 'SUREDONATION_PRO_VER' );
457
458 // Detect GiveWP rows once at enqueue time so the JS doesn't have to
459 // spin a loader (and won't ever flash an error) just to decide whether
460 // to render the optional migration step.
461 $has_givewp_data = false;
462 try {
463 $has_givewp_data = \SureDonation\Inc\Import\Givewp\Source::get_instance()->has_givewp_data();
464 } catch ( \Throwable $e ) {
465 $has_givewp_data = false;
466 }
467
468 wp_localize_script(
469 'suredonation-onboarding',
470 'suredonation_onboarding',
471 [
472 'apiUrl' => rest_url( 'suredonation/v1' ),
473 'nonce' => wp_create_nonce( 'wp_rest' ),
474 'pluginUrl' => SUREDONATION_URL,
475 'adminUrl' => admin_url(),
476 'dashboardUrl' => admin_url( 'admin.php?page=suredonation' ),
477 'paymentsUrl' => admin_url( 'admin.php?page=suredonation#/settings?tab=payments' ),
478 'campaignsUrl' => admin_url( 'admin.php?page=suredonation#/campaigns' ),
479 'docsUrl' => 'https://suredonation.com/docs/',
480 'isProActive' => $is_pro,
481 'hasGiveWPData' => $has_givewp_data,
482 'currentUser' => [
483 'firstName' => $current_user ? $current_user->first_name : '',
484 'lastName' => $current_user ? $current_user->last_name : '',
485 'email' => $current_user ? $current_user->user_email : '',
486 ],
487 'privacyUrl' => 'https://suredonation.com/privacy-policy/',
488 ]
489 );
490
491 // Shim suredonation_admin on the onboarding page too — shared API
492 // helpers (e.g. importApi.js) read apiUrl + nonce from that global,
493 // so without this the migration session fails the REST cookie check.
494 wp_localize_script(
495 'suredonation-onboarding',
496 'suredonation_admin',
497 [
498 'apiUrl' => rest_url( 'suredonation/v1' ),
499 'nonce' => wp_create_nonce( 'wp_rest' ),
500 'is_pro_active' => $is_pro,
501 ]
502 );
503 }
504 }
505