PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.0.0
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.0.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.0.0, at inc/admin/admin.php

496 lines 15.9 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\Traits\Get_Instance;
14
15 // Exit if accessed directly.
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit;
18 }
19
20 /**
21 * Admin class.
22 *
23 * @since 0.0.1
24 */
25 class Admin {
26 use Get_Instance;
27
28 /**
29 * Constructor.
30 *
31 * @since 0.0.1
32 */
33 public function __construct() {
34 add_action( 'admin_menu', [ $this, 'register_menu' ] );
35 add_action( 'admin_menu', [ $this, 'hide_forms_submenu' ], 999 );
36 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_scripts' ] );
37 add_action( 'admin_init', [ $this, 'redirect_cpt_listings' ] );
38 }
39
40 /**
41 * Redirect the default CPT list screens for suredonation_form and
42 * suredonation_cmpgn to their corresponding plugin pages.
43 *
44 * For the form listing, attempt to resolve the originating campaign
45 * from the referer (the "back" arrow on the form editor sends the
46 * user here) and redirect to that campaign's single view.
47 *
48 * @return void
49 * @since 1.0.0
50 */
51 public function redirect_cpt_listings() {
52 global $pagenow;
53
54 if ( 'edit.php' !== $pagenow ) {
55 return;
56 }
57
58 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading post_type from query, no state mutation.
59 $post_type = isset( $_GET['post_type'] ) ? sanitize_key( wp_unslash( $_GET['post_type'] ) ) : '';
60
61 if ( 'suredonation_form' === $post_type ) {
62 $campaign_id = $this->resolve_campaign_id_from_referer();
63 $target = $campaign_id
64 ? admin_url( 'admin.php?page=suredonation#/campaigns/' . $campaign_id )
65 : admin_url( 'admin.php?page=suredonation#/campaigns' );
66
67 wp_safe_redirect( $target );
68 exit;
69 }
70
71 if ( 'suredonation_cmpgn' === $post_type ) {
72 wp_safe_redirect( admin_url( 'admin.php?page=suredonation#/campaigns' ) );
73 exit;
74 }
75 }
76
77 /**
78 * Resolve the campaign ID from the referer when the form list page
79 * is reached from the form editor's "back" button.
80 *
81 * @return int Campaign ID, or 0 when it cannot be determined.
82 * @since 1.0.0
83 */
84 private function resolve_campaign_id_from_referer() {
85 $referer = wp_get_referer();
86 if ( ! $referer ) {
87 return 0;
88 }
89
90 $parts = wp_parse_url( $referer );
91 if ( empty( $parts['query'] ) ) {
92 return 0;
93 }
94
95 $query = [];
96 parse_str( $parts['query'], $query );
97
98 if ( empty( $query['post'] ) || ! is_numeric( $query['post'] ) ) {
99 return 0;
100 }
101
102 $form_id = absint( $query['post'] );
103 if ( 'suredonation_form' !== get_post_type( $form_id ) ) {
104 return 0;
105 }
106
107 $campaign_id = get_post_meta( $form_id, '_suredonation_campaign_id', true );
108 if ( ! is_numeric( $campaign_id ) ) {
109 return 0;
110 }
111 return absint( $campaign_id );
112 }
113
114 /**
115 * Enqueue admin scripts and styles.
116 *
117 * @param string $hook Current admin page hook.
118 * @return void
119 * @since 0.0.1
120 */
121 public function enqueue_admin_scripts( $hook ) {
122 // Only load on our plugin pages.
123 if ( strpos( $hook, 'suredonation' ) === false ) {
124 return;
125 }
126
127 // Onboarding gets its own lean bundle — bail before enqueuing the
128 // main admin app on that page.
129 if ( false !== strpos( $hook, Onboarding::PAGE_SLUG ) ) {
130 $this->enqueue_onboarding_assets();
131 return;
132 }
133
134 $asset_file = SUREDONATION_DIR . 'assets/build/admin.asset.php';
135
136 if ( ! file_exists( $asset_file ) ) {
137 return;
138 }
139
140 $asset = include $asset_file;
141
142 // Enqueue WordPress editor for TinyMCE support.
143 wp_enqueue_editor();
144
145 // Enqueue the media library so the campaign image picker can open it.
146 wp_enqueue_media();
147
148 // Enqueue editor buttons style (needed for TinyMCE icons).
149 wp_enqueue_style( 'editor-buttons' );
150
151 // Enqueue admin app script.
152 wp_enqueue_script(
153 'suredonation-admin',
154 SUREDONATION_URL . 'assets/build/admin.js',
155 $asset['dependencies'],
156 $asset['version'],
157 true
158 );
159
160 // Enqueue admin app styles.
161 wp_enqueue_style(
162 'suredonation-admin',
163 SUREDONATION_URL . 'assets/build/admin.css',
164 [],
165 $asset['version']
166 );
167
168 // The campaign drawer sits at a very high z-index. Lift the media library
169 // above it, and keep the media modal above its OWN backdrop so the picker
170 // is not dimmed by either overlay.
171 wp_add_inline_style(
172 'suredonation-admin',
173 '.media-modal-backdrop{z-index:2147483646 !important;}.media-modal{z-index:2147483647 !important;}'
174 );
175
176 // Load JS translations for the admin app.
177 wp_set_script_translations( 'suredonation-admin', 'suredonation' );
178
179 // Localize script with plugin data.
180 wp_localize_script(
181 'suredonation-admin',
182 'suredonation_admin',
183 [
184 'version' => SUREDONATION_VER,
185 'apiUrl' => rest_url( 'suredonation/v1' ),
186 'nonce' => wp_create_nonce( 'wp_rest' ),
187 'docsURL' => 'https://suredonation.com/docs/',
188 'plugin_url' => SUREDONATION_URL,
189 'site_url' => site_url(),
190 'admin_url' => admin_url(),
191 'is_first_campaign_created' => $this->has_campaigns(),
192 'is_onboarding_completed' => Onboarding::get_instance()->is_completed(),
193 'rotating_plugin_banner' => $this->get_rotating_plugin_banner(),
194 'smart_tags' => Helper::get_smart_tags(),
195 'is_pro_active' => defined( 'SUREDONATION_PRO_VER' ),
196 'pro_plugin_version' => defined( 'SUREDONATION_PRO_VER' ) ? SUREDONATION_PRO_VER : '',
197 'pro_plugin_name' => defined( 'SUREDONATION_PRO_PRODUCT' ) ? SUREDONATION_PRO_PRODUCT : 'SureDonation Pro',
198 'is_license_active' => defined( 'SUREDONATION_PRO_VER' ) && class_exists( 'SureDonationPro\Inc\Licensing\Licensing' ) ? \SureDonationPro\Inc\Licensing\Licensing::is_license_active() : false,
199 'pdf_library_exists' => Pdf_Utils::check_if_library_exists(),
200 'php_version_compatible' => Pdf_Utils::is_php_compatible(),
201 'pdf_nonce' => wp_create_nonce( 'suredonation_pdf_nonce' ),
202 'ajax_url' => admin_url( 'admin-ajax.php' ),
203 'plugin_installer_nonce' => wp_create_nonce( 'updates' ),
204 'plugin_manager_nonce' => wp_create_nonce( 'suredonation_plugin_manager' ),
205 'has_abilities_api' => class_exists( 'WP_Ability' ),
206 'current_user_login' => wp_get_current_user()->user_login,
207 ]
208 );
209 }
210
211 /**
212 * Register admin menu.
213 *
214 * @return void
215 * @since 0.0.1
216 */
217 public function register_menu() {
218 $menu_slug = 'suredonation';
219
220 // SureDonation mark for the admin menu — single path with evenodd
221 // fill so the inner heart cuts a hole that reveals the sidebar
222 // background (mirrors the pattern used by SureForms).
223 $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>';
224
225 // Main menu page.
226 add_menu_page(
227 __( 'SureDonation', 'suredonation' ),
228 __( 'SureDonation', 'suredonation' ),
229 'manage_options',
230 $menu_slug,
231 [ $this, 'render_dashboard' ],
232 'data:image/svg+xml;base64,' . base64_encode( $menu_icon_svg ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Building data URI for menu icon.
233 25
234 );
235
236 // Register submenus.
237 $this->register_submenus( $menu_slug );
238 }
239
240 /**
241 * Render dashboard page.
242 *
243 * @return void
244 * @since 0.0.1
245 */
246 public function render_dashboard() {
247 ?>
248 <div id="suredonation-admin-root"></div>
249 <?php
250 }
251
252 /**
253 * Hide the "All Forms" submenu item.
254 *
255 * WordPress automatically adds a submenu for the suredonation_form CPT.
256 * We hide it since forms are managed through campaigns.
257 *
258 * @return void
259 * @since 0.0.1
260 */
261 public function hide_forms_submenu() {
262 remove_submenu_page( 'suredonation', 'edit.php?post_type=suredonation_form' );
263 }
264
265 /**
266 * Check if any campaigns have been created.
267 *
268 * @return bool True if at least one campaign exists.
269 * @since 0.0.1
270 */
271 private function has_campaigns() {
272 $campaigns = get_posts(
273 [
274 'post_type' => 'suredonation_cmpgn',
275 'posts_per_page' => 1,
276 'post_status' => [ 'publish', 'draft', 'private' ],
277 'fields' => 'ids',
278 ]
279 );
280
281 return ! empty( $campaigns );
282 }
283
284 /**
285 * Get rotating plugin banner data.
286 *
287 * @return array<string,mixed>|null Plugin banner data, or null when the plugin is already active.
288 * @since 0.0.1
289 */
290 private function get_rotating_plugin_banner() {
291 // Match SureForms: never nudge a plugin that is already active, so the
292 // dashboard card is hidden once SureRank is activated.
293 $status = $this->get_plugin_status( 'surerank/surerank.php' );
294 if ( 'Activated' === $status ) {
295 return null;
296 }
297
298 // Get SVG logo as base64.
299 $logo_svg = file_get_contents( SUREDONATION_DIR . 'images/surerank.svg' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local file read.
300 $logo_data = $logo_svg ? 'data:image/svg+xml;base64,' . base64_encode( $logo_svg ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
301
302 // SureRank plugin data.
303 return [
304 'slug' => 'surerank',
305 'path' => 'surerank/surerank.php',
306 'title' => 'SureRank',
307 'singleLineDescription' => __( 'SEO Made Easy for Your Website', 'suredonation' ),
308 'subtitle' => __( 'Beautiful pages, persuasive content, and custom code in seconds. The possibilities are endless!', 'suredonation' ),
309 'logo' => $logo_data,
310 'status' => $status,
311 ];
312 }
313
314 /**
315 * Get plugin installation status.
316 *
317 * @param string $plugin_file Plugin file path (e.g., 'plugin-folder/plugin-file.php').
318 * @return string Plugin status: 'Activated', 'Installed', or 'Install'.
319 * @since 0.0.1
320 */
321 private function get_plugin_status( $plugin_file ) {
322 if ( ! function_exists( 'is_plugin_active' ) ) {
323 include_once ABSPATH . 'wp-admin/includes/plugin.php';
324 }
325
326 if ( is_plugin_active( $plugin_file ) ) {
327 return 'Activated';
328 }
329
330 $all_plugins = get_plugins();
331 if ( isset( $all_plugins[ $plugin_file ] ) ) {
332 return 'Installed';
333 }
334
335 return 'Install';
336 }
337
338 /**
339 * Register submenus.
340 *
341 * @param string $menu_slug Menu slug.
342 * @return void
343 * @since 0.0.1
344 */
345 private function register_submenus( $menu_slug ) {
346 $capability = 'manage_options';
347
348 $submenus = [
349 [
350 'id' => $menu_slug,
351 'page_title' => __( 'Dashboard', 'suredonation' ),
352 ],
353 [
354 'id' => 'suredonation#/campaigns',
355 'page_title' => __( 'Campaigns', 'suredonation' ),
356 ],
357 [
358 'id' => 'suredonation#/donations',
359 'page_title' => __( 'Donations', 'suredonation' ),
360 ],
361 [
362 'id' => 'suredonation#/donors',
363 'page_title' => __( 'Donors', 'suredonation' ),
364 ],
365 [
366 'id' => 'suredonation#/reports',
367 'page_title' => __( 'Reports', 'suredonation' ),
368 ],
369 [
370 'id' => 'suredonation#/settings',
371 'page_title' => __( 'Settings', 'suredonation' ),
372 ],
373 ];
374
375 // Register the submenus.
376 foreach ( $submenus as $submenu ) {
377 add_submenu_page(
378 $menu_slug,
379 $submenu['page_title'],
380 $submenu['page_title'],
381 $capability,
382 $submenu['id'],
383 [ $this, 'render_dashboard' ]
384 );
385 }
386
387 // Hidden onboarding page (no menu parent so it never shows in
388 // the sidebar but is still routable via admin.php?page=...).
389 add_submenu_page(
390 '',
391 __( 'SureDonation Setup', 'suredonation' ),
392 __( 'SureDonation Setup', 'suredonation' ),
393 $capability,
394 Onboarding::PAGE_SLUG,
395 [ $this, 'render_onboarding' ]
396 );
397 }
398
399 /**
400 * Render onboarding root container.
401 *
402 * @return void
403 * @since 1.0.0
404 */
405 public function render_onboarding() {
406 ?>
407 <div id="suredonation-onboarding-root"></div>
408 <?php
409 }
410
411 /**
412 * Enqueue the dedicated onboarding bundle.
413 *
414 * Loaded only on the onboarding admin page so the main admin app
415 * doesn't pay the cost on activation redirects.
416 *
417 * @return void
418 * @since 1.0.0
419 */
420 private function enqueue_onboarding_assets() {
421 $asset_file = SUREDONATION_DIR . 'assets/build/admin/onboarding/index.asset.php';
422
423 if ( ! file_exists( $asset_file ) ) {
424 return;
425 }
426
427 $asset = include $asset_file;
428
429 wp_enqueue_script(
430 'suredonation-onboarding',
431 SUREDONATION_URL . 'assets/build/admin/onboarding/index.js',
432 $asset['dependencies'],
433 $asset['version'],
434 true
435 );
436
437 wp_enqueue_style(
438 'suredonation-onboarding',
439 SUREDONATION_URL . 'assets/build/admin/onboarding/index.css',
440 [],
441 $asset['version']
442 );
443
444 wp_set_script_translations( 'suredonation-onboarding', 'suredonation' );
445
446 $current_user = wp_get_current_user();
447 $is_pro = defined( 'SUREDONATION_PRO_VER' );
448
449 // Detect GiveWP rows once at enqueue time so the JS doesn't have to
450 // spin a loader (and won't ever flash an error) just to decide whether
451 // to render the optional migration step.
452 $has_givewp_data = false;
453 try {
454 $has_givewp_data = \SureDonation\Inc\Import\Givewp\Source::get_instance()->has_givewp_data();
455 } catch ( \Throwable $e ) {
456 $has_givewp_data = false;
457 }
458
459 wp_localize_script(
460 'suredonation-onboarding',
461 'suredonation_onboarding',
462 [
463 'apiUrl' => rest_url( 'suredonation/v1' ),
464 'nonce' => wp_create_nonce( 'wp_rest' ),
465 'pluginUrl' => SUREDONATION_URL,
466 'adminUrl' => admin_url(),
467 'dashboardUrl' => admin_url( 'admin.php?page=suredonation' ),
468 'paymentsUrl' => admin_url( 'admin.php?page=suredonation#/settings?tab=payments' ),
469 'campaignsUrl' => admin_url( 'admin.php?page=suredonation#/campaigns' ),
470 'docsUrl' => 'https://suredonation.com/docs/',
471 'isProActive' => $is_pro,
472 'hasGiveWPData' => $has_givewp_data,
473 'currentUser' => [
474 'firstName' => $current_user ? $current_user->first_name : '',
475 'lastName' => $current_user ? $current_user->last_name : '',
476 'email' => $current_user ? $current_user->user_email : '',
477 ],
478 'privacyUrl' => 'https://suredonation.com/privacy-policy/',
479 ]
480 );
481
482 // Shim suredonation_admin on the onboarding page too — shared API
483 // helpers (e.g. importApi.js) read apiUrl + nonce from that global,
484 // so without this the migration session fails the REST cookie check.
485 wp_localize_script(
486 'suredonation-onboarding',
487 'suredonation_admin',
488 [
489 'apiUrl' => rest_url( 'suredonation/v1' ),
490 'nonce' => wp_create_nonce( 'wp_rest' ),
491 'is_pro_active' => $is_pro,
492 ]
493 );
494 }
495 }
496