Completed
Push — develop ( 72e688...d5488d )
by Zack
06:52
created

GravityView_Delete_Entry   F

Complexity

Total Complexity 81

Size/Duplication

Total Lines 781
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 10

Test Coverage

Coverage 25.87%

Importance

Changes 0
Metric Value
dl 0
loc 781
ccs 66
cts 255
cp 0.2587
rs 1.819
c 0
b 0
f 0
wmc 81
lcom 2
cbo 10

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A set_entry() 0 3 2
A add_hooks() 0 24 1
A getInstance() 0 8 2
B maybe_not_visible() 0 31 7
A add_template_path() 0 8 1
A delete_link_field_options() 0 28 1
A add_default_field() 0 12 2
A add_available_field() 0 12 1
A modify_visibility_caps() 0 15 2
A get_nonce_key() 0 3 1
A get_delete_link() 0 31 4
A add_delete_button() 0 27 3
B process_delete() 0 72 8
A get_delete_mode() 0 11 2
A delete_or_trash_entry() 0 54 4
A process_connected_posts() 0 32 5
A verify_nonce() 0 22 3
A get_confirm_dialog() 0 12 1
B user_can_delete_entry() 0 29 6
C check_user_cap_delete_entry() 0 77 14
A maybe_display_message() 0 12 5
A display_message() 0 37 5

How to fix   Complexity   

Complex Class

Complex classes like GravityView_Delete_Entry often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GravityView_Delete_Entry, and based on these observations, apply Extract Interface, too.

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