Completed
Pull Request — develop (#1499)
by Zack
34:04 queued 14:05
created

process_connected_posts()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 11.4436

Importance

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