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

GravityView_Duplicate_Entry::get_duplicate_link()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3.0416

Importance

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