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.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 1.7.0.9 All 211 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.13, at includes/admin/campaigns/class-charitable-campaign-list-table.php

981 lines 35.1 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
152 /*
153 * Gated on having a GOAL, not on having an end date.
154 *
155 * This used to test `! $campaign->is_endless()`, which is a
156 * question about the end date and says nothing about whether
157 * there is a target to measure against. It got both cases
158 * wrong: a campaign with a goal and no end date showed no bar
159 * at all - reported in 1.8.13 testing as the goal not showing
160 * in the campaigns list - while a campaign with an end date
161 * and no goal drew a bar that could only ever sit at 0%,
162 * since get_percent_donated() has no denominator.
163 *
164 * has_goal() is the same accessor the Charitable dashboard's
165 * own goal column and Charitable_Campaign::get_goal() use, so
166 * the two screens now agree about which campaigns have a
167 * target.
168 */
169 if ( $campaign->has_goal() ) {
170 $percent = absint( $campaign->get_percent_donated() );
171 if ( $percent > 100 ) {
172 $percent = 100;
173 }
174 $display .= '<div class="meter">
175 <span style="width: ' . $percent . '%"></span>
176 </div>';
177 }
178 break;
179
180 case 'date_created':
181 $date_format = get_option( 'date_format' );
182 $date_format = ( '' === trim( $date_format ) ) ? 'd/m/Y' : $date_format;
183 $display = date( $date_format, strtotime( $campaign->post_date ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
184 break;
185
186 case 'end_date':
187 $display = $campaign->is_endless() ? '&#8734;' : $campaign->get_end_date();
188 break;
189
190 case 'actions':
191 // determine if we share the preview or the actual live URL.
192 $status = get_post_status( $post_id );
193
194 if ( 'trash' === $status ) {
195 break;
196 }
197
198 $link = ( 'publish' === $status || 'private' === $status ) ? get_permalink( $post_id ) : charitable_get_campaign_preview_url( $post_id, true, $status );
199 $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>';
200
201 // <button><i class="fa fa-envelope"></i></button>
202
203 break;
204
205 case 'status':
206 $status = $campaign->get_status();
207
208 if ( 'finished' === $status && $campaign->has_goal() ) {
209 $status = $campaign->has_achieved_goal() ? 'successful' : 'unsuccessful';
210 }
211
212 $display = '<mark class="' . esc_attr( $status ) . '">' . $status . '</mark>';
213 break;
214
215 default:
216 $display = '';
217 }
218
219 /**
220 * Filter the output of the cell.
221 *
222 * @since 1.5.0
223 *
224 * @param string $display The content that will be displayed.
225 * @param string $column_name The name of the column.
226 * @param int $post_id The ID of the campaign being shown.
227 * @param Charitable_Campaign $campaign The Charitable_Campaign object.
228 */
229 echo apply_filters( 'charitable_campaign_column_display', $display, $column_name, $post_id, $campaign ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
230 }
231
232 /**
233 * Modify bulk messages
234 *
235 * @since 1.5.0
236 *
237 * @param array $bulk_messages Messages to show after bulk actions.
238 * @param array $bulk_counts Array showing how many items were affected by the action.
239 * @return array
240 */
241 public function bulk_messages( $bulk_messages, $bulk_counts ) {
242 $bulk_messages[ Charitable::CAMPAIGN_POST_TYPE ] = array(
243 /* translators: %s: number of items */
244 'updated' => _n( '%d campaign updated.', '%d campaigns updated.', $bulk_counts['updated'], 'charitable' ),
245 'locked' => ( 1 == $bulk_counts['locked'] )
246 ? __( '1 campaign not updated, somebody is editing it.', 'charitable' )
247 /* translators: %s: number of items */
248 : _n( '%s campaign not updated, somebody is editing it.', '%s campaigns not updated, somebody is editing them.', $bulk_counts['locked'], 'charitable' ),
249 /* translators: %s: number of items */
250 'deleted' => _n( '%s campaign permanently deleted.', '%s campaigns permanently deleted.', $bulk_counts['deleted'], 'charitable' ),
251 /* translators: %s: number of items */
252 'trashed' => _n( '%s campaign moved to the Trash.', '%s campaigns moved to the Trash.', $bulk_counts['trashed'], 'charitable' ),
253 /* translators: %s: number of items */
254 'untrashed' => _n( '%s campaign restored from the Trash.', '%s campaigns restored from the Trash.', $bulk_counts['untrashed'], 'charitable' ),
255 );
256
257 return $bulk_messages;
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_export( $which ) {
269 if ( ! current_user_can( 'export_charitable_reports' ) ) {
270 return;
271 }
272
273 if ( 'top' === $which && $this->is_campaigns_page() ) {
274 charitable_admin_view( 'campaigns-page/export' );
275 }
276 }
277
278 /**
279 * Add extra buttons after filters
280 *
281 * @since 1.6.0
282 *
283 * @param string $which The context where this is being called.
284 * @return void
285 */
286 public function add_legacy_link( $which ) {
287 if ( ! current_user_can( 'publish_posts' ) ) {
288 return;
289 }
290
291 if ( 'top' === $which && $this->is_campaigns_page() ) {
292 charitable_admin_view( 'campaigns-page/legacy-add' );
293 }
294 }
295
296
297 /**
298 * Add filters above the campaigns table.
299 *
300 * @since 1.6.36
301 *
302 * @global string $typenow The current post type.
303 *
304 * @return void
305 */
306 public function add_filters() {
307 global $typenow;
308
309 /* Show custom filters to filter orders by donor. */
310 if ( in_array( $typenow, array( Charitable::CAMPAIGN_POST_TYPE ) ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTypeDeclaration
311 charitable_admin_view( 'campaigns-page/filters' );
312 }
313 }
314
315 /**
316 * Add modal template to footer.
317 *
318 * @since 1.6.0
319 *
320 * @return void
321 */
322 public function modal_forms() {
323 if ( $this->is_campaigns_page() ) {
324 charitable_admin_view( 'campaigns-page/filter-form' );
325 charitable_admin_view( 'campaigns-page/export-form' );
326 }
327 }
328
329 /**
330 * Admin scripts and styles.
331 *
332 * Set up the scripts & styles used for the modal.
333 *
334 * @since 1.6.0
335 *
336 * @param string $hook The current page hook/slug.
337 * @return void
338 */
339 public function load_scripts( $hook ) {
340 if ( 'edit.php' != $hook ) {
341 return;
342 }
343
344 if ( $this->is_campaigns_page() ) {
345 wp_enqueue_style( 'lean-modal-css' );
346 wp_enqueue_script( 'jquery' );
347 wp_enqueue_script( 'lean-modal' );
348 wp_enqueue_script( 'charitable-admin-tables' );
349 }
350 }
351
352 /**
353 * Add custom filters to the query that returns the campaigns to be displayed.
354 *
355 * @since 1.6.36
356 *
357 * @global string $typenow The current post type.
358 *
359 * @param array $vars The array of args to pass to WP_Query.
360 * @return array
361 */
362 public function filter_request_query( $vars ) {
363 if ( ! $this->is_campaigns_page() ) {
364 return $vars;
365 }
366
367 /* No Status: fix WP's crappy handling of "all" post status. */
368 if ( ! isset( $_GET['post_status'] ) || empty( $_GET['post_status'] ) || 'all' === $_GET['post_status'] ) {
369 $vars['post_status'] = array_keys( get_post_statuses() );
370 $vars['perm'] = 'readable';
371 } else {
372 switch ( $_GET['post_status'] ) {
373 case 'active':
374 $vars['post_status'] = 'publish';
375 $vars['meta_query'] = array(
376 'relation' => 'OR',
377 array(
378 'key' => '_campaign_end_date',
379 'value' => date( 'Y-m-d H:i:s' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
380 'compare' => '>',
381 'type' => 'datetime',
382 ),
383 array(
384 'key' => '_campaign_end_date',
385 'value' => 0,
386 'compare' => '=',
387 ),
388 );
389 break;
390
391 case 'finish':
392 $vars['post_status'] = 'publish';
393 $vars['meta_query'] = array(
394 array(
395 'key' => '_campaign_end_date',
396 'value' => date( 'Y-m-d H:i:s' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
397 'compare' => '<=',
398 'type' => 'datetime',
399 ),
400 );
401 break;
402
403 default:
404 $vars['post_status'] = esc_attr( $_GET['post_status'] ); // phpcs:ignore
405 $vars['perm'] = 'readable';
406 }
407 }
408
409 if ( ! empty( $_GET['charitable_nonce'] ) &&
410 wp_verify_nonce( esc_html( $_GET['charitable_nonce'] ), 'charitable_filter_campaigns' ) ) : // phpcs:ignore
411
412 /* Set up start date query */
413 if ( isset( $_GET['start_date_from'] ) && ! empty( $_GET['start_date_from'] ) ) {
414 $start_date_from = $this->get_parsed_date( esc_html( $_GET['start_date_from'] ) ); // phpcs:ignore
415 $vars['date_query']['after'] = array(
416 'year' => $start_date_from['year'],
417 'month' => $start_date_from['month'],
418 'day' => $start_date_from['day'],
419 );
420 }
421
422 if ( isset( $_GET['start_date_to'] ) && ! empty( $_GET['start_date_to'] ) ) {
423 $start_date_to = $this->get_parsed_date( esc_html( $_GET['start_date_to'] ) ); // phpcs:ignore
424 $vars['date_query']['before'] = array(
425 'year' => $start_date_to['year'],
426 'month' => $start_date_to['month'],
427 'day' => $start_date_to['day'],
428 );
429 }
430
431 /* Set up end date query */
432 if ( isset( $_GET['end_date_from'] ) && ! empty( $_GET['end_date_from'] ) ) {
433 $end_date_from = $this->get_parsed_date( esc_html( $_GET['end_date_from'] ) ); // phpcs:ignore
434 $vars['meta_query'][] = array(
435 'key' => '_campaign_end_date',
436 'value' => sprintf( '%d-%d-%d 00:00:00', $end_date_from['year'], $end_date_from['month'], $end_date_from['day'] ),
437 'compare' => '>=',
438 'type' => 'datetime',
439 );
440 }
441
442 if ( isset( $_GET['end_date_to'] ) && ! empty( $_GET['end_date_to'] ) ) {
443 $end_date_to = $this->get_parsed_date( esc_html( $_GET['end_date_to'] ) ); // phpcs:ignore
444 $vars['meta_query'][] = array(
445 'key' => '_campaign_end_date',
446 'value' => sprintf( '%d-%d-%d 00:00:00', $end_date_to['year'], $end_date_to['month'], $end_date_to['day'] ),
447 'compare' => '<=',
448 'type' => 'datetime',
449 );
450 }
451
452 if ( isset( $vars['date_query'] ) ) {
453 $vars['date_query']['inclusive'] = true;
454 }
455
456 /* Filter by campaign. */
457 if ( isset( $_GET['campaign_id'] ) && false !== stripos( esc_html( $_GET['charitable_nonce'] ), 'all' ) ) { // phpcs:ignore
458 $vars['post__in'] = charitable_get_table( 'campaign_donations' )->get_donation_ids_for_campaign( intval( $_GET['campaign_id'] ) );
459 }
460
461 endif;
462
463 /* Restrict by author if user can only edit their own. */
464 if ( ! current_user_can( 'edit_others_campaigns' ) ) {
465 $vars['author'] = get_current_user_id();
466 }
467
468 /**
469 * Filter the campaign list query vars.
470 *
471 * @since 1.6.36
472 *
473 * @param array $vars The request query vars.
474 */
475 return apply_filters( 'charitable_filter_campaigns_list_request_vars', $vars );
476 }
477
478 /**
479 * Given a date, returns an array containing the date, month and year.
480 * 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.
481 *
482 * @since 1.6.36
483 *
484 * @param string $date A date as a string that can be parsed by strtotime.
485 * @return string[]
486 */
487 protected function get_parsed_date( $date ) {
488 $time = charitable_sanitize_date_alt_format( $date );
489
490 $args = array(
491 'year' => date( 'Y', $time ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
492 'month' => date( 'm', $time ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
493 'day' => date( 'd', $time ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
494 );
495
496 return $args;
497 }
498
499 /**
500 * Checks whether this is the campaigns page.
501 *
502 * @since 1.6.0
503 *
504 * @global string $typenow The current post type.
505 * @global string $pagenow The current admin page.
506 *
507 * @return boolean
508 */
509 private function is_campaigns_page() {
510 global $typenow, $pagenow;
511
512 return 'edit.php' === $pagenow && in_array( $typenow, array( Charitable::CAMPAIGN_POST_TYPE ), true );
513 }
514
515 /**
516 * Show blank slate.
517 *
518 * @since 1.8.1.5
519 * @version 1.8.8.6, 1.8.9.1, 1.8.10
520 *
521 * @param string $which String which tablenav is being shown.
522 *
523 * @return void
524 */
525 public function maybe_render_blank_state( $which = '' ) {
526 global $post_type, $typenow;
527
528 // Static flag to prevent double rendering
529 static $blank_state_rendered = false;
530
531 // Use $typenow as fallback if $post_type is not set
532 $current_post_type = $post_type ? $post_type : $typenow;
533
534 // Only proceed if we're on the correct post type
535 if ( $current_post_type !== $this->list_table_type ) {
536 return;
537 }
538
539 // Check post count
540 $counts = (array) wp_count_posts( $current_post_type );
541 unset( $counts['auto-draft'] );
542 $count = array_sum( $counts );
543
544 // Skip if there are posts or blank slate is disabled
545 if ( ! defined( 'CHARITABLE_FORCE_BLANK_SLATE' ) || ! CHARITABLE_FORCE_BLANK_SLATE ) {
546 if ( 0 < $count || ( defined( 'CHARITABLE_DISABLE_BLANK_SLATE' ) && CHARITABLE_DISABLE_BLANK_SLATE ) ) {
547 return;
548 }
549 }
550
551 // Prefer bottom, but render in top if bottom doesn't fire (when there are 0 posts)
552 if ( 'bottom' === $which && ! $blank_state_rendered ) {
553 $blank_state_rendered = true;
554 $this->enqueue_blank_slate_assets();
555 $this->render_blank_state();
556 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>';
557 } elseif ( 'top' === $which && ! $blank_state_rendered ) {
558 // Fallback: if bottom never fires (WordPress doesn't render it with 0 posts), render in top
559 $blank_state_rendered = true;
560 $this->enqueue_blank_slate_assets();
561 $this->render_blank_state();
562 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>';
563 }
564 }
565
566 /**
567 * Renders advice in the event that no campaigns exist yet.
568 *
569 * @since 1.8.1.5
570 * @version 1.8.10
571 *
572 * @return void
573 */
574 public function render_blank_state() {
575 ?>
576 <div class="charitable-blank-slate">
577 <!-- Page Header -->
578 <div class="page-header">
579 <h1 class="page-title"><?php esc_html_e( 'Campaigns', 'charitable' ); ?></h1>
580 <?php if ( charitable_disable_legacy_campaigns() ) : ?>
581 <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>
582 <?php else : ?>
583 <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>
584 <?php endif; ?>
585 </div>
586
587 <!-- Primary Welcome Section -->
588 <div class="welcome-card">
589 <h2 class="welcome-title"><?php esc_html_e( 'Ready to Start Fundraising?', 'charitable' ); ?></h2>
590 <p class="welcome-description">
591 <?php esc_html_e( 'Create your first campaign to start accepting donations and building your supporter community.', 'charitable' ); ?>
592 </p>
593 <?php if ( charitable_disable_legacy_campaigns() ) : ?>
594 <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>
595 <?php else : ?>
596 <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>
597 <?php endif; ?>
598 </div>
599
600 <!-- Secondary Actions -->
601 <div class="secondary-actions">
602 <div class="action-card">
603 <h3><?php esc_html_e( 'Already Have Campaigns Elsewhere?', 'charitable' ); ?></h3>
604 <?php
605 $givewp_installed = $this->is_givewp_installed();
606 $givewp_count = $givewp_installed ? $this->get_givewp_campaign_count() : 0;
607
608 if ( $givewp_installed && $givewp_count > 0 ) :
609 ?>
610 <p class="givewp-detection-text">
611 <?php
612 printf(
613 wp_kses(
614 /* translators: 1: bolded "GiveWP", 2: number of campaigns. */
615 __( 'We detected %1$s with %2$s campaigns installed on your site. Use our import tool to import them into Charitable.', 'charitable' ),
616 [
617 'strong' => [],
618 'span' => [ 'class' => [] ],
619 ]
620 ),
621 '<strong>GiveWP</strong>',
622 '<span class="givewp-count">' . absint( $givewp_count ) . '</span>'
623 );
624 ?>
625 </p>
626 <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>
627 <?php elseif ( $givewp_installed && 0 === $givewp_count ) : ?>
628 <p class="givewp-detection-text">
629 <?php
630 printf(
631 wp_kses(
632 /* translators: %s: bolded "GiveWP". */
633 __( 'Did you know you can import %s campaigns, donations, and donors right into Charitable?', 'charitable' ),
634 [ 'strong' => [] ]
635 ),
636 '<strong>GiveWP</strong>'
637 );
638 ?>
639 </p>
640 <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>
641 <?php else : ?>
642 <p><?php esc_html_e( 'Import your existing campaigns from your old fundraising platform to get started quickly with Charitable.', 'charitable' ); ?></p>
643 <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>
644 <?php endif; ?>
645 </div>
646
647 <div class="blank-slate-feature-card">
648 <div class="blank-slate-feature-title-icon-row">
649 <div class="blank-slate-feature-icon">
650 <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" />
651 </div>
652 <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>
653 </div>
654 <div class="blank-slate-feature-content">
655 <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>
656 <div class="blank-slate-button-row">
657 <?php
658 $ambassadors_state = $this->get_ambassadors_addon_state();
659
660 if ( 'upgrade' === $ambassadors_state['button_action'] ) {
661 $upgrade_url = isset( $ambassadors_state['upgrade_url'] ) ? $ambassadors_state['upgrade_url'] : '#';
662 ?>
663 <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>
664 <?php
665 } else {
666 $button_attributes = [
667 'data-action="' . esc_attr( $ambassadors_state['button_action'] ) . '"',
668 'data-slug="charitable-ambassadors"',
669 'data-type="charitable_addon"',
670 ];
671
672 if ( $ambassadors_state['button_disabled'] ) {
673 $button_attributes[] = 'disabled';
674 }
675 ?>
676 <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>
677 <?php
678 }
679 ?>
680 <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>
681 </div>
682 </div>
683 </div>
684 </div>
685
686 <!-- Help Resources -->
687 <div class="help-resources">
688 <h3><?php esc_html_e( 'Additional Resources', 'charitable' ); ?></h3>
689 <ul class="help-links">
690 <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>
691 <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>
692 <?php if ( $givewp_installed ) : ?>
693 <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>
694 <?php else : ?>
695 <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>
696 <?php endif; ?>
697 <?php if ( charitable_disable_legacy_campaigns() ) : ?>
698 <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>
699 <?php else : ?>
700 <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>
701 <?php endif; ?>
702 </ul>
703 </div>
704 </div>
705 <?php
706 }
707
708 /**
709 * Enqueue dashboard assets for AJAX button functionality on blank slate.
710 *
711 * @since 1.8.10
712 *
713 * @return void
714 */
715 private function enqueue_blank_slate_assets() {
716 $version = charitable()->get_version();
717 $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
718
719 wp_enqueue_script(
720 'charitable-admin-dashboard',
721 charitable()->get_path( 'assets', false ) . 'js/admin/charitable-admin-dashboard' . $suffix . '.js',
722 [ 'jquery' ],
723 $version,
724 true
725 );
726
727 wp_localize_script(
728 'charitable-admin-dashboard',
729 'charitable_admin',
730 [
731 'nonce' => wp_create_nonce( 'charitable-admin' ),
732 'ajaxurl' => admin_url( 'admin-ajax.php' ),
733 ]
734 );
735 }
736
737 /**
738 * Check if GiveWP is installed and active.
739 *
740 * @since 1.8.10
741 *
742 * @return bool
743 */
744 private function is_givewp_installed() {
745 if ( ! function_exists( 'is_plugin_active' ) ) {
746 include_once ABSPATH . 'wp-admin/includes/plugin.php';
747 }
748
749 return is_plugin_active( 'give/give.php' );
750 }
751
752 /**
753 * Count GiveWP forms (campaigns).
754 *
755 * @since 1.8.10
756 *
757 * @return int
758 */
759 private function get_givewp_campaign_count() {
760 global $wpdb;
761
762 return (int) $wpdb->get_var(
763 $wpdb->prepare(
764 "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = %s AND post_status IN ('publish', 'draft', 'private')",
765 'give_forms'
766 )
767 );
768 }
769
770 /**
771 * Get the Ambassadors addon button state based on install/license status.
772 *
773 * @since 1.8.10
774 *
775 * @return array
776 */
777 private function get_ambassadors_addon_state() {
778 $slug = 'charitable-ambassadors';
779
780 if ( ! function_exists( 'is_plugin_active' ) ) {
781 include_once ABSPATH . 'wp-admin/includes/plugin.php';
782 }
783
784 $plugin_file = $slug . '/' . $slug . '.php';
785 $is_installed = file_exists( WP_PLUGIN_DIR . '/' . $plugin_file );
786 $is_active = $is_installed && is_plugin_active( $plugin_file );
787 $current_plan = Charitable_Addons_Directory::get_current_plan_slug();
788 $required_plans = [ 'pro', 'agency' ];
789 $has_required_plan = in_array( $current_plan, $required_plans, true );
790
791 if ( $is_installed && $is_active ) {
792 return [
793 'button_text' => __( 'Installed', 'charitable' ),
794 'button_class' => 'charitable-dashboard-v2-installed-button',
795 'button_action' => 'installed',
796 'button_disabled' => true,
797 ];
798 }
799
800 if ( $is_installed && ! $is_active ) {
801 return [
802 'button_text' => __( 'Activate', 'charitable' ),
803 'button_class' => 'charitable-dashboard-v2-activate-button',
804 'button_action' => 'activate_addon',
805 'button_disabled' => false,
806 ];
807 }
808
809 if ( ! $has_required_plan ) {
810 return [
811 'button_text' => __( 'Upgrade To Pro', 'charitable' ),
812 'button_class' => 'charitable-dashboard-v2-upgrade-button',
813 'button_action' => 'upgrade',
814 'button_disabled' => false,
815 'upgrade_url' => 'https://wpcharitable.com/lite-upgrade/?discount=LITEUPGRADE&utm_source=WordPress&utm_campaign=liteplugin&utm_medium=blank-slate&utm_content=Ambassadors',
816 ];
817 }
818
819 return [
820 'button_text' => __( 'Install & Activate', 'charitable' ),
821 'button_class' => 'charitable-dashboard-v2-install-button',
822 'button_action' => 'install_addon',
823 'button_disabled' => false,
824 ];
825 }
826
827 /**
828 * Renders the blank slate recommendations.
829 *
830 * @since 1.8.1.5
831 * @deprecated 1.8.10 No longer used. Replaced by card-based layout in render_blank_state().
832 *
833 * @return string
834 */
835 public function render_blank_slate_recommendations() {
836
837 $charitable_plugins_third_party = new Charitable_Admin_Plugins_Third_Party();
838 $user_recommended_list = $charitable_plugins_third_party->get_recommendations( 'campaign', 3 );
839
840 if ( empty( $user_recommended_list ) ) {
841 return false;
842 }
843
844 ob_start();
845
846 if ( ! empty( $user_recommended_list ) ) {
847
848 foreach ( $user_recommended_list as $slug => $item ) {
849 echo $this->render_blank_slate_recommendation( $slug ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
850 }
851 } else {
852
853 return;
854
855 }
856
857 $output = ob_get_clean();
858
859 return $output;
860 }
861
862 /**
863 * Renders a blank slate recommendation.
864 *
865 * @since 1.8.1.5
866 *
867 * @param string $slug The slug of the recommendation.
868 *
869 * @return string
870 */
871 public function render_blank_slate_recommendation( $slug = '' ) {
872
873 // check and see if the slug refers to a Charitable addon first.
874 $recommended_addons = get_transient( '_charitable_addons' ); // @codingStandardsIgnoreLine - testing.
875
876 // Get addons data from transient or perform API query if no transient.
877 if ( false === $recommended_addons ) {
878 $recommended_addons = charitable_get_addons_data_from_server();
879 }
880
881 $recommended_addon = array();
882 $addon_slug = $slug;
883
884 if ( $recommended_addons ) {
885
886 foreach ( (array) $recommended_addons as $i => $addon ) {
887
888 if ( ! empty( $addon['slug'] ) && (string) $addon_slug === (string) $addon['slug'] ) {
889 $recommended_addon = $addon;
890 break;
891 }
892 }
893 }
894
895 if ( ! empty( $recommended_addon ) ) {
896
897 // this is a Chartiable addon.
898 $title = str_replace( 'Charitable', '', $recommended_addon['name'] );
899 $sections = ! empty( $recommended_addon['sections'] ) ? unserialize( $recommended_addon['sections'] ) : false;
900 $description = is_array( $sections ) && ! empty( $sections['description'] ) ? $sections['description'] : '';
901
902 ob_start();
903
904 ?>
905
906 <div class="charitable-intergration-step charitable-addon charitable-plugin-suggestion" data-status="<?php echo esc_attr( $slug ); ?>">
907 <div class="instructions">
908 <header class="charitable-intergration-step-header">
909 <div class="sub-header">
910 <h3><?php echo esc_html( $title ); ?></h3>
911 <span class="badge"><a href="#"><?php esc_html_e( 'Pro', 'charitable' ); ?></a></span>
912 </div>
913 </header>
914 <p class="description"><?php echo wp_strip_all_tags( $description ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></p>
915 <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>
916 </div>
917 <div class="step">
918 <div class="vertical-wrapper">
919 <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>
920 </div>
921 </div>
922 </div>
923
924
925 <?php
926
927 $output = ob_get_clean();
928
929 } else {
930
931 // this is a third party plugin.
932
933 $charitable_plugins_third_party = new Charitable_Admin_Plugins_Third_Party();
934 $plugin_data = $charitable_plugins_third_party->get_plugin( esc_attr( $slug ) );
935
936 if ( ! $plugin_data ) {
937 return '';
938 }
939
940 ob_start();
941
942 ?>
943
944 <div class="charitable-intergration-step charitable-third-party charitable-plugin-suggestion" data-status="<?php echo esc_attr( $slug ); ?>">
945 <div class="instructions">
946
947 <header class="charitable-intergration-step-header">
948 <div class="sub-header">
949 <h3><?php echo esc_html( $plugin_data['title'] ); ?></h3>
950 <span class="badge"><a href="#"><?php esc_html_e( 'Partner', 'charitable' ); ?></a></span>
951 </div>
952 </header>
953
954 <p class="description"><?php echo esc_html( $plugin_data['excerpt'] ); ?></p>
955 <?php
956 $plugin_button_html = $charitable_plugins_third_party->get_plugin_button_html( $slug, false, '' );
957 echo $plugin_button_html; // phpcs:ignore
958 ?>
959 </div>
960 <div class="step">
961 <div class="vertical-wrapper">
962
963 <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>
964
965 </div>
966 </div>
967 </div>
968
969
970 <?php
971
972 $output = ob_get_clean();
973
974 }
975
976 return $output;
977 }
978 }
979
980 endif;
981