Completed
Push — develop ( 846913...22c9fc )
by Zack
17s queued 12s
created

GravityView_Delete_Entry::add_template_path()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 8
ccs 0
cts 0
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * The GravityView Delete Entry Extension
4
 *
5
 * Delete entries in GravityView.
6
 *
7
 * @since     1.5.1
8
 * @package   GravityView
9
 * @license   GPL2+
10
 * @author    GravityView <[email protected]>
11
 * @link      http://gravityview.co
12
 * @copyright Copyright 2014, Katz Web Services, Inc.
13
 */
14
15
if ( ! defined( 'WPINC' ) ) {
16
	die;
17
}
18
19
/**
20
 * @since 1.5.1
21
 */
22
final class GravityView_Delete_Entry {
23
24
	static $file;
25
	static $instance;
26
	var $entry;
27
	var $form;
28
	var $view_id;
29
	var $is_valid = null;
30
31
	/**
32
	 * Component instances.
33
	 * @var array
34
	 * @since 2.9.2
35
	 */
36
	public $instances = array();
37
38
	/**
39
	 * The value of the `delete_redirect` option when the setting is to redirect to Multiple Entries after delete
40
	 * @since 2.9.2
41
	 */
42
	const REDIRECT_TO_MULTIPLE_ENTRIES_VALUE = 1;
43
44
	/**
45
	 * The value of the `delete_redirect` option when the setting is to redirect to URL
46
	 * @since 2.9.2
47
	 */
48
	const REDIRECT_TO_URL_VALUE = 2;
49
50
	function __construct() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
51
52
		self::$file = plugin_dir_path( __FILE__ );
53
54
		if ( is_admin() ) {
55
			$this->load_components( 'admin' );
56
		}
57
58
		$this->add_hooks();
59
	}
60
61
	/**
62
	 * Load other files related to Delete Entry functionality
63
	 *
64
	 * @since 2.9.2
65
	 *
66
	 * @param $component
67
	 */
68
	private function load_components( $component ) {
69
70
		$dir = trailingslashit( self::$file );
71
72
		$filename  = $dir . 'class-delete-entry-' . $component . '.php';
73
		$classname = 'GravityView_Delete_Entry_' . str_replace( ' ', '_', ucwords( str_replace( '-', ' ', $component ) ) );
74 22
75
		// Loads component and pass extension's instance so that component can talk each other.
76 22
		require_once $filename;
77
78
		$this->instances[ $component ] = new $classname( $this );
79
		$this->instances[ $component ]->load();
80 22
	}
81
82
	/**
83
	 * @since 1.9.2
84
	 */
85
	private function add_hooks() {
86
87
		add_action( 'wp', array( $this, 'process_delete' ), 10000 );
88
89
		add_action( 'gravityview_before', array( $this, 'maybe_display_message' ) );
90
91
		// add template path to check for field
92
		add_filter( 'gravityview_template_paths', array( $this, 'add_template_path' ) );
93
94
		add_action( 'gravityview/edit-entry/publishing-action/after', array( $this, 'add_delete_button' ), 10, 4 );
95 44
96 44
		add_action( 'gravityview/delete-entry/deleted', array( $this, 'process_connected_posts' ), 10, 2 );
97 44
		add_action( 'gravityview/delete-entry/trashed', array( $this, 'process_connected_posts' ), 10, 2 );
98
99
		add_filter( 'gravityview/field/is_visible', array( $this, 'maybe_not_visible' ), 10, 3 );
100
101
		add_filter( 'gravityview/api/reserved_query_args', array( $this, 'add_reserved_arg' ) );
102
	}
103
104
	/**
105
	 * Adds "delete" to the list of internal reserved query args
106
	 *
107
	 * @since 2.10
108
	 *
109
	 * @param array $args Existing reserved args
110
	 *
111
	 * @return array
112
	 */
113
	public function add_reserved_arg( $args ) {
114
115
		$args[] = 'delete';
116
117
		return $args;
118
	}
119
120
	/**
121
	 * Return the instantiated class object
122
	 *
123
	 * @since  1.5.1
124
	 * @return GravityView_Delete_Entry
125
	 */
126
	static function getInstance() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
127
128
		if ( empty( self::$instance ) ) {
129 1
			self::$instance = new self();
130
		}
131
132
		return self::$instance;
133 1
	}
134
135 1
	/**
136
	 * Hide the field or not.
137
	 *
138
	 * For non-logged in users.
139
	 * For users that have no delete rights on any of the current entries.
140
	 *
141
	 * @param bool $visible Visible or not.
142
	 * @param \GV\Field $field The field.
143
	 * @param \GV\View $view The View context.
144
	 *
145
	 * @return bool
146
	 */
147
	public function maybe_not_visible( $visible, $field, $view ) {
148
		if ( 'delete_link' !== $field->ID ) {
149
			return $visible;
150
		}
151
152
		if ( ! $view ) {
153
			return $visible;
154
		}
155
156
		static $visibility_cache_for_view = array();
157
158
		if ( ! is_null( $result = \GV\Utils::get( $visibility_cache_for_view, $view->ID, null ) ) ) {
159
			return $result;
160
		}
161
162
		foreach ( $view->get_entries()->all() as $entry ) {
163
			if ( self::check_user_cap_delete_entry( $entry->as_entry(), $field->as_configuration(), $view ) ) {
164
				// At least one entry is deletable for this user
165
				$visibility_cache_for_view[ $view->ID ] = true;
166
				return true;
167
			}
168
		}
169
170
		$visibility_cache_for_view[ $view->ID ] = false;
171
172
		return false;
173
	}
174
175
	/**
176
	 * Include this extension templates path
177
	 *
178
	 * @since  1.5.1
179
	 * @param array $file_paths List of template paths ordered
180
	 */
181
	function add_template_path( $file_paths ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
182
183
		// Index 100 is the default GravityView template path.
184
		// Index 110 is Edit Entry link
185
		$file_paths[115] = self::$file;
186
187
		return $file_paths;
188
	}
189
190
	/**
191
	 * Make sure there's an entry
192
	 *
193
	 * @since 1.5.1
194
	 * @param [type] $entry [description]
0 ignored issues
show
Documentation introduced by
The doc-type [type] could not be parsed: Unknown type name "" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
195
	 */
196
	function set_entry( $entry = null ) {
0 ignored issues
show
Unused Code introduced by
The parameter $entry is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
197
		_deprecated_function( __METHOD__, '2.9.2' );
198
	}
199
200
	/**
201
	 * Generate a consistent nonce key based on the Entry ID
202
	 *
203
	 * @since 1.5.1
204
	 * @param  int $entry_id Entry ID
205
	 * @return string           Key used to validate request
206
	 */
207
	public static function get_nonce_key( $entry_id ) {
208
		return sprintf( 'delete_%s', $entry_id );
209
	}
210
211
	/**
212
	 * Generate a nonce link with the base URL of the current View embed
213
	 *
214
	 * We don't want to link to the single entry, because when deleted, there would be nothing to return to.
215
	 *
216
	 * @since 1.5.1
217
	 * @param  array       $entry Gravity Forms entry array
218
	 * @param  int         $view_id The View id. Not optional since 2.0
219
	 * @return string|null If directory link is valid, the URL to process the delete request. Otherwise, `NULL`.
220
	 */
221
	public static function get_delete_link( $entry, $view_id = 0, $post_id = null ) {
222
		if ( ! $view_id ) {
223
			/** @deprecated path */
224
			$view_id = gravityview_get_view_id();
225
		}
226
227
		$base = GravityView_API::directory_link( $post_id ?: $view_id, true );
228
229
		if ( empty( $base ) ) {
230
			gravityview()->log->error( 'Post ID does not exist: {post_id}', array( 'post_id' => $post_id ) );
231
232
			return null;
233
		}
234
235
		$gv_entry = \GV\GF_Entry::from_entry( $entry );
236
237
		// Use the slug instead of the ID for consistent security
238
		$entry_slug = $gv_entry->get_slug();
239
240
		$actionurl = add_query_arg(
241
			array(
242
				'action'   => 'delete',
243
				'entry_id' => $entry_slug,
244
				'gvid'     => $view_id,
245
				'view_id'  => $view_id,
246
			),
247
			$base
248
		);
249
250
		$url = wp_nonce_url( $actionurl, 'delete_' . $entry_slug, 'delete' );
251
252
		return $url;
253
	}
254
255
256
	/**
257
	 * Add a Delete button to the "#publishing-action" section of the Delete Entry form
258
	 *
259
	 * @since 1.5.1
260
	 * @since 2.0.13 Added $post_id
261
	 *
262
	 * @param array $form    Gravity Forms form array
263
	 * @param array $entry   Gravity Forms entry array
264
	 * @param int $view_id GravityView View ID
265
	 * @param int $post_id Current post ID. May be same as View ID.
266
	 *
267
	 * @return void
268
	 */
269 22
	public function add_delete_button( $form = array(), $entry = array(), $view_id = null, $post_id = null ) {
270 22
271 22
		// Only show the link to those who are allowed to see it.
272
		if ( ! self::check_user_cap_delete_entry( $entry, array(), $view_id ) ) {
273
			return;
274
		}
275
276
		/**
277
		 * @filter `gravityview/delete-entry/show-delete-button` Should the Delete button be shown in the Edit Entry screen?
278
		 * @param boolean $show_entry Default: true
279
		 */
280
		$show_delete_button = apply_filters( 'gravityview/delete-entry/show-delete-button', true );
281
282
		// If the button is hidden by the filter, don't show.
283
		if ( ! $show_delete_button ) {
284
			return;
285
		}
286
287
		$attributes = array(
288
			'class' => 'btn btn-sm button button-small alignright pull-right btn-danger gv-button-delete',
289
			'tabindex' => ( GFCommon::$tab_index ++ ),
290
			'onclick' => self::get_confirm_dialog(),
291
		);
292
293
		echo gravityview_get_link( self::get_delete_link( $entry, $view_id, $post_id ), esc_attr__( 'Delete', 'gravityview' ), $attributes );
294
295 22
	}
296 22
297
	/**
298
	 * Handle the deletion request, if $_GET['action'] is set to "delete"
299
	 *
300
	 * 1. Check referrer validity
301 22
	 * 2. Make sure there's an entry with the slug of $_GET['entry_id']
302
	 * 3. If so, attempt to delete the entry. If not, set the error status
303 22
	 * 4. Remove `action=delete` from the URL
304
	 * 5. Redirect to the page using `wp_redirect()`
305 22
	 *
306
	 * @since 1.5.1
307
	 * @uses wp_redirect()
308
	 * @return void
309
	 */
310 22
	public function process_delete() {
311
312
		/* Unslash and Parse $_GET array. */
313 22
		$get_fields = wp_parse_args(
314
			wp_unslash( $_GET ),
315 22
			array(
316 22
				'action'   => '',
317 22
				'entry_id' => '',
318 22
				'gvid'     => '',
319 22
				'view_id'  => '',
320 22
				'delete'   => '',
321
			)
322 22
		);
323
324 22
		// If the form is not submitted, return early
325
		if ( 'delete' !== $get_fields['action'] || empty( $get_fields['entry_id'] ) ) {
326
			return;
327
		}
328
329
		// Make sure it's a GravityView request
330
		$valid_nonce_key = wp_verify_nonce( $get_fields['delete'], self::get_nonce_key( $get_fields['entry_id'] ) );
331
332
		if ( ! $valid_nonce_key ) {
333
			gravityview()->log->debug( 'Delete entry not processed: nonce validation failed.' );
334
335
			return;
336
		}
337
338
		// Get the entry slug
339
		$entry_slug = esc_attr( $get_fields['entry_id'] );
340
341 22
		// Redirect after deleting the entry.
342
		$view = \GV\View::by_id( $get_fields['view_id'] );
343
344 22
		// See if there's an entry there
345 1
		$entry = gravityview_get_entry( $entry_slug, true, false, $view );
346
347
		$delete_redirect_base = esc_url_raw( remove_query_arg( array( 'action', 'gvid', 'entry_id' ) ) );
348
349
		if ( ! $entry ) {
350
351
			gravityview()->log->debug( 'Delete entry failed: there was no entry with the entry slug {entry_slug}', array( 'entry_slug' => $entry_slug ) );
352 21
353
			$this->_redirect_and_exit( $delete_redirect_base, __( 'The entry does not exist.', 'gravityview' ), 'error' );
354
		}
355 21
356
		$has_permission = $this->user_can_delete_entry( $entry, \GV\Utils::_GET( 'gvid', \GV\Utils::_GET( 'view_id' ) ) );
357
358
		if ( is_wp_error( $has_permission ) ) {
359
			$this->_redirect_and_exit( $delete_redirect_base, $has_permission->get_error_message(), 'error' );
360 21
		}
361 21
362 21
		// Delete the entry
363
		$delete_response = $this->delete_or_trash_entry( $entry );
364
365 21
		if ( is_wp_error( $delete_response ) ) {
366
			$this->_redirect_and_exit( $delete_redirect_base, $delete_response->get_error_message(), 'error' );
367 21
		}
368
369
		if ( (int) $view->settings->get( 'delete_redirect' ) === self::REDIRECT_TO_URL_VALUE ) {
370
371
			$form                 = GFAPI::get_form( $entry['form_id'] );
372
			$redirect_url_setting = $view->settings->get( 'delete_redirect_url' );
373
			$redirect_url         = GFCommon::replace_variables( $redirect_url_setting, $form, $entry, false, false, false, 'text' );
374
375
			$this->_redirect_and_exit( $redirect_url, '', '', false );
376
		}
377
378
		// Redirect to multiple entries
379
		$this->_redirect_and_exit( $delete_redirect_base, '', $delete_response, true );
380
	}
381
382 2
	/**
383
	 * Redirects the user to a URL and exits.
384
	 *
385 2
	 * @since 2.9.2
386
	 *
387
	 * @param string $url The URL to redirect to.
388
	 * @param string $message Message to pass through URL.
389
	 * @param string $status The deletion status ("deleted", "trashed", or "error").
390
	 * @param bool   $safe_redirect Whether to use wp_safe_redirect() or not.
391
	 */
392
	private function _redirect_and_exit( $url, $message = '', $status = '', $safe_redirect = true ) {
393
394
		$delete_redirect_args = array(
395
			'status'  => $status,
396
			'message' => $message,
397
		);
398
399
		$delete_redirect_args = array_filter( $delete_redirect_args );
400
401
		/**
402
		 * @filter `gravityview/delete-entry/redirect-args` Modify the query args added to the delete entry redirect
403
		 * @since 2.9.2
404
		 *
405
		 * @param array $delete_redirect_args Array with `_delete_nonce`, `message` and `status` keys
406
		 */
407
		$delete_redirect_args = apply_filters( 'gravityview/delete-entry/redirect-args', $delete_redirect_args );
408
409
		$delete_redirect_url = add_query_arg( $delete_redirect_args, $url );
410
411
		if ( $safe_redirect ) {
412
			wp_safe_redirect( $delete_redirect_url );
413
		} else {
414
			wp_redirect( $delete_redirect_url );
415
		}
416
417
		exit();
418
	}
419
420
	/**
421
	 * Delete mode: permanently delete, or move to trash?
422
	 *
423
	 * @return string `delete` or `trash`
424
	 */
425
	private function get_delete_mode() {
426
427
		/**
428
		 * @filter `gravityview/delete-entry/mode` Delete mode: permanently delete, or move to trash?
429
		 * @since 1.13.1
430
		 * @param string $delete_mode Delete mode: `trash` or `delete`. Default: `delete`
431
		 */
432
		$delete_mode = apply_filters( 'gravityview/delete-entry/mode', 'delete' );
433
434
		return ( 'trash' === $delete_mode ) ? 'trash' : 'delete';
435
	}
436
437
	/**
438
	 * @since 1.13.1
439
	 *
440
	 * @uses GFAPI::delete_entry()
441
	 * @uses GFAPI::update_entry_property()
442
	 *
443
	 * @return WP_Error|string "deleted" or "trashed" if successful, WP_Error if GFAPI::delete_entry() or updating entry failed.
444
	 */
445
	private function delete_or_trash_entry( $entry ) {
446
447
		$entry_id = $entry['id'];
448
449
		$mode = $this->get_delete_mode();
450
451
		if ( 'delete' === $mode ) {
452
453 2
			gravityview()->log->debug( 'Starting delete entry: {entry_id}', array( 'entry_id' => $entry_id ) );
454
455
			// Delete the entry
456
			$delete_response = GFAPI::delete_entry( $entry_id );
457
458
			if ( ! is_wp_error( $delete_response ) ) {
459
				$delete_response = 'deleted';
460
461
				/**
462
				 * @action `gravityview/delete-entry/deleted` Triggered when an entry is deleted
463
				 * @since 1.16.4
464
				 * @param  int $entry_id ID of the Gravity Forms entry
465
				 * @param  array $entry Deleted entry array
466
				*/
467
				do_action( 'gravityview/delete-entry/deleted', $entry_id, $entry );
468
			}
469
470
			gravityview()->log->debug( 'Delete response: {delete_response}', array( 'delete_response' => $delete_response ) );
471
472
		} else {
473
474
			gravityview()->log->debug( 'Starting trash entry: {entry_id}', array( 'entry_id' => $entry_id ) );
475
476
			$trashed = GFAPI::update_entry_property( $entry_id, 'status', 'trash' );
477
			new GravityView_Cache();
478
479
			if ( ! $trashed ) {
480
				$delete_response = new WP_Error( 'trash_entry_failed', __( 'Moving the entry to the trash failed.', 'gravityview' ) );
481
			} else {
482
483
				/**
484
				 * @action `gravityview/delete-entry/trashed` Triggered when an entry is trashed
485
				 * @since 1.16.4
486
				 * @param  int $entry_id ID of the Gravity Forms entry
487
				 * @param  array $entry Deleted entry array
488
				 */
489
				do_action( 'gravityview/delete-entry/trashed', $entry_id, $entry );
490
491
				$delete_response = 'trashed';
492
			}
493
494
			gravityview()->log->debug( ' Trashed? {delete_response}', array( 'delete_response' => $delete_response ) );
495
		}
496
497
		return $delete_response;
498
	}
499
500
	/**
501
	 * Delete or trash a post connected to an entry
502
	 *
503
	 * @since 1.17
504
	 *
505
	 * @param int $entry_id ID of entry being deleted/trashed
506
	 * @param array $entry Array of the entry being deleted/trashed
507
	 */
508
	public function process_connected_posts( $entry_id = 0, $entry = array() ) {
509
510
		// The entry had no connected post
511
		if ( empty( $entry['post_id'] ) ) {
512
			return;
513
		}
514
515
		/**
516
		 * @filter `gravityview/delete-entry/delete-connected-post` Should posts connected to an entry be deleted when the entry is deleted?
517
		 * @since 1.17
518
		 * @param boolean $delete_post If trashing an entry, trash the post. If deleting an entry, delete the post. Default: true
519
		 */
520
		$delete_post = apply_filters( 'gravityview/delete-entry/delete-connected-post', true );
521
522
		if ( false === $delete_post ) {
523
			return;
524
		}
525
526
		$action = current_action();
527
528
		if ( 'gravityview/delete-entry/deleted' === $action ) {
529
			$result = wp_delete_post( $entry['post_id'], true );
530
		} else {
531
			$result = wp_trash_post( $entry['post_id'] );
532
		}
533
534
		if ( false === $result ) {
535
			gravityview()->log->error(
536
				'(called by {action}): Error processing the Post connected to the entry.',
537
				array(
538
					'action' => $action,
539
					'data' => $entry,
540
				)
541
			);
542
		} else {
543
			gravityview()->log->debug(
544
				'(called by {action}): Successfully processed Post connected to the entry.',
545
				array(
546
					'action' => $action,
547
					'data' => $entry,
548
				)
549
			);
550
		}
551
	}
552
553
	/**
554
	 * Is the current nonce valid for editing the entry?
555
	 *
556
	 * @since 1.5.1
557
	 * @return boolean
558
	 */
559
	public function verify_nonce() {
560
561
		// No delete entry request was made
562
		if ( empty( $_GET['entry_id'] ) || empty( $_GET['delete'] ) ) {
563
			return false;
564
		}
565
566
		$nonce_key = self::get_nonce_key( $_GET['entry_id'] );
567
568
		$valid = wp_verify_nonce( $_GET['delete'], $nonce_key );
569
570
		/**
571
		 * @filter `gravityview/delete-entry/verify_nonce` Override Delete Entry nonce validation. Return true to declare nonce valid.
572
		 * @since 1.15.2
573
		 * @see wp_verify_nonce()
574
		 * @param int|boolean $valid False if invalid; 1 or 2 when nonce was generated
575
		 * @param string $nonce_key Name of nonce action used in wp_verify_nonce. $_GET['delete'] holds the nonce value itself. Default: `delete_{entry_id}`
576
		 */
577
		$valid = apply_filters( 'gravityview/delete-entry/verify_nonce', $valid, $nonce_key );
578
579
		return $valid;
580
	}
581
582
	/**
583
	 * Get the onclick attribute for the confirm dialogs that warns users before they delete an entry
584
	 *
585
	 * @since 1.5.1
586
	 * @return string HTML `onclick` attribute
587
	 */
588
	public static function get_confirm_dialog() {
589
590
		$confirm = __( 'Are you sure you want to delete this entry? This cannot be undone.', 'gravityview' );
591
592
		/**
593
		 * @filter `gravityview/delete-entry/confirm-text` Modify the Delete Entry Javascript confirmation text
594
		 * @param string $confirm Default: "Are you sure you want to delete this entry? This cannot be undone."
595
		 */
596
		$confirm = apply_filters( 'gravityview/delete-entry/confirm-text', $confirm );
597
598
		return 'return window.confirm(\'' . esc_js( $confirm ) . '\');';
599
	}
600
601
	/**
602
	 * Check if the user can edit the entry
603
	 *
604
	 * - Is the nonce valid?
605
	 * - Does the user have the right caps for the entry
606
	 * - Is the entry in the trash?
607
	 *
608 22
	 * @since 1.5.1
609
	 * @param  array $entry Gravity Forms entry array
610 22
	 * @return boolean|WP_Error        True: can edit form. WP_Error: nope.
611
	 */
612
	function user_can_delete_entry( $entry = array(), $view_id = null ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
613
614
		$error = null;
615
616 22
		if ( ! $this->verify_nonce() ) {
617
			$error = __( 'The link to delete this entry is not valid; it may have expired.', 'gravityview' );
618 22
		}
619
620
		if ( ! self::check_user_cap_delete_entry( $entry, array(), $view_id ) ) {
621
			$error = __( 'You do not have permission to delete this entry.', 'gravityview' );
622
		}
623
624
		if ( $entry['status'] === 'trash' ) {
625
			if ( 'trash' === $this->get_delete_mode() ) {
626
				$error = __( 'The entry is already in the trash.', 'gravityview' );
627
			} else {
628
				$error = __( 'You cannot delete the entry; it is already in the trash.', 'gravityview' );
629
			}
630
		}
631
632
		// No errors; everything's fine here!
633
		if ( empty( $error ) ) {
634
			return true;
635
		}
636
637
		gravityview()->log->error( '{error}', array( 'erorr' => $error ) );
638
639
		return new WP_Error( 'gravityview-delete-entry-permissions', $error );
640
	}
641
642
643
	/**
644
	 * checks if user has permissions to view the link or delete a specific entry
645
	 *
646
	 * @since 1.5.1
647
	 * @since 1.15 Added `$view_id` param
648
	 *
649
	 * @param  array $entry Gravity Forms entry array
650
	 * @param array $field Field settings (optional)
651
	 * @param int|\GV\View $view Pass a View ID to check caps against. If not set, check against current View (@deprecated no longer optional)
652
	 * @return bool
653
	 */
654
	public static function check_user_cap_delete_entry( $entry, $field = array(), $view = 0 ) {
655
		if ( ! $view ) {
656
			/** @deprecated path */
657
			$view_id = GravityView_View::getInstance()->getViewId();
658
			$view = \GV\View::by_id( $view_id );
659
		} else {
660
			if ( ! $view instanceof \GV\View ) {
661
				$view = \GV\View::by_id( $view );
662
			}
663
			$view_id = $view->ID;
664
		}
665
666
		$current_user = wp_get_current_user();
667
668
		$entry_id = isset( $entry['id'] ) ? $entry['id'] : null;
669
670
		// Or if they can delete any entries (as defined in Gravity Forms), we're good.
671
		if ( GVCommon::has_cap( array( 'gravityforms_delete_entries', 'gravityview_delete_others_entries' ), $entry_id ) ) {
672
673
			gravityview()->log->debug( 'Current user has `gravityforms_delete_entries` or `gravityview_delete_others_entries` capability.' );
674 23
675 23
			return true;
676
		}
677
678
		// If field options are passed, check if current user can view the link
679
		if ( ! empty( $field ) ) {
680 23
681 23
			// If capability is not defined, something is not right!
682
			if ( empty( $field['allow_edit_cap'] ) ) {
683 23
684
				gravityview()->log->error( 'Cannot read delete entry field caps', array( 'data' => $field ) );
685
686 23
				return false;
687
			}
688 23
689
			if ( GVCommon::has_cap( $field['allow_edit_cap'] ) ) {
690
691 23
				// Do not return true if cap is read, as we need to check if the current user created the entry
692
				if ( $field['allow_edit_cap'] !== 'read' ) {
693 22
					return true;
694
				}
695 22
			} else {
696
697
				gravityview()->log->debug( 'User {user_id} is not authorized to view delete entry link ', array( 'user_id' => $current_user->ID ) );
698
699
				return false;
700 2
			}
701
		}
702
703 1
		if ( ! isset( $entry['created_by'] ) ) {
704
705 1
			gravityview()->log->error( 'Entry `created_by` doesn\'t exist.' );
706
707 1
			return false;
708
		}
709
710
		$user_delete = $view->settings->get( 'user_delete' );
711
712
		// Only checks user_delete view option if view is already set
713
		if ( $view && empty( $user_delete ) ) {
714
			gravityview()->log->debug( 'User Delete is disabled. Returning false.' );
715
			return false;
716
		}
717
718
		// If the logged-in user is the same as the user who created the entry, we're good.
719
		if ( is_user_logged_in() && intval( $current_user->ID ) === intval( $entry['created_by'] ) ) {
720
721
			gravityview()->log->debug( 'User {user_id} created the entry.', array( 'user_id' => $current_user->ID ) );
722
723
			return true;
724
		}
725
726 1
		return false;
727
	}
728
729
730
	/**
731
	 * After processing delete entry, the user will be redirected to the referring View or embedded post/page. Display a message on redirection.
732
	 *
733 1
	 * If success, there will be `status` URL parameters `status=>success`
734
	 * If an error, there will be `status` and `message` URL parameters `status=>error&message=example`
735
	 *
736 1
	 * @since 1.15.2 Only show message when the URL parameter's View ID matches the current View ID
737 1
	 * @since 1.5.1
738 1
	 *
739
	 * @param int $current_view_id The ID of the View being rendered
740
	 * @return void
741
	 */
742
	public function maybe_display_message( $current_view_id = 0 ) {
743
744
		if ( empty( $_GET['status'] ) || ! self::verify_nonce() ) {
745
			return;
746
		}
747
748
		// Entry wasn't deleted from current View
749
		if ( isset( $_GET['view_id'] ) && intval( $_GET['view_id'] ) !== intval( $current_view_id ) ) {
750
			return;
751
		}
752
753
		$this->display_message();
754
	}
755
756
	public function display_message() {
757
758
		if ( empty( $_GET['status'] ) || empty( $_GET['delete'] ) ) {
759
			return;
760
		}
761
762
		$status = esc_attr( $_GET['status'] );
763
		$message_from_url = \GV\Utils::_GET( 'message' );
764
		$message_from_url = rawurldecode( stripslashes_deep( $message_from_url ) );
765 37
		$class = '';
766 37
767 37
		switch ( $status ) {
768
			case 'error':
769
				$class = ' gv-error error';
770
				$error_message = __( 'There was an error deleting the entry: %s', 'gravityview' );
771
				$message = sprintf( $error_message, $message_from_url );
772
				break;
773
			case 'trashed':
774
				$message = __( 'The entry was successfully moved to the trash.', 'gravityview' );
775
				break;
776
			default:
777
				$message = __( 'The entry was successfully deleted.', 'gravityview' );
778
				break;
779
		}
780
781
		/**
782
		 * @filter `gravityview/delete-entry/message` Modify the Delete Entry messages
783
		 * @since 1.13.1
784
		 * @param string $message Message to be displayed
785
		 * @param string $status Message status (`error` or `success`)
786
		 * @param string $message_from_url The original error message, if any, without the "There was an error deleting the entry:" prefix
787
		 */
788
		$message = apply_filters( 'gravityview/delete-entry/message', esc_attr( $message ), $status, $message_from_url );
789
790
		echo GVCommon::generate_notice( $message, $class );
791
	}
792
793
794
} // end class
795
796
GravityView_Delete_Entry::getInstance();
797
798