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