Completed
Pull Request — develop (#1499)
by Zack
27:50 queued 07:53
created

GravityView_Delete_Entry::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 10
ccs 0
cts 4
cp 0
crap 6
rs 9.9332
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
	}
102
103
	/**
104
	 * Return the instantiated class object
105
	 *
106
	 * @since  1.5.1
107
	 * @return GravityView_Delete_Entry
108
	 */
109
	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...
110
111
		if ( empty( self::$instance ) ) {
112
			self::$instance = new self();
113
		}
114
115
		return self::$instance;
116
	}
117
118
	/**
119
	 * Hide the field or not.
120
	 *
121
	 * For non-logged in users.
122
	 * For users that have no delete rights on any of the current entries.
123
	 *
124
	 * @param bool $visible Visible or not.
125
	 * @param \GV\Field $field The field.
126
	 * @param \GV\View $view The View context.
127
	 *
128
	 * @return bool
129 1
	 */
130
	public function maybe_not_visible( $visible, $field, $view ) {
131
		if ( 'delete_link' !== $field->ID ) {
132
			return $visible;
133 1
		}
134
135 1
		if ( ! $view ) {
136
			return $visible;
137
		}
138
139
		static $visibility_cache_for_view = array();
140
141
		if ( ! is_null( $result = \GV\Utils::get( $visibility_cache_for_view, $view->ID, null ) ) ) {
142
			return $result;
143
		}
144
145
		foreach ( $view->get_entries()->all() as $entry ) {
146
			if ( self::check_user_cap_delete_entry( $entry->as_entry(), $field->as_configuration(), $view ) ) {
147
				// At least one entry is deletable for this user
148
				$visibility_cache_for_view[ $view->ID ] = true;
149
				return true;
150
			}
151
		}
152
153
		$visibility_cache_for_view[ $view->ID ] = false;
154
155
		return false;
156
	}
157
158
	/**
159
	 * Include this extension templates path
160
	 *
161
	 * @since  1.5.1
162
	 * @param array $file_paths List of template paths ordered
163
	 */
164
	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...
165
166
		// Index 100 is the default GravityView template path.
167
		// Index 110 is Edit Entry link
168
		$file_paths[115] = self::$file;
169
170
		return $file_paths;
171
	}
172
173
	/**
174
	 * Make sure there's an entry
175
	 *
176
	 * @since 1.5.1
177
	 * @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...
178
	 */
179
	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...
180
		_deprecated_function( __METHOD__, '2.9.2' );
181
	}
182
183
	/**
184
	 * Generate a consistent nonce key based on the Entry ID
185
	 *
186
	 * @since 1.5.1
187
	 * @param  int $entry_id Entry ID
188
	 * @return string           Key used to validate request
189
	 */
190
	public static function get_nonce_key( $entry_id ) {
191
		return sprintf( 'delete_%s', $entry_id );
192
	}
193
194
	/**
195
	 * Generate a nonce link with the base URL of the current View embed
196
	 *
197
	 * We don't want to link to the single entry, because when deleted, there would be nothing to return to.
198
	 *
199
	 * @since 1.5.1
200
	 * @param  array       $entry Gravity Forms entry array
201
	 * @param  int         $view_id The View id. Not optional since 2.0
202
	 * @return string|null If directory link is valid, the URL to process the delete request. Otherwise, `NULL`.
203
	 */
204
	public static function get_delete_link( $entry, $view_id = 0, $post_id = null ) {
205
		if ( ! $view_id ) {
206
			/** @deprecated path */
207
			$view_id = gravityview_get_view_id();
208
		}
209
210
		$base = GravityView_API::directory_link( $post_id ?: $view_id, true );
211
212
		if ( empty( $base ) ) {
213
			gravityview()->log->error( 'Post ID does not exist: {post_id}', array( 'post_id' => $post_id ) );
214
215
			return null;
216
		}
217
218
		$gv_entry = \GV\GF_Entry::from_entry( $entry );
219
220
		// Use the slug instead of the ID for consistent security
221
		$entry_slug = $gv_entry->get_slug();
222
223
		$actionurl = add_query_arg(
224
			array(
225
				'action'   => 'delete',
226
				'entry_id' => $entry_slug,
227
				'gvid'     => $view_id,
228
				'view_id'  => $view_id,
229
			),
230
			$base
231
		);
232
233
		$url = wp_nonce_url( $actionurl, 'delete_' . $entry_slug, 'delete' );
234
235
		return $url;
236
	}
237
238
239
	/**
240
	 * Add a Delete button to the "#publishing-action" section of the Delete Entry form
241
	 *
242
	 * @since 1.5.1
243
	 * @since 2.0.13 Added $post_id
244
	 *
245
	 * @param array $form    Gravity Forms form array
246
	 * @param array $entry   Gravity Forms entry array
247
	 * @param int $view_id GravityView View ID
248
	 * @param int $post_id Current post ID. May be same as View ID.
249
	 *
250
	 * @return void
251
	 */
252
	public function add_delete_button( $form = array(), $entry = array(), $view_id = null, $post_id = null ) {
253
254
		// Only show the link to those who are allowed to see it.
255
		if ( ! self::check_user_cap_delete_entry( $entry, array(), $view_id ) ) {
256
			return;
257
		}
258
259
		/**
260
		 * @filter `gravityview/delete-entry/show-delete-button` Should the Delete button be shown in the Edit Entry screen?
261
		 * @param boolean $show_entry Default: true
262
		 */
263
		$show_delete_button = apply_filters( 'gravityview/delete-entry/show-delete-button', true );
264
265
		// If the button is hidden by the filter, don't show.
266
		if ( ! $show_delete_button ) {
267
			return;
268
		}
269 22
270 22
		$attributes = array(
271 22
			'class' => 'btn btn-sm button button-small alignright pull-right btn-danger gv-button-delete',
272
			'tabindex' => ( GFCommon::$tab_index ++ ),
273
			'onclick' => self::get_confirm_dialog(),
274
		);
275
276
		echo gravityview_get_link( self::get_delete_link( $entry, $view_id, $post_id ), esc_attr__( 'Delete', 'gravityview' ), $attributes );
277
278
	}
279
280
	/**
281
	 * Handle the deletion request, if $_GET['action'] is set to "delete"
282
	 *
283
	 * 1. Check referrer validity
284
	 * 2. Make sure there's an entry with the slug of $_GET['entry_id']
285
	 * 3. If so, attempt to delete the entry. If not, set the error status
286
	 * 4. Remove `action=delete` from the URL
287
	 * 5. Redirect to the page using `wp_redirect()`
288
	 *
289
	 * @since 1.5.1
290
	 * @uses wp_redirect()
291
	 * @return void
292
	 */
293
	public function process_delete() {
294
295 22
		/* Unslash and Parse $_GET array. */
296 22
		$get_fields = wp_parse_args(
297
			wp_unslash( $_GET ),
298
			array(
299
				'action'   => '',
300
				'entry_id' => '',
301 22
				'gvid'     => '',
302
				'view_id'  => '',
303 22
				'delete'   => '',
304
			)
305 22
		);
306
307
		// If the form is not submitted, return early
308
		if ( 'delete' !== $get_fields['action'] || empty( $get_fields['entry_id'] ) ) {
309
			return;
310 22
		}
311
312
		// Make sure it's a GravityView request
313 22
		$valid_nonce_key = wp_verify_nonce( $get_fields['delete'], self::get_nonce_key( $get_fields['entry_id'] ) );
314
315 22
		if ( ! $valid_nonce_key ) {
316 22
			gravityview()->log->debug( 'Delete entry not processed: nonce validation failed.' );
317 22
318 22
			return;
319 22
		}
320 22
321
		// Get the entry slug
322 22
		$entry_slug = esc_attr( $get_fields['entry_id'] );
323
324 22
		// Redirect after deleting the entry.
325
		$view = \GV\View::by_id( $get_fields['view_id'] );
326
327
		// See if there's an entry there
328
		$entry = gravityview_get_entry( $entry_slug, true, false, $view );
329
330
		$delete_redirect_base = esc_url_raw( remove_query_arg( array( 'action', 'gvid', 'entry_id' ) ) );
331
332
		if ( ! $entry ) {
333
334
			gravityview()->log->debug( 'Delete entry failed: there was no entry with the entry slug {entry_slug}', array( 'entry_slug' => $entry_slug ) );
335
336
			$this->_redirect_and_exit( $delete_redirect_base, __( 'The entry does not exist.', 'gravityview' ), 'error' );
337
		}
338
339
		$has_permission = $this->user_can_delete_entry( $entry, \GV\Utils::_GET( 'gvid', \GV\Utils::_GET( 'view_id' ) ) );
340
341 22
		if ( is_wp_error( $has_permission ) ) {
342
			$this->_redirect_and_exit( $delete_redirect_base, $has_permission->get_error_message(), 'error' );
343
		}
344 22
345 1
		// Delete the entry
346
		$delete_response = $this->delete_or_trash_entry( $entry );
347
348
		if ( is_wp_error( $delete_response ) ) {
349
			$this->_redirect_and_exit( $delete_redirect_base, $delete_response->get_error_message(), 'error' );
350
		}
351
352 21
		if ( (int) $view->settings->get( 'delete_redirect' ) === self::REDIRECT_TO_URL_VALUE ) {
353
354
			$form                 = GFAPI::get_form( $entry['form_id'] );
355 21
			$redirect_url_setting = $view->settings->get( 'delete_redirect_url' );
356
			$redirect_url         = GFCommon::replace_variables( $redirect_url_setting, $form, $entry, false, false, false, 'text' );
357
358
			$this->_redirect_and_exit( $redirect_url, '', '', false );
359
		}
360 21
361 21
		// Redirect to multiple entries
362 21
		$this->_redirect_and_exit( $delete_redirect_base, '', $delete_response, true );
363
	}
364
365 21
	/**
366
	 * Redirects the user to a URL and exits.
367 21
	 *
368
	 * @since 2.9.2
369
	 *
370
	 * @param string $url The URL to redirect to.
371
	 * @param string $message Message to pass through URL.
372
	 * @param string $status The deletion status ("deleted", "trashed", or "error").
373
	 * @param bool   $safe_redirect Whether to use wp_safe_redirect() or not.
374
	 */
375
	private function _redirect_and_exit( $url, $message = '', $status = '', $safe_redirect = true ) {
376
377
		$delete_redirect_args = array(
378
			'status'  => $status,
379
			'message' => $message,
380
		);
381
382 2
		$delete_redirect_args = array_filter( $delete_redirect_args );
383
384
		/**
385 2
		 * @filter `gravityview/delete-entry/redirect-args` Modify the query args added to the delete entry redirect
386
		 * @since 2.9.2
387
		 *
388
		 * @param array $delete_redirect_args Array with `_delete_nonce`, `message` and `status` keys
389
		 */
390
		$delete_redirect_args = apply_filters( 'gravityview/delete-entry/redirect-args', $delete_redirect_args );
391
392
		$delete_redirect_url = add_query_arg( $delete_redirect_args, $url );
393
394
		if ( $safe_redirect ) {
395
			wp_safe_redirect( $delete_redirect_url );
396
		} else {
397
			wp_redirect( $delete_redirect_url );
398
		}
399
400
		exit();
401
	}
402
403
	/**
404
	 * Delete mode: permanently delete, or move to trash?
405
	 *
406
	 * @return string `delete` or `trash`
407
	 */
408
	private function get_delete_mode() {
409
410
		/**
411
		 * @filter `gravityview/delete-entry/mode` Delete mode: permanently delete, or move to trash?
412
		 * @since 1.13.1
413
		 * @param string $delete_mode Delete mode: `trash` or `delete`. Default: `delete`
414
		 */
415
		$delete_mode = apply_filters( 'gravityview/delete-entry/mode', 'delete' );
416
417
		return ( 'trash' === $delete_mode ) ? 'trash' : 'delete';
418
	}
419
420
	/**
421
	 * @since 1.13.1
422
	 *
423
	 * @uses GFAPI::delete_entry()
424
	 * @uses GFAPI::update_entry_property()
425
	 *
426
	 * @return WP_Error|string "deleted" or "trashed" if successful, WP_Error if GFAPI::delete_entry() or updating entry failed.
427
	 */
428
	private function delete_or_trash_entry( $entry ) {
429
430
		$entry_id = $entry['id'];
431
432
		$mode = $this->get_delete_mode();
433
434
		if ( 'delete' === $mode ) {
435
436
			gravityview()->log->debug( 'Starting delete entry: {entry_id}', array( 'entry_id' => $entry_id ) );
437
438
			// Delete the entry
439
			$delete_response = GFAPI::delete_entry( $entry_id );
440
441
			if ( ! is_wp_error( $delete_response ) ) {
442
				$delete_response = 'deleted';
443
444
				/**
445
				 * @action `gravityview/delete-entry/deleted` Triggered when an entry is deleted
446
				 * @since 1.16.4
447
				 * @param  int $entry_id ID of the Gravity Forms entry
448
				 * @param  array $entry Deleted entry array
449
				*/
450
				do_action( 'gravityview/delete-entry/deleted', $entry_id, $entry );
451
			}
452
453 2
			gravityview()->log->debug( 'Delete response: {delete_response}', array( 'delete_response' => $delete_response ) );
454
455
		} else {
456
457
			gravityview()->log->debug( 'Starting trash entry: {entry_id}', array( 'entry_id' => $entry_id ) );
458
459
			$trashed = GFAPI::update_entry_property( $entry_id, 'status', 'trash' );
460
			new GravityView_Cache();
461
462
			if ( ! $trashed ) {
463
				$delete_response = new WP_Error( 'trash_entry_failed', __( 'Moving the entry to the trash failed.', 'gravityview' ) );
464
			} else {
465
466
				/**
467
				 * @action `gravityview/delete-entry/trashed` Triggered when an entry is trashed
468
				 * @since 1.16.4
469
				 * @param  int $entry_id ID of the Gravity Forms entry
470
				 * @param  array $entry Deleted entry array
471
				 */
472
				do_action( 'gravityview/delete-entry/trashed', $entry_id, $entry );
473
474
				$delete_response = 'trashed';
475
			}
476
477
			gravityview()->log->debug( ' Trashed? {delete_response}', array( 'delete_response' => $delete_response ) );
478
		}
479
480
		return $delete_response;
481
	}
482
483
	/**
484
	 * Delete or trash a post connected to an entry
485
	 *
486
	 * @since 1.17
487
	 *
488
	 * @param int $entry_id ID of entry being deleted/trashed
489
	 * @param array $entry Array of the entry being deleted/trashed
490
	 */
491
	public function process_connected_posts( $entry_id = 0, $entry = array() ) {
492
493
		// The entry had no connected post
494
		if ( empty( $entry['post_id'] ) ) {
495
			return;
496
		}
497
498
		/**
499
		 * @filter `gravityview/delete-entry/delete-connected-post` Should posts connected to an entry be deleted when the entry is deleted?
500
		 * @since 1.17
501
		 * @param boolean $delete_post If trashing an entry, trash the post. If deleting an entry, delete the post. Default: true
502
		 */
503
		$delete_post = apply_filters( 'gravityview/delete-entry/delete-connected-post', true );
504
505
		if ( false === $delete_post ) {
506
			return;
507
		}
508
509
		$action = current_action();
510
511
		if ( 'gravityview/delete-entry/deleted' === $action ) {
512
			$result = wp_delete_post( $entry['post_id'], true );
513
		} else {
514
			$result = wp_trash_post( $entry['post_id'] );
515
		}
516
517
		if ( false === $result ) {
518
			gravityview()->log->error(
519
				'(called by {action}): Error processing the Post connected to the entry.',
520
				array(
521
					'action' => $action,
522
					'data' => $entry,
523
				)
524
			);
525
		} else {
526
			gravityview()->log->debug(
527
				'(called by {action}): Successfully processed Post connected to the entry.',
528
				array(
529
					'action' => $action,
530
					'data' => $entry,
531
				)
532
			);
533
		}
534
	}
535
536
	/**
537
	 * Is the current nonce valid for editing the entry?
538
	 *
539
	 * @since 1.5.1
540
	 * @return boolean
541
	 */
542
	public function verify_nonce() {
543
544
		// No delete entry request was made
545
		if ( empty( $_GET['entry_id'] ) || empty( $_GET['delete'] ) ) {
546
			return false;
547
		}
548
549
		$nonce_key = self::get_nonce_key( $_GET['entry_id'] );
550
551
		$valid = wp_verify_nonce( $_GET['delete'], $nonce_key );
552
553
		/**
554
		 * @filter `gravityview/delete-entry/verify_nonce` Override Delete Entry nonce validation. Return true to declare nonce valid.
555
		 * @since 1.15.2
556
		 * @see wp_verify_nonce()
557
		 * @param int|boolean $valid False if invalid; 1 or 2 when nonce was generated
558
		 * @param string $nonce_key Name of nonce action used in wp_verify_nonce. $_GET['delete'] holds the nonce value itself. Default: `delete_{entry_id}`
559
		 */
560
		$valid = apply_filters( 'gravityview/delete-entry/verify_nonce', $valid, $nonce_key );
561
562
		return $valid;
563
	}
564
565
	/**
566
	 * Get the onclick attribute for the confirm dialogs that warns users before they delete an entry
567
	 *
568
	 * @since 1.5.1
569
	 * @return string HTML `onclick` attribute
570
	 */
571
	public static function get_confirm_dialog() {
572
573
		$confirm = __( 'Are you sure you want to delete this entry? This cannot be undone.', 'gravityview' );
574
575
		/**
576
		 * @filter `gravityview/delete-entry/confirm-text` Modify the Delete Entry Javascript confirmation text
577
		 * @param string $confirm Default: "Are you sure you want to delete this entry? This cannot be undone."
578
		 */
579
		$confirm = apply_filters( 'gravityview/delete-entry/confirm-text', $confirm );
580
581
		return 'return window.confirm(\'' . esc_js( $confirm ) . '\');';
582
	}
583
584
	/**
585
	 * Check if the user can edit the entry
586
	 *
587
	 * - Is the nonce valid?
588
	 * - Does the user have the right caps for the entry
589
	 * - Is the entry in the trash?
590
	 *
591
	 * @since 1.5.1
592
	 * @param  array $entry Gravity Forms entry array
593
	 * @return boolean|WP_Error        True: can edit form. WP_Error: nope.
594
	 */
595
	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...
596
597
		$error = null;
598
599
		if ( ! $this->verify_nonce() ) {
600
			$error = __( 'The link to delete this entry is not valid; it may have expired.', 'gravityview' );
601
		}
602
603
		if ( ! self::check_user_cap_delete_entry( $entry, array(), $view_id ) ) {
604
			$error = __( 'You do not have permission to delete this entry.', 'gravityview' );
605
		}
606
607
		if ( $entry['status'] === 'trash' ) {
608 22
			if ( 'trash' === $this->get_delete_mode() ) {
609
				$error = __( 'The entry is already in the trash.', 'gravityview' );
610 22
			} else {
611
				$error = __( 'You cannot delete the entry; it is already in the trash.', 'gravityview' );
612
			}
613
		}
614
615
		// No errors; everything's fine here!
616 22
		if ( empty( $error ) ) {
617
			return true;
618 22
		}
619
620
		gravityview()->log->error( '{error}', array( 'erorr' => $error ) );
621
622
		return new WP_Error( 'gravityview-delete-entry-permissions', $error );
623
	}
624
625
626
	/**
627
	 * checks if user has permissions to view the link or delete a specific entry
628
	 *
629
	 * @since 1.5.1
630
	 * @since 1.15 Added `$view_id` param
631
	 *
632
	 * @param  array $entry Gravity Forms entry array
633
	 * @param array $field Field settings (optional)
634
	 * @param int|\GV\View $view Pass a View ID to check caps against. If not set, check against current View (@deprecated no longer optional)
635
	 * @return bool
636
	 */
637
	public static function check_user_cap_delete_entry( $entry, $field = array(), $view = 0 ) {
638
		if ( ! $view ) {
639
			/** @deprecated path */
640
			$view_id = GravityView_View::getInstance()->getViewId();
641
			$view = \GV\View::by_id( $view_id );
642
		} else {
643
			if ( ! $view instanceof \GV\View ) {
644
				$view = \GV\View::by_id( $view );
645
			}
646
			$view_id = $view->ID;
647
		}
648
649
		$current_user = wp_get_current_user();
650
651
		$entry_id = isset( $entry['id'] ) ? $entry['id'] : null;
652
653
		// Or if they can delete any entries (as defined in Gravity Forms), we're good.
654
		if ( GVCommon::has_cap( array( 'gravityforms_delete_entries', 'gravityview_delete_others_entries' ), $entry_id ) ) {
655
656
			gravityview()->log->debug( 'Current user has `gravityforms_delete_entries` or `gravityview_delete_others_entries` capability.' );
657
658
			return true;
659
		}
660
661
		// If field options are passed, check if current user can view the link
662
		if ( ! empty( $field ) ) {
663
664
			// If capability is not defined, something is not right!
665
			if ( empty( $field['allow_edit_cap'] ) ) {
666
667
				gravityview()->log->error( 'Cannot read delete entry field caps', array( 'data' => $field ) );
668
669
				return false;
670
			}
671
672
			if ( GVCommon::has_cap( $field['allow_edit_cap'] ) ) {
673
674 23
				// Do not return true if cap is read, as we need to check if the current user created the entry
675 23
				if ( $field['allow_edit_cap'] !== 'read' ) {
676
					return true;
677
				}
678
			} else {
679
680 23
				gravityview()->log->debug( 'User {user_id} is not authorized to view delete entry link ', array( 'user_id' => $current_user->ID ) );
681 23
682
				return false;
683 23
			}
684
		}
685
686 23
		if ( ! isset( $entry['created_by'] ) ) {
687
688 23
			gravityview()->log->error( 'Entry `created_by` doesn\'t exist.' );
689
690
			return false;
691 23
		}
692
693 22
		$user_delete = $view->settings->get( 'user_delete' );
694
695 22
		// Only checks user_delete view option if view is already set
696
		if ( $view && empty( $user_delete ) ) {
697
			gravityview()->log->debug( 'User Delete is disabled. Returning false.' );
698
			return false;
699
		}
700 2
701
		// If the logged-in user is the same as the user who created the entry, we're good.
702
		if ( is_user_logged_in() && intval( $current_user->ID ) === intval( $entry['created_by'] ) ) {
703 1
704
			gravityview()->log->debug( 'User {user_id} created the entry.', array( 'user_id' => $current_user->ID ) );
705 1
706
			return true;
707 1
		}
708
709
		return false;
710
	}
711
712
713
	/**
714
	 * After processing delete entry, the user will be redirected to the referring View or embedded post/page. Display a message on redirection.
715
	 *
716
	 * If success, there will be `status` URL parameters `status=>success`
717
	 * If an error, there will be `status` and `message` URL parameters `status=>error&message=example`
718
	 *
719
	 * @since 1.15.2 Only show message when the URL parameter's View ID matches the current View ID
720
	 * @since 1.5.1
721
	 *
722
	 * @param int $current_view_id The ID of the View being rendered
723
	 * @return void
724
	 */
725
	public function maybe_display_message( $current_view_id = 0 ) {
726 1
727
		if ( empty( $_GET['status'] ) || ! self::verify_nonce() ) {
728
			return;
729
		}
730
731
		// Entry wasn't deleted from current View
732
		if ( isset( $_GET['view_id'] ) && intval( $_GET['view_id'] ) !== intval( $current_view_id ) ) {
733 1
			return;
734
		}
735
736 1
		$this->display_message();
737 1
	}
738 1
739
	public function display_message() {
740
741
		if ( empty( $_GET['status'] ) || empty( $_GET['delete'] ) ) {
742
			return;
743
		}
744
745
		$status = esc_attr( $_GET['status'] );
746
		$message_from_url = \GV\Utils::_GET( 'message' );
747
		$message_from_url = rawurldecode( stripslashes_deep( $message_from_url ) );
748
		$class = '';
749
750
		switch ( $status ) {
751
			case 'error':
752
				$class = ' gv-error error';
753
				$error_message = __( 'There was an error deleting the entry: %s', 'gravityview' );
754
				$message = sprintf( $error_message, $message_from_url );
755
				break;
756
			case 'trashed':
757
				$message = __( 'The entry was successfully moved to the trash.', 'gravityview' );
758
				break;
759
			default:
760
				$message = __( 'The entry was successfully deleted.', 'gravityview' );
761
				break;
762
		}
763
764
		/**
765 37
		 * @filter `gravityview/delete-entry/message` Modify the Delete Entry messages
766 37
		 * @since 1.13.1
767 37
		 * @param string $message Message to be displayed
768
		 * @param string $status Message status (`error` or `success`)
769
		 * @param string $message_from_url The original error message, if any, without the "There was an error deleting the entry:" prefix
770
		 */
771
		$message = apply_filters( 'gravityview/delete-entry/message', esc_attr( $message ), $status, $message_from_url );
772
773
		echo GVCommon::generate_notice( $message, $class );
774
	}
775
776
777
} // end class
778
779
GravityView_Delete_Entry::getInstance();
780
781