Completed
Push — master ( 6337a4...1e5684 )
by Zack
11s
created

includes/class-template.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * GravityView templating engine class
4
 *
5
 * @package   GravityView
6
 * @license   GPL2+
7
 * @author    Katz Web Services, Inc.
8
 * @link      http://gravityview.co
9
 * @copyright Copyright 2014, Katz Web Services, Inc.
10
 *
11
 * @since 1.0.0
12
 */
13
14
/** If this file is called directly, abort. */
15
if ( ! defined( 'ABSPATH' ) ) {
16
	die;
17
}
18
19
if( ! class_exists( 'Gamajo_Template_Loader' ) ) {
20
	require( GRAVITYVIEW_DIR . 'includes/lib/class-gamajo-template-loader.php' );
21
}
22
23
class GravityView_View extends Gamajo_Template_Loader {
24
25
	/**
26
	 * Prefix for filter names.
27
	 * @var string
28
	 */
29
	protected $filter_prefix = 'gravityview';
30
31
	/**
32
	 * Directory name where custom templates for this plugin should be found in the theme.
33
	 * @var string
34
	 */
35
	protected $theme_template_directory = 'gravityview';
36
37
	/**
38
	 * Reference to the root directory path of this plugin.
39
	 * @var string
40
	 */
41
	protected $plugin_directory = GRAVITYVIEW_DIR;
42
43
	/**
44
	 * Store templates locations that have already been located
45
	 * @var array
46
	 */
47
	protected $located_templates = array();
48
49
	/**
50
	 * The name of the template, like "list", "table", or "datatables"
51
	 * @var string
52
	 */
53
	protected $template_part_slug = '';
54
55
	/**
56
	 * The name of the file part, like "body" or "single"
57
	 * @var string
58
	 */
59
	protected $template_part_name = '';
60
61
	/**
62
	 * @var int Gravity Forms form ID
63
	 */
64
	protected $form_id = NULL;
65
66
	/**
67
	 * @var int View ID
68
	 * @todo: this needs to be public until extensions support 1.7+
69
	 */
70
	public $view_id = NULL;
71
72
	/**
73
	 * @var array Fields for the form
74
	 */
75
	protected $fields = array();
76
77
	/**
78
	 * @var string Current screen. Defaults to "directory" or "single"
79
	 */
80
	protected $context = 'directory';
81
82
	/**
83
	 * @var int|null If in embedded post or page, the ID of it
84
	 */
85
	protected $post_id = NULL;
86
87
	/**
88
	 * @var array Gravity Forms form array at ID $form_id
89
	 */
90
	protected $form = NULL;
91
92
	/**
93
	 * @var array Configuration for the View
94
	 */
95
	protected $atts = array();
96
97
	/**
98
	 * @var array Entries for the current result. Single item in array for single entry View
99
	 */
100
	protected $entries = array();
101
102
	/**
103
	 * @var int Total entries count for the current result.
104
	 */
105
	protected $total_entries = 0;
106
107
	/**
108
	 * @var string The label to display back links
109
	 */
110
	protected $back_link_label = '';
111
112
	/**
113
	 * @var array Array with `offset` and `page_size` keys
114
	 */
115
	protected $paging = array();
116
117
	/**
118
	 * @var array Array with `sort_field` and `sort_direction` keys
119
	 */
120
	protected $sorting = array();
121
122
	/**
123
	 * @var bool Whether to hide the results until a search is performed
124
	 * @since 1.5.4
125
	 */
126
	protected $hide_until_searched = false;
127
128
	/**
129
	 * Current entry in the loop
130
	 * @var array
131
	 */
132
	protected $_current_entry = array();
133
134
	/**
135
	 * @var array
136
	 */
137
	protected $_current_field = array();
138
139
	/**
140
	 * @var GravityView_View
141
	 */
142
	static $instance = NULL;
143
144
	/**
145
	 * Construct the view object
146
	 * @param  array       $atts Associative array to set the data of
147
	 */
148
	function __construct( $atts = array() ) {
149
150
		$atts = wp_parse_args( $atts, array(
151
			'form_id' => NULL,
152
			'view_id' => NULL,
153
			'fields'  => NULL,
154
			'context' => NULL,
155
			'post_id' => NULL,
156
			'form'    => NULL,
157
			'atts'	  => NULL,
158
		) );
159
160
		foreach ($atts as $key => $value) {
161
			if( is_null( $value ) ) {
162
				continue;
163
			}
164
			$this->{$key} = $value;
165
		}
166
167
168
		// Add granular overrides
169
		add_filter( $this->filter_prefix . '_get_template_part', array( $this, 'add_id_specific_templates' ), 10, 3 );
170
171
172
		// widget logic
173
		add_action( 'gravityview_before', array( $this, 'render_widget_hooks' ) );
174
		add_action( 'gravityview_after', array( $this, 'render_widget_hooks' ) );
175
176
		/**
177
		 * Clear the current entry after the loop is done
178
		 * @since 1.7.3
179
		 */
180
		add_action( 'gravityview_footer', array( $this, 'clearCurrentEntry' ), 500 );
181
182
		self::$instance = &$this;
183
	}
184
185
	/**
186
	 * @param null $passed_post
187
	 *
188
	 * @return GravityView_View
189
	 */
190
	static function getInstance( $passed_post = NULL ) {
191
192
		if( empty( self::$instance ) ) {
193
			self::$instance = new self( $passed_post );
194
		}
195
196
		return self::$instance;
197
	}
198
199
	/**
200
	 * @param string|null $key The key to a specific attribute of the current field
201
	 * @return array|mixed|null If $key is set and attribute exists at $key, return that. If not set, return NULL. Otherwise, return current field array
202
	 */
203
	public function getCurrentField( $key = NULL ) {
204
205
		if( !empty( $key ) ) {
206
			if( isset( $this->_current_field[ $key ] ) ) {
207
				return $this->_current_field[ $key ];
208
			}
209
			return NULL;
210
		}
211
212
		return $this->_current_field;
213
	}
214
215
	public function setCurrentFieldSetting( $key, $value ) {
216
217
		if( !empty( $this->_current_field ) ) {
218
			$this->_current_field['field_settings'][ $key ] = $value;
219
		}
220
221
	}
222
223
	public function getCurrentFieldSetting( $key ) {
224
		$settings = $this->getCurrentField('field_settings');
225
226
		if( $settings && !empty( $settings[ $key ] ) ) {
227
			return $settings[ $key ];
228
		}
229
230
		return NULL;
231
	}
232
233
	/**
234
	 * @param array $passed_field
235
	 */
236
	public function setCurrentField( $passed_field ) {
237
238
		$existing_field = $this->getCurrentField();
239
240
		$set_field = wp_parse_args( $passed_field, $existing_field );
241
242
		$this->_current_field = $set_field;
243
244
		/**
245
		 * Backward compatibility
246
		 * @deprecated 1.6.2
247
		 */
248
		$this->field_data = $set_field;
249
	}
250
251
	/**
252
	 * @param string|null $key The key to a specific field in the fields array
253
	 * @return array|mixed|null If $key is set and field exists at $key, return that. If not set, return NULL. Otherwise, return array of fields.
254
	 */
255
	public function getAtts( $key = NULL ) {
256
257
		if( !empty( $key ) ) {
258
			if( isset( $this->atts[ $key ] ) ) {
259
				return $this->atts[ $key ];
260
			}
261
			return NULL;
262
		}
263
264
		return $this->atts;
265
	}
266
267
	/**
268
	 * @param array $atts
269
	 */
270
	public function setAtts( $atts ) {
271
		$this->atts = $atts;
272
	}
273
274
	/**
275
	 * @return array
276
	 */
277
	public function getForm() {
278
		return $this->form;
279
	}
280
281
	/**
282
	 * @param array $form
283
	 */
284
	public function setForm( $form ) {
285
		$this->form = $form;
286
	}
287
288
	/**
289
	 * @return int|null
290
	 */
291
	public function getPostId() {
292
		return $this->post_id;
293
	}
294
295
	/**
296
	 * @param int|null $post_id
297
	 */
298
	public function setPostId( $post_id ) {
299
		$this->post_id = $post_id;
300
	}
301
302
	/**
303
	 * @return string
304
	 */
305
	public function getContext() {
306
		return $this->context;
307
	}
308
309
	/**
310
	 * @param string $context
311
	 */
312
	public function setContext( $context ) {
313
		$this->context = $context;
314
	}
315
316
	/**
317
	 * @param string|null $key The key to a specific field in the fields array
318
	 * @return array|mixed|null If $key is set and field exists at $key, return that. If not set, return NULL. Otherwise, return array of fields.
319
	 */
320
	public function getFields( $key = null ) {
321
322
		$fields = empty( $this->fields ) ? NULL : $this->fields;
323
324
		if( $fields && !empty( $key ) ) {
325
			$fields = isset( $fields[ $key ] ) ? $fields[ $key ] : NULL;
326
		}
327
328
		return $fields;
329
	}
330
331
	/**
332
     * Get the fields for a specific context
333
     *
334
     * @since 1.19.2
335
     *
336
	 * @param string $context [Optional] "directory", "single", or "edit"
337
	 *
338
	 * @return array Array of GravityView field layout configurations
339
	 */
340
	public function getContextFields( $context = '' ) {
0 ignored issues
show
The function name getContextFields is in camel caps, but expected get_context_fields instead as per the coding standard.
Loading history...
341
342
	    if( '' === $context ) {
343
	        $context = $this->getContext();
344
        }
345
346
		$fields = $this->getFields();
347
348
        foreach ( (array) $fields as $key => $context_fields ) {
349
350
            // Formatted as `{context}_{template id}-{zone name}`, so we want just the $context to match against
351
            $matches = explode( '_', $key );
352
353
            if( isset( $matches[0] ) && $matches[0] === $context ) {
354
                return $context_fields;
355
            }
356
        }
357
358
		return array();
359
    }
360
361
	/**
362
	 * @param array $fields
363
	 */
364
	public function setFields( $fields ) {
365
		$this->fields = $fields;
366
	}
367
368
	/**
369
	 * @param string $key The key to a specific field in the fields array
370
	 * @return array|mixed|null If $key is set and field exists at $key, return that. If not set, return NULL. Otherwise, return array of fields.
371
	 */
372
	public function getField( $key ) {
373
374
		if( !empty( $key ) ) {
375
			if( isset( $this->fields[ $key ] ) ) {
376
				return $this->fields[ $key ];
377
			}
378
		}
379
380
		return NULL;
0 ignored issues
show
TRUE, FALSE and NULL must be lowercase; expected null, but found NULL.
Loading history...
381
	}
382
383
	/**
384
	 * @param string $key The key to a specific field in the fields array
385
	 * @param mixed $value The value to set for the field
386
	 */
387
	public function setField( $key, $value ) {
388
		$this->fields[ $key ] = $value;
389
	}
390
391
	/**
392
	 * @return int
393
	 */
394
	public function getViewId() {
395
		return absint( $this->view_id );
396
	}
397
398
	/**
399
	 * @param int $view_id
400
	 */
401
	public function setViewId( $view_id ) {
402
		$this->view_id = intval( $view_id );
403
	}
404
405
	/**
406
	 * @return int
407
	 */
408
	public function getFormId() {
409
		return $this->form_id;
410
	}
411
412
	/**
413
	 * @param int $form_id
414
	 */
415
	public function setFormId( $form_id ) {
416
		$this->form_id = $form_id;
417
	}
418
419
	/**
420
	 * @return array
421
	 */
422
	public function getEntries() {
423
		return $this->entries;
424
	}
425
426
	/**
427
	 * @param array $entries
428
	 */
429
	public function setEntries( $entries ) {
430
		$this->entries = $entries;
431
	}
432
433
	/**
434
	 * @return int
435
	 */
436
	public function getTotalEntries() {
437
		return (int)$this->total_entries;
438
	}
439
440
	/**
441
	 * @param int $total_entries
442
	 */
443
	public function setTotalEntries( $total_entries ) {
444
		$this->total_entries = intval( $total_entries );
445
	}
446
447
	/**
448
	 * @return array
449
	 */
450
	public function getPaging() {
451
452
	    $default_params = array(
453
            'offset' => 0,
454
            'page_size' => 20,
455
        );
456
457
		return wp_parse_args( $this->paging, $default_params );
458
	}
459
460
	/**
461
	 * @param array $paging
462
	 */
463
	public function setPaging( $paging ) {
464
		$this->paging = $paging;
465
	}
466
467
	/**
468
	 * Get an array with pagination information
469
	 *
470
	 * @since 1.13
471
	 * 
472
	 * @return array {
473
	 *  @type int $first The starting entry number (counter, not ID)
474
	 *  @type int $last The last displayed entry number (counter, not ID)
475
	 *  @type int $total The total number of entries
476
	 * }
477
	 */
478
	public function getPaginationCounts() {
479
480
		$paging = $this->getPaging();
481
		$offset = $paging['offset'];
482
		$page_size = $paging['page_size'];
483
		$total = $this->getTotalEntries();
484
485
		if ( empty( $total ) ) {
486
			do_action( 'gravityview_log_debug', __METHOD__ . ': No entries. Returning empty array.' );
487
488
			return array();
489
		}
490
491
		$first = empty( $offset ) ? 1 : $offset + 1;
492
493
		// If the page size + starting entry is larger than total, the total is the max.
494
		$last = ( $offset + $page_size > $total ) ? $total : $offset + $page_size;
495
496
		/**
497
		 * @filter `gravityview_pagination_counts` Modify the displayed pagination numbers
498
		 * @since 1.13
499
		 * @param array $counts Array with $first, $last, $total numbers in that order
500
		 */
501
		list( $first, $last, $total ) = apply_filters( 'gravityview_pagination_counts', array( $first, $last, $total ) );
502
503
		return array( 'first' => (int) $first, 'last' => (int) $last, 'total' => (int) $total );
504
	}
505
506
	/**
507
	 * @return array
508
	 */
509
	public function getSorting() {
510
511
		$defaults_params = array(
512
            'sort_field' => 'date_created',
513
            'sort_direction' => 'ASC',
514
            'is_numeric' => false,
515
        );
516
517
		return wp_parse_args( $this->sorting, $defaults_params );
518
	}
519
520
	/**
521
	 * @param array $sorting
522
	 */
523
	public function setSorting( $sorting ) {
524
		$this->sorting = $sorting;
525
	}
526
527
	/**
528
	 * @return string
529
	 */
530
	public function getBackLinkLabel() {
531
532
		$back_link_label = GravityView_API::replace_variables( $this->back_link_label, $this->getForm(), $this->getCurrentEntry() );
533
534
		$back_link_label = do_shortcode( $back_link_label );
535
536
		return $back_link_label;
537
	}
538
539
	/**
540
	 * @param string $back_link_label
541
	 */
542
	public function setBackLinkLabel( $back_link_label ) {
543
		$this->back_link_label = $back_link_label;
544
	}
545
546
	/**
547
	 * @return boolean
548
	 */
549
	public function isHideUntilSearched() {
550
		return $this->hide_until_searched;
551
	}
552
553
	/**
554
	 * @param boolean $hide_until_searched
555
	 */
556
	public function setHideUntilSearched( $hide_until_searched ) {
557
		$this->hide_until_searched = $hide_until_searched;
558
	}
559
560
	/**
561
	 * @return string
562
	 */
563
	public function getTemplatePartSlug() {
564
		return $this->template_part_slug;
565
	}
566
567
	/**
568
	 * @param string $template_part_slug
569
	 */
570
	public function setTemplatePartSlug( $template_part_slug ) {
571
		$this->template_part_slug = $template_part_slug;
572
	}
573
574
	/**
575
	 * @return string
576
	 */
577
	public function getTemplatePartName() {
578
		return $this->template_part_name;
579
	}
580
581
	/**
582
	 * @param string $template_part_name
583
	 */
584
	public function setTemplatePartName( $template_part_name ) {
585
		$this->template_part_name = $template_part_name;
586
	}
587
588
	/**
589
	 * Return the current entry. If in the loop, the current entry. If single entry, the currently viewed entry.
590
	 * @return array
591
	 */
592
	public function getCurrentEntry() {
593
594
		if( in_array( $this->getContext(), array( 'edit', 'single') ) ) {
595
			$entries = $this->getEntries();
596
			$entry = $entries[0];
597
		} else {
598
			$entry = $this->_current_entry;
599
		}
600
601
		/** @since 1.16 Fixes DataTables empty entry issue */
602
		if ( empty( $entry ) && ! empty( $this->_current_field['entry'] ) ) {
603
			$entry = $this->_current_field['entry'];
604
		}
605
606
		return $entry;
607
	}
608
609
	/**
610
	 * @param array $current_entry
611
	 * @return void
612
	 */
613
	public function setCurrentEntry( $current_entry ) {
614
		$this->_current_entry = $current_entry;
615
	}
616
617
	/**
618
	 * Clear the current entry after all entries in the loop have been displayed.
619
	 *
620
	 * @since 1.7.3
621
	 * @return void
622
	 */
623
	public function clearCurrentEntry() {
624
		$this->_current_entry = NULL;
625
	}
626
627
	/**
628
	 * Render an output zone, as configured in the Admin
629
	 *
630
	 * @since 1.16.4 Added $echo parameter
631
	 *
632
	 * @param string $zone The zone name, like 'footer-left'
633
	 * @param array $atts
634
	 * @param bool $echo Whether to print the output
635
	 *
636
	 * @return string|null
637
	 */
638
	public function renderZone( $zone = '', $atts = array(), $echo = true ) {
639
640
		if( empty( $zone ) ) {
641
			do_action('gravityview_log_error', 'GravityView_View[renderZone] No zone defined.');
642
			return NULL;
643
		}
644
645
		$defaults = array(
646
			'slug' => $this->getTemplatePartSlug(),
647
			'context' => $this->getContext(),
648
			'entry' => $this->getCurrentEntry(),
649
			'form' => $this->getForm(),
650
			'hide_empty' => $this->getAtts('hide_empty'),
651
		);
652
653
		$final_atts = wp_parse_args( $atts, $defaults );
654
655
		$output = '';
656
657
		$final_atts['zone_id'] = "{$final_atts['context']}_{$final_atts['slug']}-{$zone}";
658
659
		$fields = $this->getField( $final_atts['zone_id'] );
660
661
		// Backward compatibility
662
		if( 'table' === $this->getTemplatePartSlug() ) {
663
			/**
664
			 * @filter `gravityview_table_cells` Modify the fields displayed in a table
665
			 * @param array $fields
666
			 * @param GravityView_View $this
667
			 */
668
			$fields = apply_filters("gravityview_table_cells", $fields, $this );
669
		}
670
671
		if( empty( $fields ) ) {
672
673
			do_action('gravityview_log_error', 'GravityView_View[renderZone] Empty View configuration for this context.', $fields );
0 ignored issues
show
Expected 1 spaces after opening bracket; 0 found
Loading history...
674
675
			return NULL;
0 ignored issues
show
TRUE, FALSE and NULL must be lowercase; expected null, but found NULL.
Loading history...
676
		}
677
678
		$field_output = '';
679
		foreach ( $fields as $field ) {
680
			$final_atts['field'] = $field;
681
682
			$field_output .= gravityview_field_output( $final_atts );
683
		}
684
685
		/**
686
		 * If a zone has no field output, choose whether to show wrapper
687
		 * False by default to keep backward compatibility
688
		 * @since 1.7.6
689
		 * @param boolean $hide_empty_zone Default: false
690
		 */
691
		if( empty( $field_output ) && apply_filters( 'gravityview/render/hide-empty-zone', false ) ) {
692
			return NULL;
0 ignored issues
show
TRUE, FALSE and NULL must be lowercase; expected null, but found NULL.
Loading history...
693
		}
694
695
		if( !empty( $final_atts['wrapper_class'] ) ) {
696
			$output .= '<div class="'.gravityview_sanitize_html_class( $final_atts['wrapper_class'] ).'">';
697
		}
698
699
		$output .= $field_output;
700
701
		if( !empty( $final_atts['wrapper_class'] ) ) {
702
			$output .= '</div>';
703
		}
704
705
		if( $echo ) {
706
			echo $output;
707
		}
708
709
		return $output;
710
	}
711
712
	/**
713
	 * In order to improve lookup times, we store located templates in a local array.
714
	 *
715
	 * This improves performance by up to 1/2 second on a 250 entry View with 7 columns showing
716
	 *
717
	 * @inheritdoc
718
	 * @see Gamajo_Template_Loader::locate_template()
719
	 * @return null|string NULL: Template not found; String: path to template
720
	 */
721
	function locate_template( $template_names, $load = false, $require_once = true ) {
722
723
		if( is_string( $template_names ) && isset( $this->located_templates[ $template_names ] ) ) {
724
725
			$located = $this->located_templates[ $template_names ];
726
727
		} else {
728
729
			// Set $load to always false so we handle it here.
730
			$located = parent::locate_template( $template_names, false, $require_once );
731
732
			if( is_string( $template_names ) ) {
733
				$this->located_templates[ $template_names ] = $located;
734
			}
735
		}
736
737
		if ( $load && $located ) {
738
			load_template( $located, $require_once );
739
		}
740
741
		return $located;
742
	}
743
744
	/**
745
	 * Magic Method: Instead of throwing an error when a variable isn't set, return null.
746
	 * @param  string      $name Key for the data retrieval.
747
	 * @return mixed|null    The stored data.
748
	 */
749
	public function __get( $name ) {
750
		if( isset( $this->{$name} ) ) {
751
			return $this->{$name};
752
		} else {
753
			return NULL;
0 ignored issues
show
TRUE, FALSE and NULL must be lowercase; expected null, but found NULL.
Loading history...
754
		}
755
	}
756
757
	/**
758
	 * Enable overrides of GravityView templates on a granular basis
759
	 *
760
	 * The loading order is:
761
	 *
762
	 * - view-[View ID]-table-footer.php
763
	 * - form-[Form ID]-table-footer.php
764
	 * - page-[ID of post or page where view is embedded]-table-footer.php
765
	 * - table-footer.php
766
	 *
767
	 * @see  Gamajo_Template_Loader::get_template_file_names() Where the filter is
768
	 * @param array $templates Existing list of templates.
769
	 * @param string $slug      Name of the template base, example: `table`, `list`, `datatables`, `map`
770
	 * @param string $name      Name of the template part, example: `body`, `footer`, `head`, `single`
771
	 *
772
	 * @return array $templates Modified template array, merged with existing $templates values
773
	 */
774
	function add_id_specific_templates( $templates, $slug, $name ) {
775
776
		$additional = array();
777
778
		// form-19-table-body.php
779
		$additional[] = sprintf( 'form-%d-%s-%s.php', $this->getFormId(), $slug, $name );
780
781
		// view-3-table-body.php
782
		$additional[] = sprintf( 'view-%d-%s-%s.php', $this->getViewId(), $slug, $name );
783
784
		if( $this->getPostId() ) {
785
786
			// page-19-table-body.php
787
			$additional[] = sprintf( 'page-%d-%s-%s.php', $this->getPostId(), $slug, $name );
788
		}
789
790
		// Combine with existing table-body.php and table.php
791
		$templates = array_merge( $additional, $templates );
792
793
		do_action( 'gravityview_log_debug', '[add_id_specific_templates] List of Template Files', $templates );
794
795
		return $templates;
796
	}
797
798
	// Load the template
799
	public function render( $slug, $name, $require_once = true ) {
800
801
		$this->setTemplatePartSlug( $slug );
802
803
		$this->setTemplatePartName( $name );
804
		
805
		$template_file = $this->get_template_part( $slug, $name, false );
806
807
		do_action( 'gravityview_log_debug', '[render] Rendering Template File', $template_file );
808
809
		if( !empty( $template_file) ) {
810
811
			if ( $require_once ) {
812
				require_once( $template_file );
813
			} else {
814
				require( $template_file );
815
			}
816
817
		}
818
	}
819
820
	/**
821
	 *
822
	 * @param $view_id
823
	 */
824
	public function render_widget_hooks( $view_id ) {
825
826
		if( empty( $view_id ) || 'single' == gravityview_get_context() ) {
827
			do_action( 'gravityview_log_debug', __METHOD__ . ' - Not rendering widgets; single entry' );
828
			return;
829
		}
830
831
		$view_data = gravityview_get_current_view_data( $view_id );
832
833
		// get View widget configuration
834
		$widgets = (array)$view_data['widgets'];
835
836
		switch( current_filter() ) {
837
			default:
838
			case 'gravityview_before':
839
				$zone = 'header';
840
				break;
841
			case 'gravityview_after':
842
				$zone = 'footer';
843
				break;
844
		}
845
846
		/**
847
		 * Filter widgets not in the current zone
848
		 * @since 1.16
849
		 */
850
		foreach( $widgets as $key => $widget ) {
851
			// The widget isn't in the current zone
852
			if( false === strpos( $key, $zone ) ) {
853
				unset( $widgets[ $key ] );
854
			}
855
		}
856
857
		/**
858
		 * Prevent output if no widgets to show.
859
		 * @since 1.16
860
		 */
861
		if ( empty( $widgets ) ) {
862
			do_action( 'gravityview_log_debug', sprintf( 'No widgets for View #%s', $view_id ) );
863
			return;
864
		}
865
866
		// Prevent being called twice
867
		if( did_action( $zone.'_'.$view_id.'_widgets' ) ) {
868
			do_action( 'gravityview_log_debug', sprintf( '%s - Not rendering %s; already rendered', __METHOD__ , $zone.'_'.$view_id.'_widgets' ) );
869
			return;
870
		}
871
872
		$rows = GravityView_Plugin::get_default_widget_areas();
873
874
		// TODO: Move to sep. method, use an action instead
875
		wp_enqueue_style( 'gravityview_default_style' );
876
877
		$default_css_class = 'gv-grid gv-widgets-' . $zone;
878
879
		if( 0 === GravityView_View::getInstance()->getTotalEntries() ) {
880
			$default_css_class .= ' gv-widgets-no-results';
881
		}
882
883
		/**
884
		 * @filter `gravityview/widgets/wrapper_css_class` The CSS class applied to the widget container `<div>`.
885
		 * @since 1.16.2
886
		 * @param string $css_class Default: `gv-grid gv-widgets-{zone}` where `{zone}` is replaced by the current `$zone` value. If the View has no results, adds ` gv-widgets-no-results`
887
		 * @param string $zone Current widget zone, either `header` or `footer`
888
		 * @param array $widgets Array of widget configurations for the current zone, as set by `gravityview_get_current_view_data()['widgets']`
889
		 */
890
		$css_class = apply_filters('gravityview/widgets/wrapper_css_class', $default_css_class, $zone, $widgets );
891
892
		$css_class = gravityview_sanitize_html_class( $css_class );
893
894
		// TODO Convert to partials
895
		?>
896
		<div class="<?php echo $css_class; ?>">
897
			<?php
898
			foreach( $rows as $row ) {
899
				foreach( $row as $col => $areas ) {
900
					$column = ($col == '2-2') ? '1-2 gv-right' : $col.' gv-left';
901
				?>
902
					<div class="gv-grid-col-<?php echo esc_attr( $column ); ?>">
903
						<?php
904
						if( !empty( $areas ) ) {
905
							foreach( $areas as $area ) {
906
								if( !empty( $widgets[ $zone .'_'. $area['areaid'] ] ) ) {
907
									foreach( $widgets[ $zone .'_'. $area['areaid'] ] as $widget ) {
908
										do_action( "gravityview_render_widget_{$widget['id']}", $widget );
909
									}
910
								}
911
							}
912
						} ?>
913
					</div>
914
				<?php } // $row ?>
915
			<?php } // $rows ?>
916
		</div>
917
918
		<?php
919
920
		/**
921
		 * Prevent widgets from being called twice.
922
		 * Checking for loop_start prevents themes and plugins that pre-process shortcodes from triggering the action before displaying. Like, ahem, the Divi theme and WordPress SEO plugin
923
		 */
924
		if( did_action( 'wp_head' ) ) {
925
			do_action( $zone.'_'.$view_id.'_widgets' );
926
		}
927
	}
928
929
}
930
931