Skip to content

Instantly share code, notes, and snippets.

@wpmudev-sls
Last active May 3, 2024 08:40
Show Gist options
  • Save wpmudev-sls/02a03fb6ef57b7a09c0fac7ddcbde393 to your computer and use it in GitHub Desktop.
Save wpmudev-sls/02a03fb6ef57b7a09c0fac7ddcbde393 to your computer and use it in GitHub Desktop.
[SmartCrawl Pro] Post meta import (JSON and CSV)
<?php
/**
* Plugin Name: [SmartCrawl Pro] Post meta import (JSON and CSV) (Rev. 2)
* Description: Adds a "SmartCrawl Import" menu item where the user can easily import the plugin data
* Author: Anderson Salas @ WPMUDEV
* Task: SLS-4232
* Author URI: https://premium.wpmudev.org
* License: GPLv2 or later
*/
/*
* Post meta export: https://gist.github.com/wpmudev-sls/ef60ae7796c18fa5773d17a469164790
*/
add_action( 'plugins_loaded', function() {
if ( ! class_exists( '\SmartCrawl\SmartCrawl' ) ) {
return; // SmartCrawl is not installed/enabled.
}
if ( ! class_exists( 'SmartCrawl_Custom_Meta_Importer' ) ) {
class SmartCrawl_Custom_Meta_Importer {
private static $instance;
private $wpdb;
private $selected_fields = array();
private $excluded_types = array();
private $excluded_slugs = array();
public static function get() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
public function add_submenu_item() {
add_submenu_page(
\SmartCrawl\Settings::TAB_DASHBOARD,
'SmartCrawl Import',
'SmartCrawl Import',
'manage_options',
'wds_import',
array( $this, 'admin_importer_page' )
);
}
public function __construct() {
global $wpdb;
$this->wpdb = $wpdb;
add_action( 'admin_menu', array( $this, 'add_submenu_item' ) );
add_action( 'admin_footer', array( $this, 'admin_importer_scripts' ) );
add_action( 'wp_ajax_wds_custom_import', array( $this, 'ajax_handle_import' ) );
}
public function admin_importer_scripts() {
?>
<script>
( function( $ ) {
$( '#sc-import-form' ).submit( function( e ){
e.preventDefault();
if ( ! confirm( 'All matching fields will be overwritten. This action can NOT be undone. Are you sure?' ) ) {
e.preventDefault();
return false;
}
var formData = new FormData( $(this)[0] );
formData.append('action','wds_custom_import');
$( '.sc-begin-import' ).attr( 'disabled', true );
$( '.import-spinner' ).addClass( 'is-active' );
$( '.sc-begin-import' ).addClass( 'disabled' );
$.ajax({
url : ajaxurl,
type : 'POST',
data : formData,
async: true,
cache: false,
contentType: false,
enctype : 'multipart/form-data',
processData: false,
success: function ( response ) {
$( '.sc-begin-import' ).removeClass( 'disabled' );
$( '.import-spinner' ).removeClass( 'is-active' );
$( '.sc-begin-import' ).attr( 'disabled', false );
if ( ! response.success ) {
alert( response.data.error );
} else {
alert( response.data.message );
}
},
error: function( response ) {
$( '.sc-begin-import' ).removeClass( 'disabled' );
$( '.import-spinner' ).removeClass( 'is-active' );
$( '.sc-begin-import' ).attr( 'disabled', false );
alert( 'An error occurried. Please try again' );
},
});
});
$( '.advanced-import-toggle' ).click( function() {
$( '.advanced-import-toggle-container' ).remove();
$( '.no-advanced' ).remove();
$( '.advanced-import-container' ).show();
});
})( window.jQuery )
</script>
<?php
}
public function admin_importer_page() {
?>
<div style="max-width:500px; background: white; border: solid 1px #dadada; padding: 10px 20px 20px 20px; margin-top: 20px;">
<form id="sc-import-form" method="post" enctype="multipart/form-data">
<?php wp_nonce_field( 'wds_sq_import' ); ?>
<h3>SmartCrawl Import</h3>
<p>Select a CSV or JSON export file and click <strong>Import</strong> to migrate all the compatible data. Do not close this browser tab until the process is done. A site backup before the import is strongly advised.</p>
<input type="file" name="import_file" style="background: white; border: solid 1px silver; padding: 3px 5px; border-radius: 3px; margin: 0 5px 0 0;" required />
<input type="submit" name="submit" id="submit" class="button button-primary sc-begin-import no-advanced" value="Import" />
<div class="spinner import-spinner no-advanced" style="float:none;width:auto;height:auto; padding: 10px 0 10px 20px; vertical-align:-5px"></div>
<div class="advanced-import-toggle-container" style="padding-top:12px">
<a href="#" class="advanced-import-toggle" sytle="display:block">Custom import (Advanced) &raquo;</a>
</div>
<div class="advanced-import-container" style="display: none; border-top: solid 1px #dfdfdf; margin-top: 10px;">
<h4>Import settings</h4>
<h5>Post data:</h5>
<?php
$fields = array( 'meta_title', 'meta_description', 'focus_keywords', 'open_graph', 'twitter' );
foreach( $fields as $i => $field ) {
?>
<div style="display: inline-block; min-width: 160px; margin-bottom: 4px">
<label>
<input type="checkbox" name="fields[<?php echo $field; ?>]" checked="checked" /> <?php echo ucfirst( str_ireplace( '_', ' ', $field ) ); ?>
</label>
</div>
<?php } ?>
<div style="margin-top:10px">
<small>* Twitter/Open Graph image fields can be the attachment ID (1,2,3...) or an image URL (https://example.com/wp-content/uploads/2023/10/picture.jpg). External URLs are not supported.</small>
</div>
<h5>Excluded post types:</h5>
<input type="text" name="excluded_types" style="display:block; width: 100%" placeholder="Comma-separated slugs. Example: post, product"/>
<h5>Excluded post names:</h5>
<input type="text" name="excluded_slugs" style="display:block; width: 100%" placeholder="Comma-separated slugs. Example: hello-world, hoodie-with-logo"/>
<div style="margin-top: 15px">
<input type="submit" name="submit" id="submit" class="button button-primary sc-begin-import" value="Import" />
<div class="spinner import-spinner" style="float:none;width:auto;height:auto; padding: 10px 0 10px 20px; vertical-align:-5px"></div>
</div>
</div>
</form>
</div>
<?php
}
private function do_import( $data, &$success, &$skipped ) {
$success = 0;
$skipped = 0;
$slugs = array();
foreach ( $data as $post ) {
if ( empty( $post['post_name'] ) ) {
$skipped++;
continue;
}
$slugs[] = $post['post_name'];
}
if ( empty( $slugs ) ) {
return;
}
$slugs_in = '(' . implode( ', ', array_fill( 0, count( $slugs ), '%s' ) ) . ')';
$prefix = $this->wpdb->prefix;
$query = "SELECT ID, post_name FROM {$prefix}posts WHERE post_name IN $slugs_in";
$result = $this->wpdb->get_results( $this->wpdb->prepare( $query, $slugs ), ARRAY_A );
if ( false === $this->wpdb->result ) {
wp_send_json_error( array( 'error' => 'Database error (0): ' . $this->wpdb->last_error ) );
}
if ( empty( $result ) ) {
return;
}
$id_map = array_column( $result, 'ID', 'post_name' );
foreach( $data as $post ) {
$modified = false;
if ( empty( $post['post_name'] ) || ! isset( $id_map[ $post['post_name'] ] ) ) {
continue;
}
if ( in_array( $post['post_type'], $this->excluded_types ) || in_array( $post['post_name'], $this->excluded_slugs ) ) {
$skipped++;
continue;
}
$target_id = $id_map[ $post['post_name'] ];
if ( ! empty( $post['meta_title'] ) && in_array( 'meta_title', $this->selected_fields ) ) {
update_post_meta( $target_id, '_wds_title', $post['meta_title'] );
$modified = true;
}
if ( ! empty( $post['meta_description'] ) && in_array( 'meta_description', $this->selected_fields ) ) {
update_post_meta( $target_id, '_wds_metadesc', $post['meta_description'] );
$modified = true;
}
if ( ! empty( $post['focus_keywords'] ) && in_array( 'focus_keywords', $this->selected_fields ) ) {
update_post_meta( $target_id, '_wds_focus-keywords', $post['focus_keywords'] );
$modified = true;
}
if ( ! empty( $post['canonical'] ) ) {
update_post_meta( $target_id, '_wds_canonical', $post['canonical'] );
$modified = true;
}
if ( ! empty( $post['noindex'] ) ) {
update_post_meta( $target_id, '_wds_meta-robots-noindex', $post['noindex'] );
$modified = true;
}
if ( ! empty( $post['nofollow'] ) ) {
update_post_meta( $target_id, '_wds_meta-robots-nofollow', $post['nofollow'] );
$modified = true;
}
if ( ! empty( $post['robotsadv'] ) ) {
update_post_meta( $target_id, '_wds_meta-robots-adv', $post['robotsadv'] );
$modified = true;
}
if ( ! empty( $post['autolinks'] ) ) {
update_post_meta( $target_id, '_wds_autolinks-exclude', $post['autolinks'] );
$modified = true;
}
if ( ! empty( $post['redirect'] ) ) {
update_post_meta( $target_id, '_wds_redirect', $post['redirect'] );
$modified = true;
}
$og_meta = array();
if ( ! empty( $post['og_title'] ) ) {
$og_meta['title'] = $post['og_title'];
}
if ( ! empty( $post['og_description'] ) ) {
$og_meta['description'] = $post['og_description'];
}
if ( ! empty( $post['og_image'] ) ) {
$og_meta['images'] = array();
$og_image = $post['og_image'];
if ( is_numeric( $og_image ) ) {
// Numeric: Pass as it.
$og_image = array( (int) $og_image );
} elseif( false !== filter_var( $og_image, FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED ) ) {
// Valid URL string: attempt to resolve attachment ID.
$og_image = attachment_url_to_postid( $og_image );
$og_image = 0 !== $og_image ? array( $og_image ) : array();
} else {
// None of above: Skip.
$og_image = array();
}
$og_meta['images'] = $og_image;
}
if ( ! empty( $og_meta ) && in_array( 'open_graph', $this->selected_fields ) ) {
$new_og_meta = array(
'title' => isset( $og_meta['title'] ) ? $og_meta['title'] : '',
'description' => isset( $og_meta['description'] ) ? $og_meta['description'] : '',
'images' => isset( $og_meta['images'] ) ? $og_meta['images'] : array()
);
update_post_meta( $target_id, '_wds_opengraph', $new_og_meta );
$modified = true;
}
$tw_meta = array();
if ( ! empty( $post['tw_title'] ) ) {
$tw_meta['title'] = $post['tw_title'];
}
if ( ! empty( $post['og_description'] ) ) {
$tw_meta['description'] = $post['tw_description'];
}
if ( ! empty( $post['tw_image'] ) ) {
$tw_meta['images'] = array();
$tw_image = $post['tw_image'];
if ( is_numeric( $tw_image ) ) {
// Numeric: Pass as it.
$tw_image = array( (int) $tw_image );
} elseif( false !== filter_var( $tw_image, FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED ) ) {
// Valid URL string: attempt to resolve attachment ID.
$tw_image = attachment_url_to_postid( $tw_image );
$tw_image = 0 !== $tw_image ? array( $tw_image ) : array();
} else {
// None of above: Skip.
$tw_image = array();
}
$tw_meta['images'] = $tw_image;
}
if ( ! empty( $tw_meta ) && in_array( 'twitter', $this->selected_fields ) ) {
$new_tw_meta = array(
'title' => isset( $tw_meta['title'] ) ? $tw_meta['title'] : '',
'description' => isset( $tw_meta['description'] ) ? $tw_meta['description'] : '',
'images' => isset( $tw_meta['images'] ) ? $tw_meta['images'] : array(),
);
update_post_meta( $target_id, '_wds_twitter', $new_tw_meta );
$modified = true;
}
if ( $modified ) {
$success++;
} else {
$skipped++;
}
}
}
public function ajax_handle_import() {
if ( empty( $_POST[ '_wpnonce' ] ) || ! wp_verify_nonce( $_POST[ '_wpnonce' ], 'wds_sq_import' ) ) {
wp_send_json_error( array( 'error' => 'Invalid request. Please try again.' ) );
}
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'error' => 'Invalid request. Please try again.' ) );
}
if ( ! function_exists( 'wp_handle_upload' ) ) {
require_once( ABSPATH . 'wp-admin/includes/file.php' );
}
if ( empty( $_FILES['import_file'] ) ) {
wp_send_json_error( array( 'error' => 'Missing or invalid import file.' ) );
}
if ( empty( $_REQUEST['fields'] ) || ! is_array( $_REQUEST['fields'] ) ) {
wp_send_json_error( array( 'error' => 'At least one compatible field must be imported.' ) );
}
if ( ! empty( $_REQUEST['excluded_types'] ) ) {
$excluded_types = array();
foreach( explode(',', $_REQUEST['excluded_types'] ) as $type ) {
$type = trim( $type );
if ( ! empty( $type ) ) {
$excluded_types[] = $type;
}
}
$this->excluded_types = $excluded_types;
}
if ( ! empty( $_REQUEST['excluded_slugs'] ) ) {
$excluded_slugs = array();
foreach( explode(',', $_REQUEST['excluded_slugs'] ) as $slug ) {
$slug = trim( $slug );
if ( ! empty( $slug ) ) {
$excluded_slugs[] = $slug;
}
}
$this->excluded_slugs = $excluded_slugs;
}
$this->selected_fields = array_keys( $_REQUEST['fields'] );
$uploaded_file = $_FILES['import_file'];
$allowed_mimes = array(
'csv' => 'text/csv',
'json' => 'application/json',
);
$filetype = wp_check_filetype( $uploaded_file['name'], $allowed_mimes );
if ( ! $filetype['ext'] ) {
wp_send_json_error( array( 'error' => 'Invalid file type: Only CSV and JSON export files are supported.' ) );
}
$filedata = array();
if ( 'csv' === $filetype['ext'] ) {
$filedata = array_map( 'str_getcsv', file( $uploaded_file['tmp_name'] ) );
array_walk( $filedata, function( &$a ) use ( $filedata ) {
$a = array_combine( $filedata[0], $a );
});
array_shift( $filedata );
} else if ( 'json' === $filetype['ext'] ) {
$fileconents = file_get_contents( $uploaded_file['tmp_name'] );
if ( empty( $fileconents ) ) {
wp_send_json_error( array( 'error' => 'Error while reading file. [Code:001]' ) );
}
$filedata = json_decode( $fileconents, true );
if ( false === $filedata || ! is_array( $filedata ) ) {
wp_send_json_error( array( 'error' => 'Error while reading file. [Code:002]' ) );
}
}
if ( ! is_array( $filedata ) ) {
wp_send_json_error( array( 'error' => 'Error while reading file. [Code:003]' ) );
}
if ( ! empty( $filedata ) ) {
if ( ! empty( $filedata[0] ) && ( ! is_array( $filedata[0] ) || empty( $filedata[0]['post_type'] ) ) ) {
wp_send_json_error( array( 'error' => 'Incompatible ' . strtoupper( $filetype['ext'] ) . ' export file. [Code:004]' ) );
} else if ( ! isset( $filedata[0] ) ) {
wp_send_json_error( array( 'error' => 'Incompatible ' . strtoupper( $filetype['ext'] ) . ' export file. [Code:005]' ) );
}
} else {
wp_send_json_error( array( 'error' => 'The ' . strtoupper( $filetype['ext'] ) . ' export file is empty.' ) );
}
$this->wpdb->show_errors = false; // Supress DB errors.
$this->do_import( $filedata, $success, $skipped );
$message = 'Data import completed (' . $success . ' success, ' . $skipped . ' skipped)';
wp_send_json_success( array( 'message' => $message ) );
}
}
SmartCrawl_Custom_Meta_Importer::get();
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment