Completed
Push — develop ( 8b20d3...ccc9af )
by Zack
06:46
created

GravityView_Delete_Entry::get_delete_mode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

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 11
ccs 0
cts 3
cp 0
crap 6
rs 9.9
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    Katz Web Services, Inc.
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
	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...
32
33
		self::$file = plugin_dir_path( __FILE__ );
34
35
		$this->add_hooks();
36
	}
37
38
	/**
39
	 * @since 1.9.2
40
	 */
41
	private function add_hooks() {
42
43
		add_action( 'wp', array( $this, 'process_delete' ), 10000 );
44
45
		add_filter( 'gravityview_entry_default_fields', array( $this, 'add_default_field'), 10, 3 );
46
47
		add_action( 'gravityview_before', array( $this, 'maybe_display_message' ) );
48
49
		// For the Delete Entry Link, you don't want visible to all users.
50
		add_filter( 'gravityview_field_visibility_caps', array( $this, 'modify_visibility_caps'), 10, 5 );
51
52
		// Modify the field options based on the name of the field type
53
		add_filter( 'gravityview_template_delete_link_options', array( $this, 'delete_link_field_options' ), 10, 5 );
54
55
		// add template path to check for field
56
		add_filter( 'gravityview_template_paths', array( $this, 'add_template_path' ) );
57
58
		add_action( 'gravityview/edit-entry/publishing-action/after', array( $this, 'add_delete_button'), 10, 4 );
59
60
		add_action ( 'gravityview/delete-entry/deleted', array( $this, 'process_connected_posts' ), 10, 2 );
61
		add_action ( 'gravityview/delete-entry/trashed', array( $this, 'process_connected_posts' ), 10, 2 );
62
63
		add_filter( 'gravityview/field/is_visible', array( $this, 'maybe_not_visible' ), 10, 3 );
64
	}
65
66
	/**
67
	 * Return the instantiated class object
68
	 *
69
	 * @since  1.5.1
70
	 * @return GravityView_Delete_Entry
71
	 */
72 22
	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...
73
74 22
		if( empty( self::$instance ) ) {
75
			self::$instance = new self;
76
		}
77
78 22
		return self::$instance;
79
	}
80
81
	/**
82
	 * Hide the field or not.
83
	 *
84
	 * For non-logged in users.
85
	 * For users that have no delete rights on any of the current entries.
86
	 *
87
	 * @param bool $visible Visible or not.
88
	 * @param \GV\Field $field The field.
89
	 * @param \GV\View $view The View context.
90
	 *
91
	 * @return bool
92
	 */
93 44
	public function maybe_not_visible( $visible, $field, $view ) {
94 44
		if ( 'delete_link' !== $field->ID ) {
95 44
			return $visible;
96
		}
97
98
		if ( ! $view ) {
99
			return $visible;
100
		}
101
102
		static $visibility_cache_for_view = array();
103
104
		if ( ! is_null( $result = \GV\Utils::get( $visibility_cache_for_view, $view->ID, null ) ) ) {
105
			return $result;
106
		}
107
108
		foreach ( $view->get_entries()->all() as $entry ) {
109
			if ( self::check_user_cap_delete_entry( $entry->as_entry(), $field->as_configuration(), $view ) ) {
110
				// At least one entry is deletable for this user
111
				$visibility_cache_for_view[ $view->ID ] = true;
112
				return true;
113
			}
114
		}
115
116
		$visibility_cache_for_view[ $view->ID ] = false;
117
118
		return false;
119
	}
120
121
	/**
122
	 * Include this extension templates path
123
	 *
124
	 * @since  1.5.1
125
	 * @param array $file_paths List of template paths ordered
126
	 */
127 1
	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...
128
129
		// Index 100 is the default GravityView template path.
130
		// Index 110 is Edit Entry link
131 1
		$file_paths[ 115 ] = self::$file;
132
133 1
		return $file_paths;
134
	}
135
136
	/**
137
	 * Add "Delete Link Text" setting to the edit_link field settings
138
	 *
139
	 * @since  1.5.1
140
	 * @param  [type] $field_options [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...
141
	 * @param  [type] $template_id   [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...
142
	 * @param  [type] $field_id      [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...
143
	 * @param  [type] $context       [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...
144
	 * @param  [type] $input_type    [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...
145
	 * @return [type]                [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...
146
	 */
147
	function delete_link_field_options( $field_options, $template_id, $field_id, $context, $input_type ) {
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...
148
149
		// Always a link, never a filter
150
		unset( $field_options['show_as_link'], $field_options['search_filter'] );
151
152
		// Delete Entry link should only appear to visitors capable of editing entries
153
		unset( $field_options['only_loggedin'], $field_options['only_loggedin_cap'] );
154
155
		$add_option['delete_link'] = array(
0 ignored issues
show
Coding Style Comprehensibility introduced by
$add_option was never initialized. Although not strictly required by PHP, it is generally a good practice to add $add_option = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
156
			'type' => 'text',
157
			'label' => __( 'Delete Link Text', 'gravityview' ),
158
			'desc' => NULL,
159
			'value' => __('Delete Entry', 'gravityview'),
160
			'merge_tags' => true,
161
		);
162
163
		$field_options['allow_edit_cap'] = array(
164
			'type' => 'select',
165
			'label' => __( 'Allow the following users to delete the entry:', 'gravityview' ),
166
			'choices' => GravityView_Render_Settings::get_cap_choices( $template_id, $field_id, $context, $input_type ),
167
			'tooltip' => 'allow_edit_cap',
168
			'class' => 'widefat',
169
			'value' => 'read', // Default: entry creator
170
		);
171
172
173
		return array_merge( $add_option, $field_options );
174
	}
175
176
177
	/**
178
	 * Add Edit Link as a default field, outside those set in the Gravity Form form
179
	 *
180
	 * @since 1.5.1
181
	 * @param array $entry_default_fields Existing fields
182
	 * @param  string|array $form form_ID or form object
183
	 * @param  string $zone   Either 'single', 'directory', 'edit', 'header', 'footer'
184
	 */
185
	function add_default_field( $entry_default_fields, $form = array(), $zone = '' ) {
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...
186
187
		if( 'edit' !== $zone ) {
188
			$entry_default_fields['delete_link'] = array(
189
				'label' => __( 'Delete Entry', 'gravityview' ),
190
				'type'  => 'delete_link',
191
				'desc'  => __( 'A link to delete the entry. Respects the Delete Entry permissions.', 'gravityview' ),
192
			);
193
		}
194
195
		return $entry_default_fields;
196
	}
197
198
	/**
199
	 * Add Delete Entry Link to the Add Field dialog
200
	 * @since 1.5.1
201
	 * @param array $available_fields
202
	 */
203
	function add_available_field( $available_fields = array() ) {
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...
204
205
		$available_fields['delete_link'] = array(
206
			'label_text' => __( 'Delete Entry', 'gravityview' ),
207
			'field_id' => 'delete_link',
208
			'label_type' => 'field',
209
			'input_type' => 'delete_link',
210
			'field_options' => NULL
211
		);
212
213
		return $available_fields;
214
	}
215
216
	/**
217
	 * Change wording for the Edit context to read Entry Creator
218
	 *
219
	 * @since 1.5.1
220
	 * @param  array 	   $visibility_caps        Array of capabilities to display in field dropdown.
221
	 * @param  string      $field_type  Type of field options to render (`field` or `widget`)
0 ignored issues
show
Bug introduced by
There is no parameter named $field_type. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
222
	 * @param  string      $template_id Table slug
223
	 * @param  float       $field_id    GF Field ID - Example: `3`, `5.2`, `entry_link`, `created_by`
224
	 * @param  string      $context     What context are we in? Example: `single` or `directory`
225
	 * @param  string      $input_type  (textarea, list, select, etc.)
226
	 * @return array                   Array of field options with `label`, `value`, `type`, `default` keys
227
	 */
228
	public function modify_visibility_caps( $visibility_caps = array(), $template_id = '', $field_id = '', $context = '', $input_type = '' ) {
0 ignored issues
show
Unused Code introduced by
The parameter $context 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...
Unused Code introduced by
The parameter $input_type 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...
229
230
		$caps = $visibility_caps;
231
232
		// If we're configuring fields in the edit context, we want a limited selection
233
		if( $field_id === 'delete_link' ) {
234
235
			// Remove other built-in caps.
236
			unset( $caps['publish_posts'], $caps['gravityforms_view_entries'], $caps['delete_others_posts'] );
237
238
			$caps['read'] = _x('Entry Creator', 'User capability', 'gravityview');
239
		}
240
241
		return $caps;
242
	}
243
244
	/**
245
	 * Make sure there's an entry
246
	 *
247
	 * @since 1.5.1
248
	 * @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...
249
	 */
250 22
	function set_entry( $entry = 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...
251 22
		$this->entry = empty( $entry ) ? GravityView_View::getInstance()->entries[0] : $entry;
0 ignored issues
show
Documentation introduced by
The property $entries is declared protected in GravityView_View. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

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