Completed
Push — master ( 2cfa6f...8927a4 )
by Zack
10:00 queued 06:05
created

includes/class-frontend-views.php (4 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 Frontend functions
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
15
class GravityView_frontend {
16
17
	/**
18
	 * Regex strings that are used to determine whether the current request is a GravityView search or not.
19
	 * @see GravityView_frontend::is_searching()
20
	 * @since 1.7.4.1
21
	 * @var array
22
	 */
23
	private static $search_parameters = array( 'gv_search', 'gv_start', 'gv_end', 'gv_id', 'gv_by', 'filter_*' );
24
25
	/**
26
	 * Is the currently viewed post a `gravityview` post type?
27
	 * @var boolean
28
	 */
29
	var $is_gravityview_post_type = false;
30
31
	/**
32
	 * Does the current post have a `[gravityview]` shortcode?
33
	 * @var boolean
34
	 */
35
	var $post_has_shortcode = false;
36
37
	/**
38
	 * The Post ID of the currently viewed post. Not necessarily GV
39
	 * @var int
40
	 */
41
	var $post_id = null;
42
43
	/**
44
	 * Are we currently viewing a single entry?
45
	 * If so, the int value of the entry ID. Otherwise, false.
46
	 * @var int|boolean
47
	 */
48
	var $single_entry = false;
49
50
	/**
51
	 * If we are viewing a single entry, the entry data
52
	 * @var array|false
53
	 */
54
	var $entry = false;
55
56
	/**
57
	 * When displaying the single entry we should always know to which View it belongs (the context is everything!)
58
	 * @var null
59
	 */
60
	var $context_view_id = null;
61
62
	/**
63
	 * The View is showing search results
64
	 * @since 1.5.4
65
	 * @var boolean
66
	 */
67
	var $is_search = false;
68
69
	/**
70
	 * The view data parsed from the $post
71
	 *
72
	 * @see  GravityView_View_Data::__construct()
73
	 * @var GravityView_View_Data
74
	 */
75
	var $gv_output_data = null;
76
77
	/**
78
	 * @var GravityView_frontend
79
	 */
80
	static $instance;
81
82
	/**
83
	 * Class constructor, enforce Singleton pattern
84
	 */
85
	private function __construct() {}
86
87
	private function initialize() {
88
		add_action( 'wp', array( $this, 'parse_content'), 11 );
89
		add_filter( 'parse_query', array( $this, 'parse_query_fix_frontpage' ), 10 );
90
		add_action( 'template_redirect', array( $this, 'set_entry_data'), 1 );
91
92
		// Enqueue scripts and styles after GravityView_Template::register_styles()
93
		add_action( 'wp_enqueue_scripts', array( $this, 'add_scripts_and_styles' ), 20 );
94
95
		// Enqueue and print styles in the footer. Added 1 priorty so stuff gets printed at 10 priority.
96
		add_action( 'wp_print_footer_scripts', array( $this, 'add_scripts_and_styles' ), 1 );
97
98
		add_filter( 'the_title', array( $this, 'single_entry_title' ), 1, 2 );
99
		add_filter( 'the_content', array( $this, 'insert_view_in_content' ) );
100
		add_filter( 'comments_open', array( $this, 'comments_open' ), 10, 2 );
101
	}
102
103
	/**
104
	 * Get the one true instantiated self
105
	 * @return GravityView_frontend
106
	 */
107
	public static function getInstance() {
108
109
		if ( empty( self::$instance ) ) {
110
			self::$instance = new self;
111
			self::$instance->initialize();
112
		}
113
114
		return self::$instance;
115
	}
116
117
	/**
118
	 * @return GravityView_View_Data
119
	 */
120
	public function getGvOutputData() {
121
		return $this->gv_output_data;
122
	}
123
124
	/**
125
	 * @param GravityView_View_Data $gv_output_data
126
	 */
127
	public function setGvOutputData( $gv_output_data ) {
128
		$this->gv_output_data = $gv_output_data;
129
	}
130
131
	/**
132
	 * @return boolean
133
	 */
134
	public function isSearch() {
135
		return $this->is_search;
136
	}
137
138
	/**
139
	 * @param boolean $is_search
140
	 */
141
	public function setIsSearch( $is_search ) {
142
		$this->is_search = $is_search;
143
	}
144
145
	/**
146
	 * @return bool|int
147
	 */
148
	public function getSingleEntry() {
149
		return $this->single_entry;
150
	}
151
152
	/**
153
	 * Sets the single entry ID and also the entry
154
	 * @param bool|int|string $single_entry
155
	 */
156
	public function setSingleEntry( $single_entry ) {
157
158
		$this->single_entry = $single_entry;
159
160
	}
161
162
	/**
163
	 * @return array
164
	 */
165
	public function getEntry() {
166
		return $this->entry;
167
	}
168
169
	/**
170
	 * Set the current entry
171
	 * @param array|int $entry Entry array or entry slug or ID
172
	 */
173
	public function setEntry( $entry ) {
174
175
		if ( ! is_array( $entry ) ) {
176
			$entry = GVCommon::get_entry( $entry );
177
		}
178
179
		$this->entry = $entry;
180
	}
181
182
	/**
183
	 * @return int
184
	 */
185
	public function getPostId() {
186
		return $this->post_id;
187
	}
188
189
	/**
190
	 * @param int $post_id
191
	 */
192
	public function setPostId( $post_id ) {
193
		$this->post_id = $post_id;
194
	}
195
196
	/**
197
	 * @return boolean
198
	 */
199
	public function isPostHasShortcode() {
200
		return $this->post_has_shortcode;
201
	}
202
203
	/**
204
	 * @param boolean $post_has_shortcode
205
	 */
206
	public function setPostHasShortcode( $post_has_shortcode ) {
207
		$this->post_has_shortcode = $post_has_shortcode;
208
	}
209
210
	/**
211
	 * @return boolean
212
	 */
213
	public function isGravityviewPostType() {
214
		return $this->is_gravityview_post_type;
215
	}
216
217
	/**
218
	 * @param boolean $is_gravityview_post_type
219
	 */
220
	public function setIsGravityviewPostType( $is_gravityview_post_type ) {
221
		$this->is_gravityview_post_type = $is_gravityview_post_type;
222
	}
223
224
	/**
225
	 * Set the context view ID used when page contains multiple embedded views or displaying the single entry view
226
	 *
227
	 *
228
	 *
229
	 * @param null $view_id
230
	 */
231
	public function set_context_view_id( $view_id = null ) {
232
233
		if ( ! empty( $view_id ) ) {
234
235
			$this->context_view_id = $view_id;
236
237
		} elseif ( isset( $_GET['gvid'] ) && $this->getGvOutputData()->has_multiple_views() ) {
238
			/**
239
			 * used on a has_multiple_views context
240
			 * @see GravityView_API::entry_link
241
			 * @see GravityView_View_Data::getInstance()->has_multiple_views()
242
			 */
243
			$this->context_view_id = $_GET['gvid'];
244
245
		} elseif ( ! $this->getGvOutputData()->has_multiple_views() )  {
246
			$array_keys = array_keys( $this->getGvOutputData()->get_views() );
247
			$this->context_view_id = array_pop( $array_keys );
248
			unset( $array_keys );
249
		}
250
251
	}
252
253
	/**
254
	 * Returns the the view_id context when page contains multiple embedded views or displaying single entry view
255
	 *
256
	 * @since 1.5.4
257
	 *
258
	 * @return string
259
	 */
260
	public function get_context_view_id() {
261
		return $this->context_view_id;
262
	}
263
264
	/**
265
	 * Allow GravityView entry endpoints on the front page of a site
266
	 *
267
	 * @link  https://core.trac.wordpress.org/ticket/23867 Fixes this core issue
268
	 * @link https://wordpress.org/plugins/cpt-on-front-page/ Code is based on this
269
	 *
270
	 * @since 1.17.3
271
	 *
272
	 * @param WP_Query &$query (passed by reference)
273
	 *
274
	 * @return void
275
	 */
276
	public function parse_query_fix_frontpage( &$query ) {
277
		global $wp_rewrite;
278
279
		$is_front_page = ( $query->is_home || $query->is_page );
280
		$show_on_front = ( 'page' === get_option('show_on_front') );
281
		$front_page_id = get_option('page_on_front');
282
283
		if (  $is_front_page && $show_on_front && $front_page_id ) {
284
285
			// Force to be an array, potentially a query string ( entry=16 )
286
			$_query = wp_parse_args( $query->query );
287
288
			// pagename can be set and empty depending on matched rewrite rules. Ignore an empty pagename.
289
			if ( isset( $_query['pagename'] ) && '' === $_query['pagename'] ) {
290
				unset( $_query['pagename'] );
291
			}
292
293
			// this is where will break from core wordpress
294
			$ignore = array( 'preview', 'page', 'paged', 'cpage' );
295
			$endpoints = rgobj( $wp_rewrite, 'endpoints' );
296
			foreach ( (array) $endpoints as $endpoint ) {
297
				$ignore[] = $endpoint[1];
298
			}
299
			unset( $endpoints );
300
301
			// Modify the query if:
302
			// - We're on the "Page on front" page (which we are), and:
303
			// - The query is empty OR
304
			// - The query includes keys that are associated with registered endpoints. `entry`, for example.
305
			if ( empty( $_query ) || ! array_diff( array_keys( $_query ), $ignore ) ) {
306
307
				$qv =& $query->query_vars;
308
309
				// Prevent redirect when on the single entry endpoint
310
				if( self::is_single_entry() ) {
311
					add_filter( 'redirect_canonical', '__return_false' );
312
				}
313
314
				$query->is_page = true;
315
				$query->is_home = false;
316
				$qv['page_id']  = $front_page_id;
317
318
				// Correct <!--nextpage--> for page_on_front
319
				if ( ! empty( $qv['paged'] ) ) {
320
					$qv['page'] = $qv['paged'];
321
					unset( $qv['paged'] );
322
				}
323
			}
324
325
			// reset the is_singular flag after our updated code above
326
			$query->is_singular = $query->is_single || $query->is_page || $query->is_attachment;
327
		}
328
	}
329
330
	/**
331
	 * Read the $post and process the View data inside
332
	 * @param  array  $wp Passed in the `wp` hook. Not used.
333
	 * @return void
334
	 */
335
	public function parse_content( $wp = array() ) {
336
		global $post;
337
338
		// If in admin and NOT AJAX request, get outta here.
339
		if ( GravityView_Plugin::is_admin() )  {
340
			return;
341
		}
342
343
		// Calculate requested Views
344
		$this->setGvOutputData( GravityView_View_Data::getInstance( $post ) );
345
346
		// !important: we need to run this before getting single entry (to kick the advanced filter)
347
		$this->set_context_view_id();
348
349
		$this->setIsGravityviewPostType( get_post_type( $post ) === 'gravityview' );
350
351
		$post_id = $this->getPostId() ? $this->getPostId() : (isset( $post ) ? $post->ID : null );
352
		$this->setPostId( $post_id );
353
		$post_has_shortcode = ! empty( $post->post_content ) ? gravityview_has_shortcode_r( $post->post_content, 'gravityview' ) : false;
354
		$this->setPostHasShortcode( $this->isGravityviewPostType() ? null : ! empty( $post_has_shortcode ) );
355
356
		// check if the View is showing search results (only for multiple entries View)
357
		$this->setIsSearch( $this->is_searching() );
358
359
		unset( $entry, $post_id, $post_has_shortcode );
360
	}
361
362
	/**
363
	 * Set the entry
364
	 */
365
	function set_entry_data() {
366
		$entry_id = self::is_single_entry();
367
		$this->setSingleEntry( $entry_id );
368
		$this->setEntry( $entry_id );
369
	}
370
371
	/**
372
	 * Checks if the current View is presenting search results
373
	 *
374
	 * @since 1.5.4
375
	 *
376
	 * @return boolean True: Yes, it's a search; False: No, not a search.
377
	 */
378
	function is_searching() {
379
380
		// It's a single entry, not search
381
		if ( $this->getSingleEntry() ) {
382
			return false;
383
		}
384
385
		$search_method = GravityView_Widget_Search::getInstance()->get_search_method();
386
387
		if( 'post' === $search_method ) {
388
			$get = $_POST;
389
		} else {
390
			$get = $_GET;
391
		}
392
393
		// No $_GET parameters
394
		if ( empty( $get ) || ! is_array( $get ) ) {
395
			return false;
396
		}
397
398
		// Remove empty values
399
		$get = array_filter( $get );
400
401
		// If the $_GET parameters are empty, it's no search.
402
		if ( empty( $get ) ) {
403
			return false;
404
		}
405
406
		$search_keys = array_keys( $get );
407
408
		$search_match = implode( '|', self::$search_parameters );
409
410
		foreach ( $search_keys as $search_key ) {
411
412
			// Analyze the search key $_GET parameter and see if it matches known GV args
413
			if ( preg_match( '/(' . $search_match . ')/i', $search_key ) ) {
414
				return true;
415
			}
416
		}
417
418
		return false;
419
	}
420
421
	/**
422
	 * Filter the title for the single entry view
423
	 *
424
	 * @param  string $title   current title
425
	 * @param  int $passed_post_id Post ID
426
	 * @return string          (modified) title
427
	 */
428
	public function single_entry_title( $title, $passed_post_id = null ) {
429
		global $post;
430
431
		// If this is the directory view, return.
432
		if ( ! $this->getSingleEntry() ) {
433
			return $title;
434
		}
435
436
		$entry = $this->getEntry();
437
438
		/**
439
		 * @filter `gravityview/single/title/out_loop` Apply the Single Entry Title filter outside the WordPress loop?
440
		 * @param boolean $in_the_loop Whether to apply the filter to the menu title and the meta tag <title> - outside the loop
441
		 * @param array $entry Current entry
442
		 */
443
		$apply_outside_loop = apply_filters( 'gravityview/single/title/out_loop' , in_the_loop(), $entry );
444
445
		if ( ! $apply_outside_loop ) {
446
			return $title;
447
		}
448
449
		// User reported WooCommerce doesn't pass two args.
450
		if ( empty( $passed_post_id ) )  {
451
			return $title;
452
		}
453
454
		// Don't modify the title for anything other than the current view/post.
455
		// This is true for embedded shortcodes and Views.
456
		if ( is_object( $post ) && (int) $post->ID !== (int) $passed_post_id ) {
457
			return $title;
458
		}
459
460
		$context_view_id = $this->get_context_view_id();
461
462
		if ( $this->getGvOutputData()->has_multiple_views() && ! empty( $context_view_id ) ) {
463
			$view_meta = $this->getGvOutputData()->get_view( $context_view_id );
464
		} else {
465
			foreach ( $this->getGvOutputData()->get_views() as $view_id => $view_data ) {
466
				if ( intval( $view_data['form_id'] ) === intval( $entry['form_id'] ) ) {
467
					$view_meta = $view_data;
468
					break;
469
				}
470
			}
471
		}
472
473
		if ( ! empty( $view_meta['atts']['single_title'] ) ) {
474
475
			$title = $view_meta['atts']['single_title'];
476
477
			// We are allowing HTML in the fields, so no escaping the output
478
			$title = GravityView_API::replace_variables( $title, $view_meta['form'], $entry );
479
480
			$title = do_shortcode( $title );
481
		}
482
483
		return $title;
484
	}
485
486
487
	/**
488
	 * In case View post is called directly, insert the view in the post content
489
	 *
490
	 * @access public
491
	 * @static
492
	 * @param mixed $content
493
	 * @return string Add the View output into View CPT content
494
	 */
495
	public function insert_view_in_content( $content ) {
496
497
		// Plugins may run through the content in the header. WP SEO does this for its OpenGraph functionality.
498
		if ( ! did_action( 'loop_start' ) ) {
499
500
			do_action( 'gravityview_log_debug', '[insert_view_in_content] Not processing yet: loop_start hasn\'t run yet. Current action:', current_filter() );
501
502
			return $content;
503
		}
504
505
		//	We don't want this filter to run infinite loop on any post content fields
506
		remove_filter( 'the_content', array( $this, 'insert_view_in_content' ) );
507
508
		// Otherwise, this is called on the Views page when in Excerpt mode.
509
		if ( is_admin() ) {
510
			return $content;
511
		}
512
513
		if ( $this->isGravityviewPostType() ) {
514
515
			/** @since 1.7.4 */
516
			if ( is_preview() && ! gravityview_get_form_id( $this->post_id ) ) {
517
				$content .= __( 'When using a Start Fresh template, you must save the View before a Preview is available.', 'gravityview' );
518
			} else {
519
				foreach ( $this->getGvOutputData()->get_views() as $view_id => $data ) {
520
					$content .= $this->render_view( array( 'id' => $view_id ) );
521
				}
522
			}
523
		}
524
525
		//	Add the filter back in
526
		add_filter( 'the_content', array( $this, 'insert_view_in_content' ) );
527
528
		return $content;
529
	}
530
531
	/**
532
	 * Disable comments on GravityView post types
533
	 * @param  boolean $open    existing status
534
	 * @param  int $post_id Post ID
535
	 * @return boolean
536
	 */
537
	public function comments_open( $open, $post_id ) {
538
539
		if ( $this->isGravityviewPostType() ) {
540
			$open = false;
541
		}
542
543
		/**
544
		 * @filter `gravityview/comments_open` Whether to set comments to open or closed.
545
		 * @since  1.5.4
546
		 * @param  boolean $open Open or closed status
547
		 * @param  int $post_id Post ID to set comment status for
548
		 */
549
		$open = apply_filters( 'gravityview/comments_open', $open, $post_id );
550
551
		return $open;
552
	}
553
554
555
	/**
556
	 * Core function to render a View based on a set of arguments
557
	 *
558
	 * @access public
559
	 * @static
560
	 * @param array $passed_args {
561
	 *
562
	 *      Settings for rendering the View
563
	 *
564
	 *      @type int $id View id
565
	 *      @type int $page_size Number of entries to show per page
566
	 *      @type string $sort_field Form field id to sort
567
	 *      @type string $sort_direction Sorting direction ('ASC' or 'DESC')
568
	 *      @type string $start_date - Ymd
569
	 *      @type string $end_date - Ymd
570
	 *      @type string $class - assign a html class to the view
571
	 *      @type string $offset (optional) - This is the start point in the current data set (0 index based).
572
	 * }
573
	 *
574
	 * @return string|null HTML output of a View, NULL if View isn't found
575
	 */
576
	public function render_view( $passed_args ) {
577
578
		// validate attributes
579
		if ( empty( $passed_args['id'] ) ) {
580
			do_action( 'gravityview_log_error', '[render_view] Returning; no ID defined.', $passed_args );
581
			return null;
582
		}
583
584
		// Solve problem when loading content via admin-ajax.php
585
		// @hack
586
		if ( ! $this->getGvOutputData() ) {
587
588
			do_action( 'gravityview_log_error', '[render_view] gv_output_data not defined; parsing content.', $passed_args );
589
590
			$this->parse_content();
591
		}
592
593
		// Make 100% sure that we're dealing with a properly called situation
594
		if ( ! is_object( $this->getGvOutputData() ) || ! is_callable( array( $this->getGvOutputData(), 'get_view' ) ) ) {
595
596
			do_action( 'gravityview_log_error', '[render_view] gv_output_data not an object or get_view not callable.', $this->getGvOutputData() );
597
598
			return null;
599
		}
600
601
		$view_id = $passed_args['id'];
602
603
		$view_data = $this->getGvOutputData()->get_view( $view_id, $passed_args );
604
605
		do_action( 'gravityview_log_debug', '[render_view] View Data: ', $view_data );
606
607
		do_action( 'gravityview_log_debug', '[render_view] Init View. Arguments: ', $passed_args );
608
609
		// The passed args were always winning, even if they were NULL.
610
		// This prevents that. Filters NULL, FALSE, and empty strings.
611
		$passed_args = array_filter( $passed_args, 'strlen' );
612
613
		//Override shortcode args over View template settings
614
		$atts = wp_parse_args( $passed_args, $view_data['atts'] );
615
616
		do_action( 'gravityview_log_debug', '[render_view] Arguments after merging with View settings: ', $atts );
617
618
		// It's password protected and you need to log in.
619
		if ( post_password_required( $view_id ) ) {
620
621
			do_action( 'gravityview_log_error', sprintf( '[render_view] Returning: View %d is password protected.', $view_id ) );
622
623
			// If we're in an embed or on an archive page, show the password form
624
			if ( get_the_ID() !== $view_id ) {
625
				return get_the_password_form();
626
			}
627
628
			// Otherwise, just get outta here
629
			return null;
630
		}
631
632
		/**
633
		 * Don't render View if user isn't allowed to see it
634
		 * @since 1.15
635
		 * @since 1.17.2 Added check for if a user has no caps but is logged in (member of multisite, but not any site). Treat as if logged-out.
636
		 */
637
		if( is_user_logged_in() && ! ( empty( wp_get_current_user()->caps ) && empty( wp_get_current_user()->roles ) ) && false === GVCommon::has_cap( 'read_gravityview', $view_id ) ) {
638
639
			do_action( 'gravityview_log_debug', sprintf( '%s Returning: View %d is not visible by current user.', __METHOD__, $view_id ) );
640
641
			return null;
642
		}
643
644
		if( $this->isGravityviewPostType() ) {
645
646
			/**
647
			 * @filter `gravityview_direct_access` Should Views be directly accessible, or only visible using the shortcode?
648
			 * @see https://codex.wordpress.org/Function_Reference/register_post_type#public
649
			 * @see GravityView_Post_Types::init_post_types
650
			 * @since 1.15.2
651
			 * @param[in,out] boolean `true`: allow Views to be accessible directly. `false`: Only allow Views to be embedded via shortcode. Default: `true`
652
			 * @param int $view_id The ID of the View currently being requested. `0` for general setting
653
			 */
654
			$direct_access = apply_filters( 'gravityview_direct_access', true, $view_id );
655
656
			$embed_only = ! empty( $atts['embed_only'] );
657
658
			if( ! $direct_access || ( $embed_only && ! GVCommon::has_cap( 'read_private_gravityviews' ) ) ) {
659
				return __( 'You are not allowed to view this content.', 'gravityview' );
660
			}
661
		}
662
663
		ob_start();
664
665
		/**
666
		 * Set globals for templating
667
		 * @deprecated 1.6.2
668
		 */
669
		global $gravityview_view;
670
671
		$gravityview_view = new GravityView_View( $view_data );
672
673
		$post_id = ! empty( $atts['post_id'] ) ? intval( $atts['post_id'] ) : $this->getPostId();
674
675
		$gravityview_view->setPostId( $post_id );
676
677
		if ( ! $this->getSingleEntry() ) {
678
679
			// user requested Directory View
680
			do_action( 'gravityview_log_debug', '[render_view] Executing Directory View' );
681
682
			//fetch template and slug
683
			$view_slug = apply_filters( 'gravityview_template_slug_'. $view_data['template_id'], 'table', 'directory' );
684
685
			do_action( 'gravityview_log_debug', '[render_view] View template slug: ', $view_slug );
686
687
			/**
688
			 * Disable fetching initial entries for views that don't need it (DataTables)
689
			 */
690
			$get_entries = apply_filters( 'gravityview_get_view_entries_'.$view_slug, true );
691
692
			/**
693
			 * Hide View data until search is performed
694
			 * @since 1.5.4
695
			 */
696
			if ( ! empty( $atts['hide_until_searched'] ) && ! $this->isSearch() ) {
697
				$gravityview_view->setHideUntilSearched( true );
698
				$get_entries = false;
699
			}
700
0 ignored issues
show
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
701
702
			if ( $get_entries ) {
703
704
				if ( ! empty( $atts['sort_columns'] ) ) {
705
					// add filter to enable column sorting
706
					add_filter( 'gravityview/template/field_label', array( $this, 'add_columns_sort_links' ) , 100, 3 );
707
				}
708
709
				$view_entries = self::get_view_entries( $atts, $view_data['form_id'] );
710
711
				do_action( 'gravityview_log_debug', sprintf( '[render_view] Get Entries. Found %s entries total, showing %d entries', $view_entries['count'], sizeof( $view_entries['entries'] ) ) );
712
713
			} else {
714
715
				$view_entries = array( 'count' => null, 'entries' => null, 'paging' => null );
716
717
				do_action( 'gravityview_log_debug', '[render_view] Not fetching entries because `gravityview_get_view_entries_'.$view_slug.'` is false' );
718
			}
719
720
			$gravityview_view->setPaging( $view_entries['paging'] );
721
			$gravityview_view->setContext( 'directory' );
722
			$sections = array( 'header', 'body', 'footer' );
723
724
		} else {
725
726
			// user requested Single Entry View
727
			do_action( 'gravityview_log_debug', '[render_view] Executing Single View' );
728
0 ignored issues
show
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
729
730
			/**
731
			 * @action `gravityview_render_entry_{View ID}` Before rendering a single entry for a specific View ID
732
			 * @since 1.17
733
			 */
734
			do_action( 'gravityview_render_entry_'.$view_data['id'] );
735
736
			$entry = $this->getEntry();
737
738
			// You are not permitted to view this entry.
739
			if ( empty( $entry ) || ! self::is_entry_approved( $entry, $atts ) ) {
740
741
				do_action( 'gravityview_log_debug', '[render_view] Entry does not exist. This may be because of View filters limiting access.' );
742
743
				/**
744
				 * @filter `gravityview/render/entry/not_visible` Modify the message shown to users when the entry doesn't exist or they aren't allowed to view it.
745
				 * @since 1.6
746
				 * @param string $message Default: "You have attempted to view an entry that is not visible or may not exist."
747
				 */
748
				$message = apply_filters( 'gravityview/render/entry/not_visible', __( 'You have attempted to view an entry that is not visible or may not exist.', 'gravityview' ) );
749
750
				/**
751
				 * @since 1.6
752
				 */
753
				echo esc_attr( $message );
754
755
				return null;
756
			}
757
758
			// We're in single view, but the view being processed is not the same view the single entry belongs to.
759
			// important: do not remove this as it prevents fake attempts of displaying entries from other views/forms
760
			if ( $this->getGvOutputData()->has_multiple_views() && $view_id != $this->get_context_view_id() ) {
761
				do_action( 'gravityview_log_debug', '[render_view] In single entry view, but the entry does not belong to this View. Perhaps there are multiple views on the page. View ID: '. $view_id );
762
				return null;
763
			}
764
765
			//fetch template and slug
766
			$view_slug = apply_filters( 'gravityview_template_slug_' . $view_data['template_id'], 'table', 'single' );
767
			do_action( 'gravityview_log_debug', '[render_view] View single template slug: ', $view_slug );
768
769
			//fetch entry detail
770
			$view_entries['count'] = 1;
771
			$view_entries['entries'][] = $entry;
772
			do_action( 'gravityview_log_debug', '[render_view] Get single entry: ', $view_entries['entries'] );
773
774
			$back_link_label = isset( $atts['back_link_label'] ) ? $atts['back_link_label'] : null;
775
776
			// set back link label
777
			$gravityview_view->setBackLinkLabel( $back_link_label );
778
			$gravityview_view->setContext( 'single' );
779
			$sections = array( 'single' );
780
781
		}
782
783
		// add template style
784
		self::add_style( $view_data['template_id'] );
785
786
		// Prepare to render view and set vars
787
		$gravityview_view->setEntries( $view_entries['entries'] );
788
		$gravityview_view->setTotalEntries( $view_entries['count'] );
789
790
		// If Edit
791
		if ( 'edit' === gravityview_get_context() ) {
792
793
			do_action( 'gravityview_log_debug', '[render_view] Edit Entry ' );
794
795
			do_action( 'gravityview_edit_entry', $this->getGvOutputData() );
796
797
			return ob_get_clean();
798
799
		} else {
800
			// finaly we'll render some html
801
			$sections = apply_filters( 'gravityview_render_view_sections', $sections, $view_data['template_id'] );
802
803
			do_action( 'gravityview_log_debug', '[render_view] Sections to render: ', $sections );
804
			foreach ( $sections as $section ) {
805
				do_action( 'gravityview_log_debug', '[render_view] Rendering '. $section . ' section.' );
806
				$gravityview_view->render( $view_slug, $section, false );
807
			}
808
		}
809
810
		//@todo: check why we need the IF statement vs. print the view id always.
811
		if ( $this->isGravityviewPostType() || $this->isPostHasShortcode() ) {
812
			// Print the View ID to enable proper cookie pagination
813
			echo '<input type="hidden" class="gravityview-view-id" value="' . esc_attr( $view_id ) . '">';
814
		}
815
		$output = ob_get_clean();
816
817
		return $output;
818
	}
819
820
	/**
821
	 * Process the start and end dates for a view - overrides values defined in shortcode (if needed)
822
	 *
823
	 * The `start_date` and `end_date` keys need to be in a format processable by GFFormsModel::get_date_range_where(),
824
	 * which uses \DateTime() format.
825
	 *
826
	 * You can set the `start_date` or `end_date` to any value allowed by {@link http://www.php.net//manual/en/function.strtotime.php strtotime()},
827
	 * including strings like "now" or "-1 year" or "-3 days".
828
	 *
829
	 * @see GFFormsModel::get_date_range_where
830
	 *
831
	 * @param  array      $args            View settings
832
	 * @param  array      $search_criteria Search being performed, if any
833
	 * @return array                       Modified `$search_criteria` array
834
	 */
835
	public static function process_search_dates( $args, $search_criteria = array() ) {
836
837
		$return_search_criteria = $search_criteria;
838
839
		foreach ( array( 'start_date', 'end_date' ) as $key ) {
840
0 ignored issues
show
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
841
842
			// Is the start date or end date set in the view or shortcode?
843
			// If so, we want to make sure that the search doesn't go outside the bounds defined.
844
			if ( ! empty( $args[ $key ] ) ) {
845
846
				// Get a timestamp and see if it's a valid date format
847
				$date = strtotime( $args[ $key ] );
848
849
				// The date was invalid
850
				if ( empty( $date ) ) {
851
					do_action( 'gravityview_log_error', __METHOD__ . ' Invalid ' . $key . ' date format: ' . $args[ $key ] );
852
					continue;
853
				}
854
855
				// The format that Gravity Forms expects for start_date and day-specific (not hour/second-specific) end_date
856
				$datetime_format = 'Y-m-d H:i:s';
857
				$search_is_outside_view_bounds = false;
858
859
				if( ! empty( $search_criteria[ $key ] ) ) {
860
861
					$search_date = strtotime( $search_criteria[ $key ] );
862
863
					// The search is for entries before the start date defined by the settings
864
					switch ( $key ) {
865
						case 'end_date':
866
							/**
867
							 * If the end date is formatted as 'Y-m-d', it should be formatted without hours and seconds
868
							 * so that Gravity Forms can convert the day to 23:59:59 the previous day.
869
							 *
870
							 * If it's a relative date ("now" or "-1 day"), then it should use the precise date format
871
							 *
872
							 * @see GFFormsModel::get_date_range_where
873
							 */
874
							$datetime_format               = gravityview_is_valid_datetime( $args[ $key ] ) ? 'Y-m-d' : 'Y-m-d H:i:s';
875
							$search_is_outside_view_bounds = ( $search_date > $date );
876
							break;
877
						case 'start_date':
878
							$search_is_outside_view_bounds = ( $search_date < $date );
879
							break;
880
					}
881
				}
882
883
				// If there is no search being performed, or if there is a search being performed that's outside the bounds
884
				if ( empty( $search_criteria[ $key ] ) || $search_is_outside_view_bounds ) {
885
886
					// Then we override the search and re-set the start date
887
					$return_search_criteria[ $key ] = date_i18n( $datetime_format , $date, true );
888
				}
889
			}
890
		}
891
892
		if( isset( $return_search_criteria['start_date'] ) && isset( $return_search_criteria['end_date'] ) ) {
893
			// The start date is AFTER the end date. This will result in no results, but let's not force the issue.
894
			if ( strtotime( $return_search_criteria['start_date'] ) > strtotime( $return_search_criteria['end_date'] ) ) {
895
				do_action( 'gravityview_log_error', __METHOD__ . ' Invalid search: the start date is after the end date.', $return_search_criteria );
896
			}
897
		}
898
899
		return $return_search_criteria;
900
	}
901
902
903
	/**
904
	 * Process the approved only search criteria according to the View settings
905
	 *
906
	 * @param  array      $args            View settings
907
	 * @param  array      $search_criteria Search being performed, if any
908
	 * @return array                       Modified `$search_criteria` array
909
	 */
910
	public static function process_search_only_approved( $args, $search_criteria ) {
911
912
		if ( ! empty( $args['show_only_approved'] ) ) {
913
914
			$search_criteria['field_filters'][] = array(
915
				'key' => GravityView_Entry_Approval::meta_key,
916
				'value' => GravityView_Entry_Approval_Status::APPROVED
0 ignored issues
show
Each line in an array declaration must end in a comma
Loading history...
917
			);
918
919
			$search_criteria['field_filters']['mode'] = 'all'; // force all the criterias to be met
920
921
			do_action( 'gravityview_log_debug', '[process_search_only_approved] Search Criteria if show only approved: ', $search_criteria );
922
		}
923
924
		return $search_criteria;
925
	}
926
927
928
	/**
929
	 * Check if a certain entry is approved.
930
	 *
931
	 * If we pass the View settings ($args) it will check the 'show_only_approved' setting before
932
	 *   checking the entry approved field, returning true if show_only_approved = false.
933
	 *
934
	 * @since 1.7
935
	 * @since 1.18 Converted check to use GravityView_Entry_Approval_Status::is_approved
936
	 *
937
	 * @uses GravityView_Entry_Approval_Status::is_approved
938
	 *
939
	 * @param array $entry  Entry object
940
	 * @param array $args   View settings (optional)
941
	 *
942
	 * @return bool
943
	 */
944
	public static function is_entry_approved( $entry, $args = array() ) {
945
946
		if ( empty( $entry['id'] ) || ( array_key_exists( 'show_only_approved', $args ) && ! $args['show_only_approved'] ) ) {
947
			// is implicitly approved if entry is null or View settings doesn't require to check for approval
948
			return true;
949
		}
950
951
		$is_approved = gform_get_meta( $entry['id'], GravityView_Entry_Approval::meta_key );
952
953
		return GravityView_Entry_Approval_Status::is_approved( $is_approved );
954
	}
955
956
	/**
957
	 * Parse search criteria for a entries search.
958
	 *
959
	 * array(
960
	 * 	'search_field' => 1, // ID of the field
961
	 *  'search_value' => '', // Value of the field to search
962
	 *  'search_operator' => 'contains', // 'is', 'isnot', '>', '<', 'contains'
963
	 *  'show_only_approved' => 0 or 1 // Boolean
964
	 * )
965
	 *
966
	 * @param  array $args    Array of args
967
	 * @param  int $form_id Gravity Forms form ID
968
	 * @return array          Array of search parameters, formatted in Gravity Forms mode, using `status` key set to "active" by default, `field_filters` array with `key`, `value` and `operator` keys.
969
	 */
970
	public static function get_search_criteria( $args, $form_id ) {
971
972
		/**
973
		 * @filter `gravityview_fe_search_criteria` Modify the search criteria
974
		 * @see GravityView_Widget_Search::filter_entries Adds the default search criteria
975
		 * @param array $search_criteria Empty `field_filters` key
976
		 * @param int $form_id ID of the Gravity Forms form that is being searched
977
		 */
978
		$search_criteria = apply_filters( 'gravityview_fe_search_criteria', array( 'field_filters' => array() ), $form_id );
979
980
		$original_search_criteria = $search_criteria;
981
982
		do_action( 'gravityview_log_debug', '[get_search_criteria] Search Criteria after hook gravityview_fe_search_criteria: ', $search_criteria );
983
984
		// implicity search
985
		if ( ! empty( $args['search_value'] ) ) {
986
987
			// Search operator options. Options: `is` or `contains`
988
			$operator = ! empty( $args['search_operator'] ) && in_array( $args['search_operator'], array( 'is', 'isnot', '>', '<', 'contains' ) ) ? $args['search_operator'] : 'contains';
989
990
			$search_criteria['field_filters'][] = array(
991
				'key' => rgget( 'search_field', $args ), // The field ID to search
992
				'value' => _wp_specialchars( $args['search_value'] ), // The value to search. Encode ampersands but not quotes.
993
				'operator' => $operator,
994
			);
995
		}
996
997
		if( $search_criteria !== $original_search_criteria ) {
998
			do_action( 'gravityview_log_debug', '[get_search_criteria] Search Criteria after implicity search: ', $search_criteria );
999
		}
1000
1001
		// Handle setting date range
1002
		$search_criteria = self::process_search_dates( $args, $search_criteria );
1003
1004
		if( $search_criteria !== $original_search_criteria ) {
1005
			do_action( 'gravityview_log_debug', '[get_search_criteria] Search Criteria after date params: ', $search_criteria );
1006
		}
1007
1008
		// remove not approved entries
1009
		$search_criteria = self::process_search_only_approved( $args, $search_criteria );
1010
1011
		/**
1012
		 * @filter `gravityview_status` Modify entry status requirements to be included in search results.
1013
		 * @param string $status Default: `active`. Accepts all Gravity Forms entry statuses, including `spam` and `trash`
1014
		 */
1015
		$search_criteria['status'] = apply_filters( 'gravityview_status', 'active', $args );
1016
1017
		return $search_criteria;
1018
	}
1019
1020
1021
1022
	/**
1023
	 * Core function to calculate View multi entries (directory) based on a set of arguments ($args):
1024
	 *   $id - View id
1025
	 *   $page_size - Page
1026
	 *   $sort_field - form field id to sort
1027
	 *   $sort_direction - ASC / DESC
1028
	 *   $start_date - Ymd
1029
	 *   $end_date - Ymd
1030
	 *   $class - assign a html class to the view
1031
	 *   $offset (optional) - This is the start point in the current data set (0 index based).
1032
	 *
1033
	 *
1034
	 *
1035
	 * @uses  gravityview_get_entries()
1036
	 * @access public
1037
	 * @param array $args\n
1038
	 *   - $id - View id
1039
	 *   - $page_size - Page
1040
	 *   - $sort_field - form field id to sort
1041
	 *   - $sort_direction - ASC / DESC
1042
	 *   - $start_date - Ymd
1043
	 *   - $end_date - Ymd
1044
	 *   - $class - assign a html class to the view
1045
	 *   - $offset (optional) - This is the start point in the current data set (0 index based).
1046
	 * @param int $form_id Gravity Forms Form ID
1047
	 * @return array Associative array with `count`, `entries`, and `paging` keys. `count` has the total entries count, `entries` is an array with Gravity Forms full entry data, `paging` is an array with `offset` and `page_size` keys
1048
	 */
1049
	public static function get_view_entries( $args, $form_id ) {
1050
1051
		do_action( 'gravityview_log_debug', '[get_view_entries] init' );
1052
		// start filters and sorting
1053
1054
		/**
1055
		 * Process search parameters
1056
		 * @var array
1057
		 */
1058
		$search_criteria = self::get_search_criteria( $args, $form_id );
1059
1060
		// Paging & offset
1061
		$page_size = ! empty( $args['page_size'] ) ? intval( $args['page_size'] ) : apply_filters( 'gravityview_default_page_size', 25 );
1062
1063
		if ( -1 === $page_size ) {
1064
			$page_size = PHP_INT_MAX;
1065
		}
1066
1067
		if ( isset( $args['offset'] ) ) {
1068
			$offset = intval( $args['offset'] );
1069
		} else {
1070
			$curr_page = empty( $_GET['pagenum'] ) ? 1 : intval( $_GET['pagenum'] );
1071
			$offset = ( $curr_page - 1 ) * $page_size;
1072
		}
1073
1074
		$paging = array(
1075
			'offset' => $offset,
1076
			'page_size' => $page_size,
1077
		);
1078
1079
		do_action( 'gravityview_log_debug', __METHOD__ . ': Paging: ', $paging );
1080
1081
		// Sorting
1082
		$sorting = self::updateViewSorting( $args, $form_id );
1083
1084
		$parameters = array(
1085
			'search_criteria' => $search_criteria,
1086
			'sorting' => $sorting,
1087
			'paging' => $paging,
1088
			'cache' => isset( $args['cache'] ) ? $args['cache'] : true,
1089
		);
1090
1091
		/**
1092
		 * @filter `gravityview_get_entries` Filter get entries criteria
1093
		 * @param array $parameters Array with `search_criteria`, `sorting` and `paging` keys.
1094
		 * @param array $args View configuration args. {
1095
		 *      @type int $id View id
1096
		 *      @type int $page_size Number of entries to show per page
1097
		 *      @type string $sort_field Form field id to sort
1098
		 *      @type string $sort_direction Sorting direction ('ASC' or 'DESC')
1099
		 *      @type string $start_date - Ymd
1100
		 *      @type string $end_date - Ymd
1101
		 *      @type string $class - assign a html class to the view
1102
		 *      @type string $offset (optional) - This is the start point in the current data set (0 index based).
1103
		 * }
1104
		 * @param int $form_id ID of Gravity Forms form
1105
		 */
1106
		$parameters = apply_filters( 'gravityview_get_entries', $parameters, $args, $form_id );
1107
1108
		/**
1109
		 * @filter `gravityview_get_entries_{View ID}` Filter get entries criteria
1110
		 * @param array $parameters Array with `search_criteria`, `sorting` and `paging` keys.
1111
		 * @param array $args View configuration args.
1112
		 */
1113
		$parameters = apply_filters( 'gravityview_get_entries_'.$args['id'], $parameters, $args, $form_id );
1114
1115
		do_action( 'gravityview_log_debug', __METHOD__ . ': $parameters passed to gravityview_get_entries(): ', $parameters );
1116
1117
		//fetch entries
1118
		$count = 0;
1119
		$entries = gravityview_get_entries( $form_id, $parameters, $count );
1120
1121
		do_action( 'gravityview_log_debug', sprintf( '%s: Get Entries. Found: %s entries', __METHOD__, $count ), $entries );
1122
1123
		/**
1124
		 * @filter `gravityview_view_entries` Filter the entries output to the View
1125
		 * @deprecated since 1.5.2
1126
		 * @param array $args View settings associative array
1127
		 * @var array
1128
		 */
1129
		$entries = apply_filters( 'gravityview_view_entries', $entries, $args );
1130
1131
		/**
1132
		 * @filter `gravityview/view/entries` Filter the entries output to the View
1133
		 * @param array $criteria associative array containing count, entries & paging
1134
		 * @param array $args View settings associative array
1135
		 * @since 1.5.2
1136
		 */
1137
		return apply_filters( 'gravityview/view/entries', compact( 'count', 'entries', 'paging' ), $args );
1138
1139
	}
1140
1141
1142
	/**
1143
	 * Updates the View sorting criteria
1144
	 *
1145
	 * @since 1.7
1146
	 *
1147
	 * @param array $args View settings. Required to have `sort_field` and `sort_direction` keys
1148
	 * @param int $form_id The ID of the form used to sort
1149
	 * @return array $sorting Array with `key`, `direction` and `is_numeric` keys
1150
	 */
1151
	public static function updateViewSorting( $args, $form_id ) {
1152
		$sorting = array();
1153
		$sort_field_id = isset( $_GET['sort'] ) ? $_GET['sort'] : rgar( $args, 'sort_field' );
1154
		$sort_direction = isset( $_GET['dir'] ) ? $_GET['dir'] : rgar( $args, 'sort_direction' );
1155
1156
		$sort_field_id = self::_override_sorting_id_by_field_type( $sort_field_id, $form_id );
1157
1158
		if ( ! empty( $sort_field_id ) ) {
1159
			$sorting = array(
1160
				'key' => $sort_field_id,
1161
				'direction' => strtolower( $sort_direction ),
1162
				'is_numeric' => GVCommon::is_field_numeric( $form_id, $sort_field_id )
1163
			);
1164
		}
1165
1166
		GravityView_View::getInstance()->setSorting( $sorting );
1167
1168
		do_action( 'gravityview_log_debug', '[updateViewSorting] Sort Criteria : ', $sorting );
1169
1170
		return $sorting;
1171
1172
	}
1173
1174
	/**
1175
	 * Override sorting per field
1176
	 *
1177
	 * Currently only modifies sorting ID when sorting by the full name. Sorts by first name.
1178
	 * Use the `gravityview/sorting/full-name` filter to override.
1179
	 *
1180
	 * @todo Filter from GravityView_Field
1181
	 * @since 1.7.4
1182
	 *
1183
	 * @param int|string $sort_field_id Field used for sorting (`id` or `1.2`)
1184
	 * @param int $form_id GF Form ID
1185
	 *
1186
	 * @return string Possibly modified sorting ID
1187
	 */
1188
	private static function _override_sorting_id_by_field_type( $sort_field_id, $form_id ) {
1189
1190
		$form = gravityview_get_form( $form_id );
1191
1192
		$sort_field = GFFormsModel::get_field( $form, $sort_field_id );
1193
1194
		switch ( $sort_field['type'] ) {
1195
1196
			case 'address':
1197
				// Sorting by full address
1198
				if ( floatval( $sort_field_id ) === floor( $sort_field_id ) ) {
1199
1200
					/**
1201
					 * Override how to sort when sorting address
1202
					 *
1203
					 * @since 1.8
1204
					 *
1205
					 * @param string $address_part `street`, `street2`, `city`, `state`, `zip`, or `country` (default: `city`)
1206
					 * @param string $sort_field_id Field used for sorting
1207
					 * @param int $form_id GF Form ID
1208
					 */
1209
					$address_part = apply_filters( 'gravityview/sorting/address', 'city', $sort_field_id, $form_id );
1210
1211
					switch( strtolower( $address_part ) ){
1212
						case 'street':
1213
							$sort_field_id .= '.1';
1214
							break;
1215
						case 'street2':
1216
							$sort_field_id .= '.2';
1217
							break;
1218
						default:
1219
						case 'city':
1220
							$sort_field_id .= '.3';
1221
							break;
1222
						case 'state':
1223
							$sort_field_id .= '.4';
1224
							break;
1225
						case 'zip':
1226
							$sort_field_id .= '.5';
1227
							break;
1228
						case 'country':
1229
							$sort_field_id .= '.6';
1230
							break;
1231
					}
1232
1233
				}
1234
				break;
1235
			case 'name':
1236
				// Sorting by full name, not first, last, etc.
1237
				if ( floatval( $sort_field_id ) === floor( $sort_field_id ) ) {
1238
					/**
1239
					 * @filter `gravityview/sorting/full-name` Override how to sort when sorting full name.
1240
					 * @since 1.7.4
1241
					 * @param[in,out] string $name_part Sort by `first` or `last` (default: `first`)
1242
					 * @param[in] string $sort_field_id Field used for sorting
1243
					 * @param[in] int $form_id GF Form ID
1244
					 */
1245
					$name_part = apply_filters( 'gravityview/sorting/full-name', 'first', $sort_field_id, $form_id );
1246
1247
					if ( 'last' === strtolower( $name_part ) ) {
1248
						$sort_field_id .= '.6';
1249
					} else {
1250
						$sort_field_id .= '.3';
1251
					}
1252
				}
1253
				break;
1254
			case 'list':
1255
				$sort_field_id = false;
1256
				break;
1257
			case 'time':
1258
1259
				/**
1260
				 * @filter `gravityview/sorting/time` Override how to sort when sorting time
1261
				 * @see GravityView_Field_Time
1262
				 * @since 1.14
1263
				 * @param[in,out] string $name_part Field used for sorting
1264
				 * @param[in] int $form_id GF Form ID
1265
				 */
1266
				$sort_field_id = apply_filters( 'gravityview/sorting/time', $sort_field_id, $form_id );
1267
				break;
1268
		}
1269
1270
		return $sort_field_id;
1271
	}
1272
1273
	/**
1274
	 * Verify if user requested a single entry view
1275
	 * @return boolean|string false if not, single entry slug if true
1276
	 */
1277
	public static function is_single_entry() {
1278
1279
		$var_name = GravityView_Post_Types::get_entry_var_name();
1280
1281
		$single_entry = get_query_var( $var_name );
1282
1283
		/**
1284
		 * Modify the entry that is being displayed.
1285
		 *
1286
		 * @internal Should only be used by things like the oEmbed functionality.
1287
		 * @since 1.6
1288
		 */
1289
		$single_entry = apply_filters( 'gravityview/is_single_entry', $single_entry );
1290
1291
		if ( empty( $single_entry ) ){
1292
			return false;
1293
		} else {
1294
			return $single_entry;
1295
		}
1296
	}
1297
1298
1299
	/**
1300
	 * Register styles and scripts
1301
	 *
1302
	 * @access public
1303
	 * @return void
1304
	 */
1305
	public function add_scripts_and_styles() {
1306
		global $post, $posts;
1307
		// enqueue template specific styles
1308
		if ( $this->getGvOutputData() ) {
1309
1310
			$views = $this->getGvOutputData()->get_views();
1311
1312
			foreach ( $views as $view_id => $data ) {
1313
1314
				/**
1315
				 * Don't enqueue the scripts or styles if it's not going to be displayed.
1316
				 * @since 1.15
1317
				 */
1318
				if( is_user_logged_in() && false === GVCommon::has_cap( 'read_gravityview', $view_id ) ) {
1319
					continue;
1320
				}
1321
1322
				// By default, no thickbox
1323
				$js_dependencies = array( 'jquery', 'gravityview-jquery-cookie' );
1324
				$css_dependencies = array();
1325
1326
				// If the thickbox is enqueued, add dependencies
1327
				if ( ! empty( $data['atts']['lightbox'] ) ) {
1328
1329
					/**
1330
					 * @filter `gravity_view_lightbox_script` Override the lightbox script to enqueue. Default: `thickbox`
1331
					 * @param string $script_slug If you want to use a different lightbox script, return the name of it here.
1332
					 */
1333
					$js_dependencies[] = apply_filters( 'gravity_view_lightbox_script', 'thickbox' );
1334
1335
					/**
1336
					 * @filter `gravity_view_lightbox_style` Modify the lightbox CSS slug. Default: `thickbox`
1337
					 * @param string $script_slug If you want to use a different lightbox script, return the name of its CSS file here.
1338
					 */
1339
					$css_dependencies[] = apply_filters( 'gravity_view_lightbox_style', 'thickbox' );
1340
				}
1341
1342
				/**
1343
				 * If the form has checkbox fields, enqueue dashicons
1344
				 * @see https://github.com/katzwebservices/GravityView/issues/536
1345
				 * @since 1.15
1346
				 */
1347
				if( gravityview_view_has_single_checkbox_or_radio( $data['form'], $data['fields'] ) ) {
1348
					$css_dependencies[] = 'dashicons';
1349
				}
1350
1351
				wp_register_script( 'gravityview-jquery-cookie', plugins_url( 'assets/lib/jquery.cookie/jquery.cookie.min.js', GRAVITYVIEW_FILE ), array( 'jquery' ), GravityView_Plugin::version, true );
1352
1353
				$script_debug = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
1354
1355
				wp_register_script( 'gravityview-fe-view', plugins_url( 'assets/js/fe-views' . $script_debug . '.js', GRAVITYVIEW_FILE ), apply_filters( 'gravityview_js_dependencies', $js_dependencies ) , GravityView_Plugin::version, true );
1356
1357
				wp_enqueue_script( 'gravityview-fe-view' );
1358
1359
				if ( ! empty( $data['atts']['sort_columns'] ) ) {
1360
					wp_enqueue_style( 'gravityview_font', plugins_url( 'assets/css/font.css', GRAVITYVIEW_FILE ), $css_dependencies, GravityView_Plugin::version, 'all' );
1361
				}
1362
1363
				$this->enqueue_default_style( $css_dependencies );
1364
1365
				self::add_style( $data['template_id'] );
1366
			}
1367
1368
			if ( 'wp_print_footer_scripts' === current_filter() ) {
1369
1370
				$js_localization = array(
1371
					'cookiepath' => COOKIEPATH,
1372
					'clear' => _x( 'Clear', 'Clear all data from the form', 'gravityview' ),
1373
					'reset' => _x( 'Reset', 'Reset the search form to the state that existed on page load', 'gravityview' ),
1374
				);
1375
1376
				/**
1377
				 * @filter `gravityview_js_localization` Modify the array passed to wp_localize_script()
1378
				 * @param array $js_localization The data padded to the Javascript file
1379
				 * @param array $views Array of View data arrays with View settings
1380
				 */
1381
				$js_localization = apply_filters( 'gravityview_js_localization', $js_localization, $views );
1382
1383
				wp_localize_script( 'gravityview-fe-view', 'gvGlobals', $js_localization );
1384
			}
1385
		}
1386
	}
1387
1388
	/**
1389
	 * Handle enqueuing the `gravityview_default_style` stylesheet
1390
	 *
1391
	 * @since 1.17
1392
	 *
1393
	 * @param array $css_dependencies Dependencies for the `gravityview_default_style` stylesheet
1394
	 *
1395
	 * @return void
1396
	 */
1397
	private function enqueue_default_style( $css_dependencies = array() ) {
1398
1399
		/**
1400
		 * @filter `gravityview_use_legacy_search_css` Should GravityView use the legacy Search Bar stylesheet (from before Version 1.17)?
1401
		 * @since 1.17
1402
		 * @param bool $use_legacy_search_style If true, loads `gv-legacy-search(-rtl).css`. If false, loads `gv-default-styles(-rtl).css`. `-rtl` is added on RTL websites. Default: `false`
1403
		 */
1404
		$use_legacy_search_style = apply_filters( 'gravityview_use_legacy_search_style', false );
1405
1406
		$rtl = is_rtl() ? '-rtl' : '';
1407
1408
		$css_file_base = $use_legacy_search_style ? 'gv-legacy-search' : 'gv-default-styles';
1409
1410
		$path = gravityview_css_url( $css_file_base . $rtl . '.css' );
1411
1412
		wp_enqueue_style( 'gravityview_default_style', $path, $css_dependencies, GravityView_Plugin::version, 'all' );
1413
	}
1414
1415
	/**
1416
	 * Add template extra style if exists
1417
	 * @param string $template_id
1418
	 */
1419
	public static function add_style( $template_id ) {
1420
1421
		if ( ! empty( $template_id ) && wp_style_is( 'gravityview_style_' . $template_id, 'registered' ) ) {
1422
			do_action( 'gravityview_log_debug', sprintf( '[add_style] Adding extra template style for %s', $template_id ) );
1423
			wp_enqueue_style( 'gravityview_style_' . $template_id );
1424
		} elseif ( empty( $template_id ) ) {
1425
			do_action( 'gravityview_log_error', '[add_style] Cannot add template style; template_id is empty' );
1426
		} else {
1427
			do_action( 'gravityview_log_error', sprintf( '[add_style] Cannot add template style; %s is not registered', 'gravityview_style_'.$template_id ) );
1428
		}
1429
1430
	}
1431
1432
1433
	/**
1434
	 * Inject the sorting links on the table columns
1435
	 *
1436
	 * Callback function for hook 'gravityview/template/field_label'
1437
	 * @see GravityView_API::field_label() (in includes/class-api.php)
1438
	 *
1439
	 * @since 1.7
1440
	 *
1441
	 * @param string $label Field label
1442
	 * @param array $field Field settings
1443
	 *
1444
	 * @return string Field Label
1445
	 */
1446
	public function add_columns_sort_links( $label = '', $field, $form ) {
1447
1448
		/**
1449
		 * Not a table-based template; don't add sort icons
1450
		 * @since 1.12
1451
		 */
1452
		if( ! preg_match( '/table/ism', GravityView_View::getInstance()->getTemplatePartSlug() ) ) {
1453
			return $label;
1454
		}
1455
1456
		if ( ! $this->is_field_sortable( $field['id'], $form ) ) {
1457
			return $label;
1458
		}
1459
1460
		$sorting = GravityView_View::getInstance()->getSorting();
1461
1462
		$class = 'gv-sort';
1463
1464
		$sort_field_id = self::_override_sorting_id_by_field_type( $field['id'], $form['id'] );
1465
1466
		$sort_args = array(
1467
			'sort' => $field['id'],
1468
			'dir' => 'asc',
1469
		);
1470
1471
		if ( ! empty( $sorting['key'] ) && (string) $sort_field_id === (string) $sorting['key'] ) {
1472
			//toggle sorting direction.
1473
			if ( 'asc' === $sorting['direction'] ) {
1474
				$sort_args['dir'] = 'desc';
1475
				$class .= ' gv-icon-sort-desc';
1476
			} else {
1477
				$sort_args['dir'] = 'asc';
1478
				$class .= ' gv-icon-sort-asc';
1479
			}
1480
		} else {
1481
			$class .= ' gv-icon-caret-up-down';
1482
		}
1483
1484
		$url = add_query_arg( $sort_args, remove_query_arg( array('pagenum') ) );
1485
1486
		return '<a href="'. esc_url_raw( $url ) .'" class="'. $class .'" ></a>&nbsp;'. $label;
1487
1488
	}
1489
1490
	/**
1491
	 * Checks if field (column) is sortable
1492
	 *
1493
	 * @param string $field Field settings
1494
	 * @param array $form Gravity Forms form array
1495
	 *
1496
	 * @since 1.7
1497
	 *
1498
	 * @return bool True: Yes, field is sortable; False: not sortable
1499
	 */
1500
	public function is_field_sortable( $field_id = '', $form = array() ) {
1501
1502
		$field_type = $field_id;
1503
1504
		if( is_numeric( $field_id ) ) {
1505
			$field = GFFormsModel::get_field( $form, $field_id );
1506
			$field_type = $field->type;
1507
		}
1508
1509
		$not_sortable = array(
1510
			'edit_link',
1511
			'delete_link',
1512
		);
1513
1514
		/**
1515
		 * @filter `gravityview/sortable/field_blacklist` Modify what fields should never be sortable.
1516
		 * @since 1.7
1517
		 * @param[in,out] array $not_sortable Array of field types that aren't sortable
1518
		 * @param string $field_type Field type to check whether the field is sortable
1519
		 * @param array $form Gravity Forms form
1520
		 */
1521
		$not_sortable = apply_filters( 'gravityview/sortable/field_blacklist', $not_sortable, $field_type, $form );
1522
1523
		if ( in_array( $field_type, $not_sortable ) ) {
1524
			return false;
1525
		}
1526
1527
		return apply_filters( "gravityview/sortable/formfield_{$form['id']}_{$field_id}", apply_filters( "gravityview/sortable/field_{$field_id}", true, $form ) );
1528
1529
	}
1530
1531
}
1532
1533
GravityView_frontend::getInstance();
1534
1535
1536
1537