first commit
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
/**
|
||||
* Class for the customizer importer.
|
||||
*
|
||||
* Code is mostly from the Customizer Export/Import plugin.
|
||||
*
|
||||
* @see https://wordpress.org/plugins/customizer-export-import/
|
||||
*
|
||||
* @package Merlin WP
|
||||
*/
|
||||
|
||||
|
||||
class Merlin_Customizer_Importer {
|
||||
/**
|
||||
* Import customizer from a DAT file, generated by the Customizer Export/Import plugin.
|
||||
*
|
||||
* @param string $customizer_import_file_path path to the customizer import file.
|
||||
*/
|
||||
public static function import( $customizer_import_file_path ) {
|
||||
// Try to import the customizer settings.
|
||||
$results = self::import_customizer_options( $customizer_import_file_path );
|
||||
|
||||
// Check for errors, else write the results to the log file.
|
||||
if ( is_wp_error( $results ) ) {
|
||||
Merlin_Logger::get_instance()->error( $results->get_error_message() );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Merlin_Logger::get_instance()->info( __( 'The customizer import has finished successfully', 'thinkai' ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Imports uploaded mods and calls WordPress core customize_save actions so
|
||||
* themes that hook into them can act before mods are saved to the database.
|
||||
*
|
||||
* Update: WP core customize_save actions were removed, because of some errors.
|
||||
*
|
||||
* @since 1.1.1
|
||||
* @param string $import_file_path Path to the import file.
|
||||
* @return WP_Error
|
||||
*/
|
||||
public static function import_customizer_options( $import_file_path ) {
|
||||
// Setup global vars.
|
||||
global $wp_customize;
|
||||
|
||||
// Setup internal vars.
|
||||
$template = get_template();
|
||||
|
||||
// Make sure we have an import file.
|
||||
if ( ! file_exists( $import_file_path ) ) {
|
||||
return new \WP_Error(
|
||||
'missing_cutomizer_import_file',
|
||||
sprintf(
|
||||
esc_html__( 'Error: The customizer import file is missing! File path: %s', 'thinkai' ),
|
||||
$import_file_path
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Get the upload data.
|
||||
$raw = thinkai_filesystem()->get_contents( $import_file_path );
|
||||
|
||||
// Make sure we got the data.
|
||||
if ( empty( $raw ) ) {
|
||||
return new \WP_Error(
|
||||
'customizer_import_data_missing_content',
|
||||
esc_html__( 'Error: The customizer import file does not have any content in it. Please make sure to use the correct customizer import file.', 'thinkai' )
|
||||
);
|
||||
}
|
||||
|
||||
$data = unserialize( $raw );
|
||||
|
||||
// Data checks.
|
||||
if ( ! is_array( $data ) && ( ! isset( $data['template'] ) || ! isset( $data['mods'] ) ) ) {
|
||||
return new \WP_Error(
|
||||
'customizer_import_data_error',
|
||||
esc_html__( 'Error: The customizer import file is not in a correct format. Please make sure to use the correct customizer import file.', 'thinkai' )
|
||||
);
|
||||
}
|
||||
if ( $data['template'] !== $template ) {
|
||||
return new \WP_Error(
|
||||
'customizer_import_wrong_theme',
|
||||
esc_html__( 'Error: The customizer import file is not suitable for current theme. You can only import customizer settings for the same theme or a child theme.', 'thinkai' )
|
||||
);
|
||||
}
|
||||
|
||||
// Import images.
|
||||
if ( apply_filters( 'merlin_customizer_import_images', true ) ) {
|
||||
$data['mods'] = self::import_customizer_images( $data['mods'] );
|
||||
}
|
||||
|
||||
// Import custom options.
|
||||
if ( isset( $data['options'] ) ) {
|
||||
// Require modified customizer options class.
|
||||
if ( ! class_exists( '\WP_Customize_Setting' ) ) {
|
||||
require_once ABSPATH . 'wp-includes/class-wp-customize-setting.php';
|
||||
}
|
||||
|
||||
foreach ( $data['options'] as $option_key => $option_value ) {
|
||||
$option = new Merlin_Customizer_Option( $wp_customize, $option_key, array(
|
||||
'default' => '',
|
||||
'type' => 'option',
|
||||
'capability' => 'edit_theme_options',
|
||||
) );
|
||||
|
||||
$option->import( $option_value );
|
||||
}
|
||||
}
|
||||
|
||||
// Should the customizer import use the WP customize_save* hooks?
|
||||
$use_wp_customize_save_hooks = apply_filters( 'merlin_enable_wp_customize_save_hooks', false );
|
||||
|
||||
if ( $use_wp_customize_save_hooks ) {
|
||||
do_action( 'customize_save', $wp_customize );
|
||||
}
|
||||
|
||||
// Loop through the mods and save the mods.
|
||||
foreach ( $data['mods'] as $key => $val ) {
|
||||
if ( $use_wp_customize_save_hooks ) {
|
||||
do_action( 'customize_save_' . $key, $wp_customize );
|
||||
}
|
||||
|
||||
set_theme_mod( $key, $val );
|
||||
}
|
||||
|
||||
if ( $use_wp_customize_save_hooks ) {
|
||||
do_action( 'customize_save_after', $wp_customize );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function: Customizer import - imports images for settings saved as mods.
|
||||
*
|
||||
* @param array $mods An array of customizer mods.
|
||||
* @return array The mods array with any new import data.
|
||||
*/
|
||||
private static function import_customizer_images( $mods ) {
|
||||
foreach ( $mods as $key => $val ) {
|
||||
if ( self::customizer_is_image_url( $val ) ) {
|
||||
$data = self::customizer_sideload_image( $val );
|
||||
if ( ! is_wp_error( $data ) ) {
|
||||
$mods[ $key ] = $data->url;
|
||||
|
||||
// Handle header image controls.
|
||||
if ( isset( $mods[ $key . '_data' ] ) ) {
|
||||
$mods[ $key . '_data' ] = $data;
|
||||
update_post_meta( $data->attachment_id, '_wp_attachment_is_custom_header', get_stylesheet() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $mods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function: Customizer import
|
||||
* Taken from the core media_sideload_image function and
|
||||
* modified to return an array of data instead of html.
|
||||
*
|
||||
* @param string $file The image file path.
|
||||
* @return array An array of image data.
|
||||
*/
|
||||
private static function customizer_sideload_image( $file ) {
|
||||
$data = new \stdClass();
|
||||
|
||||
if ( ! function_exists( 'media_handle_sideload' ) ) {
|
||||
require_once( ABSPATH . 'wp-admin/includes/media.php' );
|
||||
require_once( ABSPATH . 'wp-admin/includes/file.php' );
|
||||
require_once( ABSPATH . 'wp-admin/includes/image.php' );
|
||||
}
|
||||
if ( ! empty( $file ) ) {
|
||||
// Set variables for storage, fix file filename for query strings.
|
||||
preg_match( '/[^\?]+\.(jpe?g|jpe|gif|png)\b/i', $file, $matches );
|
||||
$file_array = array();
|
||||
$file_array['name'] = basename( $matches[0] );
|
||||
|
||||
// Download file to temp location.
|
||||
$file_array['tmp_name'] = download_url( $file );
|
||||
|
||||
// If error storing temporarily, return the error.
|
||||
if ( is_wp_error( $file_array['tmp_name'] ) ) {
|
||||
return $file_array['tmp_name'];
|
||||
}
|
||||
|
||||
// Do the validation and storage stuff.
|
||||
$id = media_handle_sideload( $file_array, 0 );
|
||||
|
||||
// If error storing permanently, unlink.
|
||||
if ( is_wp_error( $id ) ) {
|
||||
unlink( $file_array['tmp_name'] );
|
||||
return $id;
|
||||
}
|
||||
|
||||
// Build the object to return.
|
||||
$meta = wp_get_attachment_metadata( $id );
|
||||
$data->attachment_id = $id;
|
||||
$data->url = wp_get_attachment_url( $id );
|
||||
$data->thumbnail_url = wp_get_attachment_thumb_url( $id );
|
||||
$data->height = $meta['height'];
|
||||
$data->width = $meta['width'];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see whether a string is an image url or not.
|
||||
*
|
||||
* @param string $string The string to check.
|
||||
* @return bool Whether the string is an image url or not.
|
||||
*/
|
||||
private static function customizer_is_image_url( $string = '' ) {
|
||||
if ( is_string( $string ) ) {
|
||||
if ( preg_match( '/\.(jpg|jpeg|png|gif)/i', $string ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* A class that extends WP_Customize_Setting so we can access
|
||||
* the protected updated method when importing options.
|
||||
*
|
||||
* Used in the Customizer importer.
|
||||
*
|
||||
* @package Merlin WP
|
||||
*/
|
||||
|
||||
final class Merlin_Customizer_Option extends \WP_Customize_Setting {
|
||||
/**
|
||||
* Import an option value for this setting.
|
||||
*
|
||||
* @since 1.1.1
|
||||
* @param mixed $value The option value.
|
||||
* @return void
|
||||
*/
|
||||
public function import( $value ) {
|
||||
$this->update( $value );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
/**
|
||||
* Class for downloading a file from a given URL.
|
||||
*
|
||||
* @package Merlin WP
|
||||
*/
|
||||
|
||||
class Merlin_Downloader {
|
||||
/**
|
||||
* Holds full path to where the files will be saved.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $download_directory_path = '';
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
*
|
||||
* @param string $download_directory_path Full path to where the files will be saved.
|
||||
*/
|
||||
public function __construct( $download_directory_path = '' ) {
|
||||
$this->set_download_directory_path( $download_directory_path );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Download file from a given URL.
|
||||
*
|
||||
* @param string $url URL of file to download.
|
||||
* @param string $filename Filename of the file to save.
|
||||
* @return string|WP_Error Full path to the downloaded file or WP_Error object with error message.
|
||||
*/
|
||||
public function download_file( $url, $filename ) {
|
||||
$content = $this->get_content_from_url( $url );
|
||||
|
||||
// Check if there was an error and break out.
|
||||
if ( is_wp_error( $content ) ) {
|
||||
Merlin_Logger::get_instance()->error( $content->get_error_message(), array( 'url' => $url, 'filename' => $filename ) );
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
$saved_file = thinkai_filesystem()->put_contents( $this->download_directory_path . $filename, $content );
|
||||
|
||||
if ( ! empty( $saved_file ) ) {
|
||||
return $this->download_directory_path . $filename;
|
||||
}
|
||||
|
||||
Merlin_Logger::get_instance()->error( __( 'The file was not able to save to disk, while trying to download it', 'thinkai' ), array( 'url' => $url, 'filename' => $filename ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper function: get content from an URL.
|
||||
*
|
||||
* @param string $url URL to the content file.
|
||||
* @return string|WP_Error, content from the URL or WP_Error object with error message.
|
||||
*/
|
||||
private function get_content_from_url( $url ) {
|
||||
// Test if the URL to the file is defined.
|
||||
if ( empty( $url ) ) {
|
||||
return new \WP_Error(
|
||||
'missing_url',
|
||||
__( 'Missing URL for downloading a file!', 'thinkai' )
|
||||
);
|
||||
}
|
||||
|
||||
// Get file content from the server.
|
||||
$response = wp_remote_get(
|
||||
$url,
|
||||
array( 'timeout' => apply_filters( 'merlin_timeout_for_downloading_import_file', 20 ) )
|
||||
);
|
||||
|
||||
// Test if the get request was not successful.
|
||||
if ( is_wp_error( $response ) || 200 !== $response['response']['code'] ) {
|
||||
// Collect the right format of error data (array or WP_Error).
|
||||
$response_error = $this->get_error_from_response( $response );
|
||||
|
||||
return new \WP_Error(
|
||||
'download_error',
|
||||
sprintf(
|
||||
__( 'An error occurred while fetching file from: %1$s%2$s%3$s!%4$sReason: %5$s - %6$s.', 'thinkai' ),
|
||||
'<strong>',
|
||||
$url,
|
||||
'</strong>',
|
||||
'<br>',
|
||||
$response_error['error_code'],
|
||||
$response_error['error_message']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Return content retrieved from the URL.
|
||||
return wp_remote_retrieve_body( $response );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper function: get the right format of response errors.
|
||||
*
|
||||
* @param array|WP_Error $response Array or WP_Error or the response.
|
||||
* @return array Error code and error message.
|
||||
*/
|
||||
private function get_error_from_response( $response ) {
|
||||
$response_error = array();
|
||||
|
||||
if ( is_array( $response ) ) {
|
||||
$response_error['error_code'] = $response['response']['code'];
|
||||
$response_error['error_message'] = $response['response']['message'];
|
||||
}
|
||||
else {
|
||||
$response_error['error_code'] = $response->get_error_code();
|
||||
$response_error['error_message'] = $response->get_error_message();
|
||||
}
|
||||
|
||||
return $response_error;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get download_directory_path attribute.
|
||||
*/
|
||||
public function get_download_directory_path() {
|
||||
return $this->download_directory_path;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set download_directory_path attribute.
|
||||
* If no valid path is specified, the default WP upload directory will be used.
|
||||
*
|
||||
* @param string $download_directory_path Path, where the files will be saved.
|
||||
*/
|
||||
public function set_download_directory_path( $download_directory_path ) {
|
||||
if ( file_exists( $download_directory_path ) ) {
|
||||
$this->download_directory_path = $download_directory_path;
|
||||
}
|
||||
else {
|
||||
$upload_dir = wp_upload_dir();
|
||||
$this->download_directory_path = apply_filters( 'merlin_upload_file_path', trailingslashit( $upload_dir['path'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check, if the file already exists and return his full path.
|
||||
*
|
||||
* @param string $filename The name of the file.
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public function fetch_existing_file( $filename ) {
|
||||
if ( file_exists( $this->download_directory_path . $filename ) ) {
|
||||
return $this->download_directory_path . $filename;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* Class for the custom WP hooks.
|
||||
*
|
||||
* @package Merlin WP
|
||||
*/
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class Merlin_Hooks {
|
||||
/**
|
||||
* The class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'merlin_widget_settings_array', array( $this, 'fix_custom_menu_widget_ids' ) );
|
||||
add_action( 'import_start', array( $this, 'maybe_disable_creating_different_size_images_during_import' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the menu IDs in the custom menu widgets in the widget import data.
|
||||
* This solves the issue with custom menu widgets not having the correct (new) menu ID, because they
|
||||
* have the old menu ID from the export site.
|
||||
*
|
||||
* @param array $widget The widget settings array.
|
||||
*/
|
||||
public function fix_custom_menu_widget_ids( $widget ) {
|
||||
// Skip (no changes needed), if this is not a custom menu widget.
|
||||
if ( ! array_key_exists( 'nav_menu', $widget ) || empty( $widget['nav_menu'] ) || ! is_int( $widget['nav_menu'] ) ) {
|
||||
return $widget;
|
||||
}
|
||||
|
||||
// Get import data, with new menu IDs.
|
||||
$importer = new ProteusThemes\WPContentImporter2\Importer( array( 'fetch_attachments' => true ), new ProteusThemes\WPContentImporter2\WPImporterLogger() );
|
||||
$importer->restore_import_data_transient();
|
||||
|
||||
$importer_mapping = $importer->get_mapping();
|
||||
$term_ids = empty( $importer_mapping['term_id'] ) ? array() : $importer_mapping['term_id'];
|
||||
|
||||
// Set the new menu ID for the widget.
|
||||
$widget['nav_menu'] = empty( $term_ids[ $widget['nav_menu'] ] ) ? $widget['nav_menu'] : $term_ids[ $widget['nav_menu'] ];
|
||||
|
||||
return $widget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper function for the after all import action hook.
|
||||
*
|
||||
* @param int $selected_import_index The selected demo import index.
|
||||
*/
|
||||
public function after_all_import_action( $selected_import_index ) {
|
||||
do_action( 'merlin_after_all_import', $selected_import_index );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe disables generation of multiple image sizes (thumbnails) in the content import step.
|
||||
*/
|
||||
public function maybe_disable_creating_different_size_images_during_import() {
|
||||
if ( ! apply_filters( 'merlin_regenerate_thumbnails_in_content_import', true ) ) {
|
||||
add_filter( 'intermediate_image_sizes_advanced', '__return_null' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/**
|
||||
* The logger class, which will abstract the use of the monolog library.
|
||||
* More about monolog: https://github.com/Seldaek/monolog
|
||||
*/
|
||||
|
||||
use Monolog\Logger as MonologLogger;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
|
||||
class Merlin_Logger {
|
||||
/**
|
||||
* @var object instance of the monolog logger class.
|
||||
*/
|
||||
private $log;
|
||||
|
||||
|
||||
/**
|
||||
* @var string The absolute path to the log file.
|
||||
*/
|
||||
private $log_path;
|
||||
|
||||
|
||||
/**
|
||||
* @var string The name of the logger instance.
|
||||
*/
|
||||
private $logger_name;
|
||||
|
||||
|
||||
/**
|
||||
* The instance *Singleton* of this class
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the *Singleton* instance of this class.
|
||||
*
|
||||
* @return object EasyDigitalDownloadsFastspring *Singleton* instance.
|
||||
*
|
||||
* @codeCoverageIgnore Nothing to test, default PHP singleton functionality.
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( null === static::$instance ) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Logger constructor.
|
||||
*
|
||||
* Protected constructor to prevent creating a new instance of the
|
||||
* *Singleton* via the `new` operator from outside of this class.
|
||||
*/
|
||||
protected function __construct( $log_path = null, $name = 'merlin-logger' ) {
|
||||
$this->log_path = $log_path;
|
||||
$this->logger_name = $name;
|
||||
|
||||
if ( empty( $this->log_path ) ) {
|
||||
$upload_dir = wp_upload_dir();
|
||||
$logger_dir = $upload_dir['basedir'] . '/merlin-wp';
|
||||
|
||||
if ( ! file_exists( $logger_dir ) ) {
|
||||
wp_mkdir_p( $logger_dir );
|
||||
}
|
||||
|
||||
$this->log_path = $logger_dir . '/main.log';
|
||||
}
|
||||
|
||||
$this->initialize_logger();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the monolog logger class.
|
||||
*/
|
||||
private function initialize_logger() {
|
||||
if ( empty( $this->log_path ) || empty( $this->logger_name ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->log = new MonologLogger( $this->logger_name );
|
||||
$this->log->pushHandler( new StreamHandler( $this->log_path, MonologLogger::DEBUG ) );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log message for log level: debug.
|
||||
*
|
||||
* @param string $message The log message.
|
||||
* @param array $context The log context.
|
||||
*
|
||||
* @return boolean Whether the record has been processed.
|
||||
*/
|
||||
public function debug( $message, $context = array() ) {
|
||||
return $this->log->debug( $message, $context );
|
||||
}
|
||||
|
||||
/**
|
||||
* Log message for log level: info.
|
||||
*
|
||||
* @param string $message The log message.
|
||||
* @param array $context The log context.
|
||||
*
|
||||
* @return boolean Whether the record has been processed.
|
||||
*/
|
||||
public function info( $message, $context = array() ) {
|
||||
return $this->log->info( $message, $context );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log message for log level: notice.
|
||||
*
|
||||
* @param string $message The log message.
|
||||
* @param array $context The log context.
|
||||
*
|
||||
* @return boolean Whether the record has been processed.
|
||||
*/
|
||||
public function notice( $message, $context = array() ) {
|
||||
return $this->log->notice( $message, $context );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log message for log level: warning.
|
||||
*
|
||||
* @param string $message The log message.
|
||||
* @param array $context The log context.
|
||||
*
|
||||
* @return boolean Whether the record has been processed.
|
||||
*/
|
||||
public function warning( $message, $context = array() ) {
|
||||
return $this->log->warning( $message, $context );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log message for log level: error.
|
||||
*
|
||||
* @param string $message The log message.
|
||||
* @param array $context The log context.
|
||||
*
|
||||
* @return boolean Whether the record has been processed.
|
||||
*/
|
||||
public function error( $message, $context = array() ) {
|
||||
return $this->log->error( $message, $context );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log message for log level: alert.
|
||||
*
|
||||
* @param string $message The log message.
|
||||
* @param array $context The log context.
|
||||
*
|
||||
* @return boolean Whether the record has been processed.
|
||||
*/
|
||||
public function alert( $message, $context = array() ) {
|
||||
return $this->log->alert( $message, $context );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log message for log level: emergency.
|
||||
*
|
||||
* @param string $message The log message.
|
||||
* @param array $context The log context.
|
||||
*
|
||||
* @return boolean Whether the record has been processed.
|
||||
*/
|
||||
public function emergency( $message, $context = array() ) {
|
||||
return $this->log->emergency( $message, $context );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Private clone method to prevent cloning of the instance of the *Singleton* instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function __clone() {}
|
||||
|
||||
|
||||
/**
|
||||
* Private unserialize method to prevent unserializing of the *Singleton* instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __wakeup() {}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* Class for the Redux importer.
|
||||
*
|
||||
* @see https://wordpress.org/plugins/redux-framework/
|
||||
*
|
||||
* @package Merlin WP
|
||||
*/
|
||||
|
||||
class Merlin_Redux_Importer {
|
||||
/**
|
||||
* Import Redux data from a JSON file, generated by the Redux plugin.
|
||||
*
|
||||
* @param array $import_data Array of arrays. Child array contains 'option_name' and 'file_path'.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function import( $import_data ) {
|
||||
// Redux plugin is not active!
|
||||
if ( ! class_exists( 'ReduxFramework' ) || ! class_exists( 'ReduxFrameworkInstances' ) || empty( $import_data ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ( $import_data as $redux_item ) {
|
||||
$redux_options_raw_data = thinkai_filesystem()->get_contents( $redux_item['file_path'] );
|
||||
$redux_options_data = json_decode( $redux_options_raw_data, true );
|
||||
$redux_framework = ReduxFrameworkInstances::get_instance( $redux_item['option_name'] );
|
||||
|
||||
if ( isset( $redux_framework->args['opt_name'] ) ) {
|
||||
$redux_framework->set_options( $redux_options_data );
|
||||
|
||||
Merlin_Logger::get_instance()->debug( __( 'The Redux Framework data was imported' , 'thinkai'), $redux_item );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
/**
|
||||
* Class for the widget importer.
|
||||
*
|
||||
* Code is mostly from the Widget Importer & Exporter plugin.
|
||||
*
|
||||
* @see https://wordpress.org/plugins/widget-importer-exporter/
|
||||
*
|
||||
* @package Merlin WP
|
||||
*/
|
||||
|
||||
class Merlin_Widget_Importer {
|
||||
/**
|
||||
* Import widgets from WIE or JSON file.
|
||||
*
|
||||
* @param string $widget_import_file_path path to the widget import file.
|
||||
*/
|
||||
public static function import( $widget_import_file_path ) {
|
||||
if ( empty( $widget_import_file_path ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self::unset_default_widgets();
|
||||
|
||||
$results = self::import_widgets( $widget_import_file_path );
|
||||
|
||||
if ( is_wp_error( $results ) ) {
|
||||
Merlin_Logger::get_instance()->error( $results->get_error_message() );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
self::format_results_for_log( $results );
|
||||
$message = ob_get_clean();
|
||||
|
||||
Merlin_Logger::get_instance()->debug( $message );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Imports widgets from a json file.
|
||||
*
|
||||
* @param string $data_file path to json file with WordPress widget export data.
|
||||
*/
|
||||
private static function import_widgets( $data_file ) {
|
||||
// Get widgets data from file.
|
||||
$data = self::process_import_file( $data_file );
|
||||
|
||||
// Return from this function if there was an error.
|
||||
if ( is_wp_error( $data ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
// Import the widget data and save the results.
|
||||
return self::import_data( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process import file - this parses the widget data and returns it.
|
||||
*
|
||||
* @param string $file path to json file.
|
||||
* @return WP_Error|object
|
||||
*/
|
||||
private static function process_import_file( $file ) {
|
||||
// File exists?
|
||||
if ( ! file_exists( $file ) ) {
|
||||
return new \WP_Error(
|
||||
'widget_import_file_not_found',
|
||||
__( 'Error: Widget import file could not be found.', 'thinkai' )
|
||||
);
|
||||
}
|
||||
|
||||
// Get file contents and decode.
|
||||
$data = thinkai_filesystem()->get_contents( $file );
|
||||
|
||||
// Return from this function if there was an error.
|
||||
if ( empty( $data ) ) {
|
||||
return new \WP_Error(
|
||||
'widget_import_file_missing_content',
|
||||
__( 'Error: Widget import file does not have any content in it.', 'thinkai' )
|
||||
);
|
||||
}
|
||||
|
||||
return json_decode( $data );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Import widget JSON data
|
||||
*
|
||||
* @global array $wp_registered_sidebars
|
||||
* @param object $data JSON widget data.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private static function import_data( $data ) {
|
||||
global $wp_registered_sidebars;
|
||||
|
||||
// Have valid data? If no data or could not decode.
|
||||
if ( empty( $data ) || ! is_object( $data ) ) {
|
||||
return new \WP_Error(
|
||||
'corrupted_widget_import_data',
|
||||
__( 'Error: Widget import data could not be read. Please try a different file.', 'thinkai' )
|
||||
);
|
||||
}
|
||||
|
||||
// Hook before import.
|
||||
do_action( 'merlin_widget_importer_before_widgets_import', $data );
|
||||
$data = apply_filters( 'merlin_before_widgets_import_data', $data );
|
||||
|
||||
// Get all available widgets site supports.
|
||||
$available_widgets = self::available_widgets();
|
||||
|
||||
// Get all existing widget instances.
|
||||
$widget_instances = array();
|
||||
|
||||
foreach ( $available_widgets as $widget_data ) {
|
||||
$widget_instances[ $widget_data['id_base'] ] = get_option( 'widget_' . $widget_data['id_base'] );
|
||||
}
|
||||
|
||||
// Begin results.
|
||||
$results = array();
|
||||
|
||||
// Loop import data's sidebars.
|
||||
foreach ( $data as $sidebar_id => $widgets ) {
|
||||
// Skip inactive widgets (should not be in export file).
|
||||
if ( 'wp_inactive_widgets' == $sidebar_id ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if sidebar is available on this site. Otherwise add widgets to inactive, and say so.
|
||||
if ( isset( $wp_registered_sidebars[ $sidebar_id ] ) ) {
|
||||
$sidebar_available = true;
|
||||
$use_sidebar_id = $sidebar_id;
|
||||
$sidebar_message_type = 'success';
|
||||
$sidebar_message = '';
|
||||
}
|
||||
else {
|
||||
$sidebar_available = false;
|
||||
$use_sidebar_id = 'wp_inactive_widgets'; // Add to inactive if sidebar does not exist in theme.
|
||||
$sidebar_message_type = 'error';
|
||||
$sidebar_message = __( 'Sidebar does not exist in theme (moving widget to Inactive)', 'thinkai' );
|
||||
}
|
||||
|
||||
// Result for sidebar.
|
||||
$results[ $sidebar_id ]['name'] = ! empty( $wp_registered_sidebars[ $sidebar_id ]['name'] ) ? $wp_registered_sidebars[ $sidebar_id ]['name'] : $sidebar_id; // Sidebar name if theme supports it; otherwise ID.
|
||||
$results[ $sidebar_id ]['message_type'] = $sidebar_message_type;
|
||||
$results[ $sidebar_id ]['message'] = $sidebar_message;
|
||||
$results[ $sidebar_id ]['widgets'] = array();
|
||||
|
||||
// Loop widgets.
|
||||
foreach ( $widgets as $widget_instance_id => $widget ) {
|
||||
$fail = false;
|
||||
|
||||
// Get id_base (remove -# from end) and instance ID number.
|
||||
$id_base = preg_replace( '/-[0-9]+$/', '', $widget_instance_id );
|
||||
$instance_id_number = str_replace( $id_base . '-', '', $widget_instance_id );
|
||||
|
||||
// Does site support this widget?
|
||||
if ( ! $fail && ! isset( $available_widgets[ $id_base ] ) ) {
|
||||
$fail = true;
|
||||
$widget_message_type = 'error';
|
||||
$widget_message = __( 'Site does not support widget', 'thinkai' ); // Explain why widget not imported.
|
||||
}
|
||||
|
||||
// Filter to modify settings object before conversion to array and import.
|
||||
// Leave this filter here for backwards compatibility with manipulating objects (before conversion to array below).
|
||||
// Ideally the newer wie_widget_settings_array below will be used instead of this.
|
||||
$widget = apply_filters( 'merlin_widget_settings', $widget ); // Object.
|
||||
|
||||
// Convert multidimensional objects to multidimensional arrays.
|
||||
// Some plugins like Jetpack Widget Visibility store settings as multidimensional arrays.
|
||||
// Without this, they are imported as objects and cause fatal error on Widgets page.
|
||||
// If this creates problems for plugins that do actually intend settings in objects then may need to consider other approach: https://wordpress.org/support/topic/problem-with-array-of-arrays.
|
||||
// It is probably much more likely that arrays are used than objects, however.
|
||||
$widget = json_decode( json_encode( $widget ), true );
|
||||
|
||||
// Filter to modify settings array.
|
||||
// This is preferred over the older wie_widget_settings filter above.
|
||||
// Do before identical check because changes may make it identical to end result (such as URL replacements).
|
||||
$widget = apply_filters( 'merlin_widget_settings_array', $widget );
|
||||
|
||||
// Does widget with identical settings already exist in same sidebar?
|
||||
if ( ! $fail && isset( $widget_instances[ $id_base ] ) ) {
|
||||
// Get existing widgets in this sidebar.
|
||||
$sidebars_widgets = get_option( 'sidebars_widgets' );
|
||||
$sidebar_widgets = isset( $sidebars_widgets[ $use_sidebar_id ] ) ? $sidebars_widgets[ $use_sidebar_id ] : array(); // Check Inactive if that's where will go.
|
||||
|
||||
// Loop widgets with ID base.
|
||||
$single_widget_instances = ! empty( $widget_instances[ $id_base ] ) ? $widget_instances[ $id_base ] : array();
|
||||
foreach ( $single_widget_instances as $check_id => $check_widget ) {
|
||||
// Is widget in same sidebar and has identical settings?
|
||||
if ( in_array( "$id_base-$check_id", $sidebar_widgets ) && (array) $widget == $check_widget ) {
|
||||
$fail = true;
|
||||
$widget_message_type = 'warning';
|
||||
$widget_message = __( 'Widget already exists', 'thinkai' ); // Explain why widget not imported.
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No failure.
|
||||
if ( ! $fail ) {
|
||||
// Add widget instance.
|
||||
$single_widget_instances = get_option( 'widget_' . $id_base ); // All instances for that widget ID base, get fresh every time.
|
||||
$single_widget_instances = ! empty( $single_widget_instances ) ? $single_widget_instances : array( '_multiwidget' => 1 ); // Start fresh if have to.
|
||||
$single_widget_instances[] = $widget; // Add it.
|
||||
|
||||
// Get the key it was given.
|
||||
end( $single_widget_instances );
|
||||
$new_instance_id_number = key( $single_widget_instances );
|
||||
|
||||
// If key is 0, make it 1.
|
||||
// When 0, an issue can occur where adding a widget causes data from other widget to load, and the widget doesn't stick (reload wipes it).
|
||||
if ( '0' === strval( $new_instance_id_number ) ) {
|
||||
$new_instance_id_number = 1;
|
||||
$single_widget_instances[ $new_instance_id_number ] = $single_widget_instances[0];
|
||||
unset( $single_widget_instances[0] );
|
||||
}
|
||||
|
||||
// Move _multiwidget to end of array for uniformity.
|
||||
if ( isset( $single_widget_instances['_multiwidget'] ) ) {
|
||||
$multiwidget = $single_widget_instances['_multiwidget'];
|
||||
unset( $single_widget_instances['_multiwidget'] );
|
||||
$single_widget_instances['_multiwidget'] = $multiwidget;
|
||||
}
|
||||
|
||||
// Update option with new widget.
|
||||
update_option( 'widget_' . $id_base, $single_widget_instances );
|
||||
|
||||
// Assign widget instance to sidebar.
|
||||
$sidebars_widgets = get_option( 'sidebars_widgets' ); // Which sidebars have which widgets, get fresh every time.
|
||||
$new_instance_id = $id_base . '-' . $new_instance_id_number; // Use ID number from new widget instance.
|
||||
$sidebars_widgets[ $use_sidebar_id ][] = $new_instance_id; // Add new instance to sidebar.
|
||||
update_option( 'sidebars_widgets', $sidebars_widgets ); // Save the amended data.
|
||||
|
||||
// After widget import action.
|
||||
$after_widget_import = array(
|
||||
'sidebar' => $use_sidebar_id,
|
||||
'sidebar_old' => $sidebar_id,
|
||||
'widget' => $widget,
|
||||
'widget_type' => $id_base,
|
||||
'widget_id' => $new_instance_id,
|
||||
'widget_id_old' => $widget_instance_id,
|
||||
'widget_id_num' => $new_instance_id_number,
|
||||
'widget_id_num_old' => $instance_id_number,
|
||||
);
|
||||
do_action( 'merlin_widget_importer_after_single_widget_import', $after_widget_import );
|
||||
|
||||
// Success message.
|
||||
if ( $sidebar_available ) {
|
||||
$widget_message_type = 'success';
|
||||
$widget_message = __( 'Imported', 'thinkai' );
|
||||
}
|
||||
else {
|
||||
$widget_message_type = 'warning';
|
||||
$widget_message = __( 'Imported to Inactive', 'thinkai' );
|
||||
}
|
||||
}
|
||||
|
||||
// Result for widget instance.
|
||||
$results[ $sidebar_id ]['widgets'][ $widget_instance_id ]['name'] = isset( $available_widgets[ $id_base ]['name'] ) ? $available_widgets[ $id_base ]['name'] : $id_base; // Widget name or ID if name not available (not supported by site).
|
||||
$results[ $sidebar_id ]['widgets'][ $widget_instance_id ]['title'] = ! empty( $widget['title'] ) ? $widget['title'] : __( 'No Title', 'thinkai' ); // Show "No Title" if widget instance is untitled.
|
||||
$results[ $sidebar_id ]['widgets'][ $widget_instance_id ]['message_type'] = $widget_message_type;
|
||||
$results[ $sidebar_id ]['widgets'][ $widget_instance_id ]['message'] = $widget_message;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Hook after import.
|
||||
do_action( 'merlin_widget_importer_after_widgets_import', $data );
|
||||
|
||||
// Return results.
|
||||
return apply_filters( 'merlin_widget_import_results', $results );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Available widgets.
|
||||
*
|
||||
* Gather site's widgets into array with ID base, name, etc.
|
||||
*
|
||||
* @global array $wp_registered_widget_controls
|
||||
* @return array $available_widgets, Widget information
|
||||
*/
|
||||
private static function available_widgets() {
|
||||
global $wp_registered_widget_controls;
|
||||
|
||||
$widget_controls = $wp_registered_widget_controls;
|
||||
$available_widgets = array();
|
||||
|
||||
foreach ( $widget_controls as $widget ) {
|
||||
if ( ! empty( $widget['id_base'] ) && ! isset( $available_widgets[ $widget['id_base'] ] ) ) {
|
||||
$available_widgets[ $widget['id_base'] ]['id_base'] = $widget['id_base'];
|
||||
$available_widgets[ $widget['id_base'] ]['name'] = $widget['name'];
|
||||
}
|
||||
}
|
||||
|
||||
return apply_filters( 'merlin_available_widgets', $available_widgets );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove widgets from sidebars.
|
||||
* By default none are removed, but with the filter you can remove them.
|
||||
*/
|
||||
private static function unset_default_widgets() {
|
||||
$widget_areas = apply_filters( 'merlin_unset_default_widgets_args', false );
|
||||
|
||||
if ( empty( $widget_areas ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
update_option( 'sidebars_widgets', $widget_areas );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format results for log file
|
||||
*
|
||||
* @param array $results widget import results.
|
||||
*/
|
||||
private static function format_results_for_log( $results ) {
|
||||
if ( empty( $results ) ) {
|
||||
esc_html_e( 'No results for widget import!', 'thinkai' );
|
||||
}
|
||||
|
||||
// Loop sidebars.
|
||||
foreach ( $results as $sidebar ) {
|
||||
echo esc_html( $sidebar['name'] ) . ' : ' . esc_html( $sidebar['message'] ) . PHP_EOL . PHP_EOL;
|
||||
// Loop widgets.
|
||||
foreach ( $sidebar['widgets'] as $widget ) {
|
||||
echo esc_html( $widget['name'] ) . ' - ' . esc_html( $widget['title'] ) . ' - ' . esc_html( $widget['message'] ) . PHP_EOL;
|
||||
}
|
||||
echo PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user