Completed
Push — develop ( 2c39bb...a4e86c )
by Gennady
15:59 queued 10s
created

GravityView_Delete_Entry::maybe_not_visible()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

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