first commit
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
namespace Elementor\TemplateLibrary\Classes;
|
||||
|
||||
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
|
||||
use Elementor\Core\Files\Uploads_Manager;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor template library import images.
|
||||
*
|
||||
* Elementor template library import images handler class is responsible for
|
||||
* importing remote images used by the template library.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Import_Images {
|
||||
|
||||
/**
|
||||
* Replaced images IDs.
|
||||
*
|
||||
* The IDs of all the new imported images. An array containing the old
|
||||
* attachment ID and the new attachment ID generated after the import.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_replace_image_ids = [];
|
||||
|
||||
/**
|
||||
* Get image hash.
|
||||
*
|
||||
* Retrieve the sha1 hash of the image URL.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access private
|
||||
*
|
||||
* @param string $attachment_url The attachment URL.
|
||||
*
|
||||
* @return string Image hash.
|
||||
*/
|
||||
private function get_hash_image( $attachment_url ) {
|
||||
return sha1( $attachment_url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get saved image.
|
||||
*
|
||||
* Retrieve new image ID, if the image has a new ID after the import.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access private
|
||||
*
|
||||
* @param array $attachment The attachment.
|
||||
*
|
||||
* @return false|array New image ID or false.
|
||||
*/
|
||||
private function get_saved_image( $attachment ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( isset( $this->_replace_image_ids[ $attachment['id'] ] ) ) {
|
||||
return $this->_replace_image_ids[ $attachment['id'] ];
|
||||
}
|
||||
|
||||
$post_id = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
'SELECT `post_id` FROM `' . $wpdb->postmeta . '`
|
||||
WHERE `meta_key` = \'_elementor_source_image_hash\'
|
||||
AND `meta_value` = %s
|
||||
;',
|
||||
$this->get_hash_image( $attachment['url'] )
|
||||
)
|
||||
);
|
||||
|
||||
if ( $post_id ) {
|
||||
$new_attachment = [
|
||||
'id' => $post_id,
|
||||
'url' => wp_get_attachment_url( $post_id ),
|
||||
];
|
||||
$this->_replace_image_ids[ $attachment['id'] ] = $new_attachment;
|
||||
|
||||
return $new_attachment;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import image.
|
||||
*
|
||||
* Import a single image from a remote server, upload the image WordPress
|
||||
* uploads folder, create a new attachment in the database and updates the
|
||||
* attachment metadata.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @since 3.2.0 New `$parent_post_id` option added
|
||||
* @access public
|
||||
*
|
||||
* @param array $attachment The attachment.
|
||||
* @param int $parent_post_id Optional.
|
||||
*
|
||||
* @return false|array Imported image data, or false.
|
||||
*/
|
||||
public function import( $attachment, $parent_post_id = null ) {
|
||||
if ( isset( $attachment['tmp_name'] ) ) {
|
||||
// Used when called to import a directly-uploaded file.
|
||||
$filename = $attachment['name'];
|
||||
$file_content = false;
|
||||
|
||||
// security validation in case the tmp_name has been tampered with
|
||||
if ( is_uploaded_file( $attachment['tmp_name'] ) ) {
|
||||
$file_content = Utils::file_get_contents( $attachment['tmp_name'] );
|
||||
}
|
||||
} else {
|
||||
// Used when attachment information is passed to this method.
|
||||
if ( ! empty( $attachment['id'] ) ) {
|
||||
$saved_image = $this->get_saved_image( $attachment );
|
||||
|
||||
if ( $saved_image ) {
|
||||
return $saved_image;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the file name and extension from the url.
|
||||
$filename = basename( $attachment['url'] );
|
||||
|
||||
$request = wp_safe_remote_get( $attachment['url'] );
|
||||
|
||||
// Make sure the request returns a valid result.
|
||||
if ( is_wp_error( $request ) || ( ! empty( $request['response']['code'] ) && 200 !== (int) $request['response']['code'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file_content = wp_remote_retrieve_body( $request );
|
||||
}
|
||||
|
||||
if ( empty( $file_content ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$filetype = wp_check_filetype( $filename );
|
||||
|
||||
// If the file type is not recognized by WordPress, exit here to avoid creation of an empty attachment document.
|
||||
if ( ! $filetype['ext'] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 'svg' === $filetype['ext'] ) {
|
||||
// In case that unfiltered-files upload is not enabled, SVG images should not be imported.
|
||||
if ( ! Uploads_Manager::are_unfiltered_uploads_enabled() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$svg_handler = Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' );
|
||||
|
||||
$file_content = $svg_handler->sanitizer( $file_content );
|
||||
}
|
||||
|
||||
$upload = wp_upload_bits(
|
||||
$filename,
|
||||
null,
|
||||
$file_content
|
||||
);
|
||||
|
||||
$post = [
|
||||
'post_title' => $filename,
|
||||
'guid' => $upload['url'],
|
||||
];
|
||||
|
||||
$info = wp_check_filetype( $upload['file'] );
|
||||
|
||||
if ( $info ) {
|
||||
$post['post_mime_type'] = $info['type'];
|
||||
} else {
|
||||
// For now just return the origin attachment
|
||||
return $attachment;
|
||||
// return new \WP_Error( 'attachment_processing_error', esc_html__( 'Invalid file type.', 'elementor' ) );
|
||||
}
|
||||
|
||||
$post_id = wp_insert_attachment( $post, $upload['file'], $parent_post_id );
|
||||
|
||||
apply_filters( 'elementor/template_library/import_images/new_attachment', $post_id );
|
||||
|
||||
// On REST requests.
|
||||
if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
|
||||
require_once ABSPATH . '/wp-admin/includes/image.php';
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'wp_read_video_metadata' ) ) {
|
||||
require_once ABSPATH . '/wp-admin/includes/media.php';
|
||||
}
|
||||
|
||||
wp_update_attachment_metadata(
|
||||
$post_id,
|
||||
wp_generate_attachment_metadata( $post_id, $upload['file'] )
|
||||
);
|
||||
update_post_meta( $post_id, '_elementor_source_image_hash', $this->get_hash_image( $attachment['url'] ) );
|
||||
|
||||
$new_attachment = [
|
||||
'id' => $post_id,
|
||||
'url' => $upload['url'],
|
||||
];
|
||||
|
||||
if ( ! empty( $attachment['id'] ) ) {
|
||||
$this->_replace_image_ids[ $attachment['id'] ] = $new_attachment;
|
||||
}
|
||||
|
||||
return $new_attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import local file.
|
||||
*
|
||||
* Import a local file directly to WordPress media library.
|
||||
* Used for importing files that are already downloaded locally (e.g., from extracted ZIP).
|
||||
*
|
||||
* @since 3.33.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $local_file_path The local file path.
|
||||
* @param int $parent_post_id Optional. Parent post ID.
|
||||
*
|
||||
* @return false|array Imported image data, or false on failure.
|
||||
*/
|
||||
public function import_local_file( $local_file_path, $parent_post_id = null ) {
|
||||
global $wp_filesystem;
|
||||
|
||||
if ( ! $wp_filesystem->exists( $local_file_path ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'media_handle_sideload' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/media.php';
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/image.php';
|
||||
}
|
||||
|
||||
$file_array = [
|
||||
'name' => basename( $local_file_path ),
|
||||
'tmp_name' => $local_file_path,
|
||||
];
|
||||
|
||||
$attachment_id = media_handle_sideload( $file_array, $parent_post_id );
|
||||
|
||||
if ( is_wp_error( $attachment_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
apply_filters( 'elementor/template_library/import_images/new_attachment', $attachment_id );
|
||||
|
||||
return [
|
||||
'id' => $attachment_id,
|
||||
'url' => wp_get_attachment_url( $attachment_id ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Template library import images constructor.
|
||||
*
|
||||
* Initializing the images import class used by the template library through
|
||||
* the WordPress Filesystem API.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( ! function_exists( 'WP_Filesystem' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
}
|
||||
|
||||
WP_Filesystem();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
namespace Elementor\TemplateLibrary\Classes;
|
||||
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor media collector.
|
||||
*
|
||||
* Collects media URLs during export and creates a media ZIP file.
|
||||
*/
|
||||
class Media_Collector {
|
||||
|
||||
/**
|
||||
* Filename for the media mapping JSON file within the ZIP.
|
||||
*/
|
||||
private const MAPPING_FILENAME = 'media-mapping.json';
|
||||
|
||||
/**
|
||||
* Collected media URLs and their metadata.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $collected_media = [];
|
||||
|
||||
/**
|
||||
* Temporary directory for media files.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $temp_dir = '';
|
||||
|
||||
public function __construct() {
|
||||
add_action( 'elementor/templates/collect_media_url', [ $this, 'collect_media_url' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Start media collection for export (Step 1: Collect URLs).
|
||||
*/
|
||||
public function start_collection() {
|
||||
$this->collected_media = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Start media processing (Step 2: Download media files and create zip).
|
||||
*/
|
||||
public function start_processing() {
|
||||
$this->temp_dir = \Elementor\Plugin::$instance->uploads_manager->create_unique_dir();
|
||||
}
|
||||
|
||||
public function collect_media_url( string $url, array $media_data = [] ) {
|
||||
if ( ! $this->is_media_url( $url ) || isset( $this->collected_media[ $url ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->collected_media[ $url ] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single media URL (Step 2: Download and save).
|
||||
*/
|
||||
public function process_media_url( string $url ) {
|
||||
if ( ! $this->is_media_url( $url ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$local_filename = $this->download_and_save_media( $url );
|
||||
if ( $local_filename ) {
|
||||
$this->collected_media[ $url ] = $local_filename;
|
||||
return $local_filename;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function download_and_save_media( string $url ) {
|
||||
if ( $this->is_local_url( $url ) ) {
|
||||
$local_file_path = $this->get_local_file_path( $url );
|
||||
if ( $local_file_path && file_exists( $local_file_path ) ) {
|
||||
return $this->copy_local_file( $local_file_path, $url );
|
||||
}
|
||||
}
|
||||
|
||||
return $this->download_via_http( $url );
|
||||
}
|
||||
|
||||
private function is_local_url( string $url ): bool {
|
||||
$site_url = get_site_url();
|
||||
$home_url = get_home_url();
|
||||
|
||||
return strpos( $url, $site_url ) === 0 || strpos( $url, $home_url ) === 0;
|
||||
}
|
||||
|
||||
private function get_local_file_path( string $url ) {
|
||||
$site_url = get_site_url();
|
||||
$home_url = get_home_url();
|
||||
|
||||
$relative_path = str_replace( [ $site_url, $home_url ], '', $url );
|
||||
$relative_path = ltrim( $relative_path, '/' );
|
||||
|
||||
$upload_dir = wp_upload_dir();
|
||||
$uploads_path = $upload_dir['basedir'];
|
||||
|
||||
$possible_paths = [
|
||||
$uploads_path . '/' . $relative_path,
|
||||
ABSPATH . $relative_path,
|
||||
$uploads_path . '/' . basename( $url ),
|
||||
];
|
||||
|
||||
foreach ( $possible_paths as $path ) {
|
||||
if ( file_exists( $path ) ) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function copy_local_file( string $source_path, string $original_url ) {
|
||||
$original_filename = basename( $original_url );
|
||||
$extension = pathinfo( $original_filename, PATHINFO_EXTENSION );
|
||||
$name_without_extension = pathinfo( $original_filename, PATHINFO_FILENAME );
|
||||
$unique_filename = sanitize_file_name( $name_without_extension . '_' . uniqid() . '.' . $extension );
|
||||
|
||||
$destination_path = $this->temp_dir . '/' . $unique_filename;
|
||||
$copied = copy( $source_path, $destination_path );
|
||||
|
||||
return $copied ? $unique_filename : false;
|
||||
}
|
||||
|
||||
private function download_via_http( string $url ) {
|
||||
$response = wp_safe_remote_get( $url, [
|
||||
'timeout' => 30,
|
||||
'user-agent' => 'Elementor Template Exporter',
|
||||
] );
|
||||
|
||||
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file_content = wp_remote_retrieve_body( $response );
|
||||
if ( empty( $file_content ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$original_filename = basename( $url );
|
||||
$extension = pathinfo( $original_filename, PATHINFO_EXTENSION );
|
||||
$name_without_extension = pathinfo( $original_filename, PATHINFO_FILENAME );
|
||||
$unique_filename = sanitize_file_name( $name_without_extension . '_' . uniqid() . '.' . $extension );
|
||||
|
||||
$file_path = $this->temp_dir . '/' . $unique_filename;
|
||||
$saved = file_put_contents( $file_path, $file_content );
|
||||
|
||||
return $saved ? $unique_filename : false;
|
||||
}
|
||||
|
||||
public function get_collected_urls(): array {
|
||||
return array_keys( $this->collected_media );
|
||||
}
|
||||
|
||||
public function process_media_collection( array $media_urls ) {
|
||||
$this->start_processing();
|
||||
|
||||
foreach ( $media_urls as $url ) {
|
||||
$this->process_media_url( $url );
|
||||
}
|
||||
|
||||
return $this->create_media_zip();
|
||||
}
|
||||
|
||||
public function create_media_zip() {
|
||||
if ( empty( $this->collected_media ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! class_exists( '\ZipArchive' ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
$zip_filename = 'media-' . uniqid() . '.zip';
|
||||
$zip_path = $this->temp_dir . '/' . $zip_filename;
|
||||
|
||||
if ( $zip->open( $zip_path, \ZipArchive::CREATE ) !== true ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mapping = [];
|
||||
|
||||
foreach ( $this->collected_media as $url => $filename ) {
|
||||
if ( is_string( $filename ) ) {
|
||||
$file_path = $this->temp_dir . '/' . $filename;
|
||||
if ( file_exists( $file_path ) ) {
|
||||
$zip->addFile( $file_path, $filename );
|
||||
$mapping[ $url ] = $filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $mapping ) ) {
|
||||
$zip->close();
|
||||
return null;
|
||||
}
|
||||
|
||||
$mapping_json = wp_json_encode( $mapping, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
|
||||
$zip->addFromString( self::MAPPING_FILENAME, $mapping_json );
|
||||
$zip->close();
|
||||
|
||||
return $zip_path;
|
||||
}
|
||||
|
||||
public function cleanup() {
|
||||
if ( $this->temp_dir ) {
|
||||
\Elementor\Plugin::$instance->uploads_manager->remove_file_or_dir( $this->temp_dir );
|
||||
}
|
||||
}
|
||||
|
||||
private function is_media_url( $url ) {
|
||||
if ( ! is_string( $url ) || empty( $url ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( strpos( $url, 'data:' ) === 0 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$allowed_mime_types = get_allowed_mime_types();
|
||||
$file_extension = strtolower( pathinfo( $url, PATHINFO_EXTENSION ) );
|
||||
|
||||
foreach ( $allowed_mime_types as $pattern => $mime_type ) {
|
||||
$pattern_regex = '/^(' . str_replace( '|', '|', $pattern ) . ')$/i';
|
||||
if ( preg_match( $pattern_regex, $file_extension ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
namespace Elementor\TemplateLibrary\Classes;
|
||||
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor media mapper.
|
||||
*
|
||||
* Maps media URLs to their corresponding filenames.
|
||||
*/
|
||||
class Media_Mapper {
|
||||
|
||||
const TRANSIENT_KEY = 'elementor_media_mapping';
|
||||
|
||||
public static function set_mapping( $mapping, $media_dir = '' ) {
|
||||
$mapping = is_array( $mapping ) ? $mapping : [];
|
||||
|
||||
if ( ! empty( $mapping ) ) {
|
||||
set_transient( self::TRANSIENT_KEY, [
|
||||
'mapping' => $mapping,
|
||||
'media_dir' => $media_dir,
|
||||
], HOUR_IN_SECONDS );
|
||||
}
|
||||
}
|
||||
|
||||
public static function get_local_file_path( $original_url ) {
|
||||
$stored_mapping = get_transient( self::TRANSIENT_KEY );
|
||||
|
||||
if ( ! $stored_mapping || ! is_array( $stored_mapping ) || empty( $original_url ) ) {
|
||||
return $original_url;
|
||||
}
|
||||
|
||||
$mapping = $stored_mapping['mapping'] ?? [];
|
||||
$media_dir = $stored_mapping['media_dir'] ?? '';
|
||||
|
||||
if ( empty( $mapping ) ) {
|
||||
return $original_url;
|
||||
}
|
||||
|
||||
$filename = $mapping[ $original_url ] ?? null;
|
||||
if ( $filename && $media_dir ) {
|
||||
$file_path = $media_dir . '/' . $filename;
|
||||
if ( file_exists( $file_path ) ) {
|
||||
return $file_path;
|
||||
}
|
||||
}
|
||||
|
||||
return $original_url;
|
||||
}
|
||||
|
||||
public static function clear_mapping() {
|
||||
$stored_mapping = get_transient( self::TRANSIENT_KEY );
|
||||
|
||||
if ( $stored_mapping && is_array( $stored_mapping ) ) {
|
||||
$media_dir = $stored_mapping['media_dir'] ?? '';
|
||||
if ( $media_dir ) {
|
||||
Plugin::$instance->uploads_manager->remove_file_or_dir( $media_dir );
|
||||
}
|
||||
}
|
||||
|
||||
delete_transient( self::TRANSIENT_KEY );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user