Completed
Push — develop ( c04cf6...7b9575 )
by Zack
08:02
created

GravityView_Duplicate_Entry   F

Complexity

Total Complexity 77

Size/Duplication

Total Lines 798
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 6

Test Coverage

Coverage 57.03%

Importance

Changes 0
Metric Value
dl 0
loc 798
ccs 150
cts 263
cp 0.5703
rs 2.0419
c 0
b 0
f 0
wmc 77
lcom 3
cbo 6

20 Methods

Rating   Name   Duplication   Size   Complexity  
A add_default_field() 0 12 2
A add_available_field() 0 12 1
A modify_visibility_caps() 0 15 2
A __construct() 0 5 1
A add_hooks() 0 23 1
A getInstance() 0 8 2
A add_template_path() 0 8 1
A duplicate_link_field_options() 0 27 1
A get_nonce_key() 0 3 1
A get_duplicate_link() 0 18 3
C process_duplicate() 0 81 9
C duplicate_entry() 0 98 9
A verify_nonce() 0 22 3
A get_confirm_dialog() 0 17 2
A user_can_duplicate_entry() 0 21 4
C check_user_cap_duplicate_entry() 0 75 13
A maybe_display_message() 0 12 5
A display_message() 0 33 4
A make_duplicate_link_row() 0 18 2
C maybe_duplicate_list() 0 64 11

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
/**
3
 * The GravityView Duplicate Entry Extension
4
 *
5
 * Duplicate entries in GravityView.
6
 *
7
 * @since     2.5
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 2.5
21
 */
22
final class GravityView_Duplicate_Entry {
23
24
	/**
25
	 * @var string The location of this file.
26
	 */
27
	static $file;
28
29
	/**
30
	 * @var GravityView_Duplicate_Entry This instance.
31
	 */
32
	static $instance;
33
34
	var $view_id;
35
36 1
	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...
37
38 1
		self::$file = plugin_dir_path( __FILE__ );
39 1
		$this->add_hooks();
40 1
	}
41
42
	/**
43
	 * @since 2.5
44
	 */
45 1
	private function add_hooks() {
46
47 1
		add_action( 'wp', array( $this, 'process_duplicate' ), 10000 );
48
49 1
		add_filter( 'gravityview_entry_default_fields', array( $this, 'add_default_field' ), 10, 3 );
50
51 1
		add_action( 'gravityview_before', array( $this, 'maybe_display_message' ) );
52
53
		// For the Duplicate Entry Link, you don't want visible to all users.
54 1
		add_filter( 'gravityview_field_visibility_caps', array( $this, 'modify_visibility_caps' ), 10, 5 );
55
56
		// Modify the field options based on the name of the field type
57 1
		add_filter( 'gravityview_template_duplicate_link_options', array( $this, 'duplicate_link_field_options' ), 10, 5 );
58
59
		// add template path to check for field
60 1
		add_filter( 'gravityview_template_paths', array( $this, 'add_template_path' ) );
61
62
		// Entry duplication in the backend
63 1
		add_action( 'gform_entries_first_column_actions', array( $this, 'make_duplicate_link_row' ), 10, 5 );
64
65
		// Handle duplicate action in the backend
66 1
		add_action( 'gform_pre_entry_list', array( $this, 'maybe_duplicate_list' ) );
67 1
	}
68
69
	/**
70
	 * Return the instantiated class object
71
	 *
72
	 * @since  2.5
73
	 * @return GravityView_Duplicate_Entry
74
	 */
75 2
	static public function getInstance() {
76
77 2
		if ( empty( self::$instance ) ) {
78 1
			self::$instance = new self;
79
		}
80
81 2
		return self::$instance;
82
	}
83
84
	/**
85
	 * Include this extension templates path
86
	 *
87
	 * @since  2.5
88
	 *
89
	 * @param array $file_paths List of template paths ordered
90
	 *
91
	 * @return array File paths, with duplicate field path added at index 117
92
	 */
93 2
	public function add_template_path( $file_paths ) {
94
95
		// Index 100 is the default GravityView template path.
96
		// Index 110 is Edit Entry link
97 2
		$file_paths[ 117 ] = self::$file;
98
99 2
		return $file_paths;
100
	}
101
102
	/**
103
	 * Add "Duplicate Link Text" setting to the edit_link field settings
104
	 *
105
	 * @since  2.5
106
	 *
107
	 * @param  array  $field_options [description]
108
	 * @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...
109
	 * @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...
110
	 * @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...
111
	 * @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...
112
	 *
113
	 * @return array                [description]
114
	 */
115
	public function duplicate_link_field_options( $field_options, $template_id, $field_id, $context, $input_type ) {
116
117
		// Always a link, never a filter, always same window
118
		unset( $field_options['show_as_link'], $field_options['search_filter'], $field_options['new_window'] );
119
120
		// Duplicate Entry link should only appear to visitors capable of editing entries
121
		unset( $field_options['only_loggedin'], $field_options['only_loggedin_cap'] );
122
123
		$add_option['duplicate_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...
124
			'type' => 'text',
125
			'label' => __( 'Duplicate Link Text', 'gravityview' ),
126
			'desc' => NULL,
127
			'value' => __( 'Duplicate Entry', 'gravityview' ),
128
			'merge_tags' => true,
129
		);
130
131
		$field_options['allow_duplicate_cap'] = array(
132
			'type' => 'select',
133
			'label' => __( 'Allow the following users to duplicate the entry:', 'gravityview' ),
134
			'choices' => GravityView_Render_Settings::get_cap_choices( $template_id, $field_id, $context, $input_type ),
135
			'tooltip' => 'allow_duplicate_cap',
136
			'class' => 'widefat',
137
			'value' => 'read', // Default: entry creator
138
		);
139
140
		return array_merge( $add_option, $field_options );
141
	}
142
143
144
	/**
145
	 * Add Edit Link as a default field, outside those set in the Gravity Form form
146
	 *
147
	 * @since 2.5
148
	 *
149
	 * @param array $entry_default_fields Existing fields
150
	 * @param  string|array $form form_ID or form object
151
	 * @param  string $zone   Either 'single', 'directory', 'edit', 'header', 'footer'
152
	 *
153
	 * @return array $entry_default_fields, with `duplicate_link` added. Won't be added if in Edit Entry context.
154
	 */
155
	public function add_default_field( $entry_default_fields, $form = array(), $zone = '' ) {
156
157
		if ( 'edit' !== $zone ) {
158
			$entry_default_fields['duplicate_link'] = array(
159
				'label' => __( 'Duplicate Entry', 'gravityview' ),
160
				'type'  => 'duplicate_link',
161
				'desc'  => __( 'A link to duplicate the entry. Respects the Duplicate Entry permissions.', 'gravityview' ),
162
			);
163
		}
164
165
		return $entry_default_fields;
166
	}
167
168
	/**
169
	 * Add Duplicate Entry Link to the Add Field dialog
170
	 *
171
	 * @since 2.5
172
	 *
173
	 * @param array $available_fields
174
	 *
175
	 * @return array Fields with `duplicate_link` added
176
	 */
177
	public function add_available_field( $available_fields = array() ) {
178
179
		$available_fields['duplicate_link'] = array(
180
			'label_text' => __( 'Duplicate Entry', 'gravityview' ),
181
			'field_id' => 'duplicate_link',
182
			'label_type' => 'field',
183
			'input_type' => 'duplicate_link',
184
			'field_options' => NULL
185
		);
186
187
		return $available_fields;
188
	}
189
190
	/**
191
	 * Change wording for the Edit context to read Entry Creator
192
	 *
193
	 * @since 2.5
194
	 *
195
	 * @param  array 	    $visibility_caps        Array of capabilities to display in field dropdown.
196
	 * @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...
197
	 * @param  string       $template_id Table slug
198
	 * @param  float|string $field_id    GF Field ID - Example: `3`, `5.2`, `entry_link`, `created_by`
199
	 * @param  string       $context     What context are we in? Example: `single` or `directory`
200
	 * @param  string       $input_type  (textarea, list, select, etc.)
201
	 *
202
	 * @return array                   Array of field options with `label`, `value`, `type`, `default` keys
203
	 */
204
	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...
205
206
		$caps = $visibility_caps;
207
208
		// If we're configuring fields in the edit context, we want a limited selection
209
		if ( 'duplicate_link' === $field_id ) {
210
211
			// Remove other built-in caps.
212
			unset( $caps['publish_posts'], $caps['gravityforms_view_entries'], $caps['duplicate_others_posts'] );
213
214
			$caps['read'] = _x( 'Entry Creator', 'User capability', 'gravityview' );
215
		}
216
217
		return $caps;
218
	}
219
220
	/**
221
	 * Generate a consistent nonce key based on the Entry ID
222
	 *
223
	 * @since 2.5
224
	 *
225
	 * @param  int $entry_id Entry ID
226
	 *
227
	 * @return string           Key used to validate request
228
	 */
229 2
	public static function get_nonce_key( $entry_id ) {
230 2
		return sprintf( 'duplicate_%s', $entry_id );
231
	}
232
233
234
	/**
235
	 * Generate a nonce link with the base URL of the current View embed
236
	 *
237
	 * We don't want to link to the single entry, because when duplicated, there would be nothing to return to.
238
	 *
239
	 * @since 2.5
240
	 *
241
	 * @param  array       $entry Gravity Forms entry array
242
	 * @param  int         $view_id The View id. Not optional since 2.0
243
	 * @param  int         $post_id ID of the current post/page being embedded on, if any
244
	 *
245
	 * @return string|null If directory link is valid, the URL to process the duplicate request. Otherwise, `NULL`.
246
	 */
247 2
	public static function get_duplicate_link( $entry, $view_id, $post_id = null ) {
248
249 2
        $base = GravityView_API::directory_link( $post_id ? : $view_id, true );
250
251 2
		if ( empty( $base ) ) {
252
			gravityview()->log->error( 'Post ID does not exist: {post_id}', array( 'post_id' => $post_id ) );
253
			return NULL;
254
		}
255
256 2
		$actionurl = add_query_arg( array(
257 2
			'action'	=> 'duplicate',
258 2
			'entry_id'	=> $entry['id'],
259 2
			'gvid' => $view_id,
260 2
            'view_id' => $view_id,
261 2
		), $base );
262
263 2
		return add_query_arg( 'duplicate', wp_create_nonce( self::get_nonce_key( $entry['id'] ) ), $actionurl );
264
	}
265
266
	/**
267
	 * Handle the duplication request, if $_GET['action'] is set to "duplicate"
268
	 *
269
	 * 1. Check referrer validity
270
	 * 2. Make sure there's an entry with the slug of $_GET['entry_id']
271
	 * 3. If so, attempt to duplicate the entry. If not, set the error status
272
	 * 4. Remove `action=duplicate` from the URL
273
	 * 5. Redirect to the page using `wp_safe_redirect()`
274
	 *
275
	 * @since 2.5
276
	 *
277
	 * @uses wp_safe_redirect()
278
	 *
279
	 * @return void|string $url URL during tests instead of redirect.
280
	 */
281 3
	public function process_duplicate() {
282
283
		// If the form is submitted
284 3
		if ( ( ! isset( $_GET['action'] ) ) || 'duplicate' !== $_GET['action'] || ( ! isset( $_GET['entry_id'] ) ) ) {
285 3
			return;
286
		}
287
288
		// Make sure it's a GravityView request
289 1
		$valid_nonce_key = wp_verify_nonce( \GV\Utils::_GET( 'duplicate' ), self::get_nonce_key( $_GET['entry_id'] ) );
290
291 1
		if ( ! $valid_nonce_key ) {
292 1
			gravityview()->log->debug( 'Duplicate entry not processed: nonce validation failed.' );
293 1
			return;
294
		}
295
296
		// Get the entry slug
297 1
		$entry_slug = esc_attr( $_GET['entry_id'] );
298
299
		// See if there's an entry there
300 1
		$entry = gravityview_get_entry( $entry_slug, true, false );
301
302 1
		if ( $entry ) {
303
304 1
			$has_permission = $this->user_can_duplicate_entry( $entry );
305
306 1
			if ( is_wp_error( $has_permission ) ) {
307
308
				$messages = array(
309 1
					'message' => urlencode( $has_permission->get_error_message() ),
310 1
					'status' => 'error',
311
				);
312
313
			} else {
314
315
				// Duplicate the entry
316 1
				$duplicate_response = $this->duplicate_entry( $entry );
317
318 1
				if ( is_wp_error( $duplicate_response ) ) {
319
320
					$messages = array(
321 1
						'message' => urlencode( $duplicate_response->get_error_message() ),
322 1
						'status' => 'error',
323
					);
324
325 1
					gravityview()->log->error( 'Entry {entry_slug} cannot be duplicated: {error_code} {error_message}', array(
326 1
						'entry_slug'    => $entry_slug,
327 1
						'error_code'    => $duplicate_response->get_error_code(),
328 1
						'error_message' => $duplicate_response->get_error_message(),
329
					) );
330
331
				} else {
332
333
					$messages = array(
334 1
						'status' => $duplicate_response,
335
					);
336
337
				}
338
339
			}
340
341
		} else {
342
343 1
			gravityview()->log->error( 'Duplicate entry failed: there was no entry with the entry slug {entry_slug}', array( 'entry_slug' => $entry_slug ) );
344
345
			$messages = array(
346 1
				'message' => urlencode( __( 'The entry does not exist.', 'gravityview' ) ),
347 1
				'status' => 'error',
348
			);
349
		}
350
351 1
		$redirect_to_base = esc_url_raw( remove_query_arg( array( 'action', 'gvid', 'entry_id' ) ) );
352 1
		$redirect_to = add_query_arg( $messages, $redirect_to_base );
353
354 1
		if ( defined( 'DOING_GRAVITYVIEW_TESTS' ) ) {
355 1
			return $redirect_to;
356
		}
357
358
		wp_safe_redirect( $redirect_to );
359
360
		exit();
361
	}
362
363
	/**
364
	 * Duplicate the entry.
365
	 *
366
	 * Done after all the checks in self::process_duplicate.
367
	 *
368
	 * @since 2.5
369
	 *
370
	 * @param array $entry The entry to be duplicated
371
	 *
372
	 * @return WP_Error|boolean
373
	 */
374 1
	private function duplicate_entry( $entry ) {
375
376 1
		if ( ! $entry_id = \GV\Utils::get( $entry, 'id' ) ) {
377
			return new WP_Error( 'gravityview-duplicate-entry-missing', __( 'The entry does not exist.', 'gravityview' ) );
378
		}
379
380 1
		gravityview()->log->debug( 'Starting duplicate entry: {entry_id}', array( 'entry_id' => $entry_id ) );
381
382 1
		global $wpdb;
383
384 1
		$entry_table = GFFormsModel::get_entry_table_name();
385 1
		$entry_meta_table = GFFormsModel::get_entry_meta_table_name();
386
387 1
		if ( ! $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $entry_table WHERE ID = %d", $entry_id ), ARRAY_A ) ) {
388
			return new WP_Error( 'gravityview-duplicate-entry-missing', __( 'The entry does not exist.', 'gravityview' ) );
389
		}
390
391 1
		$form = GFAPI::get_form( $entry['form_id'] );
392
393 1
		$row['id'] = null;
394 1
		$row['date_created'] = date( 'Y-m-d H:i:s', time() );
395 1
		$row['date_updated'] = $row['date_created'];
396 1
		$row['is_starred'] = false;
397 1
		$row['is_read'] = false;
398 1
		$row['ip'] = rgars( $form, 'personalData/preventIP' ) ? '' : GFFormsModel::get_ip();
399 1
		$row['source_url'] = esc_url_raw( remove_query_arg( array( 'action', 'gvid', 'result', 'duplicate', 'entry_id' ) ) );
400 1
		$row['user_agent'] = \GV\Utils::_SERVER( 'HTTP_USER_AGENT' );
401 1
		$row['created_by'] = wp_get_current_user()->ID;
402
403
		/**
404
		 * @filter `gravityview/entry/duplicate/details` Modify the new entry details before it's created.
405
		 * @since 2.5
406
		 * @param[in,out] array $row The entry details
407
		 * @param array $entry The original entry
408
		 */
409 1
		$row = apply_filters( 'gravityview/entry/duplicate/details', $row, $entry );
410
411 1
		if ( ! $wpdb->insert( $entry_table, $row ) ) {
412 1
			return new WP_Error( 'gravityview-duplicate-entry-db-details', __( 'There was an error duplicating the entry.', 'gravityview' ) );
413
		}
414
415 1
		$duplicated_id = $wpdb->insert_id;
416
417 1
		$meta = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $entry_meta_table WHERE entry_id = %d", $entry_id ), ARRAY_A );
418
419 1
		$duplicate_meta = new WP_List_Util( $meta );
420
421
		// Keys that should be reset by default
422 1
		$reset_meta = array( 'is_approved', 'gravityview_unique_id', 'workflow_current_status_timestamp' );
423 1
		foreach ( $reset_meta as $meta_key ) {
424 1
			$duplicate_meta->filter( array( 'meta_key' => $meta_key ), 'NOT' );
425
		}
426
427 1
		$save_this_meta = array();
428 1
		foreach ( $duplicate_meta->get_output() as $m ) {
429 1
			$save_this_meta[] = array(
430 1
				'meta_key' => $m['meta_key'],
431 1
				'meta_value' => $m['meta_value'],
432 1
				'item_index' => $m['item_index'],
433
			);
434
		}
435
436
		// Update the row ID for later usage
437 1
		$row['id'] = $duplicated_id;
438
439
		/**
440
		 * @filter `gravityview/entry/duplicate/meta` Modify the new entry meta details.
441
		 * @param[in,out] array $save_this_meta The duplicate meta. Use/add meta_key, meta_value, item_index.
442
		 * @param array $row The duplicated entry
443
		 * @param array $entry The original entry
444
		 */
445 1
		$save_this_meta = apply_filters( 'gravityview/entry/duplicate/meta', $save_this_meta, $row, $entry );
446
447 1
		foreach ( $save_this_meta as $data ) {
448 1
			$data['form_id'] = $entry['form_id'];
449 1
			$data['entry_id'] = $duplicated_id;
450
451 1
			if ( ! $wpdb->insert( $entry_meta_table, $data ) ) {
452
				return new WP_Error( 'gravityview-duplicate-entry-db-meta', __( 'There was an error duplicating the entry.', 'gravityview' ) );
453
			}
454
		}
455
456 1
		$duplicated_entry = \GFAPI::get_entry( $duplicated_id );
457
458 1
		$duplicate_response = 'duplicated';
459
460
		/**
461
		 * @action `gravityview/duplicate-entry/duplicated` Triggered when an entry is duplicated
462
		 * @since 2.5
463
		 * @param  array $duplicated_entry The duplicated entry
464
		 * @param  array $entry The original entry
465
		*/
466 1
		do_action( 'gravityview/duplicate-entry/duplicated', $duplicated_entry, $entry );
467
468 1
		gravityview()->log->debug( 'Duplicate response: {duplicate_response}', array( 'duplicate_response' => $duplicate_response ) );
469
470 1
		return $duplicate_response;
471
	}
472
473
	/**
474
	 * Is the current nonce valid for editing the entry?
475
	 *
476
	 * @since 2.5
477
	 *
478
	 * @return boolean
479
	 */
480 1
	public function verify_nonce() {
481
482
		// No duplicate entry request was made
483 1
		if ( empty( $_GET['entry_id'] ) || empty( $_GET['duplicate'] ) ) {
484
			return false;
485
		}
486
487 1
		$nonce_key = self::get_nonce_key( $_GET['entry_id'] );
488
489 1
		$valid = wp_verify_nonce( $_GET['duplicate'], $nonce_key );
490
491
		/**
492
		 * @filter `gravityview/duplicate-entry/verify_nonce` Override Duplicate Entry nonce validation. Return true to declare nonce valid.
493
		 * @since 2.5
494
		 * @see wp_verify_nonce()
495
		 * @param int|boolean $valid False if invalid; 1 or 2 when nonce was generated
496
		 * @param string $nonce_key Name of nonce action used in wp_verify_nonce. $_GET['duplicate'] holds the nonce value itself. Default: `duplicate_{entry_id}`
497
		 */
498 1
		$valid = apply_filters( 'gravityview/duplicate-entry/verify_nonce', $valid, $nonce_key );
499
500 1
		return $valid;
501
	}
502
503
	/**
504
	 * Get the onclick attribute for the confirm dialogs that warns users before they duplicate an entry
505
	 *
506
	 * @since 2.5
507
	 *
508
	 * @return string HTML `onclick` attribute
509
	 */
510 1
	public static function get_confirm_dialog() {
511
512 1
		$confirm = __( 'Are you sure you want to duplicate this entry?', 'gravityview' );
513
514
		/**
515
		 * @filter `gravityview/duplicate-entry/confirm-text` Modify the Duplicate Entry Javascript confirmation text (will be sanitized when output)
516
		 *
517
		 * @param string $confirm Default: "Are you sure you want to duplicate this entry?". If empty, disable confirmation dialog.
518
		 */
519 1
		$confirm = apply_filters( 'gravityview/duplicate-entry/confirm-text', $confirm );
520
521 1
		if ( empty( $confirm ) ) {
522 1
			return '';
523
		}
524
525 1
		return 'return window.confirm(\''. esc_js( $confirm ) .'\');';
526
	}
527
528
	/**
529
	 * Check if the user can edit the entry
530
	 *
531
	 * - Is the nonce valid?
532
	 * - Does the user have the right caps for the entry
533
	 * - Is the entry in the trash?
534
	 *
535
	 * @since 2.5
536
	 *
537
	 * @param  array $entry Gravity Forms entry array
538
	 * @param  int   $view_id ID of the View being rendered
539
	 *
540
	 * @return boolean|WP_Error        True: can edit form. WP_Error: nope.
541
	 */
542 1
	private function user_can_duplicate_entry( $entry = array(), $view_id = null ) {
543
544 1
		$error = NULL;
545
546 1
		if ( ! $this->verify_nonce() ) {
547
			$error = __( 'The link to duplicate this entry is not valid; it may have expired.', 'gravityview' );
548
		}
549
550 1
		if ( ! self::check_user_cap_duplicate_entry( $entry, array(), $view_id ) ) {
551 1
			$error = __( 'You do not have permission to duplicate this entry.', 'gravityview' );
552
		}
553
554
		// No errors; everything's fine here!
555 1
		if ( empty( $error ) ) {
556 1
			return true;
557
		}
558
559 1
		gravityview()->log->error( '{error}', array( 'erorr' => $error ) );
560
561 1
		return new WP_Error( 'gravityview-duplicate-entry-permissions', $error );
562
	}
563
564
565
	/**
566
	 * checks if user has permissions to view the link or duplicate a specific entry
567
	 *
568
	 * @since 2.5
569
	 *
570
	 * @param  array $entry Gravity Forms entry array
571
	 * @param array $field Field settings (optional)
572
	 * @param int $view_id Pass a View ID to check caps against. If not set, check against current View
573
	 *
574
	 * @return bool
575
	 */
576 2
	public static function check_user_cap_duplicate_entry( $entry, $field = array(), $view_id = 0 ) {
577 2
		$current_user = wp_get_current_user();
578
579 2
		$entry_id = isset( $entry['id'] ) ? $entry['id'] : null;
580
581
		// Or if they can duplicate any entries (as defined in Gravity Forms), we're good.
582 2
		if ( GVCommon::has_cap( array( 'gravityforms_edit_entries', 'gform_full_access', 'gravityview_full_access' ), $entry_id ) ) {
583
584 2
			gravityview()->log->debug( 'Current user has `gravityforms_edit_entries` capability.' );
585
586 2
			return true;
587
		}
588
589
590
		// If field options are passed, check if current user can view the link
591 2
		if ( ! empty( $field ) ) {
592
593
			// If capability is not defined, something is not right!
594
			if ( empty( $field['allow_duplicate_cap'] ) ) {
595
596
				gravityview()->log->error( 'Cannot read duplicate entry field caps', array( 'data' => $field ) );
597
598
				return false;
599
			}
600
601
			if ( GVCommon::has_cap( $field['allow_duplicate_cap'] ) ) {
602
603
				// Do not return true if cap is read, as we need to check if the current user created the entry
604
				if ( 'read' !== $field['allow_duplicate_cap'] ) {
605
					return true;
606
				}
607
608
			} else {
609
610
				gravityview()->log->debug( 'User {user_id} is not authorized to view duplicate entry link ', array( 'user_id' => $current_user->ID ) );
611
612
				return false;
613
			}
614
615
		}
616
617 2
		if ( ! isset( $entry['created_by'] ) ) {
618
619 1
			gravityview()->log->error( 'Cannot duplicate entry; entry `created_by` doesn\'t exist.' );
620
621 1
			return false;
622
		}
623
624
		// Only checks user_duplicate view option if view is already set
625 2
		if ( $view_id ) {
626
627 1
			if ( ! $view = \GV\View::by_id( $view_id ) ) {
628
				return false;
629
			}
630
631 1
			$user_duplicate = $view->settings->get( 'user_duplicate', false );
632
633 1
			if ( empty( $user_duplicate ) ) {
634
635 1
				gravityview()->log->debug( 'User Duplicate is disabled. Returning false.' );
636
637 1
				return false;
638
			}
639
		}
640
641
		// If the logged-in user is the same as the user who created the entry, we're good.
642 2
		if ( is_user_logged_in() && intval( $current_user->ID ) === intval( $entry['created_by'] ) ) {
643
644 1
			gravityview()->log->debug( 'User {user_id} created the entry.', array( 'user_id' => $current_user->ID ) );
645
646 1
			return true;
647
		}
648
649 2
		return false;
650
	}
651
652
653
	/**
654
	 * After processing duplicate entry, the user will be redirected to the referring View or embedded post/page. Display a message on redirection.
655
	 *
656
	 * If success, there will be `status` URL parameters `status=>success`
657
	 * If an error, there will be `status` and `message` URL parameters `status=>error&message=example`
658
	 *
659
	 * @since 2.5
660
	 *
661
	 * @param int $current_view_id The ID of the View being rendered
662
	 *
663
	 * @return void
664
	 */
665 37
	public function maybe_display_message( $current_view_id = 0 ) {
666 37
		if ( empty( $_GET['status'] ) || ! self::verify_nonce() ) {
667 37
			return;
668
		}
669
670
		// Entry wasn't duplicated from current View
671
		if ( isset( $_GET['view_id'] ) && ( intval( $_GET['view_id'] ) !== intval( $current_view_id ) ) ) {
672
			return;
673
		}
674
675
		$this->display_message();
676
	}
677
678
	public function display_message() {
679
		if ( empty( $_GET['status'] ) || empty( $_GET['duplicate'] ) ) {
680
			return;
681
		}
682
683
		$status = esc_attr( $_GET['status'] );
684
		$message_from_url = \GV\Utils::_GET( 'message' );
685
		$message_from_url = rawurldecode( stripslashes_deep( $message_from_url ) );
686
		$class = '';
687
688
		switch ( $status ) {
689
			case 'error':
690
				$class = ' gv-error error';
691
				$error_message = __( 'There was an error duplicating the entry: %s', 'gravityview' );
692
				$message = sprintf( $error_message, $message_from_url );
693
				break;
694
			default:
695
				$message = __( 'The entry was successfully duplicated.', 'gravityview' );
696
				break;
697
		}
698
699
		/**
700
		 * @filter `gravityview/duplicate-entry/message` Modify the Duplicate Entry messages. Allows HTML; will not be further sanitized.
701
		 * @since 2.5
702
		 * @param string $message Message to be displayed, sanitized using esc_attr()
703
		 * @param string $status Message status (`error` or `success`)
704
		 * @param string $message_from_url The original error message, if any, without the "There was an error duplicating the entry:" prefix
705
		 */
706
		$message = apply_filters( 'gravityview/duplicate-entry/message', esc_attr( $message ), $status, $message_from_url );
707
708
		// DISPLAY ERROR/SUCCESS MESSAGE
709
		echo '<div class="gv-notice' . esc_attr( $class ) .'">'. $message .'</div>';
710
	}
711
712
	/**
713
	 * Add a Duplicate link to the row of actions on the entry list in the backend.
714
	 *
715
	 * @since 2.5.1
716
	 *
717
	 * @param int $form_id The form ID.
718
	 * @param int $field_id The field ID.
719
	 * @param string $value The value.
720
	 * @param array $entry The entry.
721
	 * @param string $query_string The query.
722
	 *
723
	 * @return void
724
	 */
725
	public function make_duplicate_link_row( $form_id, $field_id, $value, $entry, $query_string ) {
0 ignored issues
show
Unused Code introduced by
The parameter $query_string 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...
726
727
		/**
728
		 * @filter `gravityview/duplicate/backend/enable` Disables the duplicate link on the backend.
729
		 * @param[in,out] boolean $enable True by default. Enabled.
730
		 * @param int $form_id The form ID.
731
		 */
732
		if ( ! apply_filters( 'gravityview/duplicate/backend/enable', true, $form_id ) ) {
733
			return;
734
		}
735
736
		?>
737
		<span class="gv-duplicate">
738
			|
739
			<a href="<?php echo wp_nonce_url( add_query_arg( 'entry_id', $entry['id'] ), self::get_nonce_key( $entry['id'] ), 'duplicate' ); ?>"><?php esc_html_e( 'Duplicate', 'gravityview' ); ?></a>
740
		</span>
741
		<?php
742
	}
743
744
	/**
745
	 * Perhaps duplicate this entry if the action has been corrected.
746
	 *
747
	 * @since 2.5.1
748
	 *
749
	 * @param int $form_id The form ID.
750
	 *
751
	 * @return void
752
	 */
753
	public function maybe_duplicate_list( $form_id ) {
0 ignored issues
show
Unused Code introduced by
The parameter $form_id 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...
754
755
		if ( ! is_admin() ) {
756
			return;
757
		}
758
759
		if ( 'success' === \GV\Utils::_GET( 'result' ) ) {
760
			add_filter( 'gform_admin_messages', function( $messages ) {
761
				$messages = (array) $messages;
762
763
				$messages[] = esc_html__( 'Entry duplicated.', 'gravityview' );
764
				return $messages;
765
			} );
766
		}
767
768
		if ( 'error' === \GV\Utils::_GET( 'result' ) ) {
769
770
			$is_logging_active = class_exists( 'GravityView_Logging' ) ? GravityView_Logging::is_logging_active() : true;
771
772
			$check_logs_message = '';
773
774
			if( $is_logging_active ) {
775
				$check_logs_message = sprintf( ' <a href="%s">%s</a>',
776
					esc_url( admin_url( 'admin.php?page=gf_settings&subview=gravityformslogging' ) ),
777
					esc_html_x( 'Check the GravityView logs for more information.', 'Error message links to logging page', 'gravityview' )
778
				);
779
			}
780
781
			add_filter( 'gform_admin_error_messages', function( $messages ) use ( $check_logs_message ) {
782
				$messages = (array) $messages;
783
784
				$messages[] = esc_html__( 'There was an error duplicating the entry.', 'gravityview' ) . $check_logs_message;
785
786
				return $messages;
787
			} );
788
		}
789
790
		if ( ! wp_verify_nonce( \GV\Utils::_GET( 'duplicate' ), self::get_nonce_key( $entry_id = \GV\Utils::_GET( 'entry_id' ) ) ) ) {
791
			return;
792
		}
793
794
		if ( ! GVCommon::has_cap( array( 'gravityforms_edit_entries', 'gform_full_access', 'gravityview_full_access' ), $entry_id ) ) {
795
			return;
796
		}
797
798
		$entry = GFAPI::get_entry( $entry_id );
799
800
		if ( is_wp_error( $entry ) ) {
801
			$is_duplicated = $entry;
802
		} else {
803
			$is_duplicated = $this->duplicate_entry( $entry );
804
		}
805
806
		if ( is_wp_error( $is_duplicated ) ) {
807
			gravityview()->log->error( 'Error duplicating {id}: {error}', array( 'id' => $entry_id, 'error' => $is_duplicated->get_error_message() ) );
808
		}
809
810
		$return_url = remove_query_arg( 'duplicate' );
811
		$return_url = add_query_arg( 'result', is_wp_error( $is_duplicated ) ? 'error' : 'success', $return_url );
812
813
		echo '<script>window.location.href = ' . json_encode( $return_url ) . ';</script>';
814
815
		exit;
816
	}
817
818
819
} // end class
820
821
GravityView_Duplicate_Entry::getInstance();
822
823