Last active
May 3, 2024 08:40
-
-
Save wpmudev-sls/02a03fb6ef57b7a09c0fac7ddcbde393 to your computer and use it in GitHub Desktop.
[SmartCrawl Pro] Post meta import (JSON and CSV)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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) »</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