PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.12.2
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.12.2
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 / campaigns / class-charitable-campaign-list-table.php

class-charitable-campaign-list-table.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.12.2, at includes/admin/campaigns/class-charitable-campaign-list-table.php

963 lines 34.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Sets up the campaign list table in the admin.
4 *
5 * @package Charitable/Classes/Charitable_Campaign_List_Table
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.5.0
10 * @version 1.8.1.5, 1.8.9.1, 1.8.10
11 */
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 if ( ! class_exists( 'Charitable_Campaign_List_Table' ) ) :
18
19 /**
20 * Charitable_Campaign_List_Table class.
21 *
22 * @since 1.5.0
23 */
24 final class Charitable_Campaign_List_Table {
25
26 /**
27 * The single instance of this class.
28 *
29 * @since 1.5.0
30 *
31 * @var Charitable_Campaign_List_Table|null
32 */
33 private static $instance = null;
34
35 /**
36 * Post type.
37 *
38 * @var string
39 */
40 protected $list_table_type = 'campaign';
41
42 /**
43 * Returns and/or create the single instance of this class.
44 *
45 * @since 1.5.0
46 *
47 * @return Charitable_Campaign_List_Table
48 */
49 public static function get_instance() {
50 if ( is_null( self::$instance ) ) {
51 self::$instance = new self();
52 }
53
54 return self::$instance;
55 }
56
57 /**
58 * Updates body class for admin.
59 *
60 * @since 1.8.0
61 *
62 * @param string $classes The current body classes.
63 *
64 * @return string
65 */
66 public function admin_body_class( $classes ) {
67
68 global $pagenow;
69
70 if ( in_array( $pagenow, array( 'edit.php' ), true ) && ! empty( $_GET['post_type'] ) && $_GET['post_type'] === 'campaign' ) { // phpcs:ignore.
71 $count_posts = ( wp_count_posts( 'campaign' )->draft + wp_count_posts( 'campaign' )->publish + wp_count_posts( 'campaign' )->future + wp_count_posts( 'campaign' )->pending + wp_count_posts( 'campaign' )->private );
72 $classes .= ' charitable-campaigns charitable-campaigns-' . intval( $count_posts ) . '-published'; // this should be at start: charitable-campaigns charitable-campaigns-0-published.
73 }
74
75 if ( in_array( $pagenow, array( 'edit.php' ), true ) && ! empty( $_GET['post_status'] ) && $_GET['post_status'] === 'trash' ) { // phpcs:ignore.
76 $count_posts = ( wp_count_posts( 'campaign' )->trash );
77 $classes .= ' charitable-trash charitable-campaigns-' . intval( $count_posts ) . '-trash';
78 }
79
80 return $classes;
81 }
82
83 /**
84 * Customize campaigns columns.
85 *
86 * @see get_column_headers
87 *
88 * @since 1.5.0
89 *
90 * @return array
91 */
92 public function dashboard_columns() {
93 /**
94 * Filter the columns shown in the campaigns list table.
95 *
96 * @since 1.5.0
97 *
98 * @param array $columns The list of columns.
99 */
100 return apply_filters(
101 'charitable_campaign_dashboard_column_names',
102 array(
103 'cb' => '<input type="checkbox"/>',
104 'title' => __( 'Title', 'charitable' ),
105 'creator' => __( 'Creator', 'charitable' ),
106 'donated' => __( 'Donations', 'charitable' ),
107 'status' => __( 'Status', 'charitable' ),
108 'end_date' => __( 'End Date', 'charitable' ),
109 'date_created' => __( 'Date Created', 'charitable' ),
110 'actions' => __( 'Actions', 'charitable' ),
111 )
112 );
113 }
114
115 /**
116 * Add information to the dashboard campaign table listing.
117 *
118 * @see WP_Posts_List_Table::single_row()
119 *
120 * @since 1.5.0
121 *
122 * @param string $column_name The name of the column to display.
123 * @param int $post_id The current post ID.
124 * @return void
125 */
126 public function dashboard_column_item( $column_name, $post_id ) {
127 $campaign = charitable_get_campaign( $post_id );
128 $display = '';
129
130 switch ( $column_name ) {
131 case 'ID':
132 $display = $post_id;
133 break;
134
135 case 'creator':
136 $creator_id = $campaign->get_campaign_creator();
137 $creator_name = $campaign->get_campaign_creator_name();
138
139 if ( intval( $creator_id ) > 0 ) {
140 $display .= '<a href="' . get_edit_user_link( $creator_id ) . '" >';
141 $display .= '<img class="charitable-campaign-creator-avatar" alt="' . $creator_name . '" title="' . $creator_name . '" src="' . esc_url( get_avatar_url( $creator_id ) ) . '" />';
142 }
143 $display .= '<span class="charitable-campaign-creator-name">' . $creator_name . '</span>';
144 if ( intval( $creator_id ) > 0 ) {
145 $display .= '</a>';
146 }
147 break;
148
149 case 'donated':
150 $display = charitable_format_money( $campaign->get_donated_amount() );
151 $percent = absint( $campaign->get_percent_donated() );
152 if ( $percent > 100 ) {
153 $percent = 100;
154 }
155 if ( ! $campaign->is_endless() ) {
156 $display .= '<div class="meter">
157 <span style="width: ' . $percent . '%"></span>
158 </div>';
159 }
160 break;
161
162 case 'date_created':
163 $date_format = get_option( 'date_format' );
164 $date_format = ( '' === trim( $date_format ) ) ? 'd/m/Y' : $date_format;
165 $display = date( $date_format, strtotime( $campaign->post_date ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
166 break;
167
168 case 'end_date':
169 $display = $campaign->is_endless() ? '&#8734;' : $campaign->get_end_date();
170 break;
171
172 case 'actions':
173 // determine if we share the preview or the actual live URL.
174 $status = get_post_status( $post_id );
175
176 if ( 'trash' === $status ) {
177 break;
178 }
179
180 $link = ( 'publish' === $status || 'private' === $status ) ? get_permalink( $post_id ) : charitable_get_campaign_preview_url( $post_id, true, $status );
181 $display = '<a class="charitable-campaign-action-button" title="' . esc_html__( 'Preview', 'charitable' ) . '" href="' . esc_url( $link ) . '" target="_blank"><img src="' . charitable()->get_path( 'assets', false ) . 'images/icons/eye.svg" width="14" height="14" alt="' . esc_html__( 'Preview', 'charitable' ) . '" /></a><a class="charitable-campaign-action-button" title="' . esc_html__( 'Trash', 'charitable' ) . '" href="' . get_delete_post_link( $post_id ) . '"><img src="' . charitable()->get_path( 'assets', false ) . 'images/icons/trash.svg" width="14" height="14" alt="' . esc_html__( 'Trash', 'charitable' ) . '" /></a>';
182
183 // <button><i class="fa fa-envelope"></i></button>
184
185 break;
186
187 case 'status':
188 $status = $campaign->get_status();
189
190 if ( 'finished' === $status && $campaign->has_goal() ) {
191 $status = $campaign->has_achieved_goal() ? 'successful' : 'unsuccessful';
192 }
193
194 $display = '<mark class="' . esc_attr( $status ) . '">' . $status . '</mark>';
195 break;
196
197 default:
198 $display = '';
199 }
200
201 /**
202 * Filter the output of the cell.
203 *
204 * @since 1.5.0
205 *
206 * @param string $display The content that will be displayed.
207 * @param string $column_name The name of the column.
208 * @param int $post_id The ID of the campaign being shown.
209 * @param Charitable_Campaign $campaign The Charitable_Campaign object.
210 */
211 echo apply_filters( 'charitable_campaign_column_display', $display, $column_name, $post_id, $campaign ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
212 }
213
214 /**
215 * Modify bulk messages
216 *
217 * @since 1.5.0
218 *
219 * @param array $bulk_messages Messages to show after bulk actions.
220 * @param array $bulk_counts Array showing how many items were affected by the action.
221 * @return array
222 */
223 public function bulk_messages( $bulk_messages, $bulk_counts ) {
224 $bulk_messages[ Charitable::CAMPAIGN_POST_TYPE ] = array(
225 /* translators: %s: number of items */
226 'updated' => _n( '%d campaign updated.', '%d campaigns updated.', $bulk_counts['updated'], 'charitable' ),
227 'locked' => ( 1 == $bulk_counts['locked'] )
228 ? __( '1 campaign not updated, somebody is editing it.', 'charitable' )
229 /* translators: %s: number of items */
230 : _n( '%s campaign not updated, somebody is editing it.', '%s campaigns not updated, somebody is editing them.', $bulk_counts['locked'], 'charitable' ),
231 /* translators: %s: number of items */
232 'deleted' => _n( '%s campaign permanently deleted.', '%s campaigns permanently deleted.', $bulk_counts['deleted'], 'charitable' ),
233 /* translators: %s: number of items */
234 'trashed' => _n( '%s campaign moved to the Trash.', '%s campaigns moved to the Trash.', $bulk_counts['trashed'], 'charitable' ),
235 /* translators: %s: number of items */
236 'untrashed' => _n( '%s campaign restored from the Trash.', '%s campaigns restored from the Trash.', $bulk_counts['untrashed'], 'charitable' ),
237 );
238
239 return $bulk_messages;
240 }
241
242 /**
243 * Add extra buttons after filters
244 *
245 * @since 1.6.0
246 *
247 * @param string $which The context where this is being called.
248 * @return void
249 */
250 public function add_export( $which ) {
251 if ( ! current_user_can( 'export_charitable_reports' ) ) {
252 return;
253 }
254
255 if ( 'top' === $which && $this->is_campaigns_page() ) {
256 charitable_admin_view( 'campaigns-page/export' );
257 }
258 }
259
260 /**
261 * Add extra buttons after filters
262 *
263 * @since 1.6.0
264 *
265 * @param string $which The context where this is being called.
266 * @return void
267 */
268 public function add_legacy_link( $which ) {
269 if ( ! current_user_can( 'publish_posts' ) ) {
270 return;
271 }
272
273 if ( 'top' === $which && $this->is_campaigns_page() ) {
274 charitable_admin_view( 'campaigns-page/legacy-add' );
275 }
276 }
277
278
279 /**
280 * Add filters above the campaigns table.
281 *
282 * @since 1.6.36
283 *
284 * @global string $typenow The current post type.
285 *
286 * @return void
287 */
288 public function add_filters() {
289 global $typenow;
290
291 /* Show custom filters to filter orders by donor. */
292 if ( in_array( $typenow, array( Charitable::CAMPAIGN_POST_TYPE ) ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTypeDeclaration
293 charitable_admin_view( 'campaigns-page/filters' );
294 }
295 }
296
297 /**
298 * Add modal template to footer.
299 *
300 * @since 1.6.0
301 *
302 * @return void
303 */
304 public function modal_forms() {
305 if ( $this->is_campaigns_page() ) {
306 charitable_admin_view( 'campaigns-page/filter-form' );
307 charitable_admin_view( 'campaigns-page/export-form' );
308 }
309 }
310
311 /**
312 * Admin scripts and styles.
313 *
314 * Set up the scripts & styles used for the modal.
315 *
316 * @since 1.6.0
317 *
318 * @param string $hook The current page hook/slug.
319 * @return void
320 */
321 public function load_scripts( $hook ) {
322 if ( 'edit.php' != $hook ) {
323 return;
324 }
325
326 if ( $this->is_campaigns_page() ) {
327 wp_enqueue_style( 'lean-modal-css' );
328 wp_enqueue_script( 'jquery' );
329 wp_enqueue_script( 'lean-modal' );
330 wp_enqueue_script( 'charitable-admin-tables' );
331 }
332 }
333
334 /**
335 * Add custom filters to the query that returns the campaigns to be displayed.
336 *
337 * @since 1.6.36
338 *
339 * @global string $typenow The current post type.
340 *
341 * @param array $vars The array of args to pass to WP_Query.
342 * @return array
343 */
344 public function filter_request_query( $vars ) {
345 if ( ! $this->is_campaigns_page() ) {
346 return $vars;
347 }
348
349 /* No Status: fix WP's crappy handling of "all" post status. */
350 if ( ! isset( $_GET['post_status'] ) || empty( $_GET['post_status'] ) || 'all' === $_GET['post_status'] ) {
351 $vars['post_status'] = array_keys( get_post_statuses() );
352 $vars['perm'] = 'readable';
353 } else {
354 switch ( $_GET['post_status'] ) {
355 case 'active':
356 $vars['post_status'] = 'publish';
357 $vars['meta_query'] = array(
358 'relation' => 'OR',
359 array(
360 'key' => '_campaign_end_date',
361 'value' => date( 'Y-m-d H:i:s' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
362 'compare' => '>',
363 'type' => 'datetime',
364 ),
365 array(
366 'key' => '_campaign_end_date',
367 'value' => 0,
368 'compare' => '=',
369 ),
370 );
371 break;
372
373 case 'finish':
374 $vars['post_status'] = 'publish';
375 $vars['meta_query'] = array(
376 array(
377 'key' => '_campaign_end_date',
378 'value' => date( 'Y-m-d H:i:s' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
379 'compare' => '<=',
380 'type' => 'datetime',
381 ),
382 );
383 break;
384
385 default:
386 $vars['post_status'] = esc_attr( $_GET['post_status'] ); // phpcs:ignore
387 $vars['perm'] = 'readable';
388 }
389 }
390
391 if ( ! empty( $_GET['charitable_nonce'] ) &&
392 wp_verify_nonce( esc_html( $_GET['charitable_nonce'] ), 'charitable_filter_campaigns' ) ) : // phpcs:ignore
393
394 /* Set up start date query */
395 if ( isset( $_GET['start_date_from'] ) && ! empty( $_GET['start_date_from'] ) ) {
396 $start_date_from = $this->get_parsed_date( esc_html( $_GET['start_date_from'] ) ); // phpcs:ignore
397 $vars['date_query']['after'] = array(
398 'year' => $start_date_from['year'],
399 'month' => $start_date_from['month'],
400 'day' => $start_date_from['day'],
401 );
402 }
403
404 if ( isset( $_GET['start_date_to'] ) && ! empty( $_GET['start_date_to'] ) ) {
405 $start_date_to = $this->get_parsed_date( esc_html( $_GET['start_date_to'] ) ); // phpcs:ignore
406 $vars['date_query']['before'] = array(
407 'year' => $start_date_to['year'],
408 'month' => $start_date_to['month'],
409 'day' => $start_date_to['day'],
410 );
411 }
412
413 /* Set up end date query */
414 if ( isset( $_GET['end_date_from'] ) && ! empty( $_GET['end_date_from'] ) ) {
415 $end_date_from = $this->get_parsed_date( esc_html( $_GET['end_date_from'] ) ); // phpcs:ignore
416 $vars['meta_query'][] = array(
417 'key' => '_campaign_end_date',
418 'value' => sprintf( '%d-%d-%d 00:00:00', $end_date_from['year'], $end_date_from['month'], $end_date_from['day'] ),
419 'compare' => '>=',
420 'type' => 'datetime',
421 );
422 }
423
424 if ( isset( $_GET['end_date_to'] ) && ! empty( $_GET['end_date_to'] ) ) {
425 $end_date_to = $this->get_parsed_date( esc_html( $_GET['end_date_to'] ) ); // phpcs:ignore
426 $vars['meta_query'][] = array(
427 'key' => '_campaign_end_date',
428 'value' => sprintf( '%d-%d-%d 00:00:00', $end_date_to['year'], $end_date_to['month'], $end_date_to['day'] ),
429 'compare' => '<=',
430 'type' => 'datetime',
431 );
432 }
433
434 if ( isset( $vars['date_query'] ) ) {
435 $vars['date_query']['inclusive'] = true;
436 }
437
438 /* Filter by campaign. */
439 if ( isset( $_GET['campaign_id'] ) && false !== stripos( esc_html( $_GET['charitable_nonce'] ), 'all' ) ) { // phpcs:ignore
440 $vars['post__in'] = charitable_get_table( 'campaign_donations' )->get_donation_ids_for_campaign( intval( $_GET['campaign_id'] ) );
441 }
442
443 endif;
444
445 /* Restrict by author if user can only edit their own. */
446 if ( ! current_user_can( 'edit_others_campaigns' ) ) {
447 $vars['author'] = get_current_user_id();
448 }
449
450 /**
451 * Filter the campaign list query vars.
452 *
453 * @since 1.6.36
454 *
455 * @param array $vars The request query vars.
456 */
457 return apply_filters( 'charitable_filter_campaigns_list_request_vars', $vars );
458 }
459
460 /**
461 * Given a date, returns an array containing the date, month and year.
462 * Updated in 1.7.0.8 to reflect a change in date format, but points to a new date format function in case someone else is using the old one.
463 *
464 * @since 1.6.36
465 *
466 * @param string $date A date as a string that can be parsed by strtotime.
467 * @return string[]
468 */
469 protected function get_parsed_date( $date ) {
470 $time = charitable_sanitize_date_alt_format( $date );
471
472 $args = array(
473 'year' => date( 'Y', $time ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
474 'month' => date( 'm', $time ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
475 'day' => date( 'd', $time ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
476 );
477
478 return $args;
479 }
480
481 /**
482 * Checks whether this is the campaigns page.
483 *
484 * @since 1.6.0
485 *
486 * @global string $typenow The current post type.
487 * @global string $pagenow The current admin page.
488 *
489 * @return boolean
490 */
491 private function is_campaigns_page() {
492 global $typenow, $pagenow;
493
494 return 'edit.php' === $pagenow && in_array( $typenow, array( Charitable::CAMPAIGN_POST_TYPE ), true );
495 }
496
497 /**
498 * Show blank slate.
499 *
500 * @since 1.8.1.5
501 * @version 1.8.8.6, 1.8.9.1, 1.8.10
502 *
503 * @param string $which String which tablenav is being shown.
504 *
505 * @return void
506 */
507 public function maybe_render_blank_state( $which = '' ) {
508 global $post_type, $typenow;
509
510 // Static flag to prevent double rendering
511 static $blank_state_rendered = false;
512
513 // Use $typenow as fallback if $post_type is not set
514 $current_post_type = $post_type ? $post_type : $typenow;
515
516 // Only proceed if we're on the correct post type
517 if ( $current_post_type !== $this->list_table_type ) {
518 return;
519 }
520
521 // Check post count
522 $counts = (array) wp_count_posts( $current_post_type );
523 unset( $counts['auto-draft'] );
524 $count = array_sum( $counts );
525
526 // Skip if there are posts or blank slate is disabled
527 if ( ! defined( 'CHARITABLE_FORCE_BLANK_SLATE' ) || ! CHARITABLE_FORCE_BLANK_SLATE ) {
528 if ( 0 < $count || ( defined( 'CHARITABLE_DISABLE_BLANK_SLATE' ) && CHARITABLE_DISABLE_BLANK_SLATE ) ) {
529 return;
530 }
531 }
532
533 // Prefer bottom, but render in top if bottom doesn't fire (when there are 0 posts)
534 if ( 'bottom' === $which && ! $blank_state_rendered ) {
535 $blank_state_rendered = true;
536 $this->enqueue_blank_slate_assets();
537 $this->render_blank_state();
538 echo '<style type="text/css">#posts-filter .wp-list-table, #posts-filter .tablenav.top, .tablenav.bottom .actions, .wrap .subsubsub, .wrap .search-box, .wrap .tablenav-pages { display: none; } #posts-filter .tablenav.bottom { height: auto; } body.post-type-campaign .page-title-action { display: none; } </style>';
539 } elseif ( 'top' === $which && ! $blank_state_rendered ) {
540 // Fallback: if bottom never fires (WordPress doesn't render it with 0 posts), render in top
541 $blank_state_rendered = true;
542 $this->enqueue_blank_slate_assets();
543 $this->render_blank_state();
544 echo '<style type="text/css">#posts-filter .wp-list-table, #posts-filter .tablenav.top .actions, .wrap .subsubsub, .wrap .search-box, .wrap .tablenav-pages { display: none; } #posts-filter .tablenav.top { height: auto; } body.post-type-campaign .page-title-action { display: none; } </style>';
545 }
546 }
547
548 /**
549 * Renders advice in the event that no campaigns exist yet.
550 *
551 * @since 1.8.1.5
552 * @version 1.8.10
553 *
554 * @return void
555 */
556 public function render_blank_state() {
557 ?>
558 <div class="charitable-blank-slate">
559 <!-- Page Header -->
560 <div class="page-header">
561 <h1 class="page-title"><?php esc_html_e( 'Campaigns', 'charitable' ); ?></h1>
562 <?php if ( charitable_disable_legacy_campaigns() ) : ?>
563 <a href="<?php echo esc_url( admin_url( 'admin.php?page=charitable-campaign-builder&view=template' ) ); ?>" class="btn-primary"><?php esc_html_e( '+ Create Campaign', 'charitable' ); ?></a>
564 <?php else : ?>
565 <a href="<?php echo esc_url( admin_url( 'post-new.php?post_type=campaign' ) ); ?>" class="btn-primary"><?php esc_html_e( '+ Create Legacy Campaign', 'charitable' ); ?></a>
566 <?php endif; ?>
567 </div>
568
569 <!-- Primary Welcome Section -->
570 <div class="welcome-card">
571 <h2 class="welcome-title"><?php esc_html_e( 'Ready to Start Fundraising?', 'charitable' ); ?></h2>
572 <p class="welcome-description">
573 <?php esc_html_e( 'Create your first campaign to start accepting donations and building your supporter community.', 'charitable' ); ?>
574 </p>
575 <?php if ( charitable_disable_legacy_campaigns() ) : ?>
576 <a href="<?php echo esc_url( admin_url( 'admin.php?page=charitable-campaign-builder&view=template' ) ); ?>" class="btn-primary btn-large"><?php esc_html_e( 'Create Your First Campaign', 'charitable' ); ?></a>
577 <?php else : ?>
578 <a href="<?php echo esc_url( admin_url( 'post-new.php?post_type=campaign' ) ); ?>" class="btn-primary btn-large"><?php esc_html_e( 'Create Your First Campaign', 'charitable' ); ?></a>
579 <?php endif; ?>
580 </div>
581
582 <!-- Secondary Actions -->
583 <div class="secondary-actions">
584 <div class="action-card">
585 <h3><?php esc_html_e( 'Already Have Campaigns Elsewhere?', 'charitable' ); ?></h3>
586 <?php
587 $givewp_installed = $this->is_givewp_installed();
588 $givewp_count = $givewp_installed ? $this->get_givewp_campaign_count() : 0;
589
590 if ( $givewp_installed && $givewp_count > 0 ) :
591 ?>
592 <p class="givewp-detection-text">
593 <?php
594 printf(
595 wp_kses(
596 /* translators: 1: bolded "GiveWP", 2: number of campaigns. */
597 __( 'We detected %1$s with %2$s campaigns installed on your site. Use our import tool to import them into Charitable.', 'charitable' ),
598 [
599 'strong' => [],
600 'span' => [ 'class' => [] ],
601 ]
602 ),
603 '<strong>GiveWP</strong>',
604 '<span class="givewp-count">' . absint( $givewp_count ) . '</span>'
605 );
606 ?>
607 </p>
608 <a href="https://www.wpcharitable.com/documentation/import-export-tool-givewp-to-charitable/" class="btn-secondary" target="_blank"><?php esc_html_e( 'Import GiveWP Campaigns', 'charitable' ); ?></a>
609 <?php elseif ( $givewp_installed && 0 === $givewp_count ) : ?>
610 <p class="givewp-detection-text">
611 <?php
612 printf(
613 wp_kses(
614 /* translators: %s: bolded "GiveWP". */
615 __( 'Did you know you can import %s campaigns, donations, and donors right into Charitable?', 'charitable' ),
616 [ 'strong' => [] ]
617 ),
618 '<strong>GiveWP</strong>'
619 );
620 ?>
621 </p>
622 <a href="<?php echo esc_url( admin_url( 'admin.php?page=charitable-tools&tab=import&sub_tab=givewp' ) ); ?>" class="btn-secondary"><?php esc_html_e( 'Import Tools', 'charitable' ); ?></a>
623 <?php else : ?>
624 <p><?php esc_html_e( 'Import your existing campaigns from your old fundraising platform to get started quickly with Charitable.', 'charitable' ); ?></p>
625 <a href="<?php echo esc_url( admin_url( 'admin.php?page=charitable-tools&tab=import&sub_tab=charitable' ) ); ?>" class="btn-secondary"><?php esc_html_e( 'Import Campaigns', 'charitable' ); ?></a>
626 <?php endif; ?>
627 </div>
628
629 <div class="blank-slate-feature-card">
630 <div class="blank-slate-feature-title-icon-row">
631 <div class="blank-slate-feature-icon">
632 <img src="<?php echo esc_url( charitable()->get_path( 'assets', false ) . 'images/icons/group.svg' ); ?>" alt="<?php esc_attr_e( 'Peer-to-Peer', 'charitable' ); ?>" width="32" height="32" />
633 </div>
634 <h3 class="blank-slate-feature-title"><?php esc_html_e( 'Enable Peer-to-Peer', 'charitable' ); ?> <span class="blank-slate-pro-badge"><?php esc_html_e( 'PRO', 'charitable' ); ?></span></h3>
635 </div>
636 <div class="blank-slate-feature-content">
637 <p class="blank-slate-feature-description"><?php esc_html_e( 'Charitable Ambassadors transforms your website into a peer-to-peer fundraising or crowdfunding platform.', 'charitable' ); ?></p>
638 <div class="blank-slate-button-row">
639 <?php
640 $ambassadors_state = $this->get_ambassadors_addon_state();
641
642 if ( 'upgrade' === $ambassadors_state['button_action'] ) {
643 $upgrade_url = isset( $ambassadors_state['upgrade_url'] ) ? $ambassadors_state['upgrade_url'] : '#';
644 ?>
645 <a href="<?php echo esc_url( $upgrade_url ); ?>" class="charitable-dashboard-v2-enhance-grid-button <?php echo esc_attr( $ambassadors_state['button_class'] ); ?>" data-action="upgrade" target="_blank"><?php echo esc_html( $ambassadors_state['button_text'] ); ?></a>
646 <?php
647 } else {
648 $button_attributes = [
649 'data-action="' . esc_attr( $ambassadors_state['button_action'] ) . '"',
650 'data-slug="charitable-ambassadors"',
651 'data-type="charitable_addon"',
652 ];
653
654 if ( $ambassadors_state['button_disabled'] ) {
655 $button_attributes[] = 'disabled';
656 }
657 ?>
658 <button class="charitable-dashboard-v2-enhance-grid-button <?php echo esc_attr( $ambassadors_state['button_class'] ); ?>" <?php echo implode( ' ', $button_attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>><?php echo esc_html( $ambassadors_state['button_text'] ); ?></button>
659 <?php
660 }
661 ?>
662 <a href="https://www.wpcharitable.com/features/peer-to-peer-fundraising/" class="charitable-dashboard-v2-enhance-grid-button charitable-dashboard-v2-learn-more-button" target="_blank"><?php esc_html_e( 'Learn More', 'charitable' ); ?></a>
663 </div>
664 </div>
665 </div>
666 </div>
667
668 <!-- Help Resources -->
669 <div class="help-resources">
670 <h3><?php esc_html_e( 'Additional Resources', 'charitable' ); ?></h3>
671 <ul class="help-links">
672 <li><a href="https://www.wpcharitable.com/documentation/start-here/?utm_campaign=liteplugin&utm_source=charitableplugin&utm_medium=campaign-page&utm_content=Start%20Here%20Guide" target="_blank"><?php esc_html_e( 'Get Started With Charitable', 'charitable' ); ?></a></li>
673 <li><a href="https://www.wpcharitable.com/documentation/creating-your-first-campaign/?utm_campaign=liteplugin&utm_source=charitableplugin&utm_medium=campaign-page&utm_content=Campaign%20Guide" target="_blank"><?php esc_html_e( 'Creating Your First Campaign', 'charitable' ); ?></a></li>
674 <?php if ( $givewp_installed ) : ?>
675 <li><a href="https://www.wpcharitable.com/documentation/import-export-tool-givewp-to-charitable/?utm_campaign=liteplugin&utm_source=charitableplugin&utm_medium=campaign-page&utm_content=Import%20Guide" target="_blank"><?php esc_html_e( 'Import Campaigns from GiveWP', 'charitable' ); ?></a></li>
676 <?php else : ?>
677 <li><a href="https://www.wpcharitable.com/documentation/installing-extensions/?utm_campaign=liteplugin&utm_source=charitableplugin&utm_medium=campaign-page&utm_content=Extension%20Guide" target="_blank"><?php esc_html_e( 'How to Install and Activate Charitable Extensions', 'charitable' ); ?></a></li>
678 <?php endif; ?>
679 <?php if ( charitable_disable_legacy_campaigns() ) : ?>
680 <li><a class="legacy-campaign-link" href="<?php echo esc_url( admin_url( 'post-new.php?post_type=campaign' ) ); ?>"><?php esc_html_e( 'Create a Legacy Campaign', 'charitable' ); ?></a></li>
681 <?php else : ?>
682 <li><a class="modern-campaign-link" href="<?php echo esc_url( admin_url( 'admin.php?page=charitable-campaign-builder&view=template' ) ); ?>"><?php esc_html_e( 'Create A Modern Campaign', 'charitable' ); ?></a></li>
683 <?php endif; ?>
684 </ul>
685 </div>
686 </div>
687 <?php
688 }
689
690 /**
691 * Enqueue dashboard assets for AJAX button functionality on blank slate.
692 *
693 * @since 1.8.10
694 *
695 * @return void
696 */
697 private function enqueue_blank_slate_assets() {
698 $version = charitable()->get_version();
699 $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
700
701 wp_enqueue_script(
702 'charitable-admin-dashboard',
703 charitable()->get_path( 'assets', false ) . 'js/admin/charitable-admin-dashboard' . $suffix . '.js',
704 [ 'jquery' ],
705 $version,
706 true
707 );
708
709 wp_localize_script(
710 'charitable-admin-dashboard',
711 'charitable_admin',
712 [
713 'nonce' => wp_create_nonce( 'charitable-admin' ),
714 'ajaxurl' => admin_url( 'admin-ajax.php' ),
715 ]
716 );
717 }
718
719 /**
720 * Check if GiveWP is installed and active.
721 *
722 * @since 1.8.10
723 *
724 * @return bool
725 */
726 private function is_givewp_installed() {
727 if ( ! function_exists( 'is_plugin_active' ) ) {
728 include_once ABSPATH . 'wp-admin/includes/plugin.php';
729 }
730
731 return is_plugin_active( 'give/give.php' );
732 }
733
734 /**
735 * Count GiveWP forms (campaigns).
736 *
737 * @since 1.8.10
738 *
739 * @return int
740 */
741 private function get_givewp_campaign_count() {
742 global $wpdb;
743
744 return (int) $wpdb->get_var(
745 $wpdb->prepare(
746 "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = %s AND post_status IN ('publish', 'draft', 'private')",
747 'give_forms'
748 )
749 );
750 }
751
752 /**
753 * Get the Ambassadors addon button state based on install/license status.
754 *
755 * @since 1.8.10
756 *
757 * @return array
758 */
759 private function get_ambassadors_addon_state() {
760 $slug = 'charitable-ambassadors';
761
762 if ( ! function_exists( 'is_plugin_active' ) ) {
763 include_once ABSPATH . 'wp-admin/includes/plugin.php';
764 }
765
766 $plugin_file = $slug . '/' . $slug . '.php';
767 $is_installed = file_exists( WP_PLUGIN_DIR . '/' . $plugin_file );
768 $is_active = $is_installed && is_plugin_active( $plugin_file );
769 $current_plan = Charitable_Addons_Directory::get_current_plan_slug();
770 $required_plans = [ 'pro', 'agency' ];
771 $has_required_plan = in_array( $current_plan, $required_plans, true );
772
773 if ( $is_installed && $is_active ) {
774 return [
775 'button_text' => __( 'Installed', 'charitable' ),
776 'button_class' => 'charitable-dashboard-v2-installed-button',
777 'button_action' => 'installed',
778 'button_disabled' => true,
779 ];
780 }
781
782 if ( $is_installed && ! $is_active ) {
783 return [
784 'button_text' => __( 'Activate', 'charitable' ),
785 'button_class' => 'charitable-dashboard-v2-activate-button',
786 'button_action' => 'activate_addon',
787 'button_disabled' => false,
788 ];
789 }
790
791 if ( ! $has_required_plan ) {
792 return [
793 'button_text' => __( 'Upgrade To Pro', 'charitable' ),
794 'button_class' => 'charitable-dashboard-v2-upgrade-button',
795 'button_action' => 'upgrade',
796 'button_disabled' => false,
797 'upgrade_url' => 'https://wpcharitable.com/lite-upgrade/?discount=LITEUPGRADE&utm_source=WordPress&utm_campaign=liteplugin&utm_medium=blank-slate&utm_content=Ambassadors',
798 ];
799 }
800
801 return [
802 'button_text' => __( 'Install & Activate', 'charitable' ),
803 'button_class' => 'charitable-dashboard-v2-install-button',
804 'button_action' => 'install_addon',
805 'button_disabled' => false,
806 ];
807 }
808
809 /**
810 * Renders the blank slate recommendations.
811 *
812 * @since 1.8.1.5
813 * @deprecated 1.8.10 No longer used. Replaced by card-based layout in render_blank_state().
814 *
815 * @return string
816 */
817 public function render_blank_slate_recommendations() {
818
819 $charitable_plugins_third_party = new Charitable_Admin_Plugins_Third_Party();
820 $user_recommended_list = $charitable_plugins_third_party->get_recommendations( 'campaign', 3 );
821
822 if ( empty( $user_recommended_list ) ) {
823 return false;
824 }
825
826 ob_start();
827
828 if ( ! empty( $user_recommended_list ) ) {
829
830 foreach ( $user_recommended_list as $slug => $item ) {
831 echo $this->render_blank_slate_recommendation( $slug ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
832 }
833 } else {
834
835 return;
836
837 }
838
839 $output = ob_get_clean();
840
841 return $output;
842 }
843
844 /**
845 * Renders a blank slate recommendation.
846 *
847 * @since 1.8.1.5
848 *
849 * @param string $slug The slug of the recommendation.
850 *
851 * @return string
852 */
853 public function render_blank_slate_recommendation( $slug = '' ) {
854
855 // check and see if the slug refers to a Charitable addon first.
856 $recommended_addons = get_transient( '_charitable_addons' ); // @codingStandardsIgnoreLine - testing.
857
858 // Get addons data from transient or perform API query if no transient.
859 if ( false === $recommended_addons ) {
860 $recommended_addons = charitable_get_addons_data_from_server();
861 }
862
863 $recommended_addon = array();
864 $addon_slug = $slug;
865
866 if ( $recommended_addons ) {
867
868 foreach ( (array) $recommended_addons as $i => $addon ) {
869
870 if ( ! empty( $addon['slug'] ) && (string) $addon_slug === (string) $addon['slug'] ) {
871 $recommended_addon = $addon;
872 break;
873 }
874 }
875 }
876
877 if ( ! empty( $recommended_addon ) ) {
878
879 // this is a Chartiable addon.
880 $title = str_replace( 'Charitable', '', $recommended_addon['name'] );
881 $sections = ! empty( $recommended_addon['sections'] ) ? unserialize( $recommended_addon['sections'] ) : false;
882 $description = is_array( $sections ) && ! empty( $sections['description'] ) ? $sections['description'] : '';
883
884 ob_start();
885
886 ?>
887
888 <div class="charitable-intergration-step charitable-addon charitable-plugin-suggestion" data-status="<?php echo esc_attr( $slug ); ?>">
889 <div class="instructions">
890 <header class="charitable-intergration-step-header">
891 <div class="sub-header">
892 <h3><?php echo esc_html( $title ); ?></h3>
893 <span class="badge"><a href="#"><?php esc_html_e( 'Pro', 'charitable' ); ?></a></span>
894 </div>
895 </header>
896 <p class="description"><?php echo wp_strip_all_tags( $description ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></p>
897 <a href="<?php echo esc_url( admin_url( 'admin.php?page=charitable-addons&search=' . esc_attr( str_replace( '-', ' ', $slug ) ) ) ); ?>" class="charitable-button button-link charitable-addons"><?php esc_html_e( 'View Addons', 'charitable' ); ?></a>
898 </div>
899 <div class="step">
900 <div class="vertical-wrapper">
901 <div class="step-image"><a class="suggestion-dismiss" title="<?php esc_html_e( 'Dismiss this suggestion', 'charitable' ); ?>" data-plugin-slug="<?php echo esc_attr( $slug ); ?>" data-plugin-type="addon" href="#"><i class="" title="<?php esc_html_e( 'Dismiss this suggestion', 'charitable' ); ?>"></i></a></div>
902 </div>
903 </div>
904 </div>
905
906
907 <?php
908
909 $output = ob_get_clean();
910
911 } else {
912
913 // this is a third party plugin.
914
915 $charitable_plugins_third_party = new Charitable_Admin_Plugins_Third_Party();
916 $plugin_data = $charitable_plugins_third_party->get_plugin( esc_attr( $slug ) );
917
918 if ( ! $plugin_data ) {
919 return '';
920 }
921
922 ob_start();
923
924 ?>
925
926 <div class="charitable-intergration-step charitable-third-party charitable-plugin-suggestion" data-status="<?php echo esc_attr( $slug ); ?>">
927 <div class="instructions">
928
929 <header class="charitable-intergration-step-header">
930 <div class="sub-header">
931 <h3><?php echo esc_html( $plugin_data['title'] ); ?></h3>
932 <span class="badge"><a href="#"><?php esc_html_e( 'Partner', 'charitable' ); ?></a></span>
933 </div>
934 </header>
935
936 <p class="description"><?php echo esc_html( $plugin_data['excerpt'] ); ?></p>
937 <?php
938 $plugin_button_html = $charitable_plugins_third_party->get_plugin_button_html( $slug, false, '' );
939 echo $plugin_button_html; // phpcs:ignore
940 ?>
941 </div>
942 <div class="step">
943 <div class="vertical-wrapper">
944
945 <div class="step-image"><a class="suggestion-dismiss" title="<?php esc_html_e( 'Dismiss this suggestion', 'charitable' ); ?>" data-plugin-slug="<?php echo esc_attr( $slug ); ?>" data-plugin-type="partner" href="#"><i class="" title="<?php esc_html_e( 'Dismiss this suggestion', 'charitable' ); ?>"></i></a></div>
946
947 </div>
948 </div>
949 </div>
950
951
952 <?php
953
954 $output = ob_get_clean();
955
956 }
957
958 return $output;
959 }
960 }
961
962 endif;
963