Completed
Pull Request — master (#1461)
by Zack
17:37 queued 14:56
created

GravityView_Delete_Entry::add_default_field()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 3
dl 0
loc 12
ccs 0
cts 7
cp 0
crap 6
rs 9.8666
c 0
b 0
f 0
1
<?php
2
/**
3
 * The GravityView Delete Entry Extension
4
 *
5
 * Delete entries in GravityView.
6
 *
7
 * @since     1.5.1
8
 * @package   GravityView
9
 * @license   GPL2+
10
 * @author    Katz Web Services, Inc.
11
 * @link      http://gravityview.co
12
 * @copyright Copyright 2014, Katz Web Services, Inc.
13
 */
14
15
if ( ! defined( 'WPINC' ) ) {
16
	die;
17
}
18
19
/**
20
 * @since 1.5.1
21
 */
22
final class GravityView_Delete_Entry {
23
24
	static $file;
25
	static $instance;
26
	var $entry;
27
	var $form;
28
	var $view_id;
29
	var $is_valid = NULL;
30
31
	function __construct() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
32
33
		self::$file = plugin_dir_path( __FILE__ );
34
35
		$this->add_hooks();
36
	}
37
38
	/**
39
	 * @since 1.9.2
40
	 */
41
	private function add_hooks() {
42
43
		add_action( 'wp', array( $this, 'process_delete' ), 10000 );
44
45
		add_filter( 'gravityview_entry_default_fields', array( $this, 'add_default_field'), 10, 3 );
46
47
		add_action( 'gravityview_before', array( $this, 'maybe_display_message' ) );
48
49
		// For the Delete Entry Link, you don't want visible to all users.
50
		add_filter( 'gravityview_field_visibility_caps', array( $this, 'modify_visibility_caps'), 10, 5 );
51
52
		// Modify the field options based on the name of the field type
53
		add_filter( 'gravityview_template_delete_link_options', array( $this, 'delete_link_field_options' ), 10, 5 );
54
55
		// add template path to check for field
56
		add_filter( 'gravityview_template_paths', array( $this, 'add_template_path' ) );
57
58
		add_action( 'gravityview/edit-entry/publishing-action/after', array( $this, 'add_delete_button'), 10, 4 );
59
60
		add_action ( 'gravityview/delete-entry/deleted', array( $this, 'process_connected_posts' ), 10, 2 );
61
		add_action ( 'gravityview/delete-entry/trashed', array( $this, 'process_connected_posts' ), 10, 2 );
62
63
		add_filter( 'gravityview/field/is_visible', array( $this, 'maybe_not_visible' ), 10, 3 );
64
65
		add_action( 'gravityview/metaboxes/permissions_after', array( $this, 'view_settings_permissions_metabox' ) );
66
	}
67
68
	/**
69
	 * Return the instantiated class object
70
	 *
71
	 * @since  1.5.1
72
	 * @return GravityView_Delete_Entry
73
	 */
74 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...
75
76 22
		if( empty( self::$instance ) ) {
77
			self::$instance = new self;
78
		}
79
80 22
		return self::$instance;
81
	}
82
83
	/**
84
	 * Hide the field or not.
85
	 *
86
	 * For non-logged in users.
87
	 * For users that have no delete rights on any of the current entries.
88
	 *
89
	 * @param bool $visible Visible or not.
90
	 * @param \GV\Field $field The field.
91
	 * @param \GV\View $view The View context.
92
	 *
93
	 * @return bool
94
	 */
95 44
	public function maybe_not_visible( $visible, $field, $view ) {
96 44
		if ( 'delete_link' !== $field->ID ) {
97 44
			return $visible;
98
		}
99
100
		if ( ! $view ) {
101
			return $visible;
102
		}
103
104
		static $visibility_cache_for_view = array();
105
106
		if ( ! is_null( $result = \GV\Utils::get( $visibility_cache_for_view, $view->ID, null ) ) ) {
107
			return $result;
108
		}
109
110
		foreach ( $view->get_entries()->all() as $entry ) {
111
			if ( self::check_user_cap_delete_entry( $entry->as_entry(), $field->as_configuration(), $view ) ) {
112
				// At least one entry is deletable for this user
113
				$visibility_cache_for_view[ $view->ID ] = true;
114
				return true;
115
			}
116
		}
117
118
		$visibility_cache_for_view[ $view->ID ] = false;
119
120
		return false;
121
	}
122
123
	/**
124
	 * Include this extension templates path
125
	 *
126
	 * @since  1.5.1
127
	 * @param array $file_paths List of template paths ordered
128
	 */
129 1
	function add_template_path( $file_paths ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
130
131
		// Index 100 is the default GravityView template path.
132
		// Index 110 is Edit Entry link
133 1
		$file_paths[ 115 ] = self::$file;
134
135 1
		return $file_paths;
136
	}
137
138
	/**
139
	 * Add "Delete Link Text" setting to the edit_link field settings
140
	 *
141
	 * @since  1.5.1
142
	 * @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...
143
	 * @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...
144
	 * @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...
145
	 * @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...
146
	 * @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...
147
	 * @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...
148
	 */
149
	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...
150
151
		// Always a link, never a filter
152
		unset( $field_options['show_as_link'], $field_options['search_filter'] );
153
154
		// Delete Entry link should only appear to visitors capable of editing entries
155
		unset( $field_options['only_loggedin'], $field_options['only_loggedin_cap'] );
156
157
		$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...
158
			'type' => 'text',
159
			'label' => __( 'Delete Link Text', 'gravityview' ),
160
			'desc' => NULL,
161
			'value' => __('Delete Entry', 'gravityview'),
162
			'merge_tags' => true,
163
		);
164
165
		$field_options['allow_edit_cap'] = array(
166
			'type' => 'select',
167
			'label' => __( 'Allow the following users to delete the entry:', 'gravityview' ),
168
			'choices' => GravityView_Render_Settings::get_cap_choices( $template_id, $field_id, $context, $input_type ),
169
			'tooltip' => 'allow_edit_cap',
170
			'class' => 'widefat',
171
			'value' => 'read', // Default: entry creator
172
		);
173
174
175
		return array_merge( $add_option, $field_options );
176
	}
177
178
179
	/**
180
	 * Add Edit Link as a default field, outside those set in the Gravity Form form
181
	 *
182
	 * @since 1.5.1
183
	 * @param array $entry_default_fields Existing fields
184
	 * @param  string|array $form form_ID or form object
185
	 * @param  string $zone   Either 'single', 'directory', 'edit', 'header', 'footer'
186
	 */
187
	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...
188
189
		if( 'edit' !== $zone ) {
190
			$entry_default_fields['delete_link'] = array(
191
				'label' => __( 'Delete Entry', 'gravityview' ),
192
				'type'  => 'delete_link',
193
				'desc'  => __( 'A link to delete the entry. Respects the Delete Entry permissions.', 'gravityview' ),
194
			);
195
		}
196
197
		return $entry_default_fields;
198
	}
199
200
	/**
201
	 * Add Delete Entry Link to the Add Field dialog
202
	 * @since 1.5.1
203
	 * @param array $available_fields
204
	 */
205
	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...
206
207
		$available_fields['delete_link'] = array(
208
			'label_text' => __( 'Delete Entry', 'gravityview' ),
209
			'field_id' => 'delete_link',
210
			'label_type' => 'field',
211
			'input_type' => 'delete_link',
212
			'field_options' => null,
213
		);
214
215
		return $available_fields;
216
	}
217
218
	/**
219
	 * Render Delete Entry Permissions settings
220
	 *
221
	 * @since 2.9
222
	 *
223
	 * @param $current_settings
224
	 *
225
	 * @return void
226
	 */
227
	public function view_settings_permissions_metabox( $current_settings ) {
228
229
		GravityView_Render_Settings::render_setting_row( 'user_delete', $current_settings );
230
231
	}
232
233
	/**
234
	 * Change wording for the Edit context to read Entry Creator
235
	 *
236
	 * @since 1.5.1
237
	 * @param  array 	   $visibility_caps        Array of capabilities to display in field dropdown.
238
	 * @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...
239
	 * @param  string      $template_id Table slug
240
	 * @param  float       $field_id    GF Field ID - Example: `3`, `5.2`, `entry_link`, `created_by`
241
	 * @param  string      $context     What context are we in? Example: `single` or `directory`
242
	 * @param  string      $input_type  (textarea, list, select, etc.)
243
	 * @return array                   Array of field options with `label`, `value`, `type`, `default` keys
244
	 */
245
	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...
246
247
		$caps = $visibility_caps;
248
249
		// If we're configuring fields in the edit context, we want a limited selection
250
		if( $field_id === 'delete_link' ) {
251
252
			// Remove other built-in caps.
253
			unset( $caps['publish_posts'], $caps['gravityforms_view_entries'], $caps['delete_others_posts'] );
254
255
			$caps['read'] = _x('Entry Creator', 'User capability', 'gravityview');
256
		}
257
258
		return $caps;
259
	}
260
261
	/**
262
	 * Make sure there's an entry
263
	 *
264
	 * @since 1.5.1
265
	 * @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...
266
	 */
267 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...
268 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...
269 22
	}
270
271
	/**
272
	 * Generate a consistent nonce key based on the Entry ID
273
	 *
274
	 * @since 1.5.1
275
	 * @param  int $entry_id Entry ID
276
	 * @return string           Key used to validate request
277
	 */
278
	public static function get_nonce_key( $entry_id ) {
279
		return sprintf( 'delete_%s', $entry_id );
280
	}
281
282
283
	/**
284
	 * Generate a nonce link with the base URL of the current View embed
285
	 *
286
	 * We don't want to link to the single entry, because when deleted, there would be nothing to return to.
287
	 *
288
	 * @since 1.5.1
289
	 * @param  array       $entry Gravity Forms entry array
290
	 * @param  int         $view_id The View id. Not optional since 2.0
291
	 * @return string|null If directory link is valid, the URL to process the delete request. Otherwise, `NULL`.
292
	 */
293 22
	public static function get_delete_link( $entry, $view_id = 0, $post_id = null ) {
294 22
		if ( ! $view_id ) {
295
			/** @deprecated path */
296
			$view_id = gravityview_get_view_id();
297
		}
298
299 22
		self::getInstance()->set_entry( $entry );
300
301 22
        $base = GravityView_API::directory_link( $post_id ? : $view_id, true );
302
303 22
		if ( empty( $base ) ) {
304
			gravityview()->log->error( 'Post ID does not exist: {post_id}', array( 'post_id' => $post_id ) );
305
			return NULL;
306
		}
307
308 22
		$gv_entry = \GV\GF_Entry::from_entry( $entry );
309
310
		// Use the slug instead of the ID for consistent security
311 22
		$entry_slug = $gv_entry->get_slug();
312
313 22
		$actionurl = add_query_arg( array(
314 22
			'action'	=> 'delete',
315 22
			'entry_id'		=> $entry_slug,
316 22
			'gvid' => $view_id,
317 22
            'view_id' => $view_id,
318 22
		), $base );
319
320 22
		$url = wp_nonce_url( $actionurl, 'delete_'.$entry_slug, 'delete' );
321
322 22
		return $url;
323
	}
324
325
326
	/**
327
	 * Add a Delete button to the #publishing-action section of the Delete Entry form
328
	 *
329
	 * @since 1.5.1
330
	 * @since 2.0.13 Added $post_id
331
	 *
332
	 * @param array $form    Gravity Forms form array
333
	 * @param array $entry   Gravity Forms entry array
334
	 * @param int $view_id GravityView View ID
335
	 * @param int $post_id Current post ID. May be same as View ID.
336
	 *
337
	 * @return void
338
	 */
339 22
	public function add_delete_button( $form = array(), $entry = array(), $view_id = null, $post_id = null ) {
340
341
		// Only show the link to those who are allowed to see it.
342 22
		if( !self::check_user_cap_delete_entry( $entry, array(), $view_id ) ) {
343 1
			return;
344
		}
345
346
		/**
347
		 * @filter `gravityview/delete-entry/show-delete-button` Should the Delete button be shown in the Edit Entry screen?
348
		 * @param boolean $show_entry Default: true
349
		 */
350 21
		$show_delete_button = apply_filters( 'gravityview/delete-entry/show-delete-button', true );
351
352
		// If the button is hidden by the filter, don't show.
353 21
		if( !$show_delete_button ) {
354
			return;
355
		}
356
357
		$attributes = array(
358 21
			'class' => 'btn btn-sm button button-small alignright pull-right btn-danger gv-button-delete',
359 21
			'tabindex' => ( GFCommon::$tab_index ++ ),
360 21
			'onclick' => self::get_confirm_dialog(),
361
		);
362
363 21
		echo gravityview_get_link( self::get_delete_link( $entry, $view_id, $post_id ), esc_attr__( 'Delete', 'gravityview' ), $attributes );
364
365 21
	}
366
367
	/**
368
	 * Handle the deletion request, if $_GET['action'] is set to "delete"
369
	 *
370
	 * 1. Check referrer validity
371
	 * 2. Make sure there's an entry with the slug of $_GET['entry_id']
372
	 * 3. If so, attempt to delete the entry. If not, set the error status
373
	 * 4. Remove `action=delete` from the URL
374
	 * 5. Redirect to the page using `wp_safe_redirect()`
375
	 *
376
	 * @since 1.5.1
377
	 * @uses wp_safe_redirect()
378
	 * @return void
379
	 */
380 2
	function process_delete() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
381
382
		// If the form is submitted
383 2
		if( isset( $_GET['action'] ) && 'delete' === $_GET['action'] && isset( $_GET['entry_id'] ) ) {
384
385
			// Make sure it's a GravityView request
386
			$valid_nonce_key = wp_verify_nonce( $_GET['delete'], self::get_nonce_key( $_GET['entry_id'] ) );
387
388
			if( ! $valid_nonce_key ) {
389
				gravityview()->log->debug( 'Delete entry not processed: nonce validation failed.' );
390
				return;
391
			}
392
393
			// Get the entry slug
394
			$entry_slug = esc_attr( $_GET['entry_id'] );
395
396
			// See if there's an entry there
397
			$entry = gravityview_get_entry( $entry_slug, true, false );
398
399
			if( $entry ) {
400
401
				$has_permission = $this->user_can_delete_entry( $entry, \GV\Utils::_GET( 'gvid', \GV\Utils::_GET( 'view_id' ) ) );
402
403
				if( is_wp_error( $has_permission ) ) {
404
405
					$messages = array(
406
						'message' => urlencode( $has_permission->get_error_message() ),
407
						'status' => 'error',
408
					);
409
410
				} else {
411
412
					// Delete the entry
413
					$delete_response = $this->delete_or_trash_entry( $entry );
414
415
					if( is_wp_error( $delete_response ) ) {
416
417
						$messages = array(
418
							'message' => urlencode( $delete_response->get_error_message() ),
419
							'status' => 'error',
420
						);
421
422
					} else {
423
424
						$messages = array(
425
							'status' => $delete_response,
426
						);
427
428
					}
429
430
				}
431
432
			} else {
433
434
				gravityview()->log->debug( 'Delete entry failed: there was no entry with the entry slug {entry_slug}', array( 'entry_slug' => $entry_slug ) );
435
436
				$messages = array(
437
					'message' => urlencode( __('The entry does not exist.', 'gravityview') ),
438
					'status' => 'error',
439
				);
440
			}
441
442
			$redirect_to_base = esc_url_raw( remove_query_arg( array( 'action', 'gvid', 'entry_id' ) ) );
443
			$redirect_to = add_query_arg( $messages, $redirect_to_base );
444
445
			wp_safe_redirect( $redirect_to );
446
447
			exit();
448
449
		} // endif action is delete.
450
451 2
	}
452
453
	/**
454
	 * Delete mode: permanently delete, or move to trash?
455
	 *
456
	 * @return string `delete` or `trash`
457
	 */
458
	private function get_delete_mode() {
459
460
		/**
461
		 * @filter `gravityview/delete-entry/mode` Delete mode: permanently delete, or move to trash?
462
		 * @since 1.13.1
463
		 * @param string $delete_mode Delete mode: `trash` or `delete`. Default: `delete`
464
		 */
465
		$delete_mode = apply_filters( 'gravityview/delete-entry/mode', 'delete' );
466
467
		return ( 'trash' === $delete_mode ) ? 'trash' : 'delete';
468
	}
469
470
	/**
471
	 * @since 1.13.1
472
	 * @see GFAPI::delete_entry()
473
	 * @return WP_Error|boolean GFAPI::delete_entry() returns a WP_Error on error
474
	 */
475
	private function delete_or_trash_entry( $entry ) {
476
477
		$entry_id = $entry['id'];
478
479
		$mode = $this->get_delete_mode();
480
481
		if( 'delete' === $mode ) {
482
483
			gravityview()->log->debug( 'Starting delete entry: {entry_id}', array( 'entry_id' => $entry_id ) );
484
485
			// Delete the entry
486
			$delete_response = GFAPI::delete_entry( $entry_id );
487
488
			if( ! is_wp_error( $delete_response ) ) {
489
				$delete_response = 'deleted';
490
491
				/**
492
				 * @action `gravityview/delete-entry/deleted` Triggered when an entry is deleted
493
				 * @since 1.16.4
494
				 * @param  int $entry_id ID of the Gravity Forms entry
495
				 * @param  array $entry Deleted entry array
496
				*/
497
				do_action( 'gravityview/delete-entry/deleted', $entry_id, $entry );
498
			}
499
500
			gravityview()->log->debug( 'Delete response: {delete_response}', array( 'delete_response' => $delete_response ) );
501
502
		} else {
503
504
			gravityview()->log->debug( 'Starting trash entry: {entry_id}', array( 'entry_id' => $entry_id ) );
505
506
			$trashed = GFAPI::update_entry_property( $entry_id, 'status', 'trash' );
507
			new GravityView_Cache;
508
509
			if( ! $trashed ) {
510
				$delete_response = new WP_Error( 'trash_entry_failed', __('Moving the entry to the trash failed.', 'gravityview' ) );
511
			} else {
512
513
				/**
514
				 * @action `gravityview/delete-entry/trashed` Triggered when an entry is trashed
515
				 * @since 1.16.4
516
				 * @param  int $entry_id ID of the Gravity Forms entry
517
				 * @param  array $entry Deleted entry array
518
				 */
519
				do_action( 'gravityview/delete-entry/trashed', $entry_id, $entry );
520
521
				$delete_response = 'trashed';
522
			}
523
524
			gravityview()->log->debug( ' Trashed? {delete_response}', array( 'delete_response' => $delete_response ) );
525
		}
526
527
		return $delete_response;
528
	}
529
530
	/**
531
	 * Delete or trash a post connected to an entry
532
	 *
533
	 * @since 1.17
534
	 *
535
	 * @param int $entry_id ID of entry being deleted/trashed
536
	 * @param array $entry Array of the entry being deleted/trashed
537
	 */
538
	public function process_connected_posts( $entry_id = 0, $entry = array() ) {
539
540
		// The entry had no connected post
541
		if( empty( $entry['post_id'] ) ) {
542
			return;
543
		}
544
545
		/**
546
		 * @filter `gravityview/delete-entry/delete-connected-post` Should posts connected to an entry be deleted when the entry is deleted?
547
		 * @since 1.17
548
		 * @param boolean $delete_post If trashing an entry, trash the post. If deleting an entry, delete the post. Default: true
549
		 */
550
		$delete_post = apply_filters( 'gravityview/delete-entry/delete-connected-post', true );
551
552
		if( false === $delete_post ) {
553
			return;
554
		}
555
556
		$action = current_action();
557
558
		if( 'gravityview/delete-entry/deleted' === $action ) {
559
			$result = wp_delete_post( $entry['post_id'], true );
560
		} else {
561
			$result = wp_trash_post( $entry['post_id'] );
562
		}
563
564
		if( false === $result ) {
565
			gravityview()->log->error( '(called by {action}): Error processing the Post connected to the entry.', array( 'action' => $action, 'data' => $entry ) );
566
		} else {
567
			gravityview()->log->debug( '(called by {action}): Successfully processed Post connected to the entry.', array( 'action' => $action, 'data' => $entry ) );
568
		}
569
	}
570
571
	/**
572
	 * Is the current nonce valid for editing the entry?
573
	 *
574
	 * @since 1.5.1
575
	 * @return boolean
576
	 */
577
	public function verify_nonce() {
578
579
		// No delete entry request was made
580
		if( empty( $_GET['entry_id'] ) || empty( $_GET['delete'] ) ) {
581
			return false;
582
		}
583
584
		$nonce_key = self::get_nonce_key( $_GET['entry_id'] );
585
586
		$valid = wp_verify_nonce( $_GET['delete'], $nonce_key );
587
588
		/**
589
		 * @filter `gravityview/delete-entry/verify_nonce` Override Delete Entry nonce validation. Return true to declare nonce valid.
590
		 * @since 1.15.2
591
		 * @see wp_verify_nonce()
592
		 * @param int|boolean $valid False if invalid; 1 or 2 when nonce was generated
593
		 * @param string $nonce_key Name of nonce action used in wp_verify_nonce. $_GET['delete'] holds the nonce value itself. Default: `delete_{entry_id}`
594
		 */
595
		$valid = apply_filters( 'gravityview/delete-entry/verify_nonce', $valid, $nonce_key );
596
597
		return $valid;
598
	}
599
600
	/**
601
	 * Get the onclick attribute for the confirm dialogs that warns users before they delete an entry
602
	 *
603
	 * @since 1.5.1
604
	 * @return string HTML `onclick` attribute
605
	 */
606 22
	public static function get_confirm_dialog() {
607
608 22
		$confirm = __('Are you sure you want to delete this entry? This cannot be undone.', 'gravityview');
609
610
		/**
611
		 * @filter `gravityview/delete-entry/confirm-text` Modify the Delete Entry Javascript confirmation text
612
		 * @param string $confirm Default: "Are you sure you want to delete this entry? This cannot be undone."
613
		 */
614 22
		$confirm = apply_filters( 'gravityview/delete-entry/confirm-text', $confirm );
615
616 22
		return 'return window.confirm(\''. esc_js( $confirm ) .'\');';
617
	}
618
619
	/**
620
	 * Check if the user can edit the entry
621
	 *
622
	 * - Is the nonce valid?
623
	 * - Does the user have the right caps for the entry
624
	 * - Is the entry in the trash?
625
	 *
626
	 * @since 1.5.1
627
	 * @param  array $entry Gravity Forms entry array
628
	 * @return boolean|WP_Error        True: can edit form. WP_Error: nope.
629
	 */
630
	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...
631
632
		$error = NULL;
633
634
		if( ! $this->verify_nonce() ) {
635
			$error = __( 'The link to delete this entry is not valid; it may have expired.', 'gravityview');
636
		}
637
638
		if( ! self::check_user_cap_delete_entry( $entry, array(), $view_id ) ) {
639
			$error = __( 'You do not have permission to delete this entry.', 'gravityview');
640
		}
641
642
		if( $entry['status'] === 'trash' ) {
643
			if( 'trash' === $this->get_delete_mode() ) {
644
				$error = __( 'The entry is already in the trash.', 'gravityview' );
645
			} else {
646
				$error = __( 'You cannot delete the entry; it is already in the trash.', 'gravityview' );
647
			}
648
		}
649
650
		// No errors; everything's fine here!
651
		if( empty( $error ) ) {
652
			return true;
653
		}
654
655
		gravityview()->log->error( '{error}', array( 'erorr' => $error ) );
656
657
		return new WP_Error( 'gravityview-delete-entry-permissions', $error );
658
	}
659
660
661
	/**
662
	 * checks if user has permissions to view the link or delete a specific entry
663
	 *
664
	 * @since 1.5.1
665
	 * @since 1.15 Added `$view_id` param
666
	 *
667
	 * @param  array $entry Gravity Forms entry array
668
	 * @param array $field Field settings (optional)
669
	 * @param int|\GV\View $view Pass a View ID to check caps against. If not set, check against current View (@deprecated no longer optional)
670
	 * @return bool
671
	 */
672 23
	public static function check_user_cap_delete_entry( $entry, $field = array(), $view = 0 ) {
673 23
		if ( ! $view ) {
674
			/** @deprecated path */
675
			$view_id = GravityView_View::getInstance()->getViewId();
676
			$view = \GV\View::by_id( $view_id );
677
		} else {
678 23
			if ( ! $view instanceof \GV\View ) {
679 23
				$view = \GV\View::by_id ( $view );
680
			}
681 23
			$view_id = $view->ID;
682
		}
683
684 23
		$current_user = wp_get_current_user();
685
686 23
		$entry_id = isset( $entry['id'] ) ? $entry['id'] : NULL;
687
688
		// Or if they can delete any entries (as defined in Gravity Forms), we're good.
689 23
		if( GVCommon::has_cap( array( 'gravityforms_delete_entries', 'gravityview_delete_others_entries' ), $entry_id ) ) {
690
691 22
			gravityview()->log->debug( 'Current user has `gravityforms_delete_entries` or `gravityview_delete_others_entries` capability.' );
692
693 22
			return true;
694
		}
695
696
697
		// If field options are passed, check if current user can view the link
698 2
		if( !empty( $field ) ) {
699
700
			// If capability is not defined, something is not right!
701 1
			if( empty( $field['allow_edit_cap'] ) ) {
702
703 1
				gravityview()->log->error( 'Cannot read delete entry field caps', array( 'data' => $field ) );
704
705 1
				return false;
706
			}
707
708
			if( GVCommon::has_cap( $field['allow_edit_cap'] ) ) {
709
710
				// Do not return true if cap is read, as we need to check if the current user created the entry
711
				if( $field['allow_edit_cap'] !== 'read' ) {
712
					return true;
713
				}
714
715
			} else {
716
717
				gravityview()->log->debug( 'User {user_id} is not authorized to view delete entry link ', array( 'user_id' => $current_user->ID ) );
718
719
				return false;
720
			}
721
722
		}
723
724 1
		if( !isset( $entry['created_by'] ) ) {
725
726
			gravityview()->log->error( 'Entry `created_by` doesn\'t exist.');
727
728
			return false;
729
		}
730
731 1
		$user_delete = $view->settings->get( 'user_delete' );
732
733
		// Only checks user_delete view option if view is already set
734 1
		if ( $view && empty( $user_delete ) ) {
735 1
			gravityview()->log->debug( 'User Delete is disabled. Returning false.' );
736 1
			return false;
737
		}
738
739
		// If the logged-in user is the same as the user who created the entry, we're good.
740
		if( is_user_logged_in() && intval( $current_user->ID ) === intval( $entry['created_by'] ) ) {
741
742
			gravityview()->log->debug( 'User {user_id} created the entry.', array( 'user_id' => $current_user->ID ) );
743
744
			return true;
745
		}
746
747
		return false;
748
	}
749
750
751
	/**
752
	 * After processing delete entry, the user will be redirected to the referring View or embedded post/page. Display a message on redirection.
753
	 *
754
	 * If success, there will be `status` URL parameters `status=>success`
755
	 * If an error, there will be `status` and `message` URL parameters `status=>error&message=example`
756
	 *
757
	 * @since 1.15.2 Only show message when the URL parameter's View ID matches the current View ID
758
	 * @since 1.5.1
759
	 *
760
	 * @param int $current_view_id The ID of the View being rendered
761
	 * @return void
762
	 */
763 37
	public function maybe_display_message( $current_view_id = 0 ) {
764 37
		if( empty( $_GET['status'] ) || ! self::verify_nonce() ) {
765 37
			return;
766
		}
767
768
		// Entry wasn't deleted from current View
769
		if( isset( $_GET['view_id'] ) && intval( $_GET['view_id'] ) !== intval( $current_view_id ) ) {
770
			return;
771
		}
772
773
		$this->display_message();
774
	}
775
776
	public function display_message() {
777
778
		if ( empty( $_GET['status'] ) || empty( $_GET['delete'] ) ) {
779
			return;
780
		}
781
782
		$status = esc_attr( $_GET['status'] );
783
		$message_from_url = \GV\Utils::_GET( 'message' );
784
		$message_from_url = rawurldecode( stripslashes_deep( $message_from_url ) );
785
		$class = '';
786
787
		switch ( $status ) {
788
			case 'error':
789
				$class = ' gv-error error';
790
				$error_message = __('There was an error deleting the entry: %s', 'gravityview');
791
				$message = sprintf( $error_message, $message_from_url );
792
				break;
793
			case 'trashed':
794
				$message = __('The entry was successfully moved to the trash.', 'gravityview');
795
				break;
796
			default:
797
				$message = __('The entry was successfully deleted.', 'gravityview');
798
				break;
799
		}
800
801
		/**
802
		 * @filter `gravityview/delete-entry/message` Modify the Delete Entry messages
803
		 * @since 1.13.1
804
		 * @param string $message Message to be displayed
805
		 * @param string $status Message status (`error` or `success`)
806
		 * @param string $message_from_url The original error message, if any, without the "There was an error deleting the entry:" prefix
807
		 */
808
		$message = apply_filters( 'gravityview/delete-entry/message', esc_attr( $message ), $status, $message_from_url );
809
810
		// DISPLAY ERROR/SUCCESS MESSAGE
811
		echo '<div class="gv-notice' . esc_attr( $class ) .'">'. $message .'</div>';
812
	}
813
814
815
} // end class
816
817
GravityView_Delete_Entry::getInstance();
818
819