e back to this step to import the theme demo data.', 'one-click-demo-import' ),
'content_filetype_warn' => esc_html__( 'Invalid file type detected! Please select an XML file for the Content Import.', 'one-click-demo-import' ),
'widgets_filetype_warn' => esc_html__( 'Invalid file type detected! Please select a JSON or WIE file for the Widgets Import.', 'one-click-demo-import' ),
'customizer_filetype_warn' => esc_html__( 'Invalid file type detected! Please select a DAT file for the Customizer Import.', 'one-click-demo-import' ),
'redux_filetype_warn' => esc_html__( 'Invalid file type detected! Please select a JSON file for the Redux Import.', 'one-click-demo-import' ),
),
)
);
wp_enqueue_style( 'ocdi-main-css', OCDI_URL . 'assets/css/main.css', array() , OCDI_VERSION );
}
}
/**
* AJAX callback method for uploading the manual import files.
*/
public function upload_manual_import_files_callback() {
Helpers::verify_ajax_call();
if ( empty( $_FILES ) ) {
wp_send_json_error( esc_html__( 'Manual import files are missing! Please select the import files and try again.', 'one-click-demo-import' ) );
}
// Create a date and time string to use for demo and log file names.
Helpers::set_demo_import_start_time();
// Define log file path.
$this->log_file_path = Helpers::get_log_path();
$this->selected_index = 0;
// Get paths for the uploaded files.
$this->selected_import_files = Helpers::process_uploaded_files( $_FILES, $this->log_file_path );
// Set the name of the import files, because we used the uploaded files.
$this->import_files[ $this->selected_index ]['import_file_name'] = esc_html__( 'Manually uploaded files', 'one-click-demo-import' );
// Save the initial import data as a transient, so the next import call (in new AJAX call) can use that data.
Helpers::set_ocdi_import_data_transient( $this->get_current_importer_data() );
wp_send_json_success();
}
/**
* Main AJAX callback function for:
* 1). prepare import files (uploaded or predefined via filters)
* 2). execute 'before content import' actions (before import WP action)
* 3). import content
* 4). execute 'after content import' actions (before widget import WP action, widget import, customizer import, after import WP action)
*/
public function import_demo_data_ajax_callback() {
// Try to update PHP memory limit (so that it does not run out of it).
ini_set( 'memory_limit', Helpers::apply_filters( 'ocdi/import_memory_limit', '350M' ) );
// Verify if the AJAX call is valid (checks nonce and current_user_can).
Helpers::verify_ajax_call();
// Is this a new AJAX call to continue the previous import?
$use_existing_importer_data = $this->use_existing_importer_data();
if ( ! $use_existing_importer_data ) {
// Create a date and time string to use for demo and log file names.
Helpers::set_demo_import_start_time();
// Define log file path.
$this->log_file_path = Helpers::get_log_path();
// Get selected file index or set it to 0.
$this->selected_index = empty( $_POST['selected'] ) ? 0 : absint( $_POST['selected'] );
/**
* 1). Prepare import files.
* Manually uploaded import files or predefined import files via filter: ocdi/import_files
*/
if ( ! empty( $_FILES ) ) { // Using manual file uploads?
// Get paths for the uploaded files.
$this->selected_import_files = Helpers::process_uploaded_files( $_FILES, $this->log_file_path );
// Set the name of the import files, because we used the uploaded files.
$this->import_files[ $this->selected_index ]['import_file_name'] = esc_html__( 'Manually uploaded files', 'one-click-demo-import' );
}
elseif ( ! empty( $this->import_files[ $this->selected_index ] ) ) { // Use predefined import files from wp filter: ocdi/import_files.
// Download the import files (content, widgets and customizer files).
$this->selected_import_files = Helpers::download_import_files( $this->import_files[ $this->selected_index ] );
// Check Errors.
if ( is_wp_error( $this->selected_import_files ) ) {
// Write error to log file and send an AJAX response with the error.
Helpers::log_error_and_send_ajax_response(
$this->selected_import_files->get_error_message(),
$this->log_file_path,
esc_html__( 'Downloaded files', 'one-click-demo-import' )
);
}
// Add this message to log file.
$log_added = Helpers::append_to_file(
sprintf( /* translators: %s - the name of the selected import. */
__( 'The import files for: %s were successfully downloaded!', 'one-click-demo-import' ),
$this->import_files[ $this->selected_index ]['import_file_name']
) . Helpers::import_file_info( $this->selected_import_files ),
$this->log_file_path,
esc_html__( 'Downloaded files' , 'one-click-demo-import' )
);
}
else {
// Send JSON Error response to the AJAX call.
wp_send_json( esc_html__( 'No import files specified!', 'one-click-demo-import' ) );
}
}
// Save the initial import data as a transient, so other import parts (in new AJAX calls) can use that data.
Helpers::set_ocdi_import_data_transient( $this->get_current_importer_data() );
if ( ! $this->before_import_executed ) {
$this->before_import_executed = true;
/**
* 2). Execute the actions hooked to the 'ocdi/before_content_import_execution' action:
*
* Default actions:
* 1 - Before content import WP action (with priority 10).
*/
Helpers::do_action( 'ocdi/before_content_import_execution', $this->selected_import_files, $this->import_files, $this->selected_index );
}
/**
* 3). Import content (if the content XML file is set for this import).
* Returns any errors greater then the "warning" logger level, that will be displayed on front page.
*/
if ( ! empty( $this->selected_import_files['content'] ) ) {
$this->append_to_frontend_error_messages( $this->importer->import_content( $this->selected_import_files['content'] ) );
}
/**
* 4). Execute the actions hooked to the 'ocdi/after_content_import_execution' action:
*
* Default actions:
* 1 - Before widgets import setup (with priority 10).
* 2 - Import widgets (with priority 20).
* 3 - Import Redux data (with priority 30).
*/
Helpers::do_action( 'ocdi/after_content_import_execution', $this->selected_import_files, $this->import_files, $this->selected_index );
// Save the import data as a transient, so other import parts (in new AJAX calls) can use that data.
Helpers::set_ocdi_import_data_transient( $this->get_current_importer_data() );
// Request the customizer import AJAX call.
if ( ! empty( $this->selected_import_files['customizer'] ) ) {
wp_send_json( array( 'status' => 'customizerAJAX' ) );
}
// Request the after all import AJAX call.
if ( false !== Helpers::has_action( 'ocdi/after_all_import_execution' ) ) {
wp_send_json( array( 'status' => 'afterAllImportAJAX' ) );
}
// Update terms count.
$this->update_terms_count();
// Send a JSON response with final report.
$this->final_response();
}
/**
* AJAX callback for importing the customizer data.
* This request has the wp_customize set to 'on', so that the customizer hooks can be called
* (they can only be called with the $wp_customize instance). But if the $wp_customize is defined,
* then the widgets do not import correctly, that's why the customizer import has its own AJAX call.
*/
public function import_customizer_data_ajax_callback() {
// Verify if the AJAX call is valid (checks nonce and current_user_can).
Helpers::verify_ajax_call();
// Get existing import data.
if ( $this->use_existing_importer_data() ) {
/**
* Execute the customizer import actions.
*
* Default actions:
* 1 - Customizer import (with priority 10).
*/
Helpers::do_action( 'ocdi/customizer_import_execution', $this->selected_import_files );
}
// Request the after all import AJAX call.
if ( false !== Helpers::has_action( 'ocdi/after_all_import_execution' ) ) {
wp_send_json( array( 'status' => 'afterAllImportAJAX' ) );
}
// Send a JSON response with final report.
$this->final_response();
}
/**
* AJAX callback for the after all import action.
*/
public function after_all_import_data_ajax_callback() {
// Verify if the AJAX call is valid (checks nonce and current_user_can).
Helpers::verify_ajax_call();
// Get existing import data.
if ( $this->use_existing_importer_data() ) {
/**
* Execute the after all import actions.
*
* Default actions:
* 1 - after_import action (with priority 10).
*/
Helpers::do_action( 'ocdi/after_all_import_execution', $this->selected_import_files, $this->import_files, $this->selected_index );
}
// Update terms count.
$this->update_terms_count();
// Send a JSON response with final report.
$this->final_response();
}
/**
* Send a JSON response with final report.
*/
private function final_response() {
// Delete importer data transient for current import.
delete_transient( 'ocdi_importer_data' );
delete_transient( 'ocdi_importer_data_failed_attachment_imports' );
delete_transient( 'ocdi_import_menu_mapping' );
delete_transient( 'ocdi_import_posts_with_nav_block' );
// Display final messages (success or warning messages).
$response['title'] = esc_html__( 'Import Complete!', 'one-click-demo-import' );
$response['subtitle'] = '
' . esc_html__( 'Congrats, your demo was imported successfully. You can now begin editing your site.', 'one-click-demo-import' ) . '
';
$response['message'] = '';
if ( ! empty( $this->frontend_error_messages ) ) {
$response['subtitle'] = '' . esc_html__( 'Your import completed, but some things may not have imported properly.', 'one-click-demo-import' ) . '
';
$response['subtitle'] .= sprintf(
wp_kses(
/* translators: %s - link to the log file. */
__( 'View error log for more information.
', 'one-click-demo-import' ),
array(
'p' => [],
'a' => [
'href' => [],
'target' => [],
],
)
),
Helpers::get_log_url( $this->log_file_path )
);
$response['message'] = '' . $this->frontend_error_messages_display() . '
';
}
wp_send_json( $response );
}
/**
* Get content importer data, so we can continue the import with this new AJAX request.
*
* @return boolean
*/
private function use_existing_importer_data() {
if ( $data = get_transient( 'ocdi_importer_data' ) ) {
$this->frontend_error_messages = empty( $data['frontend_error_messages'] ) ? array() : $data['frontend_error_messages'];
$this->log_file_path = empty( $data['log_file_path'] ) ? '' : $data['log_file_path'];
$this->selected_index = empty( $data['selected_index'] ) ? 0 : $data['selected_index'];
$this->selected_import_files = empty( $data['selected_import_files'] ) ? array() : $data['selected_import_files'];
$this->import_files = empty( $data['import_files'] ) ? array() : $data['import_files'];
$this->before_import_executed = empty( $data['before_import_executed'] ) ? false : $data['before_import_executed'];
$this->imported_terms = empty( $data['imported_terms'] ) ? [] : $data['imported_terms'];
$this->importer->set_importer_data( $data );
return true;
}
return false;
}
/**
* Get the current state of selected data.
*
* @return array
*/
public function get_current_importer_data() {
return array(
'frontend_error_messages' => $this->frontend_error_messages,
'log_file_path' => $this->log_file_path,
'selected_index' => $this->selected_index,
'selected_import_files' => $this->selected_import_files,
'import_files' => $this->import_files,
'before_import_executed' => $this->before_import_executed,
'imported_terms' => $this->imported_terms,
);
}
/**
* Getter function to retrieve the private log_file_path value.
*
* @return string The log_file_path value.
*/
public function get_log_file_path() {
return $this->log_file_path;
}
/**
* Setter function to append additional value to the private frontend_error_messages value.
*
* @param string $additional_value The additional value that will be appended to the existing frontend_error_messages.
*/
public function append_to_frontend_error_messages( $text ) {
$lines = array();
if ( ! empty( $text ) ) {
$text = str_replace( '
', PHP_EOL, $text );
$lines = explode( PHP_EOL, $text );
}
foreach ( $lines as $line ) {
if ( ! empty( $line ) && ! in_array( $line , $this->frontend_error_messages ) ) {
$this->frontend_error_messages[] = $line;
}
}
}
/**
* Display the frontend error messages.
*
* @return string Text with HTML markup.
*/
public function frontend_error_messages_display() {
$output = '';
if ( ! empty( $this->frontend_error_messages ) ) {
foreach ( $this->frontend_error_messages as $line ) {
$output .= esc_html( $line );
$output .= '
';
}
}
return $output;
}
/**
* Get data from filters, after the theme has loaded and instantiate the importer.
*/
public function setup_plugin_with_filter_data() {
if ( ! ( is_admin() || ( defined( 'WP_CLI' ) && WP_CLI ) ) ) {
return;
}
// Get info of import data files and filter it.
$this->import_files = Helpers::validate_import_file_info( Helpers::apply_filters( 'ocdi/import_files', array() ) );
/**
* Register all default actions (before content import, widget, customizer import and other actions)
* to the 'before_content_import_execution' and the 'ocdi/after_content_import_execution' action hook.
*/
$import_actions = new ImportActions();
$import_actions->register_hooks();
// Importer options array.
$importer_options = Helpers::apply_filters( 'ocdi/importer_options', array(
'fetch_attachments' => true,
) );
// Logger options for the logger used in the importer.
$logger_options = Helpers::apply_filters( 'ocdi/logger_options', array(
'logger_min_level' => 'warning',
) );
// Configure logger instance and set it to the importer.
$logger = new Logger();
$logger->min_level = $logger_options['logger_min_level'];
// Create importer instance with proper parameters.
$this->importer = new Importer( $importer_options, $logger );
// Prepare registered plugins and register AJAX callbacks.
$this->plugin_installer = new PluginInstaller();
$this->plugin_installer->init();
// Prepare registered pre-created demo content pages and the AJAX callback.
$demo_content_creator = new CreateDemoContent\DemoContentCreator();
$demo_content_creator->init();
}
/**
* Getter for $plugin_page_setup.
*
* @return array
*/
public function get_plugin_page_setup() {
return $this->plugin_page_setup;
}
/**
* Output the begining of the container div for all notices, but only on OCDI pages.
*/
public function start_notice_output_capturing() {
$screen = get_current_screen();
if ( false === strpos( $screen->base, $this->plugin_page_setup['menu_slug'] ) ) {
return;
}
echo '';
}
/**
* Output the ending of the container div for all notices, but only on OCDI pages.
*/
public function finish_notice_output_capturing() {
if ( is_network_admin() ) {
return;
}
$screen = get_current_screen();
if ( false === strpos( $screen->base, $this->plugin_page_setup['menu_slug'] ) ) {
return;
}
echo '
';
}
/**
* Get the URL of the plugin settings page.
*
* @return string
*/
public function get_plugin_settings_url( $query_parameters = [] ) {
if ( empty( $this->plugin_page_setup ) ) {
$this->plugin_page_setup = Helpers::get_plugin_page_setup_data();
}
$parameters = array_merge(
array( 'page' => $this->plugin_page_setup['menu_slug'] ),
$query_parameters
);
$url = menu_page_url( $this->plugin_page_setup['parent_slug'], false );
if ( empty( $url ) ) {
$url = self_admin_url( $this->plugin_page_setup['parent_slug'] );
}
return add_query_arg( $parameters, $url );
}
/**
* Redirect from the old default OCDI settings page URL to the new one.
*/
public function redirect_from_old_default_admin_page() {
global $pagenow;
if ( $pagenow == 'themes.php' && isset( $_GET['page'] ) && $_GET['page'] == 'pt-one-click-demo-import' ) {
wp_safe_redirect( $this->get_plugin_settings_url() );
exit;
}
}
/**
* Add imported terms.
*
* Mainly it's needed for saving all imported terms and trigger terms count updates.
* WP core term defer counting is not working, since import split to chunks and we are losing `$_deffered` array
* items between ajax calls.
*/
public function add_imported_terms( $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids ){
if ( ! isset( $this->imported_terms[ $taxonomy ] ) ) {
$this->imported_terms[ $taxonomy ] = array();
}
$this->imported_terms[ $taxonomy ] = array_unique( array_merge( $this->imported_terms[ $taxonomy ], $tt_ids ) );
}
/**
* Returns an empty array if current attachment to be imported is in the failed imports list.
*
* This will skip the current attachment import.
*
* @since 3.2.0
*
* @param array $data Post data to be imported.
*
* @return array
*/
public function skip_failed_attachment_import( $data ) {
// Check if failed import.
if (
! empty( $data ) &&
! empty( $data['post_type'] ) &&
$data['post_type'] === 'attachment' &&
! empty( $data['attachment_url'] )
) {
// Get the previously failed imports.
$failed_media_imports = Helpers::get_failed_attachment_imports();
if ( ! empty( $failed_media_imports ) && in_array( $data['attachment_url'], $failed_media_imports, true ) ) {
// If the current attachment URL is in the failed imports, then skip it.
return [];
}
}
return $data;
}
/**
* Save the failed attachment import.
*
* @since 3.2.0
*
* @param WP_Error $post_id Error object.
* @param array $data Raw data imported for the post.
* @param array $meta Raw meta data, already processed.
* @param array $comments Raw comment data, already processed.
* @param array $terms Raw term data, already processed.
*/
public function handle_failed_attachment_import( $post_id, $data, $meta, $comments, $terms ) {
if ( empty( $data ) || empty( $data['post_type'] ) || $data['post_type'] !== 'attachment' ) {
return;
}
Helpers::set_failed_attachment_import( $data['attachment_url'] );
}
/**
* Save the information needed to process the navigation block.
*
* @since 3.2.0
*
* @param int $post_id The new post ID.
* @param int $original_id The original post ID.
* @param array $postdata The post data used to insert the post.
* @param array $data Post data from the WXR file.
*/
public function save_wp_navigation_import_mapping( $post_id, $original_id, $postdata, $data ) {
if ( empty( $postdata['post_content'] ) ) {
return;
}
if ( $postdata['post_type'] !== 'wp_navigation' ) {
/*
* Save the post ID that has navigation block in transient.
*/
if ( strpos( $postdata['post_content'], '' ] = '';
}
// Loop through each the posts that needs to be updated.
foreach ( $posts_nav_block as $post_id ) {
$post_nav_block = get_post( $post_id );
if ( empty( $post_nav_block ) || empty( $post_nav_block->post_content ) ) {
return;
}
wp_update_post(
[
'ID' => $post_id,
'post_content' => strtr( $post_nav_block->post_content, $replace_pairs ),
]
);
}
}
/**
* Update imported terms count.
*/
private function update_terms_count() {
foreach ( $this->imported_terms as $tax => $terms ) {
wp_update_term_count_now( $terms, $tax );
}
}
/**
* Get the import buttons HTML for the successful import page.
*
* @since 3.2.0
*
* @return string
*/
public function get_import_successful_buttons_html() {
/**
* Filter the buttons that are displayed on the successful import page.
*
* @since 3.2.0
*
* @param array $buttons {
* Array of buttons.
*
* @type string $label Button label.
* @type string $class Button class.
* @type string $href Button URL.
* @type string $target Button target. Can be `_blank`, `_parent`, `_top`. Default is `_self`.
* }
*/
$buttons = Helpers::apply_filters(
'ocdi/import_successful_buttons',
[
[
'label' => __( 'Theme Settings' , 'one-click-demo-import' ),
'class' => 'button button-primary button-hero',
'href' => admin_url( 'customize.php' ),
'target' => '_blank',
],
[
'label' => __( 'Visit Site' , 'one-click-demo-import' ),
'class' => 'button button-primary button-hero',
'href' => get_home_url(),
'target' => '_blank',
],
]
);
if ( empty( $buttons ) || ! is_array( $buttons ) ) {
return '';
}
ob_start();
foreach ( $buttons as $button ) {
if ( empty( $button['href'] ) || empty( $button['label'] ) ) {
continue;
}
$target = '_self';
if (
! empty( $button['target'] ) &&
in_array( strtolower( $button['target'] ), [ '_blank', '_parent', '_top' ], true )
) {
$target = $button['target'];
}
$class = 'button button-primary button-hero';
if ( ! empty( $button['class'] ) ) {
$class = $button['class'];
}
printf(
'%4$s',
esc_url( $button['href'] ),
esc_attr( $class ),
esc_attr( $target ),
esc_html( $button['label'] )
);
}
$buttons_html = ob_get_clean();
return empty( $buttons_html ) ? '' : $buttons_html;
}
}