Completed
Push — develop ( 3e7a99...e7c8cf )
by Zack
39:08 queued 19:08
created

GravityView_Delete_Entry::get_delete_link()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

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