PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13.1
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13.1
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 / campaign-builder / templates / class-templates.php

class-templates.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.13.1, at includes/admin/campaign-builder/templates/class-templates.php

4,015 lines 161.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Main class for template management for campaign builder.
4 *
5 * @package Charitable
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.8.0
10 * @version 1.8.0
11 */
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 if ( ! class_exists( 'Charitable_Campaign_Builder_Templates' ) ) :
19
20 /**
21 * Sets up the WordPress customizer.
22 *
23 * @since 1.8.0
24 */
25 class Charitable_Campaign_Builder_Templates {
26
27 /**
28 * One is the loneliest number that you'll ever do.
29 *
30 * @since 1.8.0
31 *
32 * @var object
33 */
34 private static $instance;
35
36 /**
37 * Field ID.
38 *
39 * @since 1.8.0
40 *
41 * @var integer
42 */
43 public $field_id;
44
45 /**
46 * Prepared templates list.
47 *
48 * @since 1.8.0
49 *
50 * @var array
51 */
52 private $prepared_templates = array();
53
54 /**
55 * Template categories data.
56 *
57 * @since 1.8.0
58 *
59 * @var array
60 */
61 private $categories;
62
63 /**
64 * Template addon labels data.
65 *
66 * @since 1.8.0
67 *
68 * @var array
69 */
70 private $addon_labels;
71
72 /**
73 * Is addon templates available?
74 *
75 * @since 1.8.0
76 *
77 * @var bool
78 */
79 private $is_addon_templates_available = false;
80
81 /**
82 * Is custom templates available?
83 *
84 * @since 1.8.0
85 *
86 * @var bool
87 */
88 private $is_custom_templates_available = false;
89
90 /**
91 * Storing template data (for now).
92 *
93 * @since 1.8.0
94 *
95 * @var array
96 */
97 private $templates_data = array();
98
99 /**
100 * Main Instance.
101 *
102 * The accessor used to build the WRONG CLASS (1.8.13.1). It assigned
103 * `new Charitable_Campaign_Builder()` -- a different class entirely -- so any caller
104 * using this accessor and expecting template methods got a fatal undefined-method
105 * error. Latent rather than live, because every real call site instantiates
106 * `Charitable_Campaign_Builder_Templates` directly and bypasses this accessor.
107 *
108 * The guard was also incoherent: `! isset( self::$instance ) && ! ( self::$instance
109 * instanceof … )` evaluated the second clause against a property it had just
110 * established was unset, so the `instanceof` half could never contribute anything.
111 * A single `instanceof` test covers both cases, since an unset property is not an
112 * instance of anything.
113 *
114 * The `admin_init` hook stays valid: this class has its own `init()` method.
115 *
116 * Pro fixed the same thing in `f19a22167d`.
117 *
118 * @since 1.8.0
119 * @since 1.8.13.1 Returns this class rather than Charitable_Campaign_Builder.
120 *
121 * @return Charitable_Campaign_Builder_Templates
122 */
123 public static function get_instance() {
124
125 if ( ! self::$instance instanceof Charitable_Campaign_Builder_Templates ) {
126
127 self::$instance = new self();
128
129 add_action( 'admin_init', array( self::$instance, 'init' ), 10 );
130 }
131
132 return self::$instance;
133 }
134
135 /**
136 * Create object instance.
137 *
138 * @since 1.8.0
139 */
140 public function __construct() {
141
142 if ( ! is_admin() ) {
143 return;
144 }
145
146 $this->field_id = 0;
147
148 $this->templates_data = $this->get_source_template_data();
149
150 $this->init();
151 }
152
153 /**
154 * Init things.
155 *
156 * @since 1.8.0
157 */
158 public function init() {
159
160 $this->hooks();
161 }
162
163 /**
164 * Hook things.
165 *
166 * @since 1.8.0
167 */
168 public function hooks() {
169
170 add_action( 'admin_print_scripts', array( $this, 'upgrade_banner_template' ) );
171 }
172
173 /**
174 * Returns the starting HTML element for a given template type.
175 *
176 * @since 1.8.0
177 *
178 * @param string $type The type of template element to retrieve.
179 * @param int $counter The counter for the template element.
180 * @param string $additional_css Additional CSS classes to add to the template element.
181 * @param array $campaign_data An array of campaign data to pass to the template element.
182 * @param string $background_image Optional background image URL for the row.
183 * @param bool $locked Optional. Whether the row is locked (no move/delete).
184 * @return string The starting HTML element for the given template type.
185 */
186 public function get_template_element_start( $type = 'row', $counter = 1, $additional_css = '', $campaign_data = array(), $background_image = '', $locked = false ) {
187
188 ob_start();
189
190 $row_bg_url = '' !== $background_image ? esc_url( $background_image ) : '';
191 $row_style_attr = '' !== $row_bg_url ? ' style="background-image:url(\'' . $row_bg_url . '\');"' : '';
192 $row_locked_css = $locked ? ' is-locked' : '';
193 $row_locked_attr = $locked ? ' data-row-locked="1"' : '';
194 $row_bg_css = '' !== $row_bg_url ? ' has-background-image' : '';
195
196 switch ( $type ) {
197 case 'header':
198 ?>
199 <!-- charitable header start -->
200 <header id="charitable-preview-header-<?php echo intval( $counter ); ?>"<?php echo $row_locked_attr; // phpcs:ignore ?> class="charitable-preview-header <?php echo esc_attr( $additional_css ); ?><?php echo esc_attr( $row_bg_css . $row_locked_css ); ?>"<?php echo $row_style_attr; // phpcs:ignore ?>>
201 <div class="row" data-row-id="<?php echo intval( $counter ); ?>" data-row-type="<?php echo esc_attr( $type ); ?>" data-row-css="<?php echo esc_attr( $additional_css ); ?>">
202 <?php
203 break;
204
205 case 'tabs':
206 $enable_tabs_css = isset( $campaign_data['layout']['advanced']['enable_tabs'] ) && '' !== trim( $campaign_data['layout']['advanced']['enable_tabs'] ) ? 'disabled' : '';
207 ?>
208 <!-- charitable tabs start -->
209 <article id="charitable-preview-tab-container" class="charitable-preview-tab-container <?php echo $enable_tabs_css; ?>"> <?php // phpcs:ignore ?>
210 <a href="#" class="charitable-hover-button charitable-field-edit" title="<?php echo esc_html__( 'Edit Area', 'charitable' ); ?>"><i class="fa fa-pencil"></i></a>
211 <?php
212 break;
213
214 default:
215 // likely a row.
216 ?>
217 <!-- charitable row/default start -->
218 <div id="charitable-preview-row-<?php echo intval( $counter ); ?>"<?php echo $row_locked_attr; // phpcs:ignore ?> class="charitable-preview-row <?php echo esc_attr( $additional_css ); ?><?php echo esc_attr( $row_bg_css . $row_locked_css ); ?>"<?php echo $row_style_attr; // phpcs:ignore ?>>
219 <div class="row" data-row-id="<?php echo intval( $counter ); ?>" data-row-type="<?php echo esc_attr( $type ); ?>" data-row-css="<?php echo esc_attr( $additional_css ); ?>">
220 <?php
221 break;
222 }
223
224 return ob_get_clean();
225 }
226
227 /**
228 * Create closing HTML tags based on the type.
229 *
230 * @since 1.8.0
231 * @param string $type The type of template element to retrieve.
232 * @param int $counter The counter for the template element.
233 *
234 * @return string The closing HTML tags for the given template type.
235 */
236 public function get_template_element_end( $type = 'row', $counter = 1 ) { // phpcs:ignore
237
238 ob_start();
239
240 switch ( $type ) {
241 case 'header':
242 ?>
243 </div>
244 </header>
245 <!-- charitable header end -->
246 <?php
247 break;
248
249 case 'tabs':
250 ?>
251 </article>
252 <!-- charitable tabs end -->
253 <?php
254 break;
255
256 default:
257 // likely a row.
258 ?>
259 </div>
260 </div>
261 <!-- charitable row/default end -->
262 <?php
263 break;
264 }
265
266 return ob_get_clean();
267 }
268
269 /**
270 * Create fields for preview area based on an array, usually for a column of the layout..
271 *
272 * @since 1.8.0
273 *
274 * @param array $fields Fields.
275 * @param array $theme Theme data.
276 * @param array $campaign_data Campaign data.
277 * @param int $last_field_id Last field ID.
278 *
279 * @return string
280 */
281 public function render_fields( $fields, $theme, $campaign_data, $last_field_id = 0, $row_locked = false ) {
282
283 if ( empty( $fields ) ) {
284 return;
285 }
286
287 $field_id = $last_field_id;
288
289 foreach ( $fields as $key => $field_type_data ) :
290
291 $field_type = is_array( $field_type_data ) && ! empty( $field_type_data['type'] ) ? esc_html( $field_type_data['type'] ) : esc_html( $key );
292
293 $type = 'Charitable_Field_' . str_replace( ' ', '_', ( ucwords( str_replace( '-', ' ', $field_type ) ) ) );
294
295 if ( class_exists( $type ) ) :
296
297 $class = new $type();
298
299 $field_settings = is_array( $field_type_data ) ? $field_type_data : array();
300
301 // define a default photo for the photo type block if it's passed from the theme JSON data.
302 if ( $field_type == 'photo' && ! empty( $field_settings['default'] ) && ! empty( $theme['meta']['slug'] ) ) {
303
304 $campaign_template_slug = esc_attr( $theme['meta']['slug'] );
305
306 // if the "default" parameter for the photo is passed then we attempt to pull that photo.
307 $default_filename = ! empty( $field_type_data['default'] ) ? esc_html( $field_type_data['default'] ) : 'photo.jpg';
308 $theme_thumbnail = ( false !== $default_filename ) && $campaign_template_slug ? charitable()->get_path( 'assets', true ) . 'images/campaign-builder/templates/' . $campaign_template_slug . '/' . $default_filename : false;
309
310 // see if the thumbnail or default photo exists.
311 if ( false !== $theme_thumbnail && file_exists( $theme_thumbnail ) ) {
312 $field_settings['default'] = charitable()->get_path( 'assets', false ) . 'images/campaign-builder/templates/' . $campaign_template_slug . '/' . $default_filename;
313 }
314 }
315
316 // Expand template-relative filenames for the campaign-hero block.
317 if ( $field_type === 'campaign-hero' && ! empty( $theme['meta']['slug'] ) ) {
318 $campaign_template_slug = esc_attr( $theme['meta']['slug'] );
319 foreach ( array( 'background_image', 'avatar_image' ) as $img_key ) {
320 if ( ! empty( $field_settings[ $img_key ] ) ) {
321 $image_value = $field_settings[ $img_key ];
322 $image_basename = basename( $image_value );
323 if ( $image_basename === $image_value ) {
324 $asset_path = charitable()->get_path( 'assets', true ) . 'images/campaign-builder/templates/' . $campaign_template_slug . '/' . $image_basename;
325 if ( file_exists( $asset_path ) ) {
326 $field_settings[ $img_key ] = charitable()->get_path( 'assets', false ) . 'images/campaign-builder/templates/' . $campaign_template_slug . '/' . $image_basename;
327 }
328 }
329 }
330 }
331 }
332
333 $field_settings['id'] = $field_id;
334
335 $charitable_field_css_classes = array(
336 'charitable-field',
337 'charitable-field-' . $field_type,
338 $class->can_be_edited ? 'charitable-can-edit' : 'charitable-no-edit',
339 $class->can_be_duplicated ? 'charitable-can-duplicate' : 'charitable-no-duplicate',
340 $class->can_be_deleted ? 'charitable-can-delete' : 'charitable-no-delete',
341 );
342
343 if ( $row_locked ) {
344 $charitable_field_css_classes[] = 'charitable-field-not-draggable';
345 }
346
347 echo '<div class="' . esc_attr( implode( ' ', $charitable_field_css_classes ) ) . '" id="charitable-field-' . intval( $field_id ) . '" data-field-id="' . intval( $field_id ) . '" data-field-type="' . esc_attr( $field_type ) . '" data-field-max="' . intval( $class->max_allowed ) . '" style="">'; // phpcs:ignore
348 if ( $class->can_be_edited ) :
349 echo '<a href="#" class="charitable-field-edit" data-type="' . esc_attr( $class->edit_type ) . '" data-section="' . esc_attr( $class->edit_section ) . '" data-edit-field-id="' . esc_attr( $class->edit_field_id ) . '" title="' . esc_attr( $class->edit_label ) . '"><i class="fa fa-pencil"></i></a>';
350 endif;
351 if ( $class->can_be_duplicated && ! $row_locked ) :
352 echo '<a href="#" class="charitable-field-duplicate" title="Duplicate Field"><i class="fa fa-files-o" aria-hidden="true"></i></a>';
353 endif;
354 if ( $class->can_be_deleted && ! $row_locked ) :
355 echo '<a href="#" class="charitable-field-delete" title="Delete Field"><i class="fa fa-trash-o"></i></a>';
356 endif;
357
358 echo $class->field_preview( $field_settings, $campaign_data, $field_id, $theme ); // phpcs:ignore
359 echo '</div>';
360
361 else :
362
363 $this->render_missing_addon_field_preview( $field_type, $field_id, $field_type_data, $campaign_data );
364
365 endif;
366
367 ++$field_id;
368
369 endforeach;
370
371 return $field_id;
372 }
373
374 /**
375 * Sorts an array by the order of another array.
376 *
377 * @param array $tabs The array to be sorted.
378 * @param array $tab_order The array that defines the order.
379 *
380 * @return array The sorted array.
381 */
382 public function sort_array_by_array( $tabs, $tab_order ) {
383
384 $ordered = array();
385
386 foreach ( $tab_order as $key ) {
387 if ( array_key_exists( $key, $tabs ) ) {
388 $ordered[ $key ] = $tabs[ $key ];
389 unset( $tabs[ $key ] );
390 }
391 }
392
393 return $ordered + $tabs;
394 }
395
396 /**
397 * Create tabs based on the template data.
398 *
399 * @since 1.8.0
400 *
401 * @param string $tabs Tab info.
402 * @param string $theme The current theme.
403 * @param array $campaign_data Campaign data..
404 *
405 * @return string
406 */
407 public function get_template_tab_nav( $tabs = false, $theme = false, $campaign_data = false ) {
408
409 if ( ! is_admin() ) {
410 return;
411 }
412
413 if ( false === $tabs || false === $theme ) {
414 return;
415 }
416
417 ob_start();
418
419 $counter = 0;
420 $tab_id = 0;
421 $advanced = ( ! empty( $campaign_data['layout']['advanced'] ) ) ? $campaign_data['layout']['advanced'] : $theme['advanced'];
422 $tab_style = isset( $advanced['tab_style'] ) && '' !== trim( $advanced['tab_style'] ) ? $advanced['tab_style'] : 'boxed';
423 $tab_size = isset( $advanced['tab_size'] ) && '' !== trim( $advanced['tab_size'] ) ? $advanced['tab_size'] : 'medium';
424 $tab_order = ! empty( $campaign_data['tab_order'] ) ? $campaign_data['tab_order'] : false;
425
426 // sort the multi-dimenstional array of $tabs based on the values of $tab_order array.
427 if ( is_array( $tabs ) && ! empty( $tabs ) && ! empty( $tab_order ) ) {
428 $tabs = $this->sort_array_by_array( $tabs, $tab_order );
429 }
430
431 ?>
432
433 <nav class="charitable-campaign-preview-nav tab-style-<?php echo esc_attr( $tab_style ); ?> tab-size-<?php echo esc_attr( $tab_size ); ?>">
434
435 <ul class="charitable-campaign-preview-nav-list">
436
437 <?php if ( is_array( $tabs ) && ! empty( $tabs ) ) : ?>
438 <?php
439
440 $counter = 1;
441
442 /*
443 The initial structure of this array looks something like:
444
445 'title' => 'My Campaign',
446 'type' => 'html',
447 'slug' => 'default',
448 'fields' => array (
449 'campaign-description',
450 )
451
452 */
453
454 foreach ( $tabs as $tab_id => $tab_info ) :
455
456 $title = ! empty( $tab_info['title'] ) ? esc_html( $tab_info['title'] ) : false;
457 $title = false === $title && ! empty( $campaign_data['tabs'][ $tab_id ]['title'] ) ? esc_html( $campaign_data['tabs'][ $tab_id ]['title'] ) : $title;
458 $type = ( isset( $tab_info['type'] ) && '' !== trim( $tab_info['type'] ) ) ? $tab_info['type'] : 'html';
459 $class = 'tab_type_' . $type . ' ';
460 $class = ( $counter === 1 ) ? 'active' : false;
461 $show_hide_css = ! empty( $campaign_data['tabs'][ $tab_id ]['visible_nav'] ) && $campaign_data['tabs'][ $tab_id ]['visible_nav'] === 'invisible' ? 'charitable-tab-hide' : false;
462
463 ?>
464 <li id="tab_<?php echo esc_attr( $tab_id ); ?>_title" data-tab-id="<?php echo esc_attr( $tab_id ); ?>" data-tab-type="<?php echo esc_attr( $type ); ?>" class="tab_title <?php echo esc_attr( $class ); ?> <?php echo esc_attr( $show_hide_css ); ?>"><a href="#"><?php echo esc_html( $title ); ?></a></li>
465 <?php
466
467 ++$counter;
468
469 endforeach;
470
471 endif;
472 ?>
473
474 </ul>
475
476 </nav>
477
478 <?php
479
480 return ob_get_clean();
481 }
482
483 /**
484 * Create tab content based on the template data.
485 *
486 * @since 1.8.0
487 *
488 * @param array $tabs Tab info.
489 * @param string $theme The current theme.
490 * @param array $campaign_data Campaign data.
491 * @param array $row_fields Row fields.
492 * @param int $last_field_id Last field ID.
493 *
494 * @return string
495 */
496 public function get_template_tab_content( $tabs = false, $theme = false, $campaign_data = array(), $row_fields = false, $last_field_id = 0 ) {
497
498 if ( ! is_admin() ) {
499 return;
500 }
501
502 if ( false === $tabs || false === $theme || ! is_array( $tabs ) ) {
503 return;
504 }
505
506 ob_start();
507
508 $counter = 0;
509 $tab_id = 0;
510 $field_id = $last_field_id;
511 $no_tab_class = ( empty( $tabs ) || false === $tabs ) ? 'empty-tabs' : false;
512
513 ?>
514
515 <div class="tab-content <?php echo esc_attr( $no_tab_class ); ?>">
516 <ul>
517 <?php if ( $tabs ) : ?>
518 <?php
519
520 $counter = 1;
521
522 foreach ( $tabs as $tab_id => $tab_info ) :
523
524 $type = ( isset( $tab_info['type'] ) && '' !== trim( $tab_info['type'] ) ) ? $tab_info['type'] : 'html';
525 $class = 'tab_type_' . $type . ' ';
526 $class .= ( $counter === 1 ) ? 'active' : false;
527 $fields = ( ! empty( $tab_info['fields'] ) ) ? $tab_info['fields'] : false;
528 $class .= ( false === $fields ) ? ' empty-tab' : false;
529
530 ?>
531
532 <li id="tab_<?php echo intval( $tab_id ); ?>_content" class="tab_content_item <?php echo esc_attr( $class ); ?>" data-tab-type="<?php echo esc_attr( $type ); ?>" data-tab-id="<?php echo esc_attr( $tab_id ); ?>">
533
534 <div class="charitable-tab-wrap ui-sortable">
535
536 <?php echo $this->tab_empty_notice(); // phpcs:ignore ?>
537
538 <?php echo charitable_builder_tab_content_preview_by_type( $type ); // phpcs:ignore ?>
539
540 <?php
541
542 if ( ! empty( $fields ) ) :
543
544 foreach ( $fields as $key => $field_info_or_id ) :
545
546 // field_info could be just field_id OR an array of information from template JSON (type, default, headline, etc.).
547
548 if ( is_array( $field_info_or_id ) ) {
549 $field_info = $field_info_or_id;
550 $field_type = ! empty( $field_info['type'] ) ? esc_attr( $field_info['type'] ) : false;
551 $field_type_data = $field_info_or_id;
552 } else {
553 $field_id = intval( $field_info_or_id );
554 $field_type = is_array( $row_fields ) && ! empty( $row_fields[ $field_id ] ) ? esc_attr( $row_fields[ $field_id ] ) : false;
555 $field_type_data = ! empty( $campaign_data['fields'][ $field_id ] ) ? $campaign_data['fields'][ $field_id ] : false;
556 }
557
558 if ( false !== $field_type ) :
559
560 $class_type = 'Charitable_Field_' . str_replace( ' ', '_', ( ucwords( str_replace( '-', ' ', $field_type ) ) ) );
561
562 if ( class_exists( $class_type ) ) :
563
564 $class = new $class_type();
565 $field_settings = is_array( $field_type_data ) ? $field_type_data : array();
566
567 // define a default photo for the photo type block if it's passed from the theme JSON data.
568 if ( $field_type === 'photo' && ! empty( $field_settings['default'] ) && ! empty( $theme['meta']['slug'] ) ) {
569
570 $campaign_template_slug = esc_attr( $theme['meta']['slug'] );
571
572 // if the "default" parameter for the photo is passed then we attempt to pull that photo.
573 $default_filename = ! empty( $field_type_data['default'] ) ? esc_html( $field_type_data['default'] ) : 'photo.jpg';
574 $theme_thumbnail = ( false !== $default_filename ) && $campaign_template_slug ? charitable()->get_path( 'assets', true ) . 'images/campaign-builder/templates/' . $campaign_template_slug . '/' . $default_filename : false;
575
576 // see if the thumbnail or default photo exists.
577 if ( false !== $theme_thumbnail && file_exists( $theme_thumbnail ) ) {
578 $field_settings['default'] = charitable()->get_path( 'assets', false ) . 'images/campaign-builder/templates/' . $campaign_template_slug . '/' . $default_filename;
579 }
580 }
581
582 // Expand template-relative filenames for the campaign-hero block.
583 if ( $field_type === 'campaign-hero' && ! empty( $theme['meta']['slug'] ) ) {
584 $campaign_template_slug = esc_attr( $theme['meta']['slug'] );
585 foreach ( array( 'background_image', 'avatar_image' ) as $img_key ) {
586 if ( ! empty( $field_settings[ $img_key ] ) ) {
587 $image_value = $field_settings[ $img_key ];
588 $image_basename = basename( $image_value );
589 if ( $image_basename === $image_value ) {
590 $asset_path = charitable()->get_path( 'assets', true ) . 'images/campaign-builder/templates/' . $campaign_template_slug . '/' . $image_basename;
591 if ( file_exists( $asset_path ) ) {
592 $field_settings[ $img_key ] = charitable()->get_path( 'assets', false ) . 'images/campaign-builder/templates/' . $campaign_template_slug . '/' . $image_basename;
593 }
594 }
595 }
596 }
597 }
598 $field_settings['id'] = $field_id;
599
600 $charitable_field_css_classes = array(
601 'charitable-field',
602 'charitable-field-' . $field_type,
603 $class->can_be_edited ? 'charitable-can-edit' : 'charitable-no-edit',
604 $class->can_be_duplicated ? 'charitable-can-duplicate' : 'charitable-no-duplicate',
605 $class->can_be_deleted ? 'charitable-can-delete' : 'charitable-no-delete',
606 );
607
608 echo '<div class="' . implode( ' ', $charitable_field_css_classes ) . '" id="charitable-field-' . intval( $field_id ) . '" data-field-id="' . intval( $field_id ) . '" data-field-type="' . esc_attr( $field_type ) . '" data-field-max="' . esc_attr( $class->max_allowed ) . '" style="">'; // phpcs:ignore
609
610 if ( $class->can_be_edited ) :
611 echo '<a href="#" class="charitable-field-edit" data-type="' . esc_attr( $class->edit_type ) . '" data-section="' . esc_attr( $class->edit_section ) . '" data-edit-field-id="' . esc_attr( $class->edit_field_id ) . '" title="' . esc_html( $class->edit_label ) . '"><i class="fa fa-pencil"></i></a>';
612 endif;
613 if ( $class->can_be_duplicated ) :
614 echo '<a href="#" class="charitable-field-duplicate" title="Duplicate Field"><i class="fa fa-files-o" aria-hidden="true"></i></a>';
615 endif;
616 if ( $class->can_be_deleted ) :
617 echo '<a href="#" class="charitable-field-delete" title="Delete Field"><i class="fa fa-trash-o"></i></a>';
618 endif;
619 echo $class->field_preview( $field_settings, $campaign_data, $field_id, 'preview', $theme ); // phpcs:ignore
620 echo '</div>';
621
622 else :
623
624 $this->render_missing_addon_field_preview( $field_type, $field_id, $field_type_data, $campaign_data );
625
626 endif; // class exists.
627
628 endif; // if type isn't false.
629
630 ++$field_id;
631
632 endforeach;
633
634 endif;
635
636 ?>
637 </div>
638
639 </li>
640
641 <?php
642
643 ++$counter;
644
645 endforeach;
646 ?>
647 <?php else : ?>
648 <li id="tab_<?php echo esc_attr( $tab_id ); ?>_content" class="tab_content_item active" data-tab-id="0" data-tab-type="html"></li>
649 <?php endif; ?>
650 </ul>
651 <?php if ( $no_tab_class ) : ?>
652 <p class="no-tab-notice"><?php echo $this->no_tab_empty_notice(); // phpcs:ignore ?></p>
653 <?php endif; ?>
654 </div>
655
656 <?php
657
658 echo ob_get_clean(); // phpcs:ignore
659
660 return absint( $field_id - 1 );
661 }
662
663 /**
664 * Display missing addon message.
665 *
666 * @since 1.8.0
667 *
668 * @param string $field_type Tab info.
669 * @param integer $field_id The current theme.
670 * @param array $field_type_data The field type data.
671 * @param array $campaign_data The campaign data.
672 *
673 * @return void
674 */
675 public function render_missing_addon_field_preview( $field_type = false, $field_id = false, $field_type_data = false, $campaign_data = false ) {
676
677 $field_label = ( false !== $field_type ) ? (string) $this->get_addon_field_label( $field_type ) : false;
678 $field_label = ( false === $field_label || '' === trim( $field_label ) ) ? 'unknown' : $field_label;
679
680 $addon_label = ( false !== $field_type ) ? (string) $this->get_addon_label( $field_type ) : false;
681 $addon_label = ( false === $addon_label || '' === trim( $addon_label ) ) ? 'unknown' : $addon_label;
682
683 $template_id = ! empty( $campaign_data['template_id'] ) ? esc_attr( $campaign_data['template_id'] ) : false;
684 $template_id = $template_id === false & ! empty( $_POST['id'] ) ? esc_attr( $_POST['id'] ) : $template_id; // phpcs:ignore
685
686 // Get a background image, preferrabbly supplied.
687 $background_image_url = $this->get_addon_placeholder_image( $field_type );
688 $css_style = false !== $background_image_url ? 'background-image: url(' . $background_image_url . ');' : false;
689 $search_term = $this->get_addon_search_term( $field_type );
690
691 $field_id = intval( $field_id );
692
693 echo '<div class="charitable-field charitable-field-missing charitable-field-' . esc_attr( $field_type ) . '" id="charitable-field-' . intval( $field_id ) . '" data-field-id="' . intval( $field_id ) . '" data-field-type="' . esc_attr( $field_type ) . '">';
694
695 echo '<a href="#" class="charitable-field-delete" title="Delete Field"><i class="fa fa-trash-o"></i></a>';
696
697 echo '<div class="charitable-missing-addon-bg" style="' . esc_attr( $css_style ) . '"></div>';
698
699 echo '<div class="charitable-missing-addon-content">';
700
701 if ( charitable_is_pro() ) {
702
703 $display_html = $this->get_addon_install_or_activate_buttons( $field_type );
704
705 if ( ! empty( $display_html['title'] ) ) {
706 echo $display_html['title']; // phpcs:ignore
707 } else {
708 echo '<h2>We\'re sorry, the <strong>' . esc_html( $field_label ) . '</strong> does not seem to be available.</h2>';
709 }
710
711 if ( ! empty( $display_html['description'] ) ) {
712 echo $display_html['description']; // phpcs:ignore
713 } else {
714 echo '<p>Please make sure that you have the latest version of the <strong>' . esc_html( $addon_label ) . '</strong> addon installed and activated.</p>';
715 }
716
717 echo '<div class="education-buttons">';
718
719 if ( ! empty( $display_html['buttons'] ) ) {
720 echo $display_html['buttons']; // phpcs:ignore
721 } else {
722 echo '<a class="button-link" target="_blank" href="' . esc_url( admin_url( 'admin.php?page=charitable-addons&search=' . $search_term ) ) . '">' . esc_html__( 'View Addons', 'charitable' ) . '</a>';
723 }
724
725 echo '</div>';
726
727 } else {
728
729 echo '<h2>We\'re sorry, the <strong>' . esc_html( $field_label ) . '</strong> is not available on your plan.</h2> <p>Please upgrade to the PRO plan to unlock all these awesome features.</p>';
730
731 echo '<div class="education-buttons">
732 <a class="button-link" target="_blank" href="https://wpcharitable.com/lite-vs-pro/">' . esc_html__( 'Learn More', 'charitable' ) . '</a> <button type="button" class="btn btn-confirm update-to-pro-link">' . esc_html__( 'Upgrade to PRO', 'charitable' ) . '</button>
733 </div>';
734
735 }
736
737 echo '<a href="#" class="charitable-field-delete" title="Delete Field"><i class="fa fa-trash-o"></i></a>';
738 echo '</div>';
739 echo '</div>';
740 }
741
742 /**
743 * Display missing addon message and button(s).
744 *
745 * @since 1.8.0.4
746 *
747 * @param string $field_type The field type.
748 *
749 * @return array|bool
750 */
751 public function get_addon_install_or_activate_buttons( $field_type = false ) {
752
753 if ( false === $field_type ) {
754 return false;
755 }
756
757 // get the list of installed plugins and charitable addon information.
758 $installed_plugins = get_plugins();
759 $addon_information = $this->get_addon_information( $field_type );
760
761 // grab the list of active plugins only once, don't need it in the loop.
762 $active_plugins = apply_filters( 'active_plugins', get_option( 'active_plugins' ) ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- 'active_plugins' is a WordPress core hook.
763
764 if ( false === $addon_information ) {
765 return false;
766 }
767
768 $slug = ! empty( $addon_information['slug'] ) ? esc_attr( $addon_information['slug'] ) : false;
769
770 if ( false === $slug ) {
771 return false;
772 }
773
774 // Determine the plugin basename.
775 $plugin_basename = $this->get_plugin_basename_from_slug( $addon_information['slug'] );
776
777 if ( isset( $installed_plugins[ $plugin_basename ] ) && ! is_plugin_active( $plugin_basename ) ) {
778
779 // if the plugin exists (installed, visible in the plugin list in WP admin) but not activated.
780
781 return array(
782 'title' => '<h2>We\'re sorry, the <strong>' . esc_html( $addon_information['name'] ) . '</strong> addon does not seem to be available.</h2>',
783 'description' => '<p>It appears you have <strong>' . esc_html( $addon_information['name'] ) . '</strong> installed but it needs to be activated.</p>',
784 'buttons' => '<a class="button-link charitable-not-activated" data-plugin-url="' . esc_attr( $addon_information['slug'] ) . '/' . esc_attr( $addon_information['slug'] ) . '.php" data-name="' . $addon_information['name'] . '" data-slug="' . $addon_information['slug'] . '" data-field-icon="" href="#">' . esc_html__( 'Activate ', 'charitable' ) . esc_html( $addon_information['name'] ) . '</a>',
785 );
786 } elseif ( is_array( $active_plugins ) && ! in_array( 'charitable-' . $slug . '/charitable-' . $slug . '.php', $active_plugins, true ) ) {
787
788 // if pro is active AND the plugin is NOT activated, then that's another CSS class to encourage an install.
789
790 return array(
791 'title' => '<h2>We\'re sorry, the <strong>' . esc_html( $addon_information['name'] ) . '</strong> addon does not seem to be available.</h2>',
792 'description' => '<p>It appears you need <strong>' . esc_html( $addon_information['name'] ) . '</strong> to be installed and activated.</p>',
793 'buttons' => '<a class="button-link charitable-not-installed" data-plugin-url="' . $addon_information['install'] . '" data-name="' . $addon_information['name'] . '" data-slug="" data-field-icon="" href="#">' . esc_html__( 'Install &amp; Activate ', 'charitable' ) . esc_html( $addon_information['name'] ) . '</a>',
794 'install_url' => ! empty( $addon_information['install'] ) ? esc_url( $addon_information['install'] ) : false,
795 );
796 }
797
798 return false;
799 }
800
801
802 /**
803 * Get information about the addon used to display the missing addon message.
804 *
805 * @since 1.8.0.4
806 *
807 * @param string $field_type The field type.
808 *
809 * @return array|bool
810 */
811 public function get_addon_information( $field_type = false ) {
812
813 if ( false === $field_type ) {
814 return false;
815 }
816
817 // Attempt to self-correct. If the $field_type doesn't start with 'charitable-', add it.
818 if ( false === strpos( strtolower( $field_type ), 'charitable-' ) ) {
819 $field_type = 'charitable-' . $field_type;
820 }
821
822 // grab the list of active plugins only once, don't need it in the loop.
823 $active_plugins = apply_filters( 'active_plugins', get_option( 'active_plugins' ) ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- 'active_plugins' is a WordPress core hook.
824
825 // get license stuff once.
826 $charitable_addons = get_transient( '_charitable_addons' ); // @codingStandardsIgnoreLine - testing.
827
828 // Get addons data from transient or perform API query if no transient.
829 if ( false === $charitable_addons ) {
830 $charitable_addons = charitable_get_addons_data_from_server();
831 }
832
833 if ( ! is_array( $charitable_addons ) || empty( $charitable_addons ) ) {
834 return false;
835 }
836
837 foreach ( $charitable_addons as $charitable_addon ) {
838 if ( ! empty( $charitable_addon['slug'] ) &&
839 ( strtolower( $field_type ) === strtolower( $charitable_addon['slug'] )
840 || strtolower( $field_type ) . 's' === strtolower( $charitable_addon['slug'] ) // covers in case the field type is 'video' when the slug is actually 'charitable-videos' (pural).
841 )
842 ) {
843 return $charitable_addon;
844 }
845 // This is to cover the case where the field type for example is 'charitable-ambassadors-team' but the slug is 'charitable-ambassadors'.
846 if ( substr_count( $field_type, '-' ) > 1 ) {
847 $modified_field_type = substr( $field_type, 0, strrpos( $field_type, '-' ) );
848 if ( ! empty( $charitable_addon['slug'] ) && strtolower( $modified_field_type ) === strtolower( $charitable_addon['slug'] ) ) {
849 return $charitable_addon;
850 }
851 }
852 }
853
854 return false;
855 }
856
857 /**
858 * Retrieve the plugin basename from the plugin slug.
859 *
860 * @since 1.7.0
861 *
862 * @param string $slug The plugin slug.
863 * @return string The plugin basename if found, else the plugin slug.
864 */
865 public function get_plugin_basename_from_slug( $slug ) {
866
867 $keys = array_keys( get_plugins() );
868
869 foreach ( $keys as $key ) {
870 if ( preg_match( '|^' . $slug . '|', $key ) ) {
871 return $key;
872 }
873 }
874
875 return $slug;
876 }
877
878 /**
879 * Get addoon field label.
880 *
881 * @since 1.8.0
882 *
883 * @param string $field_type The field type.
884 *
885 * @return array
886 */
887 public function get_addon_field_label( $field_type = false ) {
888
889 $field_type_labels = $this->get_addon_field_labels();
890
891 if ( array_key_exists( $field_type, $field_type_labels ) ) {
892 return esc_html( $field_type_labels[ $field_type ]['field_label'] );
893 }
894
895 // Try a variation.
896 if ( array_key_exists( 'charitable-' . $field_type, $field_type_labels ) ) {
897 return esc_html( $field_type_labels[ 'charitable-' . $field_type ]['field_label'] );
898 }
899
900 // Try a variation.
901 if ( array_key_exists( 'charitable-' . $field_type . 's', $field_type_labels ) ) {
902 return esc_html( $field_type_labels[ 'charitable-' . $field_type . 's' ]['field_label'] );
903 }
904
905 return false;
906 }
907
908 /**
909 * Get addoon label.
910 *
911 * @since 1.8.0
912 *
913 * @param string $field_type The field type.
914 *
915 * @return array
916 */
917 public function get_addon_label( $field_type = false ) {
918
919 $field_type_labels = $this->get_addon_field_labels();
920
921 if ( array_key_exists( $field_type, $field_type_labels ) ) {
922 return esc_html( $field_type_labels[ $field_type ]['addon_label'] );
923 }
924
925 // Try a variation.
926 if ( array_key_exists( 'charitable-' . $field_type, $field_type_labels ) ) {
927 return esc_html( $field_type_labels[ 'charitable-' . $field_type ]['addon_label'] );
928 }
929
930 // Try a variation.
931 if ( array_key_exists( 'charitable-' . $field_type . 's', $field_type_labels ) ) {
932 return esc_html( $field_type_labels[ 'charitable-' . $field_type . 's' ]['addon_label'] );
933 }
934
935 return false;
936 }
937
938 /**
939 * Get the search term that will show the correct addon if the user clicks on "View addons" in the preview
940 *
941 * @since 1.8.0
942 *
943 * @param string $addon_slug The addon slug.
944 *
945 * @return string
946 */
947 public function get_addon_search_term( $addon_slug = false ) {
948
949 if ( false === $addon_slug ) {
950 return false;
951 }
952
953 $addon_labels = $this->get_addon_field_labels();
954
955 if ( ! empty( $addon_labels[ $addon_slug ] ) ) {
956 return strtolower( $addon_labels[ $addon_slug ]['admin_search'] );
957 }
958
959 // not found? take a guess.
960 $search_term = str_replace( 'charitable-', '', $addon_slug );
961
962 return strtolower( $search_term );
963 }
964
965 /**
966 * Get the placeholder image that appears in preview when the user doesn't have the addon activated.
967 *
968 * @since 1.8.0
969 *
970 * @param string $addon_slug The addon slug.
971 *
972 * @return string
973 */
974 public function get_addon_placeholder_image( $addon_slug = false ) {
975
976 if ( false === $addon_slug ) {
977 return false;
978 }
979
980 $addon_labels = $this->get_addon_field_labels();
981
982 if ( ! empty( $addon_labels[ $addon_slug ] ) ) {
983 return strtolower( $addon_labels[ $addon_slug ]['placeholder_img'] );
984 }
985
986 return false;
987 }
988
989 /**
990 * Get addon field labels.
991 *
992 * @since 1.8.0
993 *
994 * @return array
995 */
996 public function get_addon_field_labels() {
997
998 $this->addon_labels = apply_filters(
999 'charitable_campaign_builder_addon_labels',
1000 array(
1001 'ambassadors-team' => array(
1002 'field_label' => 'Team Field',
1003 'addon_label' => 'Ambassadors Addon',
1004 'url' => false,
1005 'plan' => 'pro',
1006 'admin_search' => 'ambassador',
1007 'placeholder_img' => charitable()->get_path( 'directory', false ) . 'assets/images/campaign-builder/placeholders/field-team.jpg',
1008 ),
1009 'video' => array(
1010 'field_label' => 'Video Field',
1011 'addon_label' => 'Video Addon',
1012 'url' => false,
1013 'plan' => 'basic',
1014 'admin_search' => 'video',
1015 'placeholder_img' => charitable()->get_path( 'directory', false ) . 'assets/images/campaign-builder/placeholders/field-video.jpg',
1016 ),
1017 'updates-main' => array(
1018 'field_label' => 'Updates Field',
1019 'addon_label' => 'Simple Updates Addon',
1020 'url' => false,
1021 'plan' => 'pro',
1022 'admin_search' => 'updates',
1023 'placeholder_img' => charitable()->get_path( 'directory', false ) . 'assets/images/campaign-builder/placeholders/field-updates.jpg',
1024 ),
1025 'comments-main' => array(
1026 'field_label' => 'Comments Field',
1027 'addon_label' => 'Comments Addon',
1028 'url' => false,
1029 'plan' => 'pro',
1030 'admin_search' => 'comments',
1031 'placeholder_img' => charitable()->get_path( 'directory', false ) . 'assets/images/campaign-builder/placeholders/field-comments.jpg',
1032 ),
1033 )
1034 );
1035
1036 return $this->addon_labels;
1037 }
1038
1039 /**
1040 * Get the preview of a campaign builder template.
1041 *
1042 * @since 1.8.0
1043 *
1044 * @param string|bool $template_id The ID of the template to preview.
1045 * @param string|bool $theme The theme to use for the preview.
1046 * @param array|bool $campaign_data The data to use for the campaign.
1047 * @param bool $exclude_wrappers Whether to exclude the template wrappers or not.
1048 *
1049 * @return string The HTML preview of the template.
1050 */
1051 public function get_template_preview( $template_id = false, $theme = false, $campaign_data = false, $exclude_wrappers = false ) {
1052
1053 if ( false === $template_id ) {
1054 return false;
1055 }
1056
1057 ob_start();
1058
1059 $this->field_id = 0;
1060 $column_counter = 0;
1061 $element_counter = 0;
1062 $section_counter = 0;
1063 $field_id = 0;
1064 $last_field_id = 0;
1065
1066 $layout = isset( $theme['layout'] ) ? $theme['layout'] : false;
1067
1068 // if this is a "default" theme or there's no layout (odd) then display the "no fields preview".
1069 $no_preview_css_class = ( empty( $layout ) || ! is_array( $layout ) || ( ! empty( $theme['meta']['template_type'] ) && is_array( $theme['meta']['template_type'] ) && in_array( 'default', $theme['meta']['template_type'], true ) ) ) ? false : 'charitable-hidden';
1070 $design_wrap_css_class = ( false === $no_preview_css_class ) ? ' no-fields-mode ' : false;
1071
1072 if ( false !== $exclude_wrappers ) {
1073 ?>
1074
1075 <div>
1076 <div>
1077
1078 <?php } else { ?>
1079
1080 <div class="charitable-design-wrap<?php echo esc_attr( $design_wrap_css_class ); ?>" id="charitable-design-wrap">
1081
1082 <?php /* loading screen, sometimes needed for color previewing */ ?>
1083
1084 <div class="charitable-campaign-preview-loader"><i class="charitable-loading-spinner charitable-loading-black charitable-loading-inline"></i></div>
1085
1086 <div class="charitable-campaign-preview charitable-campaign-preview-container">
1087
1088 <?php } ?>
1089
1090 <?php
1091
1092 if ( ! empty( $layout ) && is_array( $layout ) ) {
1093
1094 foreach ( $layout as $row ) {
1095
1096 $additional_css = ! empty( $row['css_class'] ) ? esc_attr( $row['css_class'] ) : '';
1097 $row_fields = ! empty( $row['fields'] ) ? $row['fields'] : false;
1098 $row_bg_image = ! empty( $row['background_image'] ) ? $row['background_image'] : '';
1099 $row_locked = ! empty( $row['locked'] );
1100
1101 echo $this->get_template_element_start( $row['type'], $element_counter, $additional_css, $campaign_data, $row_bg_image, $row_locked ); // phpcs:ignore
1102
1103 $tabs_rendered = false;
1104
1105 foreach ( $row['columns'] as $column ) {
1106
1107 echo '<!-- column START -->';
1108
1109 echo '<div data-column-id="' . intval( $column_counter ) . '" class="column charitable-field-column">';
1110
1111 foreach ( $column as $section ) {
1112
1113 echo '<!-- section START -->';
1114
1115 ob_start();
1116
1117 switch ( $section['type'] ) {
1118 case 'fields':
1119 if ( $tabs_rendered ) {
1120 ++$last_field_id;
1121 }
1122 $last_field_id = (int) $this->render_fields( $section['fields'], $theme, $campaign_data, $last_field_id, $row_locked );
1123 break;
1124 case 'header':
1125 $last_field_id = (int) $this->render_fields( $section['fields'], $theme, $campaign_data, $last_field_id, $row_locked );
1126 break;
1127 case 'tabs':
1128 echo $this->get_template_element_start( 'tabs', null, null, $campaign_data ); // phpcs:ignore
1129 $campaign_tabs = ! empty( $campaign_data['tabs'] ) ? $campaign_data['tabs'] : array();
1130 echo $this->get_template_tab_nav( $section['tabs'], $theme, $campaign_tabs ); // phpcs:ignore
1131 $last_field_id = (int) $this->get_template_tab_content( $section['tabs'], $theme, $campaign_data, $row_fields, $last_field_id );
1132 echo $this->get_template_element_end( 'tabs' ); // phpcs:ignore
1133 $tabs_rendered = true;
1134 break;
1135 default:
1136 do_action( 'charitable_campaign_builder_preview_section_' . $section['type'], $section, $row, $theme, $campaign_data );
1137 break;
1138 }
1139
1140 $field_html = ob_get_clean();
1141
1142 $section_css_class = ( '' === $field_html && ( empty( $row['css_class'] ) || strpos( $row['css_class'], 'no-field-target' ) === false ) ) ? 'charitable-field-target' : '';
1143 $section_css_class .= ( empty( $row['css_class'] ) || strpos( $row['css_class'], 'no-field-wrap' ) === false ) ? ' charitable-field-wrap' : '';
1144
1145 echo '<div data-section-id="' . intval( $section_counter ) . '" data-section-type="' . esc_attr( $section['type'] ) . '" class="section charitable-field-section ' . esc_attr( $section_css_class ) . '">';
1146
1147 echo '<div class="charitable-drag-new-block-here"><p>Drag New Block Here.</p></div>';
1148
1149 echo $field_html; // phpcs:ignore
1150
1151 ++$section_counter;
1152
1153 echo '</div>';
1154
1155 echo '<!-- section END -->';
1156 }
1157
1158 ++$column_counter;
1159
1160 echo '</div>';
1161
1162 echo '<!-- column END -->';
1163 }
1164
1165 echo $this->get_template_element_end( $row['type'], $element_counter ); // phpcs:ignore
1166
1167 ++$element_counter;
1168 }
1169 }
1170
1171 return ob_get_clean();
1172 }
1173
1174 /**
1175 * Returns the default start of a column.
1176 *
1177 * @return string The HTML markup for the start of a column.
1178 */
1179 public function get_default_column_start() {
1180
1181 ob_start();
1182
1183 ?>
1184
1185 <div data-column-id="0" class="column charitable-field-column">
1186
1187 <?php
1188
1189 return ob_get_clean();
1190 }
1191
1192 /**
1193 * Returns the default end of a column.
1194 *
1195 * @return string The HTML markup for the end of a column.
1196 */
1197 public function get_default_column_end() {
1198
1199 ob_start();
1200
1201 ?>
1202
1203 </div>
1204
1205 <?php
1206
1207 return ob_get_clean();
1208 }
1209
1210 /**
1211 * Seed a brand-new campaign's description from the template that supplies it.
1212 *
1213 * A campaign that has never been saved has no description of its own, and
1214 * Charitable_Field_Campaign_Description::render() prefers
1215 * settings.general.description, falling back to the description FIELD's own
1216 * `content`. Where a template supplies both and they differ, whichever paths
1217 * apply this seeding and whichever do not render DIFFERENT TEXT for the same
1218 * field.
1219 *
1220 * That is exactly what happened before 1.8.13.1: the settings panel applied it
1221 * (inline, here in get_template_field_options()) while the preview paint in
1222 * Charitable_Builder_Panel_Design::preview() did not, so the preview showed the
1223 * field's `content` on page load and switched to the description the moment the
1224 * panel opened. Extracted into one method so the two callers cannot drift apart
1225 * again -- the template data was realigned in the same release, but a future
1226 * template that diverges must not resurrect the bug.
1227 *
1228 * SCOPE: new campaigns only. The guard is `! isset()`, and every path that
1229 * creates campaign_settings_v2 writes this key -- the builder save, onboarding,
1230 * the legacy bridge (as `false`) and the AI abilities (as `''`). `isset()` is
1231 * true for both `false` and `''`, so a campaign that has ever been saved is
1232 * never touched. This also only ever rewrites the array it is handed; nothing
1233 * is persisted here.
1234 *
1235 * @since 1.8.13.1
1236 *
1237 * @param array $campaign_data The campaign data.
1238 * @param string $template_id Template slug, or false when unknown.
1239 * @return array The campaign data, with the template description applied where applicable.
1240 */
1241 public function maybe_apply_template_description( $campaign_data = false, $template_id = false ) {
1242
1243 if ( false === $template_id || ! is_array( $campaign_data ) ) {
1244 return $campaign_data;
1245 }
1246
1247 if ( isset( $campaign_data['settings']['general']['description'] ) ) {
1248 return $campaign_data;
1249 }
1250
1251 /*
1252 * Prefer the copy already in memory, but do not depend on it. $templates_data is
1253 * populated by the constructor, which returns early when ! is_admin() -- so under
1254 * WP-CLI, or anywhere else the class is built outside an admin request, the
1255 * property is an empty array and reading it alone would make this a silent no-op.
1256 * get_template_data() loads independently of that.
1257 */
1258 $description = isset( $this->templates_data['templates'][ $template_id ]['settings']['general']['description'] )
1259 ? $this->templates_data['templates'][ $template_id ]['settings']['general']['description']
1260 : null;
1261
1262 if ( null === $description || '' === $description ) {
1263 $template_data = $this->get_template_data( $template_id );
1264 $description = isset( $template_data['settings']['general']['description'] )
1265 ? $template_data['settings']['general']['description']
1266 : null;
1267 }
1268
1269 if ( empty( $description ) ) {
1270 return $campaign_data;
1271 }
1272
1273 $campaign_data['settings']['general']['description'] = $description;
1274
1275 return $campaign_data;
1276 }
1277
1278 /**
1279 * Output the 'fields' or 'blocks' for the template.
1280 *
1281 * @since 1.8.0
1282 *
1283 * @param string $template_id Template slug.
1284 * @param string $theme The current theme.
1285 * @param array $campaign_data The campaign data.
1286 *
1287 * @return string
1288 */
1289 public function get_template_field_options( $template_id = false, $theme = false, $campaign_data = false ) {
1290
1291 ob_start();
1292
1293 $field_id = 0;
1294 $layout = (array) $theme['layout'];
1295
1296 // If there is no description for this campaign, then this (assumingly) must be a new campaign and we should check and see if there's a description that comes with the template.
1297 $campaign_data = $this->maybe_apply_template_description( $campaign_data, $template_id );
1298
1299 foreach ( $layout as $row ) {
1300 foreach ( $row['columns'] as $column ) {
1301 foreach ( $column as $section_key => $section ) {
1302
1303 switch ( $section['type'] ) {
1304 case 'fields':
1305 foreach ( $section['fields'] as $key => $field_type_data ) :
1306
1307 $field_type = is_array( $field_type_data ) && ! empty( $field_type_data['type'] ) ? esc_attr( $field_type_data['type'] ) : esc_attr( $key );
1308
1309 $type = 'Charitable_Field_' . str_replace( ' ', '_', ( ucwords( str_replace( '-', ' ', $field_type ) ) ) );
1310
1311 if ( is_array( $field_type_data ) ) {
1312 $campaign_data['fields'][ $field_id ] = $field_type_data;
1313 }
1314
1315 if ( class_exists( $type ) ) :
1316 $class = new $type();
1317 if ( method_exists( $class, 'settings_display' ) ) {
1318 echo $class->settings_display( $field_id, $campaign_data ); // phpcs:ignore --- second param was $this->campaign_data, but that isn't available.
1319 }
1320 endif;
1321 ++$field_id;
1322 endforeach;
1323 break;
1324 case 'tabs':
1325 foreach ( $section['tabs'] as $tab_index => $tab ) :
1326 foreach ( $tab['fields'] as $key => $field_type_data ) :
1327
1328 $field_type = is_array( $field_type_data ) && ! empty( $field_type_data['type'] ) ? esc_attr( $field_type_data['type'] ) : esc_attr( $key );
1329
1330 // header.
1331 $type = 'Charitable_Field_' . str_replace( ' ', '_', ( ucwords( str_replace( '-', ' ', $field_type ) ) ) );
1332
1333 if ( is_array( $field_type_data ) ) {
1334 $campaign_data['fields'][ $field_id ] = $field_type_data;
1335 }
1336
1337 if ( class_exists( $type ) ) :
1338 $class = new $type();
1339 if ( method_exists( $class, 'settings_display' ) ) {
1340 echo $class->settings_display( $field_id, $campaign_data ); // phpcs:ignore --- second param was $this->campaign_data, but that isn't available.
1341 }
1342 endif;
1343
1344 ++$field_id;
1345
1346 endforeach;
1347 endforeach;
1348 break;
1349 default:
1350 break;
1351 }
1352 }
1353 }
1354 }
1355
1356 return ob_get_clean();
1357 }
1358
1359 /**
1360 * Output tab options for the left sidebar.
1361 *
1362 * @since 1.8.0
1363 *
1364 * @param integer $template_id Tab info.
1365 * @param string $theme The current theme.
1366 * @param array $campaign_data Campaign data.
1367 *
1368 * @return string
1369 */
1370 public function get_template_tab_options( $template_id = false, $theme = false, $campaign_data = false ) { // phpcs:ignore
1371
1372 ob_start();
1373
1374 $max_tab_title_length = apply_filters( 'charitable_builder_design_tab_title_length', 20 );
1375
1376 $layout = (array) $theme['layout'];
1377 $tabs = array();
1378
1379 // Currently only one tab area per template, so we need to locate that tab area (if it exists) in the layout.
1380 foreach ( $layout as $row ) {
1381 foreach ( $row['columns'] as $column ) {
1382 foreach ( $column as $section ) {
1383 if ( $section['type'] === 'tabs' && ! empty( $section['tabs'] ) ) {
1384 $tabs = $section['tabs'];
1385 continue;
1386 }
1387 }
1388 }
1389 }
1390
1391 $number_of_tabs = count( $tabs );
1392 $hide_tab_nav_disabled = $number_of_tabs > 1 ? 'disabled="disabled"' : false;
1393
1394 ?>
1395
1396 <?php $enable_tabs = 'enabled'; ?>
1397
1398 <div data-field-id="" id="charitable-group-row-design-layout-options-tabs-enable" class="charitable-panel-field charitable-panel-field-toggle " data-ajax-label="enable_tabs">
1399 <span class="charitable-toggle-control">
1400 <input type="checkbox" id="charitable-panel-field-settings-charitable-campaign-enable-tabs" name="layout__advanced__enable_tabs" data-advanced-field-id="enable_tabs" value="disabled" <?php checked( $enable_tabs, 'disabled' ); ?> />
1401 <label class="charitable-toggle-control-icon" for="charitable-panel-field-settings-charitable-campaign-enable-tabs"></label>
1402 <label for="charitable-panel-field-settings-charitable-campaign-enable-tabs">Hide tab section. </label>
1403 <?php echo charitable_get_tooltip_html( esc_html__( 'Remove tabs from your public campaign page but not delete any items in the preview.', 'charitable' ), 'tooltipstered' ); // phpcs:ignore ?>
1404 </span>
1405 </div>
1406
1407 <hr />
1408
1409 <!-- group -->
1410 <div class="charitable-group charitable-layout-options-tab-group charitable-closed charitable-new-tab hidden" data-group_id="2">
1411 <div class="charitable-general-layout-heading" data-group="general-layout-tab">
1412 <a href="#" class="charitable-draggable"><i class="fa fa-bars"></i></a>
1413 <span>[ <?php echo esc_html__( 'New Tab', 'charitable' ); ?> ]</span>
1414 <a href="#" class="charitable-toggleable-group"><i class="fa fa-angle-down charitable-angle-down"></i></a>
1415 <a href="#" class="charitable-tab-group-delete" title="Delete Tab"><i class="fa fa-trash-o" aria-hidden="true"></i></a>
1416
1417 </div>
1418 <!-- rows -->
1419 <div class="charitable-group-rows">
1420 <!-- row -->
1421 <div class="charitable-group-row charitable-tab-title-row" id="tabs_xxx_row_title" data-field-id="">
1422 <label for=""><?php echo esc_html__( 'Title', 'charitable' ); ?> <?php echo charitable_get_tooltip_html( false, 'tooltipstered' ); // phpcs:ignore ?>
1423 <input type="text" class="" id="tabs_xxx_title" name="tabs__xxx__title" value="" placeholder="<?php echo esc_html__( 'Tab Title', 'charitable' ); ?> " maxlength="<?php echo intval( $max_tab_title_length ); ?>">
1424 </div>
1425 <div class="charitable-group-row charitable-tab-title-row" id="tabs_xxx_row_visible_nav" data-field-id="">
1426 <span class="charitable-toggle-control">
1427 <input type="checkbox" id="tabs_xxx_visible_nav" class="charitable-settings-tab-visible-nav" name="tabs__xxx__visible_nav" value="invisible" <?php echo esc_attr( $hide_tab_nav_disabled ); ?> />
1428 <label class="charitable-toggle-control-icon" for="tabs_xxx_visible_nav"></label>
1429 <label for="tabs_xxx_visible_nav"><?php echo esc_html__( 'Hide tab navigation.', 'charitable' ); ?> </label>
1430 <?php echo charitable_get_tooltip_html( esc_html__( 'Hide the tab in the navigation bar (only if you have one tab in your design).', 'charitable' ), 'tooltipstered' ); // phpcs:ignore ?>
1431 </span>
1432 </div>
1433 </div>
1434 <!-- end rows -->
1435 </div> <!-- end group -->
1436 <?php
1437
1438 if ( $tabs ) {
1439
1440 $counter = 0;
1441
1442 foreach ( $tabs as $tab_id => $tab_info ) :
1443
1444 $group_id = ( strtolower( $tab_id ) === 'campaign' ) ? 0 : $tab_id;
1445 $group_name_var = ( $tab_id === 'campaign' ) ? $group_id : $group_id;
1446 $group_title = isset( $tab_info['title'] ) && '' !== trim( $tab_info['title'] ) ? $tab_info['title'] : '[ New Tab ]';
1447 $group_header = ( $tab_id === 'campaign' ) ? 'Campaign' : $group_title;
1448 $group_desc = isset( $tab_info['desc'] ) && '' !== trim( $tab_info['desc'] ) ? $tab_info['desc'] : false;
1449 $group_classes = isset( $tab_info['title'] ) && '' !== trim( $tab_info['title'] ) ? false : 'charitable-new-tab';
1450 $data_group = ( $tab_id === 'campaign' ) ? 'general-layout-tab' : 'general-layout-tab'; // general-layout-campaign.
1451 $active = $counter === 0 ? 'active' : false;
1452 $closed = $counter === 0 ? 'charitable-open' : 'charitable-closed';
1453 $arrow_icon = ! $active ? 'fa fa-angle-down charitable-angle-right' : 'fa fa-angle-down charitable-angle-down';
1454 $type = ( isset( $tab_info['type'] ) && '' !== trim( $tab_info['type'] ) ) ? $tab_info['type'] : 'html';
1455 $row_class = 'html' !== $type ? 'hidden' : false;
1456
1457 $visible_nav_checked = ! empty( $tab_info['visible_nav'] ) && 'invisible' === $tab_info['visible_nav'] ? 'checked="checked"' : '';
1458 $visible_nav_checked = ( false !== $hide_tab_nav_disabled ) ? '' : $visible_nav_checked;
1459 $disabled_css = ( false !== $hide_tab_nav_disabled ) ? 'charitable-disabled' : false;
1460
1461 ?>
1462
1463 <!-- group -->
1464 <div class="charitable-group charitable-layout-options-tab-group <?php echo esc_attr( $group_classes ); ?> <?php echo esc_attr( $closed ); ?> <?php echo esc_attr( $active ); ?>" data-group_id="<?php echo esc_attr( $group_id ); ?>">
1465 <div class="charitable-general-layout-heading" data-group="<?php echo esc_attr( $data_group ); ?>">
1466 <a href="#" class="charitable-draggable"><i class="fa fa-bars"></i></a>
1467 <span><?php echo esc_html( $group_header ); ?></span>
1468 <a href="#" class="charitable-toggleable-group"><i class="<?php echo esc_attr( $arrow_icon ); ?>"></i></a>
1469 <a href="#" class="charitable-tab-group-delete" title="<?php esc_attr_e( 'Delete Tab', 'charitable' ); ?>"><i class="fa fa-trash-o" aria-hidden="true"></i></a>
1470
1471 </div>
1472 <!-- rows -->
1473 <div class="charitable-group-rows">
1474 <div class="charitable-group-row charitable-tab-title-row" id="row_tabs__<?php echo esc_attr( $group_name_var ); ?>__title" data-tab-id="<?php echo esc_attr( $group_name_var ); ?>">
1475 <label for=""><?php echo esc_html__( 'Title', 'charitable' ); ?> <?php echo charitable_get_tooltip_html( false, 'tooltipstered' ); // phpcs:ignore ?>
1476 <input type="text" class="" id="" name="tabs__<?php echo esc_attr( $group_name_var ); ?>__title" value="<?php echo esc_attr( $group_title ); ?>" placeholder="" maxlength="<?php echo intval( $max_tab_title_length ); ?>">
1477 </div>
1478 <div class="charitable-group-row charitable-tab-title-row" id="row_tabs__<?php echo esc_attr( $group_name_var ); ?>__visible_nav" data-tab-id="<?php echo esc_attr( $group_name_var ); ?>">
1479 <span class="charitable-toggle-control">
1480 <input type="checkbox" id="charitable-panel-field-settings-charitable-campaign-<?php echo esc_attr( $group_name_var ); ?>__visible_nav" class="charitable-settings-tab-visible-nav" name="tabs__<?php echo esc_attr( $group_name_var ); ?>__visible_nav" value="invisible" <?php echo esc_attr( $visible_nav_checked ); ?> <?php echo esc_attr( $visible_nav_checked ); ?> />
1481 <label class="charitable-toggle-control-icon <?php echo esc_attr( $disabled_css ); ?>" for="charitable-panel-field-settings-charitable-campaign-<?php echo esc_attr( $group_name_var ); ?>__visible_nav"></label>
1482 <label class="<?php echo esc_attr( $disabled_css ); ?>" for="charitable-panel-field-settings-charitable-campaign-<?php echo esc_attr( $group_name_var ); ?>__visible_nav">Hide tab navigation. </label>
1483 <?php echo charitable_get_tooltip_html( esc_html__( 'Hide the tab in the navigation bar (only if you have one tab in your design).', 'charitable' ), 'tooltipstered' ); // phpcs:ignore ?>
1484 </span>
1485 </div>
1486 </div>
1487 <!-- end rows -->
1488 </div> <!-- end group -->
1489
1490 <?php ++$counter; ?>
1491
1492 <?php
1493 endforeach;
1494
1495 } else {
1496 ?>
1497
1498 <!-- group -->
1499 <div class="charitable-group charitable-layout-options-tab-group charitable-layout-options-general-group active" data-group_id="0"> <!-- open or closed -->
1500 <a href="#" class="charitable-general-layout-heading charitable-toggleable-group" data-group="general-layout-campaign">
1501 <span><?php echo esc_html__( 'Campaign', 'charitable' ); ?></span>
1502 <i class="fa fa-angle-down"></i>
1503 </a>
1504 <!-- rows -->
1505 <div class="charitable-group-rows">
1506 <input type="hidden" name="tabs__campaign__type" value="html" />
1507 <!-- row -->
1508 <div class="charitable-group-row" id="" data-field-id="">
1509 <label for="">Title <?php echo charitable_get_tooltip_html( false, 'tooltipstered' ); // phpcs:ignore ?>
1510 <input type="text" class="" id="charitable-field-option-4-name" name="tabs__campaign__title" value="" placeholder="">
1511 </div>
1512 <!-- row -->
1513 <div class="charitable-group-row" id="" data-field-id="">
1514 <label for="">Description <?php echo charitable_get_tooltip_html( false, 'tooltipstered' ); // phpcs:ignore ?>
1515 <textarea class="" id="" name="tabs__campaign__desc" rows="3"></textarea>
1516 </div>
1517 </div>
1518 <!-- end rows -->
1519 </div> <!-- end group -->
1520 <?php } ?>
1521
1522 <!-- button? -->
1523 <button class="charitable-tab-groups-add charitable-btn charitable-btn-sm"><?php echo esc_html__( '+ Add New Tab', 'charitable' ); ?></button>
1524 <!-- end button -->
1525
1526 <?php
1527 return ob_get_clean();
1528 }
1529
1530 /**
1531 * Retreive data of a particular theme.
1532 *
1533 * @since 1.8.0
1534 *
1535 * @param string $template_id Template slug.
1536 * @param array $campaign_data Campaign data.
1537 * @param bool $no_cache Whether to bypass the cache or not. True to bypass the cache. False to use the cache. Default is false.
1538 *
1539 * @return array
1540 */
1541 public function get_template_data( $template_id = false, $campaign_data = array(), $no_cache = false ) {
1542
1543 if ( false === $template_id ) {
1544 $template_id = charitable_campaign_builder_default_template();
1545 }
1546
1547 $templates_data = $this->get_templates_data( $template_id, $campaign_data, $no_cache );
1548 $template_data = ! empty( $templates_data['templates'][ $template_id ] ) ? (array) $templates_data['templates'][ $template_id ] : false;
1549
1550 return $template_data;
1551 }
1552
1553 /**
1554 * Retreive data of all available campaign builder templates from the database (or generate).
1555 *
1556 * @since 1.8.0
1557 * @since 1.8.1.3 Added $no_cache parameter.
1558 *
1559 * @param string $template_id Template slug.
1560 * @param array $campaign_data Campaign data.
1561 * @param bool $no_cache Whether to bypass the cache or not. True to bypass the cache. False to use the cache. Default is false.
1562 *
1563 * @return array
1564 */
1565 public function get_templates_data( $template_id = false, $campaign_data = array(), $no_cache = false ) {
1566
1567 $templates_data = $no_cache || ( charitable_is_debug() ) || ( defined( 'CHARITABLE_BUILDER_NO_CACHE_TEMPLATE' ) && CHARITABLE_BUILDER_NO_CACHE_TEMPLATE ) ? false : get_option( 'charitable_campaign_builder_templates' );
1568
1569 // Invalidate caches written by an older plugin version so newly added templates appear after upgrade.
1570 if ( is_array( $templates_data ) && ( empty( $templates_data['cache_version'] ) || Charitable::VERSION !== $templates_data['cache_version'] ) ) {
1571 $templates_data = false;
1572 }
1573
1574 if ( empty( $templates_data ) ) {
1575
1576 // if we cannot locate, we will create the basic choices and add them into the WordPress option, while returning the data.
1577 $templates_data = $this->get_source_template_data();
1578
1579 foreach ( $templates_data['templates'] as $key => $values ) :
1580
1581 if ( is_admin() ) :
1582 $templates_data['templates'][ $key ]['preview'] = $this->get_template_preview( $key, $templates_data['templates'][ $key ], $campaign_data );
1583 $templates_data['templates'][ $key ]['field_options'] = $this->get_template_field_options( $key, $templates_data['templates'][ $key ], $campaign_data );
1584 $templates_data['templates'][ $key ]['tab_options'] = $this->get_template_tab_options( $key, $templates_data['templates'][ $key ], $campaign_data );
1585 $templates_data['templates'][ $key ]['advanced']['show_field_names'] = charitable_show_field_names_by_default( $key ) ? 'show' : 'hide';
1586 $templates_data['templates'][ $key ]['advanced']['preview_mode'] = charitable_show_preview_mode_by_default( $key ) ? 'normal' : 'minimum';
1587 endif;
1588 endforeach;
1589
1590 $templates_data['cache_version'] = Charitable::VERSION;
1591
1592 if ( ( charitable_is_debug() ) || ( defined( 'CHARITABLE_BUILDER_NO_CACHE_TEMPLATE' ) && CHARITABLE_BUILDER_NO_CACHE_TEMPLATE ) ) {
1593 delete_option( 'charitable_campaign_builder_templates', $templates_data );
1594 } else {
1595 // update the WordPress option.
1596 update_option( 'charitable_campaign_builder_templates', $templates_data );
1597 }
1598 }
1599
1600 return apply_filters( 'charitable_campaign_builder_template_data', $templates_data );
1601 }
1602
1603
1604 /**
1605 * Get the meta field data for a given template.
1606 *
1607 * @param string $field_name The name of the meta field.
1608 * @param int|bool $template_id The ID of the template to get the meta field data for.
1609 * @return mixed The value of the meta field.
1610 */
1611 public function get_template_data_meta_field( $field_name = '', $template_id = false ) {
1612
1613 if ( '' === trim( $field_name ) ) {
1614 return false;
1615 }
1616
1617 if ( false === $template_id ) {
1618 $template_id = charitable_campaign_builder_default_template();
1619 }
1620
1621 $template_data = $this->get_template_data( $template_id );
1622
1623 return ! empty( $template_data['meta'][ $field_name ] ) ? $template_data['meta'][ $field_name ] : false;
1624 }
1625
1626 /**
1627 * Out the HTML that does the display of items and sidebar search/categories of the builder.
1628 *
1629 * @since 1.8.0
1630 * @version 1.8.1.12 Broke off HTML into function to generate sections (blank vs templated).
1631 *
1632 * @return array
1633 */
1634 public function output_templates_panel() {
1635
1636 ob_start();
1637
1638 $current_template_id = false;
1639 $template_items = array(
1640 'single_column' => 0,
1641 'template' => 0,
1642 );
1643
1644 if ( isset( $_GET['campaign_id'] ) && 0 !== intval( $_GET['campaign_id'] ) ) { // phpcs:ignore
1645
1646 $template_data = charitable_get_template_data_from_campaign( abs( $_GET['campaign_id'] ) ); // phpcs:ignore
1647 $current_template_id = isset( $template_data['template_id'] ) ? $template_data['template_id'] : false;
1648
1649 }
1650
1651 $this->prepare_templates_data();
1652
1653 ?>
1654
1655 <div id="charitable-template-container" class="charitable-template-options">
1656
1657 <div class="charitable-setup-templates-sidebar">
1658
1659 <div class="charitable-setup-templates-search-wrap">
1660 <i class="fa fa-search"></i>
1661 <i class="fa fa-close"></i>
1662 <label>
1663 <input type="text" id="charitable-setup-template-search" value="" placeholder="Search Templates">
1664 </label>
1665 </div>
1666
1667 <ul class="charitable-setup-templates-categories">
1668 <?php $this->template_categories(); ?>
1669 </ul>
1670
1671 <div class="charitable-setup-templates-feedback">
1672 <h6><?php echo esc_html__( "Don't See What You're Looking For?", 'charitable' ); ?></h6>
1673 <p>
1674 <?php
1675 printf(
1676 /* translators: 1: Template ID */
1677 '<a href="#" class="send-feedback">%1$s</a> %2$s',
1678 esc_html__( 'Let us know', 'charitable' ),
1679 esc_html__( 'what templates to add in the future.', 'charitable' ) // no comma here to avoid fatal on old PHP versions (for now).
1680 );
1681 ?>
1682 </p>
1683
1684 </div>
1685
1686 </div>
1687
1688 <div id="charitable-template-list" class="charitable-template-list"> <!-- start template list -->
1689
1690 <div class="charitable-template-list-container">
1691
1692 <?php
1693
1694 if ( ! empty( $this->prepared_templates ) ) :
1695
1696 foreach ( $this->prepared_templates as $campaign_template_slug => $campaign_template_data ) :
1697
1698 if ( is_array( $campaign_template_data['meta']['template_type'] ) && array_intersect( array( 'blank' ), $campaign_template_data['meta']['template_type'] ) ) {
1699
1700 ++$template_items['single_column'];
1701
1702 } else {
1703
1704 ++$template_items['template'];
1705
1706 }
1707
1708 endforeach;
1709
1710 if ( $template_items['single_column'] > 0 ) :
1711
1712 echo '<div class="charitable-template-list-section charitable-template-list-section-blank">
1713 <h4>' . esc_html__( 'Start from scratch with a blank template...', 'charitable' ) . '</h4>
1714 </div>';
1715
1716 foreach ( $this->prepared_templates as $campaign_template_slug => $campaign_template_data ) :
1717
1718 $this->output_template_item( $campaign_template_slug, $campaign_template_data, array( 'blank' ), array(), $current_template_id );
1719
1720 endforeach;
1721
1722 endif;
1723
1724 if ( $template_items['template'] > 0 ) :
1725
1726 echo '<div class="charitable-template-list-section charitable-template-list-section-prebuilt">
1727 <h4>' . esc_html__( '...or select from a prebuilt template and be up and running in minutes!', 'charitable' ) . '</h4>
1728 </div>';
1729
1730 foreach ( $this->prepared_templates as $campaign_template_slug => $campaign_template_data ) :
1731
1732 $this->output_template_item( $campaign_template_slug, $campaign_template_data, false, array( 'blank' ), $current_template_id );
1733
1734 endforeach;
1735
1736 endif;
1737
1738 ?>
1739
1740 <?php else : ?>
1741
1742 <div class="charitable-error">
1743
1744 <p><?php echo esc_html__( 'Unable to locate templates for campaigns. Contact support.', 'charitable' ); ?></p>
1745
1746 </div>
1747
1748 <?php endif; ?>
1749
1750 </div>
1751
1752 <div class="charitable-templates-no-results" style="display: none;">
1753 <p><?php echo esc_html__( 'Sorry, we didn\'t find any templates that match your criteria.', 'charitable' ); ?></p>
1754 </div>
1755
1756 </div> <!-- end template list -->
1757
1758 </div>
1759
1760 <?php
1761
1762 return ob_get_clean();
1763 }
1764
1765 /**
1766 * HTML output for a single template item in the list.
1767 *
1768 * @since 1.8.1.12
1769 *
1770 * @param string $campaign_template_slug The template slug.
1771 * @param array $campaign_template_data The template data.
1772 * @param array $filter_include The categories to include.
1773 * @param array $filter_exclude The categories to exclude.
1774 * @param bool $current_template_id The current template ID.
1775 */
1776 private function output_template_item( $campaign_template_slug = '', $campaign_template_data = array(), $filter_include = array(), $filter_exclude = array(), $current_template_id = false ) {
1777
1778 $create_update_term = isset( $_GET['campaign_id'] ) && 0 !== intval( $_GET['campaign_id'] ) ? esc_html__( 'Restart Campaign', 'charitable' ) : esc_html__( 'Create Campaign', 'charitable' ); // phpcs:ignore
1779
1780 if ( '' === trim( $campaign_template_slug ) || empty( $campaign_template_data ) ) {
1781 return false;
1782 }
1783
1784 // If we are filtering for template categories and this template is not in the list ('template_type'), then return false.
1785 if ( ! empty( $filter_include ) && is_array( $campaign_template_data['meta']['template_type'] ) && ! empty( $campaign_template_data['meta']['template_type'] ) ) {
1786 // If none of the elements in the $filter_include array are in the template_type, return.
1787 if ( ! array_intersect( $filter_include, $campaign_template_data['meta']['template_type'] ) ) {
1788 return false;
1789 }
1790 }
1791
1792 // If we are filtering for items to exclude, then we will return false if any of the elements in the $filter_exclude array are in the template_type.
1793 if ( ! empty( $filter_exclude ) && is_array( $campaign_template_data['meta']['template_type'] ) && ! empty( $campaign_template_data['meta']['template_type'] ) ) {
1794 // If any of the elements in the $filter_exclude array are in the template_type, return.
1795 if ( array_intersect( $filter_exclude, $campaign_template_data['meta']['template_type'] ) ) {
1796 return false;
1797 }
1798 }
1799
1800 $primary = isset( $campaign_template_data['meta']['colors']['primary'] ) && ! empty( $campaign_template_data['meta']['colors']['primary'] ) ? esc_attr( $campaign_template_data['meta']['colors']['primary'] ) : false;
1801 $secondary = isset( $campaign_template_data['meta']['colors']['secondary'] ) && ! empty( $campaign_template_data['meta']['colors']['secondary'] ) ? esc_attr( $campaign_template_data['meta']['colors']['secondary'] ) : false;
1802 $tertiary = isset( $campaign_template_data['meta']['colors']['tertiary'] ) && ! empty( $campaign_template_data['meta']['colors']['tertiary'] ) ? esc_attr( $campaign_template_data['meta']['colors']['tertiary'] ) : false;
1803 $button_color = isset( $campaign_template_data['meta']['colors']['button_bg'] ) && ! empty( $campaign_template_data['meta']['colors']['button_bg'] ) ? esc_attr( $campaign_template_data['meta']['colors']['button_bg'] ) : false;
1804
1805 $template_categories = ! empty( $campaign_template_data['meta']['categories'] ) ? implode( ', ', $campaign_template_data['meta']['categories'] ) : false; // todo: santitize.
1806 $template_tags = ! empty( $campaign_template_data['meta']['search_tags'] ) ? implode( ', ', $campaign_template_data['meta']['search_tags'] ) : false; // todo: santitize.
1807 $suffixes = ! empty( $campaign_template_data['meta']['suffixes'] ) ? serialize( $campaign_template_data['meta']['suffixes'] ) : false; // todo: santitize.
1808
1809 $show_only_for_lite = ! empty( $campaign_template_data['dependencies']['is_lite'] ) ? true : false;
1810
1811 if ( charitable_is_pro() && $show_only_for_lite ) {
1812 return;
1813 }
1814
1815 if ( isset( $campaign_template_data['thumbnail_url'] ) && false !== $campaign_template_data['thumbnail_url'] ) {
1816
1817 $template_thumbnail_html = '<div class="charitable-template-thumbnail charitable-template-thumbnail-' . $campaign_template_slug . '" style="background-image: url(' . $campaign_template_data['thumbnail_url'] . ')"></div>';
1818
1819 } elseif ( file_exists( charitable()->get_path( 'assets', true ) . 'images/campaign-builder/templates/' . $campaign_template_slug . '/thumbnail.png' ) ) {
1820
1821 $template_thumbnail_html = '<div class="charitable-template-thumbnail charitable-template-thumbnail-' . $campaign_template_slug . '" style="background-image: url(' . charitable()->get_path( 'assets', false ) . 'images/campaign-builder/templates/' . $campaign_template_slug . '/thumbnail.png)"></div>';
1822
1823 } else {
1824
1825 $template_thumbnail_html = '<div class="charitable-template-thumbnail charitable-template-thumbnail-' . $campaign_template_slug . '" style="background-image: url(' . charitable()->get_path( 'assets', false ) . 'images/campaign-builder/templates/feedback.png)"></div>';
1826
1827 }
1828
1829 $create_update_css = isset( $_GET['campaign_id'] ) && 0 !== intval( $_GET['campaign_id'] ) ? 'update-campaign' : 'create-campaign'; // phpcs:ignore
1830 $template_code = esc_attr( $campaign_template_slug );
1831 $template_preview_url = charitable()->get_path( 'assets', false ) . 'images/campaign-builder/templates/' . $template_code . '/preview.png';
1832 $template_description = esc_html( wp_strip_all_tags( $campaign_template_data['meta']['description'] ) );
1833 $template_types = is_array( $campaign_template_data['meta']['template_type'] ) && ! empty( $campaign_template_data['meta']['template_type'] ) ? implode( ' ', $campaign_template_data['meta']['template_type'] ) : false;
1834 $template_types = ( false === $template_types && false !== $campaign_template_data['meta']['template_type'] ) ? esc_attr( $campaign_template_data['meta']['template_type'] ) : $template_types;
1835 ?>
1836
1837 <?php $is_new_class = ! empty( $campaign_template_data['meta']['is_new'] ) ? ' charitable-template-is-new' : ''; ?>
1838 <div class="charitable-template-list-container-item charitable-template-<?php echo esc_attr( $campaign_template_slug ); ?> <?php echo esc_attr( $template_types ); ?><?php echo esc_attr( $is_new_class ); ?>">
1839
1840 <div class="charitable-template
1841 <?php
1842 if ( $current_template_id && $campaign_template_slug === $current_template_id ) :
1843 ?>
1844 active<?php endif; ?>" data-template-code="<?php echo esc_attr( $template_code ); ?>" data-template-preview-url="<?php echo esc_url( $template_preview_url ); ?>" data-template-description="<?php echo esc_attr( $template_description ); ?>" data-template-label="<?php echo esc_html( $campaign_template_data['meta']['label'] ); ?>" data-template-type="<?php echo esc_attr( $template_types ); ?>" data-template-categories="<?php echo esc_attr( $template_categories ); ?>" data-template-tags="<?php echo esc_attr( $template_tags ); ?>" data-template-primary="<?php echo esc_attr( $primary ); ?>" data-template-secondary="<?php echo esc_attr( $secondary ); ?>" data-template-tertiary="<?php echo esc_attr( $tertiary ); ?>" data-template-button="<?php echo esc_attr( $button_color ); ?>" data-template-suffixes="<?php echo htmlspecialchars( $suffixes ); // phpcs:ignore ?>">
1845
1846 <div class="charitable-banner-container
1847 <?php
1848 if ( ! $current_template_id || $campaign_template_slug !== $current_template_id ) :
1849 ?>
1850 charitable-hidden<?php endif; ?>">
1851 <div class="charitable-banner"><?php echo esc_html__( 'Active', 'charitable' ); ?></div>
1852 </div>
1853
1854 <div class="template-thumbnail placeholder">
1855 <?php if ( 'lite-to-pro' !== $campaign_template_slug ) { ?>
1856 <div class="template-buttons
1857 <?php
1858 if ( $current_template_id && $campaign_template_slug === $current_template_id ) :
1859 ?>
1860 charitable-hidden<?php endif; ?>">
1861 <a href="#" class="button <?php echo esc_attr( $create_update_css ); ?>"><?php echo esc_html( $create_update_term ); ?></a>
1862 <?php if ( ! isset( $campaign_template_data['meta']['preview'] ) || false !== $campaign_template_data['meta']['preview'] ) : ?>
1863 <a href="#" class="button preview-campaign"><?php echo esc_html__( 'Preview', 'charitable' ); ?></a>
1864 <?php endif; ?>
1865 </div>
1866 <?php } else { ?>
1867 <div class="template-buttons">
1868 <a href="#" class="button send-feedback"><?php echo esc_html__( 'Send Feedback', 'charitable' ); ?></a>
1869 </div>
1870 <?php } ?>
1871
1872 <?php echo $template_thumbnail_html; // phpcs:ignore ?>
1873
1874 </div>
1875 <?php
1876 $template_label_html = esc_html( $campaign_template_data['meta']['label'] );
1877 if ( ! empty( $campaign_template_data['meta']['is_new'] ) ) {
1878 $template_label_html .= ' <span class="charitable-template-label-new">' . esc_html__( 'NEW', 'charitable' ) . '</span>';
1879 }
1880 printf( '<h4>%s</h4>', $template_label_html ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1881 ?>
1882 <?php printf( '<p>%s</p>', $campaign_template_data['meta']['description'] ); // phpcs:ignore ?>
1883
1884 </div>
1885
1886 </div>
1887
1888 <?php
1889 }
1890
1891 /**
1892 * Prepare templates data for output.
1893 *
1894 * @since 1.8.0
1895 * @version 1.8.1.12
1896 */
1897 private function prepare_templates_data() {
1898
1899 $templates = $this->get_templates_data();
1900 $single_templates = array();
1901
1902 if ( empty( $templates ) || empty( $templates['templates'] ) ) {
1903 return;
1904 }
1905
1906 ksort( $templates['templates'] );
1907
1908 // move any "simple" template to the front.
1909 foreach ( $templates['templates'] as $template_name => $template_info ) :
1910 if ( ! empty( $template_info['meta']['template_type'] ) && is_array( $template_info['meta']['template_type'] ) && in_array( 'simple', $template_info['meta']['template_type'] ) ) {
1911 $single_templates['templates'][ $template_name ] = $template_info;
1912 unset( $templates['templates'][ $template_name ] );
1913 }
1914 endforeach;
1915
1916 ksort( $single_templates['templates'] );
1917
1918 // Pin the Beacon family right after Simple in a deterministic order
1919 // (2 Columns first, 1 Column second, then any future Beacon variants alphabetically).
1920 $beacon_priority = array( 'beacon-split', 'beacon-single' );
1921 $beacon_pinned = array();
1922 foreach ( $beacon_priority as $beacon_slug ) {
1923 if ( isset( $templates['templates'][ $beacon_slug ] ) ) {
1924 $beacon_pinned[ $beacon_slug ] = $templates['templates'][ $beacon_slug ];
1925 unset( $templates['templates'][ $beacon_slug ] );
1926 }
1927 }
1928 foreach ( $templates['templates'] as $template_name => $template_info ) {
1929 if ( 0 === strpos( $template_name, 'beacon-' ) ) {
1930 $beacon_pinned[ $template_name ] = $template_info;
1931 unset( $templates['templates'][ $template_name ] );
1932 }
1933 }
1934
1935 $templates['templates'] = array_merge( $single_templates['templates'], $beacon_pinned, $templates['templates'] );
1936
1937 // Loop through each available template.
1938 foreach ( $templates['templates'] as $id => $template ) {
1939
1940 $this->prepared_templates[ $id ] = $this->prepare_template_render_arguments( $template );
1941
1942 }
1943 }
1944
1945 /**
1946 * Prepare arguments for rendering template item.
1947 *
1948 * @since 1.8.0
1949 *
1950 * @param array $template Template data.
1951 *
1952 * @return array Arguments.
1953 */
1954 private function prepare_template_render_arguments( $template ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded
1955
1956 return $template;
1957 }
1958
1959 /**
1960 * Generate and display categories menu.
1961 *
1962 * @since 1.8.0
1963 */
1964 public function template_categories() {
1965
1966 $templates_count = $this->get_count_in_categories();
1967
1968 $generic_categories = array(
1969 'all' => esc_html__( 'All Templates', 'charitable' ),
1970 );
1971
1972 if ( isset( $templates_count['all'], $templates_count['available'] ) && $templates_count['all'] !== $templates_count['available'] ) {
1973 $generic_categories['available'] = esc_html__( 'Available Templates', 'charitable' );
1974 }
1975
1976 $generic_categories['favorites'] = esc_html__( 'Favorite Templates', 'charitable' );
1977 $generic_categories['new'] = esc_html__( 'New Templates', 'charitable' );
1978 $generic_categories['single'] = esc_html__( 'Single', 'charitable' );
1979 $generic_categories['peer-to-peer'] = esc_html__( 'Peer-to-Peer', 'charitable' );
1980
1981 $generic_categories = apply_filters( 'charitable_campaign_builder_template_generic_categories', $generic_categories );
1982
1983 $this->output_categories( $generic_categories, $templates_count );
1984
1985 printf( '<li class="divider"></li>' );
1986
1987 $common_categories = array();
1988
1989 if ( $this->is_custom_templates_available ) {
1990 $common_categories['custom'] = esc_html__( 'Custom Templates', 'charitable' );
1991 }
1992
1993 if ( $this->is_addon_templates_available ) {
1994 $common_categories['addons'] = esc_html__( 'Addon Templates', 'charitable' );
1995 }
1996
1997 $categories = array_merge(
1998 $common_categories,
1999 $this->get_categories()
2000 );
2001
2002 $this->output_categories( $categories, $templates_count );
2003 }
2004
2005 /**
2006 * Output categories list.
2007 *
2008 * @since 1.8.0
2009 *
2010 * @param array $categories Categories list.
2011 * @param array $templates_count Templates count by categories.
2012 *
2013 * @return void
2014 */
2015 private function output_categories( $categories, $templates_count ) {
2016
2017 // Sidebar slugs that should display a small "NEW" pill next to the label.
2018 $new_category_slugs = apply_filters(
2019 'charitable_campaign_builder_template_new_category_slugs',
2020 array( 'structured-layouts' )
2021 );
2022
2023 foreach ( $categories as $slug => $name ) {
2024
2025 $class = '';
2026
2027 if ( $slug === 'all' ) {
2028 $class = ' class="active"';
2029 } elseif ( empty( $templates_count[ $slug ] ) ) {
2030 $class = ' class="charitable-hidden"';
2031 }
2032
2033 $count = isset( $templates_count[ $slug ] ) ? $templates_count[ $slug ] : '0';
2034
2035 $badge_html = in_array( $slug, $new_category_slugs, true )
2036 ? '<span class="charitable-template-category-new">' . esc_html__( 'NEW', 'charitable' ) . '</span>'
2037 : '';
2038
2039 printf(
2040 '<li data-category="%1$s"%2$s>%5$s%3$s<span>%4$s</span></li>',
2041 esc_attr( $slug ),
2042 $class, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2043 esc_html( $name ),
2044 esc_html( $count ),
2045 $badge_html // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2046 );
2047 }
2048 }
2049
2050 /**
2051 * Get categories data.
2052 *
2053 * @since 1.8.0
2054 *
2055 * @return array
2056 */
2057 public function get_categories() {
2058
2059 $this->categories = apply_filters(
2060 'charitable_campaign_builder_template_categories',
2061 array(
2062 'structured-layouts' => esc_html__( 'Structured Layouts', 'charitable' ),
2063 'animal-pets' => esc_html__( 'Animal / Pets', 'charitable' ),
2064 'club-organizations' => esc_html__( 'Club / Organizations', 'charitable' ),
2065 'environmental' => esc_html__( 'Environmental', 'charitable' ),
2066 'events' => esc_html__( 'Events', 'charitable' ),
2067 'family' => esc_html__( 'Family', 'charitable' ),
2068 'legal' => esc_html__( 'Legal', 'charitable' ),
2069 'medical' => esc_html__( 'Medical', 'charitable' ),
2070 'political' => esc_html__( 'Political', 'charitable' ),
2071 'special-occasions' => esc_html__( 'Special Occasions', 'charitable' ),
2072 'sports' => esc_html__( 'Sports', 'charitable' ),
2073 'youth' => esc_html__( 'Youth', 'charitable' ),
2074 )
2075 );
2076
2077 return $this->categories;
2078 }
2079
2080 /**
2081 * Get categories templates count.
2082 *
2083 * @since 1.8.0
2084 *
2085 * @return array
2086 */
2087 private function get_count_in_categories() {
2088
2089 $all_categories = array();
2090 $available_templates_count = 0;
2091 $favorites_templates_count = 0;
2092
2093 foreach ( $this->prepared_templates as $template_id => $template_data ) {
2094
2095 $template_meta = $template_data['meta'];
2096 $categories = $template_meta['categories'];
2097
2098 if ( $template_meta['has_access'] ) {
2099 ++$available_templates_count;
2100 }
2101
2102 if ( $template_meta['favorite'] ) {
2103 ++$favorites_templates_count;
2104 }
2105
2106 if ( is_array( $categories ) ) {
2107 // loop through $categories and add to $all_categories.
2108 foreach ( $categories as $category ) {
2109 $all_categories[] = $category;
2110 }
2111 continue;
2112 }
2113
2114 $all_categories[] = $categories;
2115 }
2116
2117 $categories_count = array_count_values( $all_categories );
2118 $categories_count['all'] = count( $this->prepared_templates );
2119 $categories_count['available'] = $available_templates_count;
2120 $categories_count['favorites'] = $favorites_templates_count;
2121
2122 return $categories_count;
2123 }
2124
2125 /**
2126 * Retrieves the source template data for the campaign builder, which hopefully ends up cached.
2127 *
2128 * @access private
2129 * @return array The source template data.
2130 */
2131 public function get_source_template_data() {
2132
2133 return array(
2134 'version' => '1.0.0',
2135 'date_added' => time(),
2136 'templates' => array(
2137 'simple-1-col' => array(
2138 'meta' => array(
2139 'label' => 'Simple 1 Column',
2140 'slug' => 'simple-1-column',
2141 'description' => 'A clean single-column starting point with the essentials. Drop in your story, photos, and donation form, then customize to match your cause.',
2142 'template_type' => array( 'single', 'simple', 'blank' ),
2143 'search_tags' => array(
2144 'default',
2145 'simple',
2146 'blank',
2147 'clean',
2148 ),
2149 'categories' => array(
2150 'default',
2151 ),
2152 'created' => '2023-10-05', // Y-m-d.
2153 'version' => '1.0',
2154 'author' => 'David Bisset',
2155 'dependencies' => false, // not used yet.
2156 'has_access' => true, // not used yet, but false if the user can't select it.
2157 'favorite' => false,
2158 'preview' => false, // show a "preview" button, default would be true.
2159 'colors' => array(
2160 'primary' => '#000000',
2161 'secondary' => '#2B66D1',
2162 'tertiary' => '#F99E36',
2163 'accent' => false,
2164 'button_bg' => '#5AA152',
2165 ),
2166 'parent_theme' => false, // not used yet.
2167 'thumbnail_url' => false, // overrides default location to look for thumbnails in template page.
2168 ),
2169 'layout' => array(
2170 array( // row.
2171 'type' => 'row',
2172 'columns' => array(
2173 array( // column.
2174 array( // section.
2175 'type' => 'fields',
2176 'fields' => array(),
2177 ),
2178 ),
2179 ),
2180 ),
2181 array( // row.
2182 'type' => 'row',
2183 'css_class' => 'no-padding no-field-target no-field-wrap',
2184 'columns' => array(
2185 array( // column.
2186 array( // section.
2187 'type' => 'tabs',
2188 'tabs' => array(
2189 array(
2190 'title' => 'Overview',
2191 'type' => '',
2192 'slug' => 'default',
2193 'fields' => array(),
2194 ),
2195 ),
2196 ),
2197 ),
2198 ),
2199 ),
2200 ),
2201 'advanced' => array(
2202 'tab_style' => 'boxed',
2203 'tab_size' => 'medium',
2204 ),
2205 'settings' => array(
2206 'general' => array(
2207 'description' => '<p><strong>This is a description of your campaign.</strong></p><p>Write one or two short paragraphs about what you want to accomplish and perhaps brief of origin of this campaign, past campaigns, and feature key people and events that will enspire people to give to your cause.</p>',
2208 ),
2209 ),
2210 ),
2211 'simple-2-col' => array(
2212 'meta' => array(
2213 'label' => 'Simple 2 Column',
2214 'slug' => 'simple-2-column',
2215 'description' => 'Two-column blank layout for storytelling on one side and donation form or media on the other. Ideal when you want side-by-side content without committing to a specific style.',
2216 'template_type' => array( 'single', 'simple', 'blank' ),
2217 'search_tags' => array(
2218 'default',
2219 'simple',
2220 'blank',
2221 'clean',
2222 ),
2223 'categories' => array(
2224 'default',
2225 ),
2226 'created' => '2023-10-05', // Y-m-d.
2227 'version' => '1.0',
2228 'author' => 'David Bisset',
2229 'dependencies' => false, // not used yet.
2230 'has_access' => true, // not used yet, but false if the user can't select it.
2231 'favorite' => false,
2232 'preview' => false, // show a "preview" button, default would be true.
2233 'colors' => array(
2234 'primary' => '#000000',
2235 'secondary' => '#2B66D1',
2236 'tertiary' => '#F99E36',
2237 'accent' => false,
2238 'button_bg' => '#5AA152',
2239 ),
2240 'parent_theme' => false, // not used yet.
2241 'thumbnail_url' => false, // overrides default location to look for thumbnails in template page.
2242 ),
2243 'layout' => array(
2244 array( // row.
2245 'type' => 'row',
2246 'columns' => array(
2247 array( // column.
2248 array( // section.
2249 'type' => 'fields',
2250 'fields' => array(),
2251 ),
2252 ),
2253 array( // column.
2254 array( // section.
2255 'type' => 'fields',
2256 'fields' => array(),
2257 ),
2258 ),
2259 ),
2260 ),
2261 array( // row.
2262 'type' => 'row',
2263 'css_class' => 'no-padding no-field-target no-field-wrap',
2264 'columns' => array(
2265 array( // column.
2266 array( // section.
2267 'type' => 'tabs',
2268 'tabs' => array(
2269 array(
2270 'title' => 'Overview',
2271 'type' => '',
2272 'slug' => 'default',
2273 'fields' => array(),
2274 ),
2275 ),
2276 ),
2277 ),
2278 ),
2279 ),
2280 ),
2281 'advanced' => array(
2282 'tab_style' => 'boxed',
2283 'tab_size' => 'medium',
2284 ),
2285 'settings' => array(
2286 'general' => array(
2287 'description' => '<p><strong>This is a description of your campaign.</strong></p><p>Write one or two short paragraphs about what you want to accomplish and perhaps brief of origin of this campaign, past campaigns, and feature key people and events that will enspire people to give to your cause.</p>',
2288 ),
2289 ),
2290 ),
2291 'simple-2-col-header' => array(
2292 'meta' => array(
2293 'label' => 'Simple 2 Column w/ header',
2294 'slug' => 'simple-2-column-header',
2295 'description' => 'Two-column layout with a banner header row up top for title and branding. Below, balance your campaign story alongside donation amounts, photos, or a donor wall.',
2296 'template_type' => array( 'single', 'simple', 'blank' ),
2297 'search_tags' => array(
2298 'default',
2299 'simple',
2300 'blank',
2301 'clean',
2302 ),
2303 'categories' => array(
2304 'default',
2305 ),
2306 'created' => '2023-10-05', // Y-m-d.
2307 'version' => '1.0',
2308 'author' => 'David Bisset',
2309 'dependencies' => false, // not used yet.
2310 'has_access' => true, // not used yet, but false if the user can't select it.
2311 'favorite' => false,
2312 'preview' => false, // show a "preview" button, default would be true.
2313 'colors' => array(
2314 'primary' => '#000000',
2315 'secondary' => '#2B66D1',
2316 'tertiary' => '#F99E36',
2317 'accent' => false,
2318 'button_bg' => '#5AA152',
2319 ),
2320 'parent_theme' => false, // not used yet.
2321 'thumbnail_url' => false, // overrides default location to look for thumbnails in template page.
2322 ),
2323 'layout' => array(
2324 array( // row.
2325 'type' => 'header',
2326 'css_class' => 'charitable-header',
2327 'columns' => array(
2328 array( // column.
2329 array( // section.
2330 'type' => 'fields',
2331 'fields' => array(),
2332 ),
2333 ),
2334 ),
2335 ),
2336 array( // row.
2337 'type' => 'row',
2338 'columns' => array(
2339 array( // column.
2340 array( // section.
2341 'type' => 'fields',
2342 'fields' => array(),
2343 ),
2344 ),
2345 array( // column.
2346 array( // section.
2347 'type' => 'fields',
2348 'fields' => array(),
2349 ),
2350 ),
2351 ),
2352 ),
2353 array( // row.
2354 'type' => 'row',
2355 'css_class' => 'no-padding no-field-target no-field-wrap',
2356 'columns' => array(
2357 array( // column.
2358 array( // section.
2359 'type' => 'tabs',
2360 'tabs' => array(
2361 array(
2362 'title' => 'Overview',
2363 'type' => '',
2364 'slug' => 'default',
2365 'fields' => array(),
2366 ),
2367 ),
2368 ),
2369 ),
2370 ),
2371 ),
2372 ),
2373 'advanced' => array(
2374 'tab_style' => 'boxed',
2375 'tab_size' => 'medium',
2376 ),
2377 'settings' => array(
2378 'general' => array(
2379 'description' => '<p><strong>This is a description of your campaign.</strong></p><p>Write one or two short paragraphs about what you want to accomplish and perhaps brief of origin of this campaign, past campaigns, and feature key people and events that will enspire people to give to your cause.</p>',
2380 ),
2381 ),
2382 ),
2383 'youth-sports' => array(
2384 'meta' => array(
2385 'label' => 'Youth Sports',
2386 'slug' => 'youth-sports',
2387 'description' => 'Accept donations for a local school\'s sports or athletics team. Designed for spirit-driven fundraisers covering uniforms, equipment, travel costs, and seasonal program support.',
2388 'template_type' => array( 'single', 'prebuilt' ),
2389 'search_tags' => array(
2390 'youth',
2391 'sports',
2392 'blue',
2393 ),
2394 'categories' => array(
2395 'sports',
2396 'youth',
2397 'special-occasions',
2398 ),
2399 'created' => '2023-10-05', // Y-m-d.
2400 'version' => '1.0',
2401 'author' => 'David Bisset',
2402 'dependencies' => false, // not used yet.
2403 'has_access' => true, // not used yet, but false if the user can't select it.
2404 'favorite' => false,
2405 'colors' => array(
2406 'primary' => '#102963',
2407 'secondary' => '#0D0D0D',
2408 'tertiary' => '#989898',
2409 'accent' => false,
2410 'button_bg' => '#ec5f25',
2411 ),
2412 'parent_theme' => false, // not used yet.
2413 'thumbnail_url' => false, // overrides default location to look for thumbnails in template page.
2414 ),
2415 'layout' => array(
2416 array( // row.
2417 'type' => 'row',
2418 'columns' => array(
2419 array( // column.
2420 array( // section.
2421 'type' => 'fields',
2422 'fields' => array(
2423 array(
2424 'type' => 'photo',
2425 'default' => 'photo.jpg',
2426 ),
2427 array(
2428 'type' => 'campaign-summary',
2429 ),
2430 ),
2431 ),
2432 ),
2433 array(
2434 array( // section.
2435 'type' => 'fields',
2436 'fields' => array(
2437 array(
2438 'type' => 'campaign-title',
2439 ),
2440 array(
2441 'type' => 'progress-bar',
2442 'show_hide' => array(
2443 'show_donated' => true,
2444 'show_goal' => true,
2445 ),
2446 'css_class' => 'sample-css-class',
2447 'label_donate' => ' ',
2448 'label_goal' => 'Goal USD: ',
2449 'meta_position' => 'top',
2450 ),
2451 array(
2452 'type' => 'donate-amount',
2453 'headline' => 'Select An Amount',
2454 ),
2455 array(
2456 'type' => 'donate-button',
2457 'button_label' => 'Donate Now',
2458 ),
2459 ),
2460 ),
2461 ),
2462 ),
2463 ),
2464 array( // row.
2465 'type' => 'row',
2466 'css_class' => 'no-padding no-field-target no-field-wrap',
2467 'columns' => array(
2468 array( // column.
2469 array( // section.
2470 'type' => 'tabs',
2471 'tabs' => array(
2472 array(
2473 'title' => 'Overview',
2474 'type' => '',
2475 'slug' => 'default',
2476 'fields' => array(
2477 array(
2478 'type' => 'campaign-description',
2479 'headline' => 'Make every minute on the field count ⚾',
2480 'content' => '<p>We are announcing a campaign to bolster our local youth sports team, comprised of enthusiastic youngsters aged 8-12. Your support will enable us to update their uniforms, provide essential supplies, ensure a safe playing environment, and offer security measures on the field. Additionally, your contributions will go towards accommodations for parents and teachers, fostering a supportive community atmosphere where these young athletes can thrive. Join us in nurturing the next generation of athletes, empowering them with the resources and encouragement they need to excel both on and off the field.</p><p>Together, let\'s create a positive and inspiring environment for our youth, fostering a love for sports and teamwork that will last a lifetime.😄</p>',
2481 ),
2482 array(
2483 'type' => 'ambassadors-team',
2484 'headline' => 'Top Team Members',
2485 'columns' => 6,
2486 'upgrade-img-bg' => 'field-team.jpg',
2487 ),
2488 ),
2489 ),
2490 array(
2491 'title' => 'Updates',
2492 'type' => '',
2493 'slug' => 'updates',
2494 'fields' => array(
2495 'updates-main' => array(
2496 'headline' => 'Top Team Members',
2497 'columns' => 6,
2498 ),
2499 ),
2500 ),
2501 array(
2502 'title' => 'Comments',
2503 'type' => '',
2504 'slug' => 'comments',
2505 'fields' => array(
2506 'comments-main' => array(
2507 'headline' => 'Top Team Members',
2508 'columns' => 6,
2509 ),
2510 ),
2511 ),
2512 ),
2513 ),
2514 ),
2515 ),
2516 ),
2517 ),
2518 'advanced' => array(
2519 'tab_style' => 'minimum',
2520 'tab_size' => 'small',
2521 ),
2522 'settings' => array(
2523 'general' => array(
2524 'description' => '<p>We are announcing a campaign to bolster our local youth sports team, comprised of enthusiastic youngsters aged 8-12. Your support will enable us to update their uniforms, provide essential supplies, ensure a safe playing environment, and offer security measures on the field. Additionally, your contributions will go towards accommodations for parents and teachers, fostering a supportive community atmosphere where these young athletes can thrive. Join us in nurturing the next generation of athletes, empowering them with the resources and encouragement they need to excel both on and off the field.</p><p>Together, let\'s create a positive and inspiring environment for our youth, fostering a love for sports and teamwork that will last a lifetime.😄</p>',
2525 ),
2526 ),
2527 ),
2528 'school-trip' => array(
2529 'meta' => array(
2530 'label' => 'School Trip',
2531 'slug' => 'school-trip',
2532 'description' => 'Raise funds for a school trip or class fundraiser. Tells the story behind the trip, breaks down costs, and lets parents and community members chip in toward the goal.',
2533 'template_type' => array( 'single', 'prebuilt' ),
2534 'search_tags' => array(
2535 'youth',
2536 'sports',
2537 'green',
2538 'brown',
2539 ),
2540 'categories' => array(
2541 'sports',
2542 'youth',
2543 'events',
2544 ),
2545 'created' => '2023-10-05', // Y-m-d.
2546 'version' => '1.0',
2547 'author' => 'David Bisset',
2548 'dependencies' => false, // not used yet.
2549 'has_access' => true, // not used yet, but false if the user can't select it.
2550 'favorite' => false,
2551 'colors' => array(
2552 'primary' => '#7A8347',
2553 'secondary' => '#000000',
2554 'tertiary' => '#5F5F5F',
2555 'accent' => false,
2556 'button_bg' => '#7A8347',
2557 ),
2558 'parent_theme' => false, // not used yet.
2559 'thumbnail_url' => false, // overrides default location to look for thumbnails in template page.
2560 'suffixes' => array(
2561 'social-links' => '-white',
2562 'social-sharing' => '-white',
2563 ),
2564 ),
2565 'layout' => array(
2566 array( // row.
2567 'type' => 'header',
2568 'css_class' => 'charitable-header',
2569 'columns' => array(
2570 array( // column.
2571 array( // section.
2572 'type' => 'fields',
2573 'fields' => array(
2574 array(
2575 'type' => 'text',
2576 'headline' => 'Help the youth learn with an adventure',
2577 'content' => 'Join us in giving our students the opportunity to explore, learn, and grow on this incredible journey!',
2578 'align' => 'center',
2579 ),
2580 ),
2581 ),
2582 ),
2583 ),
2584 ),
2585 array( // row.
2586 'type' => 'row',
2587 'columns' => array(
2588 array( // column.
2589 array( // section.
2590 'type' => 'fields',
2591 'fields' => array(
2592 array(
2593 'type' => 'campaign-description',
2594 'headline' => 'Raise fund for our school trip!',
2595 'content' => '<p>We are announcing a campaign to make the dream of a once-in-a-lifetime field trip come true for our graduating 5th graders: an unforgettable adventure in California! Your support will enable us to purchase necessary supplies, ensure safe travel, and provide accommodations for both parents and teachers accompanying the students. With your contribution, we can create lasting memories and valuable learning experiences, fostering a sense of camaraderie and curiosity that will stay with these young minds for a lifetime.</p>',
2596 ),
2597 array(
2598 'type' => 'photo',
2599 'default' => 'trip-1.jpg',
2600 ),
2601 ),
2602 ),
2603 ),
2604 array( // column.
2605 array( // section.
2606 'type' => 'fields',
2607 'fields' => array(
2608 array(
2609 'type' => 'photo',
2610 'default' => 'trip-2.jpg',
2611 ),
2612 array(
2613 'type' => 'campaign-summary',
2614 ),
2615 array(
2616 'type' => 'text',
2617 'headline' => 'Together, we can inspire these young minds and equip them with the tools they need to make a positive impact on the world.',
2618 'content' => 'Your generosity will not only provide the means for this adventure but will also contribute to the overall educational enrichment of our students. This trip isn\'t just a mere excursion; it\'s a chance for our 5th graders to expand their horizons, learn about different cultures, and engage with historical and natural wonders that will enhance their academic understanding. ',
2619 ),
2620 array(
2621 'type' => 'progress-bar',
2622 'show_hide' => array(
2623 'show_donated' => true,
2624 'show_goal' => true,
2625 ),
2626 'css_class' => 'sample-css-class',
2627 'label_donate' => 'Funded: ',
2628 'label_goal' => 'Goal: ',
2629 'meta_position' => 'top',
2630 ),
2631 array(
2632 'type' => 'social-links',
2633 'headline' => 'Follow on',
2634 'facebook_url' => 'https://facebook.com/',
2635 'twitter_url' => 'https://twitter.com/',
2636 ),
2637 ),
2638 ),
2639 ),
2640 ),
2641 ),
2642 array( // row.
2643 'type' => 'row',
2644 'css_class' => 'no-padding no-field-target no-field-wrap',
2645 'columns' => array(
2646 array( // column.
2647 array( // section.
2648 'type' => 'tabs',
2649 'tabs' => array(
2650 array(
2651 'title' => 'Donate Now',
2652 'type' => '',
2653 'slug' => 'default',
2654 'fields' => array(
2655 array(
2656 'type' => 'donation-form',
2657 ),
2658 ),
2659 ),
2660 ),
2661 ),
2662 ),
2663 ),
2664 ),
2665 ),
2666 'advanced' => array(
2667 'tab_style' => 'boxed',
2668 'tab_size' => 'medium',
2669 ),
2670 'settings' => array(
2671 'general' => array(
2672 'description' => '<p>We are announcing a campaign to make the dream of a once-in-a-lifetime field trip come true for our graduating 5th graders: an unforgettable adventure in California! Your support will enable us to purchase necessary supplies, ensure safe travel, and provide accommodations for both parents and teachers accompanying the students. With your contribution, we can create lasting memories and valuable learning experiences, fostering a sense of camaraderie and curiosity that will stay with these young minds for a lifetime.</p>',
2673 ),
2674 ),
2675 ),
2676 'beacon-split' => array(
2677 'meta' => array(
2678 'label' => 'Beacon (2 Columns)',
2679 'slug' => 'beacon-split',
2680 'description' => 'A bold, structured layout for grassroots campaigns. Locked hero banner with donation widget overlay, plus a two-column story area below.',
2681 'template_type' => array( 'single', 'prebuilt' ),
2682 'is_new' => true,
2683 'search_tags' => array(
2684 'beacon',
2685 'structured',
2686 'two-columns',
2687 'two-column',
2688 'hero-banner',
2689 'donation-widget',
2690 'modern',
2691 'overlay',
2692 'environmental',
2693 ),
2694 'categories' => array(
2695 'structured-layouts',
2696 'environmental',
2697 ),
2698 'created' => '2026-06-06', // Y-m-d.
2699 'version' => '1.0',
2700 'author' => 'David Bisset',
2701 'dependencies' => false,
2702 'has_access' => true,
2703 'favorite' => false,
2704 'colors' => array(
2705 'primary' => '#1d3a8a',
2706 'secondary' => '#0D0D0D',
2707 'tertiary' => '#5F5F5F',
2708 'accent' => false,
2709 'button_bg' => '#1d3a8a',
2710 ),
2711 'parent_theme' => false,
2712 'thumbnail_url' => false,
2713 ),
2714 'layout' => array(
2715 array( // ROW 1 — locked hero.
2716 'type' => 'header',
2717 'css_class' => 'beacon-hero-row',
2718 'locked' => true,
2719 'background_image' => '', // Hero image lives inside the campaign-hero block; row bg unused here.
2720 'columns' => array(
2721 array(
2722 array(
2723 'type' => 'fields',
2724 'fields' => array(
2725 array(
2726 'type' => 'campaign-hero',
2727 'background_image' => charitable()->get_path( 'assets', false ) . 'images/campaign-builder/templates/beacon-split/hero.jpg',
2728 'avatar_image' => charitable()->get_path( 'assets', false ) . 'images/campaign-builder/templates/beacon-split/avatar.jpg',
2729 'title_override' => 'Save Maple Grove Park',
2730 'accent_color' => '#1d3a8a',
2731 'show_raised' => true,
2732 'enable_recurring' => false,
2733 'onetime_amounts' => '50,150,500,1000',
2734 'onetime_default' => '50',
2735 'onetime_show_other'=> true,
2736 'cta_label' => 'Donate Now',
2737 ),
2738 ),
2739 ),
2740 ),
2741 ),
2742 ),
2743 array( // ROW 2 — editable body, two columns.
2744 'type' => 'row',
2745 'css_class' => 'beacon-body-row',
2746 'columns' => array(
2747 array( // Column 1 — story.
2748 array(
2749 'type' => 'fields',
2750 'fields' => array(
2751 array(
2752 'type' => 'social-sharing',
2753 'headline' => '',
2754 ),
2755 array(
2756 'type' => 'campaign-description',
2757 'headline' => 'Support Hometown Heroes for Maple Grove',
2758 'content' => '<p>For generations, Maple Grove Park has been the beating heart of our community. It is where our children take their first steps, where local musicians find their muse under the shade of ancient trees, and where neighbors gather to escape the hustle of daily life. This park is not just a patch of green space on a map; it is a living sanctuary of shared memories, vibrant wildlife, and irreplaceable local history.</p><p>But right now, this beloved community treasure is facing a quiet but devastating crisis. <strong>Commercial development plans</strong> threaten to bulldoze our history and replace our vibrant canopy with concrete. If we stay silent, the ecosystem that supports local wildlife will be destroyed, and the public space that belongs to every single one of us will be lost forever. We cannot afford to stand by and watch Maple Grove vanish.</p><p>That is why we are launching the <strong>Save Maple Grove Park</strong> initiative. Our mission is simple but powerful: to legally protect this land, restore its natural habitats, and ensure it remains a free, beautiful public park for generations to come. We are working alongside local environmental experts, city planners, and passionate residents to present an airtight preservation alternative to the city council. But an expert plan means nothing without the roaring voice of the community behind it.</p>',
2759 ),
2760 array(
2761 'type' => 'text',
2762 'headline' => 'How your donation makes a difference',
2763 'content' => '<p>Every contribution moves us closer to permanent protection for Maple Grove Park. Here is exactly where your dollars go:</p><ul><li><strong>$50</strong> funds an hour of expert legal counsel reviewing the city development proposal.</li><li><strong>$150</strong> pays for native plant restoration across a quarter-acre of degraded meadow.</li><li><strong>$500</strong> covers an environmental impact study for one section of the park.</li><li><strong>$1,000</strong> sponsors a community meeting - venue, materials, and outreach to mobilize hundreds of neighbors.</li></ul><p>No donation is too small. Every signature, every dollar, and every shared story strengthens our case before the city council on July 14.</p>',
2764 ),
2765 array(
2766 'type' => 'text',
2767 'headline' => 'A community-led plan, ready to act',
2768 'content' => '<p>This is not a protest. It is a plan. Over the past six months, our coalition of biologists, urban planners, attorneys, and lifelong residents has assembled a complete preservation proposal - one that protects the park\'s ecosystem, maintains public access, and even strengthens the city\'s tax base through eco-tourism and community programming. We will present this proposal at the July 14 council session. With your support, we will arrive with the resources, the research, and the unified voice we need to win.</p>',
2769 ),
2770 array(
2771 'type' => 'donation-wall',
2772 'headline' => 'Donor Wall',
2773 ),
2774 ),
2775 ),
2776 ),
2777 array( // Column 2 — media.
2778 array(
2779 'type' => 'fields',
2780 'fields' => array(
2781 array(
2782 'type' => 'photo',
2783 'default' => 'photo-1.jpg',
2784 ),
2785 array(
2786 'type' => 'photo',
2787 'default' => 'photo-2.jpg',
2788 ),
2789 array(
2790 'type' => 'photo',
2791 'default' => 'photo-3.jpg',
2792 ),
2793 ),
2794 ),
2795 ),
2796 ),
2797 ),
2798 ),
2799 'advanced' => array(
2800 'tab_style' => 'minimum',
2801 'tab_size' => 'small',
2802 ),
2803 'settings' => array(
2804 'general' => array(
2805 'description' => '<p>For generations, Maple Grove Park has been the beating heart of our community. It is where our children take their first steps, where local musicians find their muse under the shade of ancient trees, and where neighbors gather to escape the hustle of daily life. This park is not just a patch of green space on a map; it is a living sanctuary of shared memories, vibrant wildlife, and irreplaceable local history.</p><p>But right now, this beloved community treasure is facing a quiet but devastating crisis. <strong>Commercial development plans</strong> threaten to bulldoze our history and replace our vibrant canopy with concrete. If we stay silent, the ecosystem that supports local wildlife will be destroyed, and the public space that belongs to every single one of us will be lost forever. We cannot afford to stand by and watch Maple Grove vanish.</p><p>That is why we are launching the <strong>Save Maple Grove Park</strong> initiative. Our mission is simple but powerful: to legally protect this land, restore its natural habitats, and ensure it remains a free, beautiful public park for generations to come. We are working alongside local environmental experts, city planners, and passionate residents to present an airtight preservation alternative to the city council. But an expert plan means nothing without the roaring voice of the community behind it.</p>',
2806 ),
2807 ),
2808 ),
2809 'beacon-single' => array(
2810 'meta' => array(
2811 'label' => 'Beacon (1 Column)',
2812 'slug' => 'beacon-single',
2813 'description' => 'A bold, structured layout for grassroots campaigns. Locked hero banner with donation widget overlay, plus a single full-width column for long-form storytelling.',
2814 'template_type' => array( 'single', 'prebuilt' ),
2815 'is_new' => true,
2816 'search_tags' => array(
2817 'beacon',
2818 'structured',
2819 'one-column',
2820 'single-column',
2821 'long-form',
2822 'hero-banner',
2823 'donation-widget',
2824 'modern',
2825 'overlay',
2826 'environmental',
2827 ),
2828 'categories' => array(
2829 'structured-layouts',
2830 'environmental',
2831 ),
2832 'created' => '2026-06-06', // Y-m-d.
2833 'version' => '1.0',
2834 'author' => 'David Bisset',
2835 'dependencies' => false,
2836 'has_access' => true,
2837 'favorite' => false,
2838 'colors' => array(
2839 'primary' => '#1d3a8a',
2840 'secondary' => '#0D0D0D',
2841 'tertiary' => '#5F5F5F',
2842 'accent' => false,
2843 'button_bg' => '#1d3a8a',
2844 ),
2845 'parent_theme' => false,
2846 'thumbnail_url' => false,
2847 ),
2848 'layout' => array(
2849 array( // ROW 1 — locked hero.
2850 'type' => 'header',
2851 'css_class' => 'beacon-hero-row',
2852 'locked' => true,
2853 'background_image' => '', // Hero image lives inside the campaign-hero block; row bg unused here.
2854 'columns' => array(
2855 array(
2856 array(
2857 'type' => 'fields',
2858 'fields' => array(
2859 array(
2860 'type' => 'campaign-hero',
2861 'background_image' => charitable()->get_path( 'assets', false ) . 'images/campaign-builder/templates/beacon-single/hero.jpg',
2862 'avatar_image' => charitable()->get_path( 'assets', false ) . 'images/campaign-builder/templates/beacon-single/avatar.jpg',
2863 'title_override' => 'Save Maple Grove Park',
2864 'accent_color' => '#1d3a8a',
2865 'show_raised' => true,
2866 'enable_recurring' => false,
2867 'onetime_amounts' => '50,150,500,1000',
2868 'onetime_default' => '50',
2869 'onetime_show_other'=> true,
2870 'cta_label' => 'Donate Now',
2871 ),
2872 ),
2873 ),
2874 ),
2875 ),
2876 ),
2877 array( // ROW 2 — editable body, single full-width column.
2878 'type' => 'row',
2879 'css_class' => 'beacon-body-row',
2880 'columns' => array(
2881 array( // Column 1 — single body column.
2882 array(
2883 'type' => 'fields',
2884 'fields' => array(
2885 array(
2886 'type' => 'social-sharing',
2887 'headline' => '',
2888 ),
2889 array(
2890 'type' => 'campaign-description',
2891 'headline' => 'Support Hometown Heroes for Maple Grove',
2892 'content' => '<p>For generations, Maple Grove Park has been the beating heart of our community. It is where our children take their first steps, where local musicians find their muse under the shade of ancient trees, and where neighbors gather to escape the hustle of daily life. This park is not just a patch of green space on a map; it is a living sanctuary of shared memories, vibrant wildlife, and irreplaceable local history.</p><p>But right now, this beloved community treasure is facing a quiet but devastating crisis. <strong>Commercial development plans</strong> threaten to bulldoze our history and replace our vibrant canopy with concrete. If we stay silent, the ecosystem that supports local wildlife will be destroyed, and the public space that belongs to every single one of us will be lost forever. We cannot afford to stand by and watch Maple Grove vanish.</p><p>That is why we are launching the <strong>Save Maple Grove Park</strong> initiative. Our mission is simple but powerful: to legally protect this land, restore its natural habitats, and ensure it remains a free, beautiful public park for generations to come. We are working alongside local environmental experts, city planners, and passionate residents to present an airtight preservation alternative to the city council. But an expert plan means nothing without the roaring voice of the community behind it.</p>',
2893 ),
2894 array(
2895 'type' => 'text',
2896 'headline' => 'How your donation makes a difference',
2897 'content' => '<p>Every contribution moves us closer to permanent protection for Maple Grove Park. Here is exactly where your dollars go:</p><ul><li><strong>$50</strong> funds an hour of expert legal counsel reviewing the city development proposal.</li><li><strong>$150</strong> pays for native plant restoration across a quarter-acre of degraded meadow.</li><li><strong>$500</strong> covers an environmental impact study for one section of the park.</li><li><strong>$1,000</strong> sponsors a community meeting - venue, materials, and outreach to mobilize hundreds of neighbors.</li></ul><p>No donation is too small. Every signature, every dollar, and every shared story strengthens our case before the city council on July 14.</p>',
2898 ),
2899 array(
2900 'type' => 'photo',
2901 'default' => 'photo-1.jpg',
2902 ),
2903 array(
2904 'type' => 'text',
2905 'headline' => 'A community-led plan, ready to act',
2906 'content' => '<p>This is not a protest. It is a plan. Over the past six months, our coalition of biologists, urban planners, attorneys, and lifelong residents has assembled a complete preservation proposal - one that protects the park\'s ecosystem, maintains public access, and even strengthens the city\'s tax base through eco-tourism and community programming. We will present this proposal at the July 14 council session. With your support, we will arrive with the resources, the research, and the unified voice we need to win.</p>',
2907 ),
2908 array(
2909 'type' => 'photo',
2910 'default' => 'photo-2.jpg',
2911 ),
2912 array(
2913 'type' => 'donation-wall',
2914 'headline' => 'Donor Wall',
2915 ),
2916 ),
2917 ),
2918 ),
2919 ),
2920 ),
2921 ),
2922 'advanced' => array(
2923 'tab_style' => 'minimum',
2924 'tab_size' => 'small',
2925 ),
2926 'settings' => array(
2927 'general' => array(
2928 'description' => '<p>For generations, Maple Grove Park has been the beating heart of our community. It is where our children take their first steps, where local musicians find their muse under the shade of ancient trees, and where neighbors gather to escape the hustle of daily life. This park is not just a patch of green space on a map; it is a living sanctuary of shared memories, vibrant wildlife, and irreplaceable local history.</p><p>But right now, this beloved community treasure is facing a quiet but devastating crisis. <strong>Commercial development plans</strong> threaten to bulldoze our history and replace our vibrant canopy with concrete. If we stay silent, the ecosystem that supports local wildlife will be destroyed, and the public space that belongs to every single one of us will be lost forever. We cannot afford to stand by and watch Maple Grove vanish.</p><p>That is why we are launching the <strong>Save Maple Grove Park</strong> initiative. Our mission is simple but powerful: to legally protect this land, restore its natural habitats, and ensure it remains a free, beautiful public park for generations to come. We are working alongside local environmental experts, city planners, and passionate residents to present an airtight preservation alternative to the city council. But an expert plan means nothing without the roaring voice of the community behind it.</p>',
2929 ),
2930 ),
2931 ),
2932 'animal-sanctuary' => array(
2933 'meta' => array(
2934 'label' => 'Animal Sanctuary',
2935 'slug' => 'animal-sanctuary',
2936 'description' => 'Get funds for your mission to rescue, rehabilitate, or rehome animals in need. Showcase your team, share success stories, and connect donors to the cause that matters most.',
2937 'template_type' => array( 'single', 'prebuilt' ),
2938 'search_tags' => array(
2939 'animal-pets',
2940 'environmental',
2941 'blue',
2942 ),
2943 'categories' => array(
2944 'animal-pets',
2945 'environmental',
2946 ),
2947 'created' => '2023-10-05', // Y-m-d.
2948 'version' => '1.0',
2949 'author' => 'David Bisset',
2950 'dependencies' => false, // not used yet.
2951 'has_access' => true, // not used yet, but false if the user can't select it.
2952 'favorite' => false,
2953 'colors' => array(
2954 'primary' => '#805F93',
2955 'secondary' => '#1D1C1C',
2956 'tertiary' => '#808080',
2957 'accent' => false,
2958 'button_bg' => '#805F93',
2959 ),
2960 'parent_theme' => false, // not used yet.
2961 'thumbnail_url' => false, // overrides default location to look for thumbnails in template page.
2962 'suffixes' => array(
2963 'social-sharing' => '-dark',
2964 'social-links' => '-dark',
2965 ),
2966 ),
2967 'layout' => array(
2968 array( // row.
2969 'type' => 'row',
2970 'columns' => array(
2971 array( // column.
2972 array( // section.
2973 'type' => 'fields',
2974 'fields' => array(
2975 array(
2976 'type' => 'photo',
2977 'default' => 'photo-1.jpg',
2978 ),
2979 array(
2980 'type' => 'campaign-description',
2981 'headline' => 'Donate today to support our mission to rescue, rehabilitate, and rehome',
2982 'content' => '<p>I\'m thrilled to launch our new campaign aimed at supporting an incredible cause: an animal sanctuary dedicated to rescuing abandoned and lost animals and finding them loving homes. With your help, we aim to raise funds to maintain this sanctuary, providing a safe haven for these adorable pets and ensuring they receive the care they deserve. Your contributions will not only help us sustain the facility but also enable us to actively seek new, caring families for these animals, giving them a chance at a brighter and happier future. Together, we can make a real difference in the lives of these innocent creatures.</p>',
2983 ),
2984 array(
2985 'type' => 'campaign-summary',
2986 ),
2987 array(
2988 'type' => 'donate-button',
2989 'button_label' => 'Donate Now',
2990 ),
2991 ),
2992 ),
2993 ),
2994 array( // column.
2995 array( // section.
2996 'type' => 'fields',
2997 'fields' => array(
2998 array(
2999 'type' => 'organizer',
3000 'role_or_title' => 'Organizer',
3001 'content' => '',
3002 ),
3003 array(
3004 'type' => 'social-sharing',
3005 'headline' => 'Share Now',
3006 'facebook_url' => 'https://facebook.com/',
3007 'twitter_url' => 'https://twitter.com/',
3008 ),
3009 array(
3010 'type' => 'social-links',
3011 'headline' => 'Follow Now',
3012 'facebook_url' => 'https://facebook.com/',
3013 'twitter_url' => 'https://twitter.com/',
3014 'instagram_url' => 'https://instagram.com/',
3015 ),
3016 ),
3017 ),
3018 ),
3019 ),
3020 ),
3021 array( // row.
3022 'type' => 'row',
3023 'css_class' => 'no-padding no-field-target no-field-wrap',
3024 'columns' => array(
3025 array( // column.
3026 array( // section.
3027 'type' => 'tabs',
3028 'tabs' => array(
3029 array(
3030 'title' => 'Overview',
3031 'type' => '',
3032 'slug' => 'default',
3033 'fields' => array(
3034 array(
3035 'type' => 'text',
3036 'headline' => 'About Us',
3037 'content' => 'We are an animal santuary established in 2007.',
3038 ),
3039 ),
3040 ),
3041 ),
3042 ),
3043 ),
3044 ),
3045 ),
3046 ),
3047 'advanced' => array(
3048 'tab_style' => 'boxed',
3049 'tab_size' => 'medium',
3050 ),
3051 'settings' => array(
3052 'general' => array(
3053 'description' => '<p>I\'m thrilled to launch our new campaign aimed at supporting an incredible cause: an animal sanctuary dedicated to rescuing abandoned and lost animals and finding them loving homes. With your help, we aim to raise funds to maintain this sanctuary, providing a safe haven for these adorable pets and ensuring they receive the care they deserve. Your contributions will not only help us sustain the facility but also enable us to actively seek new, caring families for these animals, giving them a chance at a brighter and happier future. Together, we can make a real difference in the lives of these innocent creatures.</p>',
3054 ),
3055 ),
3056 ),
3057 'save-the-museum' => array(
3058 'meta' => array(
3059 'label' => 'Save The Museum',
3060 'slug' => 'save-the-museum',
3061 'description' => 'Save a historical building, residence, or important landmark. Use compelling imagery and timeline content to rally community support around preserving what matters.',
3062 'template_type' => array( 'single', 'prebuilt' ),
3063 'search_tags' => array(
3064 'legal',
3065 'special-occasions',
3066 'blue',
3067 ),
3068 'categories' => array(
3069 'legal',
3070 'club-organizations',
3071 'special-occasions',
3072 ),
3073 'created' => '2023-10-05', // Y-m-d.
3074 'version' => '1.0',
3075 'author' => 'David Bisset',
3076 'dependencies' => false, // not used yet.
3077 'has_access' => true, // not used yet, but false if the user can't select it.
3078 'favorite' => false,
3079 'colors' => array(
3080 'primary' => '#F58A07',
3081 'secondary' => '#1D3444',
3082 'tertiary' => '#5B5B5B',
3083 'accent' => false,
3084 'button_bg' => '#FFFFFF',
3085 ),
3086 'parent_theme' => false, // not used yet.
3087 'thumbnail_url' => false, // overrides default location to look for thumbnails in template page.
3088 'suffixes' => array(
3089 'social-sharing' => '-dark',
3090 'social-links' => '-dark',
3091 ),
3092 ),
3093 'layout' => array(
3094 array( // row.
3095 'type' => 'row',
3096 'columns' => array(
3097 array( // column.
3098 array( // section.
3099 'type' => 'fields',
3100 'fields' => array(
3101 array(
3102 'type' => 'photo',
3103 'default' => 'photo-1.jpg',
3104 ),
3105 array(
3106 'type' => 'campaign-description',
3107 'headline' => 'About This Project',
3108 'content' => '<p>In 1918 the top floor of the Lyric Theatre was converted into a hospital to care for the sick and dying during the Spanish Flu Epidemic. A nurse, Mrs. Lapp, was forced to care for all of families upstairs. Today nearly 100 years later the top floor of the Lyric remains unused and it’s sole occupant is the spirit of Nurse Lapp. We are trying to save this abandoned building with the help of you all.</p>',
3109 ),
3110 ),
3111 ),
3112 ),
3113 array( // column.
3114 array( // section.
3115 'type' => 'fields',
3116 'fields' => array(
3117 array(
3118 'type' => 'campaign-title',
3119 ),
3120 array(
3121 'type' => 'progress-bar',
3122 'show_hide' => array(
3123 'show_donated' => true,
3124 'show_goal' => false,
3125 ),
3126 'css_class' => 'sample-css-class',
3127 'label_donate' => 'Funded ',
3128 'label_goal' => 'Goal USD: ',
3129 'meta_position' => 'top',
3130 ),
3131 array(
3132 'type' => 'campaign-summary',
3133 ),
3134 array(
3135 'type' => 'donate-button',
3136 'button_label' => 'Donate Now',
3137 ),
3138 array(
3139 'type' => 'social-links',
3140 'headline' => 'Follow on',
3141 'facebook_url' => 'https://facebook.com/',
3142 'twitter_url' => 'https://twitter.com/',
3143 ),
3144 ),
3145 ),
3146 ),
3147 ),
3148 ),
3149 array( // row.
3150 'type' => 'row',
3151 'css_class' => 'no-padding no-field-target no-field-wrap',
3152 'columns' => array(
3153 array( // column.
3154 array( // section.
3155 'type' => 'tabs',
3156 'tabs' => array(
3157 array(
3158 'title' => 'Overview',
3159 'type' => '',
3160 'slug' => 'default',
3161 'fields' => array(
3162 array(
3163 'type' => 'text',
3164 'headline' => 'Save the Lyrique Building',
3165 'content' => '<p>Mrs. Lapp\'s spirit is said to linger in the hushed corners of the Lyric Theatre, her presence felt by those who dare to venture to the top floor. Visitors claim to have heard soft whispers and felt a comforting presence, as if she\'s still watching over the space she once tended to with such dedication. The abandoned building stands as a testament to the resilience of the human spirit during times of crisis, and it has become a symbol of hope and survival. Efforts to preserve the Lyric Theatre are fueled by the desire to honor Mrs. Lapp\'s legacy and the countless lives she touched.</p><p>The community has rallied together, sharing stories of her bravery and compassion, inspiring a new generation to join the cause. With each donation and volunteer effort, the echoes of the past grow stronger, uniting people across time in a shared mission to save this historical landmark and the memory of Nurse Lapp, reminding us all of the power of compassion and community in the face of adversity.</p>',
3166 ),
3167 array(
3168 'type' => 'photo',
3169 'default' => 'photo-2.jpg',
3170 ),
3171 ),
3172 ),
3173 array(
3174 'title' => 'Updates',
3175 'type' => '',
3176 'slug' => 'updates',
3177 'fields' => array(
3178 'updates-main' => array(
3179 'headline' => 'Top Team Members',
3180 'columns' => 6,
3181 ),
3182 ),
3183 ),
3184 array(
3185 'title' => 'Comments',
3186 'type' => '',
3187 'slug' => 'comments',
3188 'fields' => array(
3189 'comments-main' => array(
3190 'headline' => 'Top Team Members',
3191 'columns' => 6,
3192 ),
3193 ),
3194 ),
3195 ),
3196 ),
3197 ),
3198 ),
3199 ),
3200 ),
3201 'advanced' => array(
3202 'tab_style' => 'boxed',
3203 'tab_size' => 'medium',
3204 ),
3205 'settings' => array(
3206 'general' => array(
3207 'description' => '<p>In 1918 the top floor of the Lyric Theatre was converted into a hospital to care for the sick and dying during the Spanish Flu Epidemic. A nurse, Mrs. Lapp, was forced to care for all of families upstairs. Today nearly 100 years later the top floor of the Lyric remains unused and it’s sole occupant is the spirit of Nurse Lapp. We are trying to save this abandoned building with the help of you all.</p>',
3208 ),
3209 ),
3210 ),
3211 'environmental' => array(
3212 'meta' => array(
3213 'label' => 'Environmental',
3214 'slug' => 'environmental',
3215 'description' => 'Preserve nature and the earth for future generations. Highlight conservation work, scientific impact, and the local change donors are funding - ideal for habitat, land trust, and climate causes.',
3216 'template_type' => array( 'single', 'prebuilt' ),
3217 'search_tags' => array(
3218 'environmental',
3219 'animal-pets',
3220 'blue',
3221 ),
3222 'categories' => array(
3223 'environmental',
3224 'animal-pets',
3225 ),
3226 'created' => '2023-10-05', // Y-m-d.
3227 'version' => '1.0',
3228 'author' => 'David Bisset',
3229 'dependencies' => false, // not used yet.
3230 'has_access' => true, // not used yet, but false if the user can't select it.
3231 'favorite' => false,
3232 'colors' => array(
3233 'primary' => '#F9F6EE',
3234 'secondary' => '#24231E',
3235 'tertiary' => '#61AA4F',
3236 'accent' => false,
3237 'button_bg' => '#61AA4F',
3238 ),
3239 'parent_theme' => false, // not used yet.
3240 'thumbnail_url' => false, // overrides default location to look for thumbnails in template page.
3241 'suffixes' => array(
3242 'social-sharing' => '-dark',
3243 'social-links' => '-dark',
3244 ),
3245 ),
3246 'layout' => array(
3247 array( // row.
3248 'type' => 'row',
3249 'columns' => array(
3250 array( // column.
3251 array( // section.
3252 'type' => 'fields',
3253 'fields' => array(
3254 array(
3255 'type' => 'photo',
3256 'default' => 'photo-1.jpg',
3257 ),
3258 ),
3259 ),
3260 ),
3261 array( // column.
3262 array( // section.
3263 'type' => 'fields',
3264 'fields' => array(
3265 array(
3266 'type' => 'text',
3267 'headline' => 'Save Earth',
3268 'content' => 'Together, let\'s reclaim the serenity of our shorelines and ensure a cleaner, greener future for generations to come.',
3269 'width_percentage' => 100,
3270 'align' => 'left',
3271 ),
3272 array(
3273 'type' => 'campaign-description',
3274 'headline' => 'Help Preserving The Earth For Future Generations',
3275 'content' => '<p>We are announcing a campaign dedicated to restoring the natural beauty of our local beach, which has sadly fallen victim to years of neglect and pollution. Our non-profit is on a mission to raise funds for cleaning supplies, vehicles, and essential costs to orchestrate a massive cleanup effort. By contributing, you\'re not just supporting a cleaner beach but also promoting environmental health and community pride. </p>',
3276 'width_percentage' => 100,
3277 'align' => 'left',
3278 ),
3279 array(
3280 'type' => 'campaign-summary',
3281 'width_percentage' => 100,
3282 'align' => 'left',
3283 ),
3284 array(
3285 'type' => 'donate-button',
3286 'button_label' => 'Donate Now',
3287 'width_percentage' => 100,
3288 'align' => 'left',
3289 ),
3290 array(
3291 'type' => 'social-links',
3292 'headline' => ' ',
3293 'facebook_url' => 'https://facebook.com/',
3294 'twitter_url' => 'https://twitter.com/',
3295 'align' => 'left',
3296 ),
3297 ),
3298 ),
3299 ),
3300 ),
3301 ),
3302 array( // row.
3303 'type' => 'row',
3304 'css_class' => 'no-padding no-field-target no-field-wrap',
3305 'columns' => array(
3306 array( // column.
3307 array( // section.
3308 'type' => 'tabs',
3309 'tabs' => array(
3310 array(
3311 'title' => 'Overview',
3312 'type' => '',
3313 'slug' => 'default',
3314 'fields' => array(
3315 array(
3316 'type' => 'text',
3317 'headline' => 'Keep the scene green by taking the lead',
3318 'content' => 'Your support will make a significant impact, enabling us to organize community clean-up events where volunteers, equipped with the necessary resources, can work together to remove garbage and restore the beach to its natural state. Additionally, your contributions will empower us to implement educational programs aimed at raising awareness about the importance of environmental conservation. We believe that through collective action, we can not only clean up our beloved beach but also inspire a lasting change in our community\'s attitudes towards environmental responsibility. Every donation, no matter how big or small, brings us one step closer to a cleaner, healthier environment for everyone. Join us in this vital endeavor, and let\'s create a positive ripple effect that benefits both our local ecosystem and the people who call this community home.',
3319 ),
3320 array(
3321 'type' => 'photo',
3322 'default' => 'photo-2.jpg',
3323 'align' => 'center',
3324 ),
3325 ),
3326 ),
3327 ),
3328 ),
3329 ),
3330 ),
3331 ),
3332 ),
3333 'advanced' => array(
3334 'tab_style' => 'boxed',
3335 'tab_size' => 'medium',
3336 ),
3337 'settings' => array(
3338 'general' => array(
3339 'description' => '<p>We are announcing a campaign dedicated to restoring the natural beauty of our local beach, which has sadly fallen victim to years of neglect and pollution. Our non-profit is on a mission to raise funds for cleaning supplies, vehicles, and essential costs to orchestrate a massive cleanup effort. By contributing, you\'re not just supporting a cleaner beach but also promoting environmental health and community pride.</p>',
3340 ),
3341 ),
3342 ),
3343 'disaster-relief' => array(
3344 'meta' => array(
3345 'label' => 'Disaster Relief',
3346 'slug' => 'disaster-relief',
3347 'description' => 'Help those affected by natural disasters and emergency events. Move quickly with a focused story, a clear goal, and transparent allocation so urgent donations turn into immediate aid.',
3348 'template_type' => array( 'single', 'prebuilt' ),
3349 'search_tags' => array(
3350 'environmental',
3351 'medical',
3352 'family',
3353 'blue',
3354 ),
3355 'categories' => array(
3356 'environmental',
3357 'medical',
3358 'family',
3359 ),
3360 'created' => '2023-10-05', // Y-m-d.
3361 'version' => '1.0',
3362 'author' => 'David Bisset',
3363 'dependencies' => false, // not used yet.
3364 'has_access' => true, // not used yet, but false if the user can't select it.
3365 'favorite' => false,
3366 'colors' => array(
3367 'primary' => '#9F190E',
3368 'secondary' => '#202020',
3369 'tertiary' => '#FFFFFF',
3370 'accent' => false,
3371 'button_bg' => '#9F190E',
3372 ),
3373 'parent_theme' => false, // not used yet.
3374 'thumbnail_url' => false, // overrides default location to look for thumbnails in template page.
3375 'suffixes' => array(),
3376 ),
3377 'layout' => array(
3378 array( // row.
3379 'type' => 'row',
3380 'columns' => array(
3381 array( // column.
3382 array( // section.
3383 'type' => 'fields',
3384 'fields' => array(
3385 array(
3386 'type' => 'photo',
3387 'default' => 'photo-1.jpg',
3388 ),
3389 array(
3390 'type' => 'progress-bar',
3391 'show_hide' => array(
3392 'show_donated' => true,
3393 'show_goal' => true,
3394 ),
3395 'css_class' => 'sample-css-class',
3396 'label_donate' => ' ',
3397 'label_goal' => 'Goal USD: ',
3398 'meta_position' => 'top',
3399 ),
3400 ),
3401 ),
3402 ),
3403 array( // column.
3404 array( // section.
3405 'type' => 'fields',
3406 'fields' => array(
3407 array(
3408 'type' => 'text',
3409 'headline' => 'Donate Today',
3410 'content' => 'This project will only be funded if at least $270,000 is raised by December 22, 2025.',
3411 ),
3412 array(
3413 'type' => 'campaign-summary',
3414 ),
3415 array(
3416 'type' => 'donate-button',
3417 'button_label' => 'Donate Now',
3418 'width_percentage' => 100,
3419 ),
3420 array(
3421 'type' => 'social-sharing',
3422 'headline' => 'Share',
3423 'facebook_url' => 'https://facebook.com/',
3424 'twitter_url' => 'https://twitter.com/',
3425 'align' => 'left',
3426 ),
3427 array(
3428 'type' => 'social-links',
3429 'headline' => 'Follow on',
3430 'facebook_url' => 'https://facebook.com/',
3431 'twitter_url' => 'https://twitter.com/',
3432 'align' => 'left',
3433 ),
3434 ),
3435 ),
3436 ),
3437 ),
3438 ),
3439 array( // row.
3440 'type' => 'row',
3441 'css_class' => 'no-padding no-field-target no-field-wrap',
3442 'columns' => array(
3443 array( // column.
3444 array( // section.
3445 'type' => 'tabs',
3446 'tabs' => array(
3447 array(
3448 'title' => 'Overview',
3449 'type' => '',
3450 'slug' => 'default',
3451 'fields' => array(
3452 array(
3453 'type' => 'campaign-description',
3454 'headline' => 'About This Campaign',
3455 'content' => '<p>We are announcing a new campaign aimed at providing crucial support to the victims of a recent devastating natural disaster. Together, we are rallying behind our fellow community members, raising funds through this non-profit initiative to help them rebuild homes, restore essential services, and reclaim their lives. Join us in making a difference, as every contribution brings us one step closer to bringing hope and stability back to those in need.</p>',
3456 'align' => 'left',
3457 ),
3458 array(
3459 'type' => 'photo',
3460 'default' => 'photo-2.jpg',
3461 ),
3462 ),
3463 ),
3464 ),
3465 ),
3466 ),
3467 ),
3468 ),
3469 ),
3470 'advanced' => array(
3471 'tab_style' => 'boxed',
3472 'tab_size' => 'medium',
3473 ),
3474 'settings' => array(
3475 'general' => array(
3476 'description' => '<p>We are announcing a new campaign aimed at providing crucial support to the victims of a recent devastating natural disaster. Together, we are rallying behind our fellow community members, raising funds through this non-profit initiative to help them rebuild homes, restore essential services, and reclaim their lives. Join us in making a difference, as every contribution brings us one step closer to bringing hope and stability back to those in need.</p>',
3477 ),
3478 ),
3479 ),
3480 'club-organization' => array(
3481 'meta' => array(
3482 'label' => 'Club / Organization',
3483 'slug' => 'club-organization',
3484 'description' => 'Fund programs or grow operating support for a worthy organization or club. Pair member impact stories with sponsor tiers and donor recognition to keep contributors engaged.',
3485 'template_type' => array( 'single', 'prebuilt' ),
3486 'search_tags' => array(
3487 'club-organizations',
3488 'school',
3489 'blue',
3490 ),
3491 'categories' => array(
3492 'club-organizations',
3493 'school',
3494 'sports',
3495 ),
3496 'created' => '2023-10-05', // Y-m-d.
3497 'version' => '1.0',
3498 'author' => 'David Bisset',
3499 'dependencies' => false, // not used yet.
3500 'has_access' => true, // not used yet, but false if the user can't select it.
3501 'favorite' => false,
3502 'colors' => array(
3503 'primary' => '#474735',
3504 'secondary' => '#B1A17C',
3505 'tertiary' => '#F4F0EE',
3506 'accent' => false,
3507 'button_bg' => '#B49A5F',
3508 ),
3509 'parent_theme' => false, // not used yet.
3510 'thumbnail_url' => false, // overrides default location to look for thumbnails in template page.
3511 'suffixes' => array(),
3512 ),
3513 'layout' => array(
3514 array( // row.
3515 'type' => 'row',
3516 'columns' => array(
3517 array( // column.
3518 array( // section.
3519 'type' => 'fields',
3520 'fields' => array(
3521 array(
3522 'type' => 'photo',
3523 'default' => 'photo-1.jpg',
3524 ),
3525 ),
3526 ),
3527 ),
3528 array( // column.
3529 array( // section.
3530 'type' => 'fields',
3531 'fields' => array(
3532 array(
3533 'type' => 'campaign-description',
3534 'headline' => 'About This Club',
3535 'content' => '<p>We are announcing a campaign to support our cherished local country club, a haven for our community, especially our elderly residents. Our non-profit is passionately rallying for funds to ensure the maintenance of this vital space and to sustain the heartwarming annual events that bring together generations. By contributing, you\'re preserving a beloved institution that unites the young and the old, fostering a sense of belonging and community spirit. Join us in safeguarding this haven for all ages!</p>',
3536 'width_percentage' => 100,
3537 'align' => 'left',
3538 ),
3539 array(
3540 'type' => 'progress-bar',
3541 'show_hide' => array(
3542 'show_donated' => true,
3543 'show_goal' => true,
3544 ),
3545 'css_class' => 'sample-css-class',
3546 'label_donate' => ' ',
3547 'label_goal' => 'Goal USD: ',
3548 'meta_position' => 'top',
3549 'width_percentage' => 100,
3550 'align' => 'left',
3551 ),
3552 array(
3553 'type' => 'campaign-summary',
3554 'width_percentage' => 100,
3555 'align' => 'left',
3556 ),
3557 array(
3558 'type' => 'donate-button',
3559 'button_label' => 'Donate Now',
3560 'width_percentage' => 40,
3561 'align' => 'center',
3562 ),
3563 ),
3564 ),
3565 ),
3566 ),
3567 ),
3568 array( // row.
3569 'type' => 'row',
3570 'css_class' => 'no-padding no-field-target no-field-wrap',
3571 'columns' => array(
3572 array( // column.
3573 array( // section.
3574 'type' => 'tabs',
3575 'tabs' => array(
3576 array(
3577 'title' => 'Overview',
3578 'type' => '',
3579 'slug' => 'default',
3580 'fields' => array(
3581 array(
3582 'type' => 'text',
3583 'headline' => 'With 100% of donations going towards grants and programs',
3584 'content' => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliqui.Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
3585 ),
3586 array(
3587 'type' => 'photo',
3588 'default' => 'photo-2.jpg',
3589 ),
3590 ),
3591 ),
3592 array(
3593 'title' => 'Updates',
3594 'type' => '',
3595 'slug' => 'updates',
3596 'fields' => array(
3597 array(
3598 'type' => 'updates-main',
3599 'headline' => 'Top Team Members',
3600 'columns' => 6,
3601 ),
3602 ),
3603 ),
3604 array(
3605 'title' => 'Comments',
3606 'type' => '',
3607 'slug' => 'comments',
3608 'fields' => array(
3609 array(
3610 'type' => 'comments-main',
3611 'headline' => 'Top Team Members',
3612 'columns' => 6,
3613 ),
3614 ),
3615 ),
3616 ),
3617 ),
3618 ),
3619 ),
3620 ),
3621 ),
3622 'advanced' => array(
3623 'tab_style' => 'boxed',
3624 'tab_size' => 'medium',
3625 ),
3626 'settings' => array(
3627 'general' => array(
3628 'description' => '<p>We are announcing a campaign to support our cherished local country club, a haven for our community, especially our elderly residents. Our non-profit is passionately rallying for funds to ensure the maintenance of this vital space and to sustain the heartwarming annual events that bring together generations. By contributing, you\'re preserving a beloved institution that unites the young and the old, fostering a sense of belonging and community spirit. Join us in safeguarding this haven for all ages!</p>',
3629 ),
3630 ),
3631 ),
3632 'medical-causes' => array(
3633 'meta' => array(
3634 'label' => 'Medical Causes',
3635 'slug' => 'medical-causes',
3636 'description' => 'Have a goal for more research, better treatment, or a cure. Built for foundations, advocacy groups, and patient communities raising funds toward a specific medical milestone.',
3637 'template_type' => array( 'single', 'prebuilt' ),
3638 'search_tags' => array(
3639 'medical',
3640 'family',
3641 'blue',
3642 ),
3643 'categories' => array(
3644 'medical',
3645 'family',
3646 ),
3647 'created' => '2023-10-05', // Y-m-d.
3648 'version' => '1.0',
3649 'author' => 'David Bisset',
3650 'dependencies' => false, // not used yet.
3651 'has_access' => true, // not used yet, but false if the user can't select it.
3652 'favorite' => false,
3653 'colors' => array(
3654 'primary' => '#192E45',
3655 'secondary' => '#215DB7',
3656 'tertiary' => '#48A9F5',
3657 'accent' => false,
3658 'button_bg' => '#48A9F5',
3659 ),
3660 'parent_theme' => false, // not used yet.
3661 'thumbnail_url' => false, // overrides default location to look for thumbnails in template page.
3662 'suffixes' => array(),
3663 ),
3664 'layout' => array(
3665 array( // row.
3666 'type' => 'row',
3667 'columns' => array(
3668 array( // column.
3669 array( // section.
3670 'type' => 'fields',
3671 'fields' => array(
3672 array(
3673 'type' => 'video',
3674 'headline' => '',
3675 'upgrade-img-bg' => 'field-charitable-videos.jpg',
3676 ),
3677 ),
3678 ),
3679 ),
3680 array( // column.
3681 array( // section.
3682 'type' => 'fields',
3683 'fields' => array(
3684 array(
3685 'type' => 'campaign-title',
3686 ),
3687 array(
3688 'type' => 'text',
3689 'headline' => 'Cancer Research Program',
3690 'content' => 'By donating to this cause, you can improve the lives of a variety of different people in your community and feel good about doing it.',
3691 ),
3692 array(
3693 'type' => 'progress-bar',
3694 'show_hide' => array(
3695 'show_donated' => true,
3696 'show_goal' => true,
3697 ),
3698 'css_class' => 'sample-css-class',
3699 'label_donate' => ' ',
3700 'label_goal' => 'Goal USD: ',
3701 'meta_position' => 'top',
3702 ),
3703 array(
3704 'type' => 'campaign-summary',
3705 ),
3706 array(
3707 'type' => 'donate-button',
3708 'button_label' => 'Donate Now',
3709 'align' => 'left',
3710 'width_percentage' => 50,
3711 ),
3712 ),
3713 ),
3714 ),
3715 ),
3716 ),
3717 array( // row.
3718 'type' => 'row',
3719 'css_class' => 'no-padding no-field-target no-field-wrap',
3720 'columns' => array(
3721 array( // column.
3722 array( // section.
3723 'type' => 'tabs',
3724 'tabs' => array(
3725 array(
3726 'title' => 'Overview',
3727 'type' => '',
3728 'slug' => 'overview',
3729 'fields' => array(
3730 array(
3731 'type' => 'campaign-description',
3732 'headline' => 'About Our Organization',
3733 'content' => '<p>We are announcing a campaign dedicated to supporting our local hospital\'s crucial cancer research initiatives, focusing on children under 18, women, and minority communities. Our non-profit is passionately rallying for funds to maintain this specialized facility and sustain popular fundraisers accessible to our community. By contributing, you\'re directly investing in cutting-edge research, ensuring that young lives are given a fighting chance, and addressing healthcare disparities. Join us in this noble cause, as together, we strive to create a healthier future for our youth, women, and minority members.</p>',
3734 'width_percentage' => 100,
3735 'align' => 'left',
3736 ),
3737 array(
3738 'type' => 'photo',
3739 'default' => 'photo-1.jpg',
3740 ),
3741 array(
3742 'type' => 'text',
3743 'headline' => 'Thinking differently is a proven strategy for changing the world',
3744 'content' => '<p>Your support will not only fund vital research but also provide essential resources for specialized treatments, counseling, and support services tailored to the unique needs of young patients, women, and minorities battling cancer. By contributing to this campaign, you\'re helping us bridge the gaps in healthcare access, ensuring that everyone, regardless of age, gender, or ethnicity, receives the best possible care and support during their challenging journey.</p><p>Together, we can make a significant difference in the lives of those affected by cancer, offering them hope, strength, and the promise of a brighter, healthier tomorrow. Join us in our fight against cancer, and let\'s create a community where every individual receives the care and compassion they deserve.</p>',
3745 ),
3746 array(
3747 'type' => 'photo',
3748 'default' => 'photo-2.jpg',
3749 ),
3750 ),
3751 ),
3752 array(
3753 'title' => 'Comments',
3754 'type' => '',
3755 'slug' => 'comments',
3756 'fields' => array(),
3757 ),
3758 array(
3759 'title' => 'Reviews',
3760 'type' => '',
3761 'slug' => 'reviews',
3762 'fields' => array(),
3763 ),
3764 ),
3765 ),
3766 ),
3767 ),
3768 ),
3769 ),
3770 'advanced' => array(
3771 'tab_style' => 'boxed',
3772 'tab_size' => 'medium',
3773 ),
3774 'settings' => array(
3775 'general' => array(
3776 'description' => '<p>We are announcing a campaign dedicated to supporting our local hospital\'s crucial cancer research initiatives, focusing on children under 18, women, and minority communities. Our non-profit is passionately rallying for funds to maintain this specialized facility and sustain popular fundraisers accessible to our community. By contributing, you\'re directly investing in cutting-edge research, ensuring that young lives are given a fighting chance, and addressing healthcare disparities. Join us in this noble cause, as together, we strive to create a healthier future for our youth, women, and minority members.</p>',
3777 ),
3778 ),
3779 ),
3780 'medical-bills' => array(
3781 'meta' => array(
3782 'label' => 'Medical Bills',
3783 'slug' => 'medical-bills',
3784 'description' => 'Raise funds to support the medical treatment of a friend or family member. Personal layout with space for the patient\'s story, treatment plan, and updates as donations come in.',
3785 'template_type' => array( 'single', 'prebuilt' ),
3786 'search_tags' => array(
3787 'medical',
3788 'family',
3789 'blue',
3790 ),
3791 'categories' => array(
3792 'medical',
3793 'family',
3794 ),
3795 'created' => '2023-10-05', // Y-m-d.
3796 'version' => '1.0',
3797 'author' => 'David Bisset',
3798 'dependencies' => false, // not used yet.
3799 'has_access' => true, // not used yet, but false if the user can't select it.
3800 'favorite' => false,
3801 'colors' => array(
3802 'primary' => '#5C8AF3',
3803 'secondary' => '#21458F',
3804 'tertiary' => '#F7F7F7',
3805 'accent' => false,
3806 'button_bg' => '#5C8AF3',
3807 ),
3808 'parent_theme' => false, // not used yet.
3809 'thumbnail_url' => false, // overrides default location to look for thumbnails in template page.
3810 ),
3811 'layout' => array(
3812 array( // row.
3813 'type' => 'header',
3814 'columns' => array(
3815 array( // column.
3816 array( // section.
3817 'type' => 'fields',
3818 'fields' => array(
3819 array(
3820 'type' => 'campaign-title',
3821 'align' => 'center',
3822 ),
3823 ),
3824 ),
3825 ),
3826 ),
3827 ),
3828 array( // row.
3829 'type' => 'row',
3830 'css_class' => '', // 'css_class' => 'no-padding no-field-target no-field-wrap',
3831 'columns' => array(
3832 array( // column.
3833 array( // section.
3834 'type' => 'tabs',
3835 'tabs' => array(
3836 array(
3837 'title' => 'Overview',
3838 'type' => '',
3839 'slug' => 'default',
3840 'fields' => array(
3841 array(
3842 'type' => 'campaign-description',
3843 'headline' => '',
3844 'content' => '<p>On June 12, 2023 Brook’s wife Maleena was admitted to hospital while 20 weeks pregnant, where she was diagnosed with Stage 4 B-cell Lymphoma. At just 22 years old, she has been confronted with a life-altering diagnosis and is now facing a grueling fight. After experiencing inflammation and pain in her legs in March, Brooke spoke with her GP and sought treatment from chiropractors and physios.</p>',
3845 'width_percentage' => 100,
3846 'align' => 'left',
3847 ),
3848 array(
3849 'type' => 'photo',
3850 'default' => 'photo-2.jpg',
3851 ),
3852 array(
3853 'type' => 'text',
3854 'headline' => ' ',
3855 'content' => 'Devastated by Maleena\'s diagnosis, Brooke found himself navigating a whirlwind of emotions and responsibilities. He became Maleena\'s unwavering pillar of support, attending every doctor\'s appointment and chemotherapy session with her, his heart aching at the sight of his young wife enduring such pain. The couple, once planning nursery decorations and baby names, now found themselves discussing treatment options and potential outcomes. Despite the overwhelming fear and uncertainty, they clung to each other, drawing strength from their love and the life growing inside Maleena. Friends and family rallied around them, offering words of encouragement and practical help, reminding them that they were not alone in this battle. In the face of adversity, Brooke and Maleena vowed to fight together, holding onto hope and cherishing every moment they had together, knowing that their love would be the guiding light in the darkest of times.',
3856 ),
3857 array(
3858 'type' => 'photo',
3859 'default' => 'photo-1.jpg',
3860 ),
3861 ),
3862 ),
3863 array(
3864 'title' => 'Updates',
3865 'type' => '',
3866 'slug' => 'updates',
3867 'fields' => array(
3868 array(
3869 'type' => 'text',
3870 'headline' => ' ',
3871 'content' => 'This is on second tab.',
3872 ),
3873 ),
3874 ),
3875 array(
3876 'title' => 'Comments',
3877 'type' => '',
3878 'slug' => 'comments',
3879 'fields' => array(
3880 array(
3881 'type' => 'text',
3882 'headline' => ' ',
3883 'content' => 'This is on third tab.',
3884 ),
3885 ),
3886 ),
3887 ),
3888 ),
3889 ),
3890 array( // column.
3891 array( // section.
3892 'type' => 'fields',
3893 'fields' => array(
3894 array(
3895 'type' => 'campaign-summary',
3896 ),
3897 array(
3898 'type' => 'progress-bar',
3899 'show_hide' => array(
3900 'show_donated' => true,
3901 'show_goal' => true,
3902 ),
3903 'css_class' => 'sample-css-class',
3904 'label_donate' => ' ',
3905 'label_goal' => 'Goal USD: ',
3906 'meta_position' => 'top',
3907 ),
3908 array(
3909 'type' => 'text',
3910 'headline' => ' ',
3911 'content' => 'This project will only be funded if at least $270,000 is raised by December 22, 2025.',
3912 ),
3913 array(
3914 'type' => 'donate-button',
3915 'button_label' => 'Donate Now',
3916 'width_percentage' => 75,
3917 'align' => 'center',
3918 ),
3919 array(
3920 'type' => 'organizer',
3921 'role_or_title' => 'Organizer',
3922 'content' => '',
3923 ),
3924 ),
3925 ),
3926 ),
3927 ),
3928 ),
3929 ),
3930 'advanced' => array(
3931 'tab_style' => 'boxed',
3932 'tab_size' => 'medium',
3933 ),
3934 'settings' => array(
3935 'general' => array(
3936 'description' => '<p>On June 12, 2023 Brook’s wife Maleena was admitted to hospital while 20 weeks pregnant, where she was diagnosed with Stage 4 B-cell Lymphoma. At just 22 years old, she has been confronted with a life-altering diagnosis and is now facing a grueling fight. After experiencing inflammation and pain in her legs in March, Brooke spoke with her GP and sought treatment from chiropractors and physios.</p>',
3937 ),
3938 ),
3939 ),
3940
3941 ),
3942 );
3943 }
3944
3945 /**
3946 * Template for upgrade banner.
3947 *
3948 * @since 1.8.0
3949 */
3950 public function upgrade_banner_template() {
3951
3952 if ( charitable_is_pro() ) {
3953 return;
3954 }
3955
3956 $medium = esc_html__( 'Campaign Builder Templates', 'charitable' );
3957
3958 ?>
3959 <script type="text/html" id="tmpl-charitable-templates-upgrade-banner">
3960 <div class="charitable-template-upgrade-banner">
3961 <div class="charitable-template-content">
3962 <h3>
3963 <?php
3964 printf( esc_html__( 'Super Charge Your Campaigns And Donations With Pro Addons!', 'charitable' ) );
3965 ?>
3966 </h3>
3967
3968 <p>
3969 <?php
3970 // translators: %s is the amount saved.
3971 printf( esc_html__( 'With Charitable addons you can enable crowdfunding, provide additional payment methods, add videos, offer recurring donations, and more. As thanks to you for being a lite user you can unlock access and save up to $%s, automatically applied at checkout! ', 'charitable' ), '<strong>300.00</strong>' );
3972 ?>
3973 </p>
3974 </div>
3975 <div class="charitable-template-upgrade-button">
3976 <a href="<?php echo esc_url( charitable_pro_upgrade_url( $medium ) ); ?>" class="charitable-btn charitable-btn-orange charitable-btn-md" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Upgrade to PRO', 'charitable' ); ?></a>
3977 </div>
3978 </div>
3979 </script>
3980 <?php
3981 }
3982
3983 /**
3984 * Tab Empty Notice.
3985 *
3986 * @since 1.8.0
3987 */
3988 public function tab_empty_notice() {
3989
3990 return sprintf(
3991 /* translators: 1: Template ID */
3992 '<p class="empty-tab-notice">' . __( 'This tab is empty. Drag a block from the left into this area or ', 'charitable' ) . '<br/><strong><a href="%1$s" class="charitable-configure-tab-settings">' . __( 'configure tab settings', 'charitable' ) . '</a></strong>.</p>',
3993 '#'
3994 );
3995 }
3996
3997 /**
3998 * No Empty Notice.
3999 *
4000 * @since 1.8.0
4001 */
4002 public function no_tab_empty_notice() {
4003
4004 return sprintf(
4005 /* translators: 1: Template ID */
4006 __( 'There are no tabs yet for this template. You can ', 'charitable' ) . '<br/><strong><a href="%1$s" class="charitable-configure-tab-settings">' . __( 'configure tab settings', 'charitable' ) . '</a> to add a new tab</strong>.',
4007 '#'
4008 );
4009 }
4010 }
4011
4012 endif;
4013
4014 new Charitable_Campaign_Builder_Templates();
4015