403Webshell
Server IP : 198.38.94.13  /  Your IP : 216.73.217.33
Web Server : Apache
System : Linux d4744.dxb1.stableserver.net 5.14.0-611.49.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Apr 21 16:39:08 EDT 2026 x86_64
User : revivere ( 1140)
PHP Version : 8.2.33
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /home/revivere/www/wp-content/plugins/post-duplicator/includes/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /home/revivere/www/wp-content/plugins/post-duplicator/includes/api.php
<?php
namespace Mtphr\PostDuplicator;

add_action( 'rest_api_init', __NAMESPACE__ . '\register_routes' );

/**
 * Register rest routes
 */
function register_routes() {
  register_rest_route( 'post-duplicator/v1', 'duplicate-post', array(
    'methods' 	=> 'POST',
    'permission_callback' => __NAMESPACE__ . '\duplicate_post_permissions',
    'callback' => __NAMESPACE__ . '\duplicate_post',
  ) );
  
  register_rest_route( 'post-duplicator/v1', 'post-data/(?P<id>\d+)', array(
    'methods' => 'GET',
    'permission_callback' => __NAMESPACE__ . '\get_post_data_permissions',
    'callback' => __NAMESPACE__ . '\get_post_data',
    'args' => array(
      'id' => array(
        'validate_callback' => function( $param ) {
          return is_numeric( $param );
        },
      ),
    ),
  ) );
  
  register_rest_route( 'post-duplicator/v1', 'post-full-data/(?P<id>\d+)', array(
    'methods' => 'GET',
    'permission_callback' => __NAMESPACE__ . '\get_post_data_permissions',
    'callback' => __NAMESPACE__ . '\get_post_full_data',
    'args' => array(
      'id' => array(
        'validate_callback' => function( $param ) {
          return is_numeric( $param );
        },
      ),
    ),
  ) );
  
  register_rest_route( 'post-duplicator/v1', 'parent-posts', array(
    'methods' => 'GET',
    'permission_callback' => __NAMESPACE__ . '\get_parent_posts_permissions',
    'callback' => __NAMESPACE__ . '\get_parent_posts',
    'args' => array(
      'post_type' => array(
        'validate_callback' => function( $param ) {
          // Validate that it's a valid post type slug
          return post_type_exists( sanitize_key( $param ) );
        },
        'sanitize_callback' => 'sanitize_key',
      ),
      'exclude_id' => array(
        'validate_callback' => function( $param ) {
          return is_numeric( $param );
        },
        'sanitize_callback' => 'absint',
      ),
    ),
  ) );
  
  register_rest_route( 'post-duplicator/v1', 'users', array(
    'methods' => 'GET',
    'permission_callback' => __NAMESPACE__ . '\get_users_permissions',
    'callback' => __NAMESPACE__ . '\get_users',
  ) );
}

/**
 * Permission check for getting post data
 */
function get_post_data_permissions( $request ) {
  $post_id = $request->get_param( 'id' );
  
  if ( ! $post_id ) {
    return new \WP_Error( 'no_post_id', esc_html__( 'No post ID provided.', 'post-duplicator' ), array( 'status' => 403 ) );
  }
  
  $post = get_post( $post_id );
  if ( ! $post ) {
    return new \WP_Error( 'post_not_found', esc_html__( 'Post not found.', 'post-duplicator' ), array( 'status' => 404 ) );
  }
  
  if ( ! user_can_duplicate( $post ) ) {
    return new \WP_Error( 'no_permission', esc_html__( 'User does not have permission to view this post.', 'post-duplicator' ), array( 'status' => 403 ) );
  }
  
  return true;
}

/**
 * Get taxonomy and custom meta data for a post
 */
function get_post_data( $request ) {
  $post_id = $request->get_param( 'id' );
  $post = get_post( $post_id );
  
  if ( ! $post ) {
    return new \WP_Error( 'post_not_found', esc_html__( 'Post not found.', 'post-duplicator' ), array( 'status' => 404 ) );
  }
  
  // Get taxonomies
  $taxonomies_data = array();
  $taxonomies = get_object_taxonomies( $post->post_type );
  $disabled_taxonomies = array( 'post_translations', 'post_format' );
  
  foreach ( $taxonomies as $taxonomy_slug ) {
    if ( in_array( $taxonomy_slug, $disabled_taxonomies ) ) {
      continue;
    }
    
    $taxonomy = get_taxonomy( $taxonomy_slug );
    if ( ! $taxonomy ) {
      continue;
    }
    
    // Get terms currently assigned to the post
    $assigned_term_ids = wp_get_post_terms( $post_id, $taxonomy_slug, array( 'fields' => 'ids' ) );
    
    // Get ALL available terms for this taxonomy
    $all_terms = get_terms( array(
      'taxonomy' => $taxonomy_slug,
      'hide_empty' => false,
    ) );
    
    $terms_data = array();
    if ( ! is_wp_error( $all_terms ) ) {
      foreach ( $all_terms as $term ) {
        $terms_data[] = array(
          'id' => $term->term_id,
          'name' => $term->name,
          'slug' => $term->slug,
        );
      }
    }
    
    $taxonomies_data[] = array(
      'slug' => $taxonomy_slug,
      'label' => $taxonomy->labels->name,
      'hierarchical' => $taxonomy->hierarchical,
      'terms' => $terms_data,
      'assignedTermIds' => $assigned_term_ids,
    );
  }
  
  // Get custom meta fields
  $custom_meta_data = array();
  $custom_fields = get_post_custom( $post_id );
  $excluded_meta_keys = get_excluded_meta_keys();
  
  foreach ( $custom_fields as $key => $values ) {
    // Skip excluded meta keys
    if ( in_array( $key, $excluded_meta_keys, true ) ) {
      continue;
    }
    
    // Check if meta is enabled via filter (defaults to true for all meta keys, including those starting with "_")
    if ( ! apply_filters( "mtphr_post_duplicator_meta_{$key}_enabled", true ) ) {
      continue;
    }
    
    foreach ( $values as $value ) {
      // Detect data type
      $type = 'string';
      $is_serialized = false;
      $original_value = $value;
      
      // Check if serialized
      if ( is_serialized( $value ) ) {
        $is_serialized = true;
        $trimmed_value = trim( $value );

        // Never decode serialized objects; keep them as raw strings.
        if ( preg_match( '/^(?:O|C):\d+:/', $trimmed_value ) ) {
          $type = 'string';
        } else {
          $unserialized = @unserialize( $trimmed_value, array( 'allowed_classes' => false ) );
          $is_valid_unserialized = ( false !== $unserialized || 'b:0;' === $trimmed_value );

          if ( $is_valid_unserialized && is_array( $unserialized ) ) {
            $type = 'array';
            $value = wp_json_encode( $unserialized, JSON_PRETTY_PRINT );
          } else {
            $type = 'string';
          }
        }
      } elseif ( is_numeric( $value ) ) {
        // Check if it's a number (int or float)
        if ( strpos( $value, '.' ) !== false ) {
          $type = 'number';
        } else {
          $type = 'number';
        }
      } elseif ( $value === 'true' || $value === 'false' || $value === '1' || $value === '0' || $value === '' ) {
        // Could be boolean, but WordPress stores as string
        $type = 'string';
      }
      
      // Try to detect JSON
      if ( ! $is_serialized ) {
        $json_decoded = json_decode( $value, true );
        if ( json_last_error() === JSON_ERROR_NONE && ( is_array( $json_decoded ) || is_object( $json_decoded ) ) ) {
          $type = is_array( $json_decoded ) ? 'array' : 'object';
          $value = wp_json_encode( $json_decoded, JSON_PRETTY_PRINT );
        }
      }
      
      $custom_meta_data[] = array(
        'key' => $key,
        'value' => $value,
        'type' => $type,
        'isSerialized' => $is_serialized,
        'originalValue' => $original_value,
      );
    }
  }
  
  return rest_ensure_response( array(
    'taxonomies' => $taxonomies_data,
    'customMeta' => $custom_meta_data,
  ) );
}

/**
 * Get full post data including title, slug, date, author, parent, featured image
 * Works for all post types regardless of show_in_rest setting
 */
function get_post_full_data( $request ) {
  $post_id = $request->get_param( 'id' );
  $post = get_post( $post_id );
  
  if ( ! $post ) {
    return new \WP_Error( 'post_not_found', esc_html__( 'Post not found.', 'post-duplicator' ), array( 'status' => 404 ) );
  }
  
  // Get author name
  $author_name = 'Unknown Author';
  if ( $post->post_author && $post->post_author > 0 ) {
    $author = get_userdata( $post->post_author );
    if ( $author ) {
      $author_name = $author->display_name;
    }
  }
  
  // Get featured image data
  $featured_image = null;
  $featured_media_id = get_post_thumbnail_id( $post_id );
  if ( $featured_media_id && $featured_media_id > 0 ) {
    $attachment = get_post( $featured_media_id );
    if ( $attachment && wp_attachment_is_image( $featured_media_id ) ) {
      $image_url = wp_get_attachment_image_url( $featured_media_id, 'full' );
      $thumbnail_url = wp_get_attachment_image_url( $featured_media_id, 'thumbnail' );
      $alt_text = get_post_meta( $featured_media_id, '_wp_attachment_image_alt', true );
      
      $featured_image = array(
        'id' => $featured_media_id,
        'url' => $image_url ? $image_url : '',
        'thumbnail' => $thumbnail_url ? $thumbnail_url : $image_url,
        'alt' => $alt_text ? $alt_text : '',
      );
    }
  }
  
  // Get parent post data if available
  $parent_post = null;
  if ( $post->post_parent && $post->post_parent > 0 ) {
    $parent = get_post( $post->post_parent );
    if ( $parent ) {
      $parent_post = array(
        'id' => $parent->ID,
        'title' => $parent->post_title,
      );
    }
  }
  
  return rest_ensure_response( array(
    'id' => $post->ID,
    'title' => $post->post_title,
    'type' => $post->post_type,
    'status' => $post->post_status,
    'slug' => $post->post_name,
    'date' => $post->post_date,
    'author' => $author_name,
    'authorId' => $post->post_author,
    'parent' => (int) $post->post_parent,
    'parentPost' => $parent_post,
    'featuredImage' => $featured_image,
  ) );
}

/**
 * Permission check for getting parent posts
 */
function get_parent_posts_permissions( $request ) {
  // User must be logged in and have edit capabilities
  if ( ! is_user_logged_in() ) {
    return new \WP_Error( 'not_logged_in', esc_html__( 'You must be logged in to access this endpoint.', 'post-duplicator' ), array( 'status' => 401 ) );
  }
  
  // Check if user has edit capabilities
  if ( ! current_user_can( 'edit_posts' ) ) {
    return new \WP_Error( 'no_permission', esc_html__( 'You do not have permission to access this endpoint.', 'post-duplicator' ), array( 'status' => 403 ) );
  }
  
  return true;
}

/**
 * Get available parent posts for a post type (hierarchical)
 */
function get_parent_posts( $request ) {
  $post_type = $request->get_param( 'post_type' );
  $exclude_id = $request->get_param( 'exclude_id' );
  
  // Validate and sanitize post type
  if ( ! $post_type || ! post_type_exists( $post_type ) ) {
    // Default to 'page' if no valid post type specified
    $post_type = 'page';
  } else {
    $post_type = sanitize_key( $post_type );
  }
  
  // Validate and sanitize exclude_id
  if ( $exclude_id ) {
    $exclude_id = absint( $exclude_id );
  }
  
  // Get posts that can be parents (same post type, published or draft)
  $args = array(
    'post_type' => $post_type,
    'post_status' => array( 'publish', 'draft', 'private' ),
    'posts_per_page' => -1,
    'orderby' => 'menu_order title',
    'order' => 'ASC',
  );
  
  // Exclude the current post (can't be its own parent)
  if ( $exclude_id && $exclude_id > 0 ) {
    $args['post__not_in'] = array( $exclude_id );
  }
  
  $posts = get_posts( $args );
  
  // Build a map of posts by ID
  $posts_map = array();
  foreach ( $posts as $post ) {
    $posts_map[ $post->ID ] = array(
      'id' => $post->ID,
      'title' => $post->post_title,
      'parent' => $post->post_parent,
      'children' => array(),
    );
  }
  
  // Build hierarchical structure and collect descendants of excluded post
  $excluded_descendants = array();
  if ( $exclude_id && $exclude_id > 0 ) {
    $exclude_id_int = $exclude_id;
    // Recursively find all descendants of the excluded post from the full post list
    // We need to query all posts to find descendants, not just the ones we're showing
    $all_posts_args = array(
      'post_type' => $post_type,
      'post_status' => array( 'publish', 'draft', 'private' ),
      'posts_per_page' => -1,
      'fields' => 'ids',
    );
    $all_post_ids = get_posts( $all_posts_args );
    
    $find_descendants = function( $parent_id ) use ( &$find_descendants, $all_post_ids, $post_type ) {
      $descendants = array();
      foreach ( $all_post_ids as $post_id ) {
        $post = get_post( $post_id );
        if ( $post && $post->post_parent == $parent_id ) {
          $descendants[] = $post_id;
          // Recursively get descendants of this child
          $descendants = array_merge( $descendants, $find_descendants( $post_id ) );
        }
      }
      return $descendants;
    };
    $excluded_descendants = $find_descendants( $exclude_id_int );
    $excluded_descendants[] = $exclude_id_int; // Include the post itself
  }
  
  $root_posts = array();
  foreach ( $posts_map as $post_id => $post_data ) {
    // Skip if this post is a descendant of the excluded post
    if ( in_array( $post_id, $excluded_descendants ) ) {
      continue;
    }
    
    if ( $post_data['parent'] == 0 ) {
      // Root level post
      $root_posts[] = $post_id;
    } else {
      // Child post - add to parent's children array only if parent is not excluded
      if ( isset( $posts_map[ $post_data['parent'] ] ) && ! in_array( $post_data['parent'], $excluded_descendants ) ) {
        $posts_map[ $post_data['parent'] ]['children'][] = $post_id;
      } else {
        // Parent not in our list (different post type or excluded), treat as root
        $root_posts[] = $post_id;
      }
    }
  }
  
  // Recursive function to build flat list with hierarchy info
  $hierarchical_list = array();
  $build_list = function( $post_ids, $level = 0 ) use ( &$build_list, &$hierarchical_list, &$posts_map ) {
    foreach ( $post_ids as $post_id ) {
      if ( ! isset( $posts_map[ $post_id ] ) ) {
        continue;
      }
      
      $post_data = $posts_map[ $post_id ];
      $hierarchical_list[] = array(
        'id' => $post_data['id'],
        'title' => $post_data['title'],
        'level' => $level,
        'parent' => $post_data['parent'],
      );
      
      // Recursively add children
      if ( ! empty( $post_data['children'] ) ) {
        $build_list( $post_data['children'], $level + 1 );
      }
    }
  };
  
  // Build the hierarchical list starting from root posts
  $build_list( $root_posts );
  
  return rest_ensure_response( $hierarchical_list );
}

/**
 * Check if a meta value contains HTML and should be sanitized with wp_kses_post
 * 
 * @param string $meta_value The meta value to check
 * @param string $meta_key The meta key
 * @param int $post_id The post ID (optional, for ACF field detection)
 * @return bool True if the value contains HTML
 */
function meta_value_contains_html( $meta_value, $meta_key = '', $post_id = 0 ) {
	// Check if value is empty or not a string
	if ( empty( $meta_value ) || ! is_string( $meta_value ) ) {
		return false;
	}
	
	// First, check if value contains HTML tags (most reliable method)
	// Use trim to handle whitespace-only differences
	$stripped = strip_tags( trim( $meta_value ) );
	$trimmed_original = trim( $meta_value );
	if ( $stripped !== $trimmed_original && strlen( $trimmed_original ) > strlen( $stripped ) ) {
		// Contains HTML tags - the stripped version is shorter, meaning tags were removed
		return true;
	}
	
	// Check if ACF is active and this might be an ACF WYSIWYG field
	if ( function_exists( 'acf_get_field' ) && $post_id > 0 && ! empty( $meta_key ) ) {
		// For ACF flexible content fields, check if there's a corresponding field key
		// Pattern: fieldname_0_subfieldname -> _fieldname_0_subfieldname contains field key
		$field_key_meta = '_' . $meta_key;
		$field_key = get_post_meta( $post_id, $field_key_meta, true );
		
		if ( ! empty( $field_key ) && strpos( $field_key, 'field_' ) === 0 ) {
			// This is an ACF field, check its type
			$field = acf_get_field( $field_key );
			if ( $field && isset( $field['type'] ) ) {
				// Check for WYSIWYG and other HTML-capable field types
				$html_field_types = array( 'wysiwyg', 'textarea', 'oembed', 'url' );
				if ( in_array( $field['type'], $html_field_types, true ) ) {
					return true;
				}
			}
		}
		
		// Also check for common ACF field name patterns that indicate HTML content
		// Patterns like: *_editor_*, *_wysiwyg_*, *_html_*, *_content_*
		$html_patterns = array( 'editor', 'wysiwyg', 'html', 'content', 'description', 'text' );
		foreach ( $html_patterns as $pattern ) {
			if ( stripos( $meta_key, $pattern ) !== false ) {
				// Check if this is actually an ACF field by looking for the field key
				$field_key_meta = '_' . $meta_key;
				$field_key = get_post_meta( $post_id, $field_key_meta, true );
				if ( ! empty( $field_key ) && strpos( $field_key, 'field_' ) === 0 ) {
					return true;
				}
			}
		}
	}
	
	return false;
}

/**
 * Sanitize meta value appropriately based on content type
 * 
 * @param string $meta_value The meta value to sanitize
 * @param string $meta_key The meta key
 * @param int $post_id The post ID (optional, for ACF field detection)
 * @return string Sanitized meta value
 */
function sanitize_meta_value( $meta_value, $meta_key = '', $post_id = 0 ) {
	// Check if value contains HTML
	if ( meta_value_contains_html( $meta_value, $meta_key, $post_id ) ) {
		// Use wp_kses_post to preserve HTML but sanitize it
		// wp_kses_post sanitizes HTML while preserving allowed tags
		// $wpdb->insert() handles escaping automatically via prepared statements
		return wp_kses_post( $meta_value );
	}
	
	// Default to sanitize_text_field for plain text
	return sanitize_text_field( $meta_value );
}

/**
 * Duplicate a post
 */
function duplicate_post_permissions( $request ) {
  $data = $request->get_json_params();
  $original_id = isset( $data['original_id'] ) ? $data['original_id'] : false;

  if ( ! $original_id ) {
    return new \WP_Error( 'no_original_id', esc_html__( 'No original id passed.', 'post-duplicator' ), array( 'status' => 403 ) );
  }

  // Validate original_id is a positive integer
  $original_id = absint( $original_id );
  if ( ! $original_id || $original_id <= 0 ) {
    return new \WP_Error( 'invalid_original_id', esc_html__( 'Invalid original id.', 'post-duplicator' ), array( 'status' => 403 ) );
  }

  $post = get_post( $original_id );
  if ( ! $post ) {
    return new \WP_Error( 'post_not_found', esc_html__( 'Post not found.', 'post-duplicator' ), array( 'status' => 404 ) );
  }

  if ( ! user_can_duplicate( $post ) ) {
	  return new \WP_Error( 'no_permission', esc_html__( 'User does not have permission to duplicate post.', 'post-duplicator' ), array( 'status' => 403 ) );
	}

  if ( ! is_post_type_duplication_enabled( $post->post_type ) ) {
    return new \WP_Error( 'type_disabled', esc_html__( 'Duplication is disabled for this post type.', 'post-duplicator' ), array( 'status' => 403 ) );
  }

  return true;
}


/**
 * Core duplication engine.
 *
 * Performs the actual post duplication using the provided settings array.
 * Both the REST API endpoint and the PHP fallback handler use this function
 * so that all duplication logic lives in one place.
 *
 * @param WP_Post $orig     The original post object.
 * @param array   $settings Merged settings (plugin defaults + any caller overrides).
 * @return array|\WP_Error  Array with duplicate_id, permalink, duplicate, duplicate_terms
 *                          on success; WP_Error on failure.
 */
function perform_duplication( $orig, $settings ) {
	global $wpdb;

	$original_id = $orig->ID;

	$duplicate = array();

	$duplicate['post_author'] = $orig->post_author;
	$duplicate['post_content'] = $orig->post_content;
	$duplicate['post_title'] = $orig->post_title;
	$duplicate['post_excerpt'] = $orig->post_excerpt;
	$duplicate['post_status'] = $orig->post_status;
	$duplicate['comment_status'] = $orig->comment_status;
	$duplicate['ping_status'] = $orig->ping_status;
	$duplicate['post_password'] = $orig->post_password;
	$duplicate['post_name'] = $orig->post_name;
	$duplicate['to_ping'] = $orig->to_ping;
	$duplicate['pinged'] = $orig->pinged;
	$duplicate['post_content_filtered'] = $orig->post_content_filtered;
	$duplicate['post_parent'] = $orig->post_parent;
	$duplicate['menu_order'] = $orig->menu_order;
	$duplicate['post_type'] = $orig->post_type;
	$duplicate['post_mime_type'] = $orig->post_mime_type;
	// Explicitly omit guid — WordPress generates a fresh one from get_permalink()
	// after insertion. An empty string here guarantees the original's guid is
	// never carried over, including for non-public post types where get_permalink()
	// may otherwise return an ambiguous fallback URL.
	$duplicate['guid'] = '';

	// Modify the title
	if ( isset( $settings['fullTitle'] ) && ! empty( $settings['fullTitle'] ) ) {
		$duplicate['post_title'] = sanitize_text_field( $settings['fullTitle'] );
	} else {
		$appended = isset( $settings['title'] ) ? sanitize_text_field( $settings['title'] ) : esc_html__( 'Copy', 'post-duplicator' );
		$duplicate['post_title'] = $duplicate['post_title'] . ' ' . $appended;
	}

	// Modify the slug
	if ( isset( $settings['fullSlug'] ) && ! empty( $settings['fullSlug'] ) ) {
		$duplicate['post_name'] = sanitize_title( $settings['fullSlug'] );
	} else {
		$duplicate['post_name'] = sanitize_title( $duplicate['post_name'] . '-' . $settings['slug'] );
	}

	// Set the status - validate against allowed statuses
	if ( $settings['status'] != 'same' ) {
		$allowed_statuses = array( 'draft', 'publish', 'pending', 'private', 'future' );
		$requested_status = sanitize_text_field( $settings['status'] );
		if ( in_array( $requested_status, $allowed_statuses, true ) ) {
			$duplicate['post_status'] = $requested_status;
		} else {
			$duplicate['post_status'] = 'draft';
		}
	}

	// Restrict publish-gated statuses for users without publish capability
	if ( ! current_user_can( 'publish_posts' ) ) {
		if ( in_array( $duplicate['post_status'], array( 'publish', 'private', 'future' ), true ) ) {
			$duplicate['post_status'] = 'pending';
		}
	}

	// Set the type - validate against allowed post types
	if ( $settings['type'] != 'same' ) {
		$requested_type = sanitize_key( $settings['type'] );
		if ( post_type_exists( $requested_type ) && current_user_can( get_post_type_object( $requested_type )->cap->create_posts ) ) {
			$duplicate['post_type'] = $requested_type;
		} else {
			$duplicate['post_type'] = $orig->post_type;
		}
	}

	// Set the parent - only when explicitly provided in settings
	if ( array_key_exists( 'selectedParentId', $settings ) ) {
		$selected_parent = $settings['selectedParentId'];

		if ( null === $selected_parent || '' === $selected_parent || 0 === $selected_parent || '0' === $selected_parent ) {
			$duplicate['post_parent'] = 0;
		} else {
			$selected_parent_id = absint( $selected_parent );
			if ( $selected_parent_id > 0 ) {
				$selected_parent_post = get_post( $selected_parent_id );
				if (
					$selected_parent_post &&
					$selected_parent_post->post_type === $duplicate['post_type'] &&
					is_post_type_hierarchical( $duplicate['post_type'] )
				) {
					$duplicate['post_parent'] = $selected_parent_id;
				}
			}
		}
	}

	// Set the post date
	if ( $settings['timestamp'] == 'duplicate' ) {
		$timestamp     = strtotime( $orig->post_date );
		$timestamp_gmt = strtotime( $orig->post_date_gmt );
	} elseif ( $settings['timestamp'] == 'custom' && isset( $settings['customDate'] ) && ! empty( $settings['customDate'] ) ) {
		$custom_date = $settings['customDate'];
		try {
			$date_obj      = new \DateTime( $custom_date, new \DateTimeZone( 'UTC' ) );
			$gmt_date      = $date_obj->format( 'Y-m-d H:i:s' );
			$local_date    = get_date_from_gmt( $gmt_date );
			$timestamp     = strtotime( $local_date );
			$timestamp_gmt = strtotime( $gmt_date );
		} catch ( \Exception $e ) {
			$timestamp     = current_time( 'timestamp', 0 );
			$timestamp_gmt = current_time( 'timestamp', 1 );
		}
	} else {
		$timestamp     = current_time( 'timestamp', 0 );
		$timestamp_gmt = current_time( 'timestamp', 1 );
	}

	if ( isset( $settings['time_offset'] ) && $settings['time_offset'] ) {
		$offset = intval( $settings['time_offset_seconds'] + $settings['time_offset_minutes'] * 60 + $settings['time_offset_hours'] * 3600 + $settings['time_offset_days'] * 86400 );
		if ( $settings['time_offset_direction'] == 'newer' ) {
			$timestamp     = intval( $timestamp + $offset );
			$timestamp_gmt = intval( $timestamp_gmt + $offset );
		} else {
			$timestamp     = intval( $timestamp - $offset );
			$timestamp_gmt = intval( $timestamp_gmt - $offset );
		}
	}
	$duplicate['post_date']         = date( 'Y-m-d H:i:s', $timestamp );
	$duplicate['post_date_gmt']     = date( 'Y-m-d H:i:s', $timestamp_gmt );
	$duplicate['post_modified']     = date( 'Y-m-d H:i:s', current_time( 'timestamp', 0 ) );
	$duplicate['post_modified_gmt'] = date( 'Y-m-d H:i:s', current_time( 'timestamp', 1 ) );

	// Set author
	if ( isset( $settings['selectedAuthorId'] ) ) {
		$requested_author = intval( $settings['selectedAuthorId'] );
		if ( $settings['selectedAuthorId'] === null || $settings['selectedAuthorId'] === '' || $requested_author === 0 ) {
			$duplicate['post_author'] = 0;
		} elseif ( $requested_author !== get_current_user_id() ) {
			if ( current_user_can( 'edit_others_posts' ) ) {
				$duplicate['post_author'] = $requested_author;
			} else {
				$duplicate['post_author'] = get_current_user_id();
			}
		} else {
			$duplicate['post_author'] = $requested_author;
		}
	} elseif ( 'current_user' == $settings['post_author'] ) {
		$duplicate['post_author'] = get_current_user_id();
	}

	// Sanitize post content
	add_filter( 'wp_kses_allowed_html', __NAMESPACE__ . '\additional_kses', 10, 2 );
	$duplicate['post_content'] = wp_slash( wp_kses_post( $duplicate['post_content'] ) );
	remove_filter( 'wp_kses_allowed_html', __NAMESPACE__ . '\additional_kses', 10, 2 );

	// Insert the post into the database
	$duplicate_id = wp_insert_post( $duplicate );

	if ( is_wp_error( $duplicate_id ) || ! $duplicate_id ) {
		return new \WP_Error( 'insert_failed', esc_html__( 'Failed to create duplicate post.', 'post-duplicator' ) );
	}

	// Handle featured image
	if ( isset( $settings['featuredImageId'] ) ) {
		if ( empty( $settings['featuredImageId'] ) ) {
			delete_post_thumbnail( $duplicate_id );
		} else {
			$thumbnail_id = intval( $settings['featuredImageId'] );
			$attachment   = get_post( $thumbnail_id );
			if ( $attachment && wp_attachment_is_image( $thumbnail_id ) ) {
				set_post_thumbnail( $duplicate_id, $thumbnail_id );
			}
		}
	} else {
		$original_thumbnail_id = get_post_thumbnail_id( $original_id );
		if ( $original_thumbnail_id ) {
			set_post_thumbnail( $duplicate_id, $original_thumbnail_id );
		}
	}

	$duplicate_terms = wp_get_post_terms( $duplicate_id, get_object_taxonomies( $duplicate['post_type'] ) );

	// Handle taxonomies
	$include_taxonomies = false;
	if ( isset( $settings['includeTaxonomies'] ) && false !== $settings['includeTaxonomies'] ) {
		$tax_value = $settings['includeTaxonomies'];
		if ( is_bool( $tax_value ) ) {
			$include_taxonomies = $tax_value;
		} elseif ( is_string( $tax_value ) ) {
			$include_taxonomies = ! ( $tax_value === 'false' || $tax_value === '0' || $tax_value === '' );
		} elseif ( $tax_value === 0 || $tax_value === '0' ) {
			$include_taxonomies = false;
		} else {
			$include_taxonomies = (bool) $tax_value;
		}
	}

	if ( $include_taxonomies === true ) {
		if ( isset( $settings['taxonomyData'] ) && is_array( $settings['taxonomyData'] ) && ! empty( $settings['taxonomyData'] ) ) {
			foreach ( $settings['taxonomyData'] as $taxonomy_slug => $term_ids ) {
				if ( ! is_array( $term_ids ) ) {
					continue;
				}
				$taxonomy_slug = sanitize_key( $taxonomy_slug );
				if ( ! taxonomy_exists( $taxonomy_slug ) ) {
					continue;
				}
				if ( ! is_object_in_taxonomy( $duplicate['post_type'], $taxonomy_slug ) ) {
					continue;
				}
				$term_ids       = array_map( 'absint', $term_ids );
				$term_ids       = array_filter( $term_ids );
				$valid_term_ids = array();
				foreach ( $term_ids as $term_id ) {
					$term = get_term( $term_id, $taxonomy_slug );
					if ( $term && ! is_wp_error( $term ) ) {
						$valid_term_ids[] = $term_id;
					}
				}
				if ( ! empty( $valid_term_ids ) ) {
					wp_set_object_terms( $duplicate_id, $valid_term_ids, $taxonomy_slug );
				}
			}
		} elseif ( ! isset( $settings['taxonomyData'] ) ) {
			$taxonomies          = get_object_taxonomies( $duplicate['post_type'] );
			$disabled_taxonomies = array( 'post_translations' );
			foreach ( $taxonomies as $taxonomy ) {
				if ( in_array( $taxonomy, $disabled_taxonomies ) ) {
					continue;
				}
				$terms = wp_get_post_terms( $original_id, $taxonomy, array( 'fields' => 'names' ) );
				wp_set_object_terms( $duplicate_id, $terms, $taxonomy );
			}
		}
	}

	// Handle custom meta fields
	$include_custom_meta = false;
	if ( isset( $settings['includeCustomMeta'] ) && false !== $settings['includeCustomMeta'] ) {
		if ( is_bool( $settings['includeCustomMeta'] ) ) {
			$include_custom_meta = $settings['includeCustomMeta'];
		} elseif ( is_string( $settings['includeCustomMeta'] ) ) {
			$include_custom_meta = ( $settings['includeCustomMeta'] === 'true' || $settings['includeCustomMeta'] === '1' );
		} elseif ( $settings['includeCustomMeta'] === 0 || $settings['includeCustomMeta'] === '0' ) {
			$include_custom_meta = false;
		} else {
			$include_custom_meta = (bool) $settings['includeCustomMeta'];
		}
	}

	if ( $include_custom_meta === true ) {
		$excluded_meta_keys    = get_excluded_meta_keys();
		$original_custom_fields = get_post_custom( $original_id );
		$cloned_meta_data      = array();

		if ( isset( $settings['customMetaData'] ) && is_array( $settings['customMetaData'] ) ) {
			// Security: only copy keys that exist on the original post (blocks key injection).
			// For protected meta: use original values only (blocks value injection).
			foreach ( $settings['customMetaData'] as $meta_item ) {
				if ( ! isset( $meta_item['key'] ) ) {
					continue;
				}
				$meta_key = $meta_item['key'];
				if ( empty( $meta_key ) || strlen( $meta_key ) > 255 ) {
					continue;
				}
				if ( ! array_key_exists( $meta_key, $original_custom_fields ) ) {
					continue;
				}
				if ( in_array( $meta_key, $excluded_meta_keys, true ) ) {
					continue;
				}
				if ( is_protected_meta( $meta_key, 'post' ) ) {
					$cloned_meta_data[ $meta_key ] = $original_custom_fields[ $meta_key ];
				} else {
					if ( ! array_key_exists( $meta_key, $cloned_meta_data ) ) {
						$cloned_meta_data[ $meta_key ] = array();
					}
					$original_value = isset( $original_custom_fields[ $meta_key ] ) ? $original_custom_fields[ $meta_key ][0] : false;
					$meta_value     = isset( $meta_item['value'] ) ? $meta_item['value'] : '';
					if ( is_string( $meta_value ) && is_json_string( $meta_value ) ) {
						$meta_value = json_decode( $meta_value, true );
					}
					if ( is_array( $meta_value ) ) {
						if ( $original_value ) {
							if ( is_serialized( $original_value ) ) {
								$meta_value = maybe_serialize( $meta_value );
							} elseif ( is_json_string( $original_value ) ) {
								$meta_value = wp_json_encode( $meta_value );
							}
						} else {
							$meta_value = maybe_serialize( $meta_value );
						}
					}
					$cloned_meta_data[ $meta_key ][] = $meta_value;
				}
			}
		} else {
			$cloned_meta_data = $original_custom_fields;
		}

		foreach ( $cloned_meta_data as $key => $value ) {
			if ( in_array( $key, $excluded_meta_keys, true ) ) {
				continue;
			}
			if ( is_array( $value ) && count( $value ) > 0 ) {
				foreach ( $value as $i => $v ) {
					if ( ! apply_filters( "mtphr_post_duplicator_meta_{$key}_enabled", true ) ) {
						continue;
					}
					$meta_value = apply_filters( "mtphr_post_duplicator_meta_value", $v, $key, $duplicate_id, $duplicate['post_type'] );
					$data       = array(
						'post_id'    => intval( $duplicate_id ),
						'meta_key'   => sanitize_text_field( $key ),
						'meta_value' => $meta_value,
					);
					$formats = array( '%d', '%s', '%s' );
					$wpdb->insert( $wpdb->prefix . 'postmeta', $data, $formats );
				}
			}
		}
	}

	do_action( 'mtphr_post_duplicator_created', $original_id, $duplicate_id, $settings );

	return array(
		'duplicate_id'    => $duplicate_id,
		'permalink'       => get_permalink( $duplicate_id ),
		'duplicate'       => $duplicate,
		'duplicate_terms' => $duplicate_terms,
	);
}

/**
 * Duplicate a post (REST API callback)
 */
function duplicate_post( $request ) {
	$data = $request->get_json_params();

	$original_id = isset( $data['original_id'] ) ? absint( $data['original_id'] ) : 0;

	if ( ! $original_id || $original_id <= 0 ) {
		return new \WP_Error( 'invalid_original_id', esc_html__( 'Invalid original id.', 'post-duplicator' ), array( 'status' => 400 ) );
	}

	$orig = get_post( $original_id );

	if ( ! $orig ) {
		return new \WP_Error( 'post_not_found', esc_html__( 'Original post not found.', 'post-duplicator' ), array( 'status' => 404 ) );
	}

	$override_settings = $data;
	unset( $override_settings['original_id'] );
	$settings = array_merge( get_option_value(), $override_settings );

	$result = perform_duplication( $orig, $settings );

	if ( is_wp_error( $result ) ) {
		return $result;
	}

	return rest_ensure_response( array(
		'duplicate_id' => $result['duplicate_id'],
		'permalink'    => $result['permalink'],
		'other_data'   => array(
			'duplicate_post'  => $result['duplicate'],
			'duplicate_terms' => $result['duplicate_terms'],
		),
	), 200 );
}

/**
 * Permission check for getting users
 */
function get_users_permissions( $request ) {
  // User must be logged in and have edit capabilities
  if ( ! is_user_logged_in() ) {
    return new \WP_Error( 'not_logged_in', esc_html__( 'You must be logged in to access this endpoint.', 'post-duplicator' ), array( 'status' => 401 ) );
  }
  
  // Check if user has edit capabilities
  if ( ! current_user_can( 'edit_posts' ) ) {
    return new \WP_Error( 'no_permission', esc_html__( 'You do not have permission to access this endpoint.', 'post-duplicator' ), array( 'status' => 403 ) );
  }
  
  return true;
}

/**
 * Get users with edit capabilities
 * 
 * Note: This is now only called when the duplicate modal opens (via REST API),
 * not on every admin page load, so memory usage is acceptable even with full user objects.
 */
function get_users( $request ) {
  // Get users with edit_posts capability
  // Use global namespace \get_users() to avoid conflict with this function name
  $users = \get_users( [
    'capability__in' => ['edit_posts'],
    'orderby' => 'display_name',
    'order' => 'ASC',
  ] );
  
  $user_options = [];
  foreach ( $users as $user ) {
    $user_options[] = [
      'value' => (string) $user->ID,
      'label' => $user->display_name . ' (' . $user->user_login . ')',
    ];
  }
  
  return rest_ensure_response( $user_options );
}

/**
 * Add custom allowed kses
 */
function additional_kses( $allowed_tags ) {
	// Allow the center tag with its attributes
	$allowed_tags['center'] = array(
			'align' => true,
			'class' => true,
			'id' => true,
			'style' => true,
	);
	
	return $allowed_tags;
}

Youez - 2016 - github.com/yon3zu
LinuXploit