Completed
Pull Request — develop (#1499)
by Zack
16:17
created

GravityView_Delete_Entry::display_message()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
nc 4
nop 0
dl 0
loc 37
ccs 0
cts 0
cp 0
crap 30
rs 9.0168
c 0
b 0
f 0
1
<?php
2
/**
3
 * The GravityView Delete Entry Extension
4
 *
5
 * Delete entries in GravityView.
6
 *
7
 * @since     1.5.1
8
 * @package   GravityView
9
 * @license   GPL2+
10
 * @author    GravityView <[email protected]>
11
 * @link      http://gravityview.co
12
 * @copyright Copyright 2014, Katz Web Services, Inc.
13
 */
14
15
if ( ! defined( 'WPINC' ) ) {
16
	die;
17
}
18
19
/**
20
 * @since 1.5.1
21
 */
22
final class GravityView_Delete_Entry {
23
24
	static $file;
25
	static $instance;
26
	var $entry;
27
	var $form;
28
	var $view_id;
29
	var $is_valid = null;
30
31
	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
		if ( is_admin() ) {
36
			$this->load_components( 'admin' );
37
		}
38
39
		$this->add_hooks();
40
	}
41
42
	private function load_components( $component ) {
43
44
		$dir = trailingslashit( self::$file );
45
46
		$filename  = $dir . 'class-delete-entry-' . $component . '.php';
47
		$classname = 'GravityView_Delete_Entry_' . str_replace( ' ', '_', ucwords( str_replace( '-', ' ', $component ) ) );
48
49
		// Loads component and pass extension's instance so that component can
50
		// talk each other.
51
		require_once $filename;
52
		$this->instances[ $component ] = new $classname( $this );
0 ignored issues
show
Bug introduced by
The property instances does not seem to exist. Did you mean instance?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
53
		$this->instances[ $component ]->load();
0 ignored issues
show
Bug introduced by
The property instances does not seem to exist. Did you mean instance?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

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