first commit

This commit is contained in:
2024-07-31 13:12:38 +07:00
commit b4e8cbe182
10213 changed files with 3125839 additions and 0 deletions

View File

@@ -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;
}
}

View File

@@ -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 );
}
}

View File

@@ -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;
}
}

View File

@@ -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' );
}
}
}

View File

@@ -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() {}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}