Commit realizado el 12:13:52 08-04-2024
This commit is contained in:
@@ -0,0 +1,421 @@
|
||||
<?php
|
||||
/**
|
||||
* The admin-specific functionality of the plugin.
|
||||
*
|
||||
* @since 1.0
|
||||
* @package RankMathPro
|
||||
* @subpackage RankMathPro\Admin
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMathPro\Admin\CSV_Import_Export;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Traits\Hooker;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* CSV Import Export class.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class CSV_Import_Export {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* Register hooks.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->filter( 'rank_math/admin/import_export_panels', 'add_panel', 15 );
|
||||
$this->action( 'admin_enqueue_scripts', 'enqueue' );
|
||||
|
||||
$this->action( 'admin_init', 'maybe_do_import', 99 );
|
||||
$this->action( 'admin_init', 'maybe_do_export', 110 );
|
||||
$this->action( 'admin_init', 'maybe_cancel_import', 120 );
|
||||
|
||||
$this->action( 'wp_ajax_csv_import_progress', 'csv_import_progress' );
|
||||
|
||||
Import_Background_Process::get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add CSV import/export panel.
|
||||
*
|
||||
* @param array $panels Panels array.
|
||||
* @return array
|
||||
*/
|
||||
public function add_panel( $panels ) {
|
||||
// Insert after "import".
|
||||
$position = array_search( 'import', array_keys( $panels ), true ) + 1;
|
||||
$new = array_slice( $panels, 0, $position );
|
||||
$new['csv'] = [
|
||||
'view' => RANK_MATH_PRO_PATH . 'includes/views/csv-import-export-panel.php',
|
||||
'class' => 'import-export-csv',
|
||||
];
|
||||
$end = array_slice( $panels, $position );
|
||||
$result = array_merge( $new, $end );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current screen is Status & Tools > Import / Export.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_import_export_screen() {
|
||||
return is_admin() && ! wp_doing_ajax() && isset( $_GET['page'] ) && 'rank-math-status' === $_GET['page'] && isset( $_GET['view'] ) && 'import_export' === $_GET['view']; // phpcs:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue styles.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue() {
|
||||
if ( ! $this->is_import_export_screen() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Helper::add_json( 'confirmCsvImport', __( 'Are you sure you want to import meta data from this CSV file?', 'rank-math-pro' ) );
|
||||
Helper::add_json( 'confirmCsvCancel', __( 'Are you sure you want to stop the import process?', 'rank-math-pro' ) );
|
||||
Helper::add_json( 'csvProgressNonce', wp_create_nonce( 'rank_math_csv_progress' ) );
|
||||
|
||||
wp_enqueue_style( 'rank-math-pro-csv-import-export', RANK_MATH_PRO_URL . 'assets/admin/css/import-export.css', [], RANK_MATH_PRO_VERSION );
|
||||
wp_enqueue_script( 'rank-math-pro-csv-import-export', RANK_MATH_PRO_URL . 'assets/admin/js/import-export.js', [], RANK_MATH_PRO_VERSION, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add notice after import is started.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_notice() {
|
||||
if ( ! $this->is_import_export_screen() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Helper::add_notification( esc_html__( 'CSV import is in progress...', 'rank-math-pro' ), [ 'type' => 'success' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Start export if requested and allowed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_do_export() {
|
||||
if ( ! is_admin() || empty( $_POST['rank_math_pro_csv_export'] ) ) {
|
||||
return;
|
||||
}
|
||||
if ( empty( $_POST['object_types'] ) || ! is_array( $_POST['object_types'] ) ) {
|
||||
wp_die( esc_html__( 'Please select at least one object type to export.', 'rank-math-pro' ) );
|
||||
}
|
||||
if ( ! wp_verify_nonce( isset( $_REQUEST['_wpnonce'] ) ? $_REQUEST['_wpnonce'] : '', 'rank_math_pro_csv_export' ) ) {
|
||||
wp_die( esc_html__( 'Invalid nonce.', 'rank-math-pro' ) );
|
||||
}
|
||||
if ( ! current_user_can( 'export' ) ) {
|
||||
wp_die( esc_html__( 'Sorry, you are not allowed to export the content of this site.', 'rank-math-pro' ) );
|
||||
}
|
||||
|
||||
$use_advanced_options = ! empty( $_POST['use_advanced_options'] );
|
||||
$advanced_options = [
|
||||
'post_types' => isset( $_POST['post_types'] ) && is_array( $_POST['post_types'] ) ? array_map( 'sanitize_title', wp_unslash( $_POST['post_types'] ) ) : [],
|
||||
'taxonomies' => isset( $_POST['taxonomies'] ) && is_array( $_POST['taxonomies'] ) ? array_map( 'sanitize_title', wp_unslash( $_POST['taxonomies'] ) ) : [],
|
||||
'roles' => isset( $_POST['roles'] ) && is_array( $_POST['roles'] ) ? array_map( 'sanitize_title', wp_unslash( $_POST['roles'] ) ) : [],
|
||||
'readonly_columns' => ! empty( $_POST['readonly_columns'] ),
|
||||
];
|
||||
|
||||
$exporter = new Exporter( array_map( 'sanitize_title', wp_unslash( $_POST['object_types'] ) ), $use_advanced_options ? $advanced_options : false );
|
||||
$exporter->process_export();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start import if requested and allowed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_do_import() {
|
||||
if ( ! is_admin() || empty( $_POST['object_id'] ) || 'csv-import-plz' !== $_POST['object_id'] ) {
|
||||
return;
|
||||
}
|
||||
if ( empty( $_FILES['csv-import-me'] ) || empty( $_FILES['csv-import-me']['name'] ) ) {
|
||||
wp_die( esc_html__( 'Please select a file to import.', 'rank-math-pro' ) );
|
||||
}
|
||||
if ( ! wp_verify_nonce( isset( $_REQUEST['_wpnonce'] ) ? $_REQUEST['_wpnonce'] : '', 'rank_math_pro_csv_import' ) ) {
|
||||
wp_die( esc_html__( 'Invalid nonce.', 'rank-math-pro' ) );
|
||||
}
|
||||
if ( ! current_user_can( 'import' ) ) {
|
||||
wp_die( esc_html__( 'Sorry, you are not allowed to import contents to this site.', 'rank-math-pro' ) );
|
||||
}
|
||||
|
||||
// Rename file.
|
||||
$info = pathinfo( $_FILES['csv-import-me']['name'] );
|
||||
$_FILES['csv-import-me']['name'] = uniqid( 'rm-csv-' ) . ( ! empty( $info['extension'] ) ? '.' . $info['extension'] : '' );
|
||||
|
||||
// Handle file.
|
||||
$this->filter( 'upload_mimes', 'allow_csv_upload' );
|
||||
$file = wp_handle_upload( $_FILES['csv-import-me'], [ 'test_form' => false ] );
|
||||
$this->remove_filter( 'upload_mimes', 'allow_csv_upload', 10 );
|
||||
if ( ! $this->validate_file( $file ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$settings = [
|
||||
'no_overwrite' => ! empty( $_POST['no_overwrite'] ),
|
||||
];
|
||||
|
||||
$importer = new Importer();
|
||||
$importer->start( $file['file'], $settings );
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow CSV file upload.
|
||||
*
|
||||
* @param array $types Mime types keyed by the file extension regex corresponding to those types.
|
||||
* @return array
|
||||
*/
|
||||
public function allow_csv_upload( $types ) {
|
||||
$types['csv'] = 'text/csv';
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate file.
|
||||
*
|
||||
* @param mixed $file File array or object.
|
||||
* @return bool
|
||||
*/
|
||||
public function validate_file( $file ) {
|
||||
if ( is_wp_error( $file ) ) {
|
||||
Helper::add_notification( esc_html__( 'CSV could not be imported:', 'rank-math-pro' ) . ' ' . $file->get_error_message(), [ 'type' => 'error' ] );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( isset( $file['error'] ) ) {
|
||||
Helper::add_notification( esc_html__( 'CSV could not be imported:', 'rank-math-pro' ) . ' ' . $file['error'], [ 'type' => 'error' ] );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! isset( $file['file'] ) ) {
|
||||
Helper::add_notification( esc_html__( 'CSV could not be imported: Upload failed.', 'rank-math-pro' ), [ 'type' => 'error' ] );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! isset( $file['type'] ) || 'text/csv' !== $file['type'] ) {
|
||||
\unlink( $file['file'] );
|
||||
Helper::add_notification( esc_html__( 'CSV could not be imported: File type error.', 'rank-math-pro' ), [ 'type' => 'error' ] );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get import/export CSV columns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_columns() {
|
||||
$columns = [
|
||||
'id',
|
||||
'object_type',
|
||||
'slug',
|
||||
'seo_title',
|
||||
'seo_description',
|
||||
'is_pillar_content',
|
||||
'focus_keyword',
|
||||
'seo_score',
|
||||
'robots',
|
||||
'advanced_robots',
|
||||
'canonical_url',
|
||||
'primary_term',
|
||||
'schema_data',
|
||||
'social_facebook_thumbnail',
|
||||
'social_facebook_title',
|
||||
'social_facebook_description',
|
||||
'social_twitter_thumbnail',
|
||||
'social_twitter_title',
|
||||
'social_twitter_description',
|
||||
];
|
||||
|
||||
if ( Helper::is_module_active( 'redirections' ) ) {
|
||||
$columns[] = 'redirect_to';
|
||||
$columns[] = 'redirect_type';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter columns array.
|
||||
*/
|
||||
return apply_filters( 'rank_math/admin/csv_export_columns', $columns );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object types.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_possible_object_types() {
|
||||
$object_types = [
|
||||
'post' => __( 'Posts', 'rank-math-pro' ),
|
||||
'term' => __( 'Terms', 'rank-math-pro' ),
|
||||
'user' => __( 'Users', 'rank-math-pro' ),
|
||||
];
|
||||
|
||||
/**
|
||||
* Filter object types array.
|
||||
*/
|
||||
return apply_filters( 'rank_math/admin/csv_export_object_types', $object_types );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if cancel request is valid.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function maybe_cancel_import() {
|
||||
if ( ! is_admin() || empty( $_GET['rank_math_cancel_csv_import'] ) ) {
|
||||
return;
|
||||
}
|
||||
if ( ! wp_verify_nonce( isset( $_REQUEST['_wpnonce'] ) ? $_REQUEST['_wpnonce'] : '', 'rank_math_pro_cancel_csv_import' ) ) {
|
||||
Helper::add_notification( esc_html__( 'Import could not be canceled: invalid nonce. Please try again.', 'rank-math-pro' ), [ 'type' => 'error' ] );
|
||||
wp_safe_redirect( remove_query_arg( 'rank_math_cancel_csv_import' ) );
|
||||
exit;
|
||||
}
|
||||
if ( ! current_user_can( 'import' ) ) {
|
||||
Helper::add_notification( esc_html__( 'Import could not be canceled: you are not allowed to import content to this site.', 'rank-math-pro' ), [ 'type' => 'error' ] );
|
||||
wp_safe_redirect( remove_query_arg( 'rank_math_cancel_csv_import' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
self::cancel_import();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel import.
|
||||
*
|
||||
* @param bool $silent Cancel silently.
|
||||
* @return void
|
||||
*/
|
||||
public static function cancel_import( $silent = false ) {
|
||||
$file_path = get_option( 'rank_math_csv_import' );
|
||||
|
||||
delete_option( 'rank_math_csv_import' );
|
||||
delete_option( 'rank_math_csv_import_total' );
|
||||
delete_option( 'rank_math_csv_import_status' );
|
||||
delete_option( 'rank_math_csv_import_settings' );
|
||||
Import_Background_Process::get()->cancel_process();
|
||||
|
||||
if ( ! $file_path ) {
|
||||
if ( ! $silent ) {
|
||||
Helper::add_notification( esc_html__( 'Import could not be canceled.', 'rank-math-pro' ), [ 'type' => 'error' ] );
|
||||
}
|
||||
|
||||
wp_safe_redirect( remove_query_arg( 'rank_math_cancel_csv_import' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
unlink( $file_path );
|
||||
if ( ! $silent ) {
|
||||
Helper::add_notification(
|
||||
__( 'CSV import canceled.', 'rank-math-pro' ),
|
||||
[
|
||||
'type' => 'success',
|
||||
'classes' => 'is-dismissible',
|
||||
]
|
||||
);
|
||||
}
|
||||
wp_safe_redirect( remove_query_arg( 'rank_math_cancel_csv_import' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show import progress via AJAX.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function csv_import_progress() {
|
||||
check_ajax_referer( 'rank_math_csv_progress' );
|
||||
if ( ! current_user_can( 'import' ) ) {
|
||||
exit( '0' );
|
||||
}
|
||||
|
||||
self::import_progress_details();
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output import progress details.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function import_progress_details() {
|
||||
$import_in_progress = (bool) get_option( 'rank_math_csv_import' );
|
||||
if ( $import_in_progress ) {
|
||||
$total_lines = (int) get_option( 'rank_math_csv_import_total' );
|
||||
$remaining_items = Import_Background_Process::get()->count_remaining_items();
|
||||
$progress = $total_lines ? ( $total_lines - $remaining_items + 1 ) / $total_lines * 100 : 0;
|
||||
?>
|
||||
<p><?php esc_html_e( 'Import in progress...', 'rank-math-pro' ); ?></p>
|
||||
<p class="csv-import-status">
|
||||
<?php // Translators: placeholders represent count like 15/36. ?>
|
||||
<?php printf( esc_html__( 'Items processed: %1$s/%2$s', 'rank-math-pro' ), absint( min( $total_lines, $total_lines - $remaining_items + 1 ) ), absint( $total_lines ) ); ?>
|
||||
</p>
|
||||
<div id="csv-import-progress-bar">
|
||||
<div class="total">
|
||||
<div class="progress-bar" style="width: <?php echo absint( $progress ); ?>%;"></div>
|
||||
</div>
|
||||
<input type="hidden" id="csv-import-progress-value" value="<?php echo absint( $progress ); ?>">
|
||||
</div>
|
||||
<?php
|
||||
} else {
|
||||
$status = (array) get_option( 'rank_math_csv_import_status', [] );
|
||||
|
||||
$classes = 'import-finished';
|
||||
if ( ! empty( $status['errors'] ) ) {
|
||||
$classes .= ' import-errors';
|
||||
}
|
||||
|
||||
$message = self::get_import_complete_message();
|
||||
?>
|
||||
<p class="<?php echo esc_attr( $classes ); ?>"><?php echo wp_kses_post( $message ); ?></p>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status message after import is complete.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_import_complete_message() {
|
||||
$status = (array) get_option( 'rank_math_csv_import_status', [] );
|
||||
$message = sprintf(
|
||||
// Translators: placeholder is the number of rows imported.
|
||||
__( 'CSV import completed. Successfully imported %d rows.', 'rank-math-pro' ),
|
||||
count( $status['imported_rows'] )
|
||||
);
|
||||
|
||||
if ( ! empty( $status['errors'] ) ) {
|
||||
$message = __( 'CSV import completed.', 'rank-math-pro' ) . ' ';
|
||||
$message .= sprintf(
|
||||
// Translators: placeholder is the number of rows imported.
|
||||
__( 'Imported %d rows.', 'rank-math-pro' ) . ' ',
|
||||
count( $status['imported_rows'] )
|
||||
);
|
||||
|
||||
if ( ! empty( $status['errors'] ) ) {
|
||||
$message .= __( 'One or more errors occured while importing: ', 'rank-math-pro' ) . '<br>';
|
||||
$message .= join( '<br>', $status['errors'] ) . '<br>';
|
||||
}
|
||||
if ( ! empty( $status['failed_rows'] ) ) {
|
||||
$message .= __( 'The following lines could not be imported: ', 'rank-math-pro' ) . '<br>';
|
||||
$message .= join( ', ', $status['failed_rows'] );
|
||||
}
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
@@ -0,0 +1,672 @@
|
||||
<?php
|
||||
/**
|
||||
* The CSV Export class.
|
||||
*
|
||||
* @since 1.0
|
||||
* @package RankMathPro
|
||||
* @subpackage RankMathPro\Admin
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMathPro\Admin\CSV_Import_Export;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Traits\Hooker;
|
||||
use RankMath\Redirections\DB;
|
||||
use RankMath\Redirections\Cache;
|
||||
use RankMathPro\Admin\CSV;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* CSV Export.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Exporter extends CSV {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* Data
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $data = [];
|
||||
|
||||
/**
|
||||
* Term ID => slug cache.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $term_slugs = [];
|
||||
|
||||
/**
|
||||
* Not applicable placeholder.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $not_applicable_value = 'n/a';
|
||||
|
||||
/**
|
||||
* Object types we want to export.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $object_types = [];
|
||||
|
||||
/**
|
||||
* Use advanced options for export.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $use_advanced_options = false;
|
||||
|
||||
/**
|
||||
* Advanced options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $advanced_options = [];
|
||||
|
||||
/**
|
||||
* Redirection cache.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $redirection = [];
|
||||
|
||||
/**
|
||||
* Columns.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $columns = [];
|
||||
|
||||
/**
|
||||
* Whether we need link counts.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $needs_link_count = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param mixed $object_types Object types to export.
|
||||
* @param mixed $advanced_options Options.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $object_types, $advanced_options ) {
|
||||
$this->object_types = array_intersect( array_keys( CSV_Import_Export::get_possible_object_types() ), $object_types );
|
||||
$this->use_advanced_options = ! empty( $advanced_options );
|
||||
$this->advanced_options = $advanced_options;
|
||||
|
||||
if ( empty( $this->object_types ) ) {
|
||||
wp_die( esc_html__( 'Please select at least one object type to export.', 'rank-math-pro' ) );
|
||||
}
|
||||
|
||||
$this->not_applicable_value = apply_filters( 'rank_math/admin/csv_export_not_applicable', $this->not_applicable_value );
|
||||
|
||||
$this->needs_link_count = false;
|
||||
if ( $this->use_advanced_options && ! empty( $this->advanced_options['readonly_columns'] ) ) {
|
||||
if ( Helper::is_module_active( 'link-counter' ) ) {
|
||||
$this->needs_link_count = true;
|
||||
}
|
||||
|
||||
$this->filter( 'rank_math/admin/csv_export_columns', 'add_readonly_columns' );
|
||||
}
|
||||
|
||||
$this->columns = CSV_Import_Export::get_columns();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do export.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process_export() {
|
||||
$this->export(
|
||||
[
|
||||
'filename' => 'rank-math',
|
||||
'columns' => $this->columns,
|
||||
'items' => $this->get_items(),
|
||||
]
|
||||
);
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output column contents.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_items() {
|
||||
foreach ( $this->object_types as $object_type ) {
|
||||
$this->get_objects( $object_type );
|
||||
}
|
||||
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value for given column.
|
||||
*
|
||||
* @param string $column Column name.
|
||||
* @param object $object WP_Post, WP_Term or WP_User.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_column_value( $column, $object ) {
|
||||
global $wpdb;
|
||||
|
||||
$value = '';
|
||||
$object_type = 'post';
|
||||
if ( ! empty( $object->term_id ) ) {
|
||||
$object_type = 'term';
|
||||
} elseif ( ! empty( $object->user_login ) ) {
|
||||
$object_type = 'user';
|
||||
}
|
||||
|
||||
$table = "{$object_type}meta";
|
||||
$primary_column = "{$object_type}_id";
|
||||
$object_id = isset( $object->ID ) ? $object->ID : $object->$primary_column;
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$meta_rows = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
/* translators: %d: object id, %s: table name */
|
||||
"SELECT * FROM {$wpdb->$table} WHERE {$primary_column} = %d AND meta_key LIKE %s",
|
||||
$object_id,
|
||||
$wpdb->esc_like( 'rank_math_' ) . '%'
|
||||
)
|
||||
);
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$meta = $this->process_meta_rows( $meta_rows );
|
||||
|
||||
$internal_meta = (object) [];
|
||||
if ( 'post' === $object_type && $this->needs_link_count ) {
|
||||
$internal_meta = $this->get_link_counts( $object_id );
|
||||
}
|
||||
|
||||
if ( 'user' !== $object_type && in_array( $column, [ 'redirect_to', 'redirect_type' ], true ) ) {
|
||||
$redirection = $this->get_redirection( $object_type, $object_id );
|
||||
}
|
||||
|
||||
switch ( $column ) {
|
||||
case 'id':
|
||||
$value = $object_id;
|
||||
break;
|
||||
|
||||
case 'object_type':
|
||||
$value = $object_type;
|
||||
break;
|
||||
|
||||
case 'slug':
|
||||
$slug = '';
|
||||
if ( 'user' === $object_type ) {
|
||||
$slug = $object->user_nicename;
|
||||
} elseif ( 'post' === $object_type ) {
|
||||
$slug = $object->post_name;
|
||||
} elseif ( 'term' === $object_type ) {
|
||||
$slug = $object->slug;
|
||||
}
|
||||
$value = urldecode( $slug );
|
||||
break;
|
||||
|
||||
case 'seo_title':
|
||||
if ( isset( $meta['rank_math_title'] ) ) {
|
||||
$value = $meta['rank_math_title'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'seo_description':
|
||||
if ( isset( $meta['rank_math_description'] ) ) {
|
||||
$value = $meta['rank_math_description'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'is_pillar_content':
|
||||
$value = 'no';
|
||||
if ( in_array( $object_type, [ 'term', 'user' ], true ) ) {
|
||||
$value = $this->not_applicable_value;
|
||||
break;
|
||||
}
|
||||
if ( ! empty( $meta['rank_math_pillar_content'] ) ) {
|
||||
$value = 'yes';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'focus_keyword':
|
||||
if ( isset( $meta['rank_math_focus_keyword'] ) ) {
|
||||
$value = $meta['rank_math_focus_keyword'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'seo_score':
|
||||
if ( isset( $meta['rank_math_seo_score'] ) ) {
|
||||
$value = $meta['rank_math_seo_score'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'robots':
|
||||
if ( isset( $meta['rank_math_robots'] ) ) {
|
||||
$value = $this->process_robots( $meta['rank_math_robots'] );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'advanced_robots':
|
||||
if ( isset( $meta['rank_math_advanced_robots'] ) ) {
|
||||
$value = $this->process_advanced_robots( $meta['rank_math_advanced_robots'] );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'canonical_url':
|
||||
if ( isset( $meta['rank_math_canonical_url'] ) ) {
|
||||
$value = $meta['rank_math_canonical_url'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'primary_term':
|
||||
if ( in_array( $object_type, [ 'term', 'user' ], true ) ) {
|
||||
$value = $this->not_applicable_value;
|
||||
break;
|
||||
}
|
||||
$value = $this->get_primary_term( $meta );
|
||||
break;
|
||||
|
||||
case 'schema_data':
|
||||
if ( in_array( $object_type, [ 'term', 'user' ], true ) ) {
|
||||
$value = $this->not_applicable_value;
|
||||
break;
|
||||
}
|
||||
$value = $this->process_schema_data( $meta );
|
||||
break;
|
||||
|
||||
case 'social_facebook_thumbnail':
|
||||
if ( isset( $meta['rank_math_facebook_image'] ) ) {
|
||||
$value = $meta['rank_math_facebook_image'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'social_facebook_title':
|
||||
if ( isset( $meta['rank_math_facebook_title'] ) ) {
|
||||
$value = $meta['rank_math_facebook_title'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'social_facebook_description':
|
||||
if ( isset( $meta['rank_math_facebook_description'] ) ) {
|
||||
$value = $meta['rank_math_facebook_description'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'social_twitter_thumbnail':
|
||||
if ( empty( $meta['rank_math_twitter_use_facebook'] ) || 'on' !== $meta['rank_math_twitter_use_facebook'] ) {
|
||||
break;
|
||||
}
|
||||
if ( isset( $meta['rank_math_twitter_image'] ) ) {
|
||||
$value = $meta['rank_math_twitter_image'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'social_twitter_title':
|
||||
if ( ! isset( $meta['rank_math_twitter_use_facebook'] ) || 'on' !== $meta['rank_math_twitter_use_facebook'] ) {
|
||||
break;
|
||||
}
|
||||
if ( isset( $meta['rank_math_twitter_title'] ) ) {
|
||||
$value = $meta['rank_math_twitter_title'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'social_twitter_description':
|
||||
if ( ! isset( $meta['rank_math_twitter_use_facebook'] ) || 'on' !== $meta['rank_math_twitter_use_facebook'] ) {
|
||||
break;
|
||||
}
|
||||
if ( isset( $meta['rank_math_twitter_description'] ) ) {
|
||||
$value = $meta['rank_math_twitter_description'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'redirect_to':
|
||||
if ( 'user' === $object_type ) {
|
||||
$value = $this->not_applicable_value;
|
||||
break;
|
||||
}
|
||||
if ( empty( $redirection['id'] ) ) {
|
||||
break;
|
||||
}
|
||||
$value = $redirection['url_to'];
|
||||
break;
|
||||
|
||||
case 'redirect_type':
|
||||
if ( 'user' === $object_type ) {
|
||||
$value = $this->not_applicable_value;
|
||||
break;
|
||||
}
|
||||
if ( empty( $redirection['id'] ) ) {
|
||||
break;
|
||||
}
|
||||
$value = $redirection['header_code'];
|
||||
break;
|
||||
|
||||
case 'internal_link_count':
|
||||
case 'external_link_count':
|
||||
case 'incoming_link_count':
|
||||
$value = $this->not_applicable_value;
|
||||
if ( isset( $internal_meta->$column ) ) {
|
||||
$value = $internal_meta->$column;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->escape_csv( apply_filters( "rank_math/admin/csv_export_column_{$column}", $value, $object ) ); //phpcs:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* Get redirection for object.
|
||||
*
|
||||
* @param string $object_type Object type (post/term).
|
||||
* @param int $object_id Object ID.
|
||||
* @return array
|
||||
*/
|
||||
public function get_redirection( $object_type, $object_id ) {
|
||||
if ( isset( $this->redirection[ $object_id ] ) ) {
|
||||
return $this->redirection[ $object_id ];
|
||||
}
|
||||
$url = 'term' === $object_type ? get_term_link( (int) $object_id ) : get_permalink( $object_id );
|
||||
$url = wp_parse_url( $url, PHP_URL_PATH );
|
||||
$url = trim( $url, '/' );
|
||||
|
||||
$redirection = Cache::get_by_object_id( $object_id, $object_type );
|
||||
$redirection = $redirection ? DB::get_redirection_by_id( $redirection->redirection_id, 'active' ) : [
|
||||
'id' => '',
|
||||
'url_to' => '',
|
||||
'header_code' => Helper::get_settings( 'general.redirections_header_code' ),
|
||||
];
|
||||
|
||||
$this->redirection = [ $object_id => $redirection ];
|
||||
|
||||
return $redirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* From DB format to key => value.
|
||||
*
|
||||
* @param array $rows Meta data rows from DB.
|
||||
* @return array
|
||||
*/
|
||||
public function process_meta_rows( $rows ) {
|
||||
$out = [];
|
||||
foreach ( $rows as $meta ) {
|
||||
$out[ $meta->meta_key ] = $meta->meta_value;
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* From DB format to CSV compatible.
|
||||
*
|
||||
* @param array $meta Robots meta value from DB.
|
||||
* @return string
|
||||
*/
|
||||
public function process_robots( $meta ) {
|
||||
$meta = maybe_unserialize( $meta );
|
||||
|
||||
return join( ',', $meta );
|
||||
}
|
||||
|
||||
/**
|
||||
* From DB format to CSV compatible.
|
||||
*
|
||||
* @param array $meta Robots meta value from DB.
|
||||
* @return string
|
||||
*/
|
||||
public function process_advanced_robots( $meta ) {
|
||||
$meta = maybe_unserialize( $meta );
|
||||
|
||||
return http_build_query( $meta, '', ', ' );
|
||||
}
|
||||
|
||||
/**
|
||||
* From DB format to JSON-encoded.
|
||||
*
|
||||
* @param array $metadata Schema data meta value from DB.
|
||||
* @return string
|
||||
*/
|
||||
public function process_schema_data( $metadata ) {
|
||||
$output = [];
|
||||
$schema_data = $this->filter_schema_meta( $metadata );
|
||||
|
||||
if ( empty( $schema_data ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
foreach ( $schema_data as $meta_key => $meta_value ) {
|
||||
$name = substr( $meta_key, 17 );
|
||||
$meta_value = maybe_unserialize( $meta_value );
|
||||
|
||||
if ( $name ) {
|
||||
$output[ $name ] = $meta_value;
|
||||
}
|
||||
}
|
||||
|
||||
return wp_json_encode( $output, JSON_UNESCAPED_SLASHES );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the rank_math_schema_* post meta values from all the values.
|
||||
*
|
||||
* @param array $metadata Schema data meta value from DB.
|
||||
* @return array
|
||||
*/
|
||||
private function filter_schema_meta( $metadata ) {
|
||||
$found = [];
|
||||
foreach ( $metadata as $meta_key => $meta_value ) {
|
||||
if ( substr( $meta_key, 0, 17 ) === 'rank_math_schema_' ) {
|
||||
$found[ $meta_key ] = $meta_value;
|
||||
}
|
||||
}
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get primary term for given object.
|
||||
*
|
||||
* @param mixed $meta Processed meta data.
|
||||
* @return string
|
||||
*/
|
||||
public function get_primary_term( $meta ) {
|
||||
if ( empty( $meta['rank_math_primary_category'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->get_term_slug( $meta['rank_math_primary_category'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all post IDs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_post_ids() {
|
||||
global $wpdb;
|
||||
|
||||
$where = $this->get_posts_where();
|
||||
|
||||
$post_ids = $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} WHERE $where" ); // phpcs:ignore
|
||||
return $post_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all term IDs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_term_ids() {
|
||||
global $wpdb;
|
||||
$taxonomies = Helper::get_allowed_taxonomies();
|
||||
|
||||
if ( $this->use_advanced_options ) {
|
||||
if ( empty( $this->advanced_options['taxonomies'] ) ) {
|
||||
return [];
|
||||
}
|
||||
$taxonomies = $this->advanced_options['taxonomies'];
|
||||
}
|
||||
|
||||
$term_ids = get_terms(
|
||||
[
|
||||
'taxonomy' => $taxonomies,
|
||||
'fields' => 'ids',
|
||||
'hide_empty' => false,
|
||||
]
|
||||
);
|
||||
|
||||
return $term_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all user IDs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_user_ids() {
|
||||
$args = [ 'fields' => [ 'ID' ] ];
|
||||
if ( $this->use_advanced_options ) {
|
||||
if ( empty( $this->advanced_options['roles'] ) ) {
|
||||
return [];
|
||||
}
|
||||
$args['role__in'] = $this->advanced_options['roles'];
|
||||
}
|
||||
|
||||
$user_ids = get_users( $args );
|
||||
return wp_list_pluck( $user_ids, 'ID' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all items of specified object type. Output column values.
|
||||
*
|
||||
* @param string $object_type Object type to export.
|
||||
* @return array
|
||||
*/
|
||||
public function get_objects( $object_type ) {
|
||||
global $wpdb;
|
||||
$object_type_plural = $object_type . 's';
|
||||
// get_post_ids, get_term_ids, get_user_ids.
|
||||
$method = "get_{$object_type}_ids";
|
||||
$ids = $this->$method();
|
||||
if ( ! $ids ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$primary_column = 'ID';
|
||||
if ( 'term' === $object_type ) {
|
||||
$primary_column = "{$object_type}_id";
|
||||
}
|
||||
|
||||
$cols = $this->columns;
|
||||
|
||||
// Fetch 50 at a time rather than loading the entire table into memory.
|
||||
while ( $next_batch = array_splice( $ids, 0, 50 ) ) { // phpcs:ignore
|
||||
$where = 'WHERE ' . $primary_column . ' IN (' . join( ',', $next_batch ) . ')';
|
||||
|
||||
$objects = $wpdb->get_results( "SELECT * FROM {$wpdb->$object_type_plural} $where" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$current_object = 0;
|
||||
|
||||
// Begin Loop.
|
||||
foreach ( $objects as $object ) {
|
||||
$current_object++;
|
||||
$current_col = 0;
|
||||
$columns = [];
|
||||
foreach ( $cols as $column ) {
|
||||
$current_col++;
|
||||
$columns[] = $this->get_column_value( $column, $object ); // phpcs:ignore
|
||||
}
|
||||
$this->data[] = $columns;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get WHERE for post types.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_posts_where() {
|
||||
global $wpdb;
|
||||
|
||||
$post_types = Helper::get_allowed_post_types();
|
||||
if ( $this->use_advanced_options ) {
|
||||
if ( empty( $this->advanced_options['post_types'] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$post_types = $this->advanced_options['post_types'];
|
||||
}
|
||||
|
||||
$esses = array_fill( 0, count( $post_types ), '%s' );
|
||||
|
||||
$where = $wpdb->prepare( "{$wpdb->posts}.post_type IN (" . implode( ',', $esses ) . ')', $post_types ); // phpcs:ignore
|
||||
|
||||
$where .= " AND {$wpdb->posts}.post_status != 'auto-draft'";
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get slug from term ID.
|
||||
*
|
||||
* @param int $term_id Term ID.
|
||||
* @return string
|
||||
*/
|
||||
public function get_term_slug( $term_id ) {
|
||||
if ( isset( $this->term_slugs[ $term_id ] ) ) {
|
||||
return $this->term_slugs[ $term_id ];
|
||||
}
|
||||
global $wpdb;
|
||||
$where = 'term_id = ' . absint( $term_id ) . '';
|
||||
$this->term_slugs[ $term_id ] = $wpdb->get_var( "SELECT slug FROM {$wpdb->terms} WHERE $where" ); // phpcs:ignore
|
||||
|
||||
return $this->term_slugs[ $term_id ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add read-only columns.
|
||||
*
|
||||
* @param array $columns Columns.
|
||||
* @return array
|
||||
*/
|
||||
public function add_readonly_columns( $columns ) {
|
||||
$columns[] = 'seo_score';
|
||||
if ( $this->needs_link_count ) {
|
||||
$columns[] = 'internal_link_count';
|
||||
$columns[] = 'external_link_count';
|
||||
$columns[] = 'incoming_link_count';
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get post link counts.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function get_link_counts( $post_id ) {
|
||||
global $wpdb;
|
||||
|
||||
$counts = $wpdb->get_row( "SELECT * FROM {$wpdb->prefix}rank_math_internal_meta WHERE object_id = {$post_id}" ); // phpcs:ignore
|
||||
$counts = ! empty( $counts ) ? $counts : (object) [
|
||||
'internal_link_count' => '',
|
||||
'external_link_count' => '',
|
||||
'incoming_link_count' => '',
|
||||
];
|
||||
|
||||
return $counts;
|
||||
}
|
||||
}
|
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
/**
|
||||
* The CSV Import class.
|
||||
*
|
||||
* @since 1.0
|
||||
* @package RankMathPro
|
||||
* @subpackage RankMathPro\Admin
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMathPro\Admin\CSV_Import_Export;
|
||||
|
||||
use RankMath\Helper;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* CSV Import Export class.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Import_Background_Process extends \WP_Background_Process {
|
||||
|
||||
/**
|
||||
* Prefix.
|
||||
*
|
||||
* (default value: 'wp')
|
||||
*
|
||||
* @var string
|
||||
* @access protected
|
||||
*/
|
||||
protected $prefix = 'rank_math';
|
||||
|
||||
/**
|
||||
* Action.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'csv_import';
|
||||
|
||||
/**
|
||||
* Importer instance.
|
||||
*
|
||||
* @var Importer
|
||||
*/
|
||||
private $importer;
|
||||
|
||||
/**
|
||||
* Main instance.
|
||||
*
|
||||
* Ensure only one instance is loaded or can be loaded.
|
||||
*
|
||||
* @return Import_Background_Process
|
||||
*/
|
||||
public static function get() {
|
||||
static $instance;
|
||||
|
||||
if ( is_null( $instance ) || ! ( $instance instanceof Import_Background_Process ) ) {
|
||||
$instance = new Import_Background_Process();
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start creating batches.
|
||||
*
|
||||
* @param [type] $posts [description].
|
||||
*/
|
||||
public function start( $lines_number ) {
|
||||
$chunks = array_chunk( range( 0, $lines_number ), apply_filters( 'rank_math/admin/csv_import_chunk_size', 100 ) );
|
||||
foreach ( $chunks as $chunk ) {
|
||||
$this->push_to_queue( $chunk );
|
||||
}
|
||||
|
||||
Helper::add_notification(
|
||||
sprintf(
|
||||
// Translators: placeholders are opening and closing tags for link.
|
||||
__( 'CSV import in progress. You can see its progress and cancel it in the %1$sImport & Export panel%2$s.', 'rank-math-pro' ),
|
||||
'<a href="' . esc_url( Helper::get_admin_url( 'status', [ 'view' => 'import_export' ] ) ) . '">',
|
||||
'</a>'
|
||||
),
|
||||
[
|
||||
'type' => 'success',
|
||||
'classes' => 'is-dismissible',
|
||||
]
|
||||
);
|
||||
|
||||
$this->save()->dispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Task.
|
||||
*
|
||||
* Override this method to perform any actions required on each
|
||||
* queue item. Return the modified item for further processing
|
||||
* in the next pass through. Or, return false to remove the
|
||||
* item from the queue.
|
||||
*
|
||||
* @param mixed $item Queue item to iterate over.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function task( $item ) {
|
||||
try {
|
||||
$this->importer = new Importer();
|
||||
foreach ( $item as $row ) {
|
||||
$this->importer->import_line( $row );
|
||||
}
|
||||
$this->importer->batch_done( $item );
|
||||
return false;
|
||||
} catch ( \Exception $error ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import complete. Clear options & add notification.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function complete() {
|
||||
unlink( get_option( 'rank_math_csv_import' ) );
|
||||
delete_option( 'rank_math_csv_import' );
|
||||
delete_option( 'rank_math_csv_import_total' );
|
||||
delete_option( 'rank_math_csv_import_settings' );
|
||||
|
||||
$status = (array) get_option( 'rank_math_csv_import_status', [] );
|
||||
|
||||
$notification_args = [
|
||||
'type' => 'success',
|
||||
'classes' => 'is-dismissible',
|
||||
];
|
||||
|
||||
if ( ! empty( $status['errors'] ) ) {
|
||||
$notification_args = [
|
||||
'type' => 'error',
|
||||
'classes' => 'is-dismissible',
|
||||
];
|
||||
}
|
||||
|
||||
Helper::add_notification(
|
||||
CSV_Import_Export::get_import_complete_message(),
|
||||
$notification_args
|
||||
);
|
||||
|
||||
parent::complete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count remaining items in batch.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count_remaining_items() {
|
||||
if ( $this->is_queue_empty() ) {
|
||||
// This fixes an issue where get_batch() runs too early and results in a PHP notice.
|
||||
return get_option( 'rank_math_csv_import_total' );
|
||||
}
|
||||
$batch = $this->get_batch();
|
||||
$count = 0;
|
||||
if ( ! empty( $batch->data ) && is_array( $batch->data ) ) {
|
||||
foreach ( $batch->data as $items ) {
|
||||
$count += count( $items );
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
}
|
@@ -0,0 +1,777 @@
|
||||
<?php
|
||||
/**
|
||||
* The CSV Import class.
|
||||
*
|
||||
* @since 1.0
|
||||
* @package RankMathPro
|
||||
* @subpackage RankMathPro\Admin
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMathPro\Admin\CSV_Import_Export;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Redirections\DB;
|
||||
use RankMath\Redirections\Cache;
|
||||
use RankMath\Redirections\Redirection;
|
||||
use MyThemeShop\Helpers\Arr;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* CSV Importer class.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Import_Row {
|
||||
|
||||
/**
|
||||
* Row data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $data = [];
|
||||
|
||||
/**
|
||||
* Import settings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $settings = [];
|
||||
|
||||
/**
|
||||
* Columns.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $columns = [];
|
||||
|
||||
/**
|
||||
* Redirection.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $redirection = [];
|
||||
|
||||
/**
|
||||
* Object URI.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $object_uri = '';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data Row data.
|
||||
* @param array $settings Import settings.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $data, $settings ) {
|
||||
$this->data = $data;
|
||||
$this->settings = $settings;
|
||||
|
||||
foreach ( $this->data as $key => $value ) {
|
||||
// Skip empty or n/a.
|
||||
if ( empty( $value ) || $this->is_not_applicable( $value ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$clear_method = "clear_{$key}";
|
||||
if ( $this->is_clear_command( $value ) && method_exists( $this, $clear_method ) ) {
|
||||
$this->$clear_method();
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $this->settings['no_overwrite'] ) {
|
||||
$is_empty_method = "is_empty_{$key}";
|
||||
if ( ! method_exists( $this, $is_empty_method ) || ! $this->$is_empty_method() ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$import_method = "import_{$key}";
|
||||
if ( method_exists( $this, $import_method ) ) {
|
||||
$this->$import_method( $value );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do custom action after importing a row.
|
||||
*/
|
||||
do_action( 'rank_math/admin/csv_import_row', $data, $settings, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given column value is empty or not applicable.
|
||||
*
|
||||
* @param mixed $value Column value.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_not_applicable( $value ) {
|
||||
return $value === $this->settings['not_applicable_value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given column value is the delete command.
|
||||
*
|
||||
* @param mixed $value Column value.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_clear_command( $value ) {
|
||||
return $value === $this->settings['clear_command'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter.
|
||||
*
|
||||
* Return column value if is set and column name is in allowed columns list.
|
||||
*
|
||||
* @param string $property Property we want to get.
|
||||
* @return string
|
||||
*/
|
||||
public function __get( $property ) {
|
||||
if ( in_array( $property, $this->get_columns(), true ) && isset( $this->data[ $property ] ) ) {
|
||||
return $this->data[ $property ];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CSV columns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_columns() {
|
||||
if ( ! empty( $this->columns ) ) {
|
||||
return $this->columns;
|
||||
}
|
||||
$this->columns = CSV_Import_Export::get_columns();
|
||||
|
||||
return $this->columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear SEO Title column.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_seo_title() {
|
||||
$this->delete_meta( 'title' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear SEO Description column.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_seo_description() {
|
||||
$this->delete_meta( 'description' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear Focus Keyword column.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_focus_keyword() {
|
||||
$this->delete_meta( 'focus_keyword' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear Robots column.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_robots() {
|
||||
$this->delete_meta( 'robots' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear Advanced Robots column.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_advanced_robots() {
|
||||
$this->delete_meta( 'advanced_robots' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear Canonical URL column.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_canonical_url() {
|
||||
$this->delete_meta( 'canonical_url' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear Primary Term column.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_primary_term() {
|
||||
$this->delete_meta( 'primary_category' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear Schema Data column. Schema data must be valid JSON.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_schema_data() {
|
||||
$current_meta = $this->get_meta();
|
||||
foreach ( $current_meta as $key => $value ) {
|
||||
if ( substr( $key, 0, 17 ) === 'rank_math_schema_' ) {
|
||||
// Cut off "rank_math_" prefix.
|
||||
$this->delete_meta( substr( $key, 10 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear FB Thumbnail column.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_social_facebook_thumbnail() {
|
||||
$this->delete_meta( 'facebook_image' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear FB Title column.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_social_facebook_title() {
|
||||
$this->delete_meta( 'facebook_title' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear FB Description column.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_social_facebook_description() {
|
||||
$this->delete_meta( 'facebook_description' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear Twitter Thumbnail column.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_social_twitter_thumbnail() {
|
||||
$this->delete_meta( 'twitter_image' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear Twitter Title column.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_social_twitter_title() {
|
||||
$this->delete_meta( 'twitter_title' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear Twitter Description column.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_social_twitter_description() {
|
||||
$this->delete_meta( 'twitter_description' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear Redirection URL column. Only if 'redirect_type' column is set, too.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_redirect_to() {
|
||||
if ( ! $this->is_empty_redirect_to() ) {
|
||||
DB::delete( $this->get_redirection()['id'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import slug column.
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @return void
|
||||
*/
|
||||
public function import_slug( $value ) {
|
||||
switch ( $this->object_type ) {
|
||||
case 'post':
|
||||
wp_update_post(
|
||||
[
|
||||
'ID' => $this->id,
|
||||
'post_name' => $value,
|
||||
]
|
||||
);
|
||||
break;
|
||||
|
||||
case 'term':
|
||||
global $wpdb;
|
||||
$wpdb->update(
|
||||
$wpdb->terms,
|
||||
[ 'slug' => sanitize_title( $value ) ], // Update.
|
||||
[ 'term_id' => sanitize_title( $value ) ], // Where.
|
||||
[ '%s' ], // Format.
|
||||
[ '%d' ] // Where format.
|
||||
);
|
||||
break;
|
||||
|
||||
case 'user':
|
||||
update_user_meta( $this->id, 'rank_math_permalink', $value );
|
||||
break;
|
||||
}
|
||||
|
||||
// Refresh URI.
|
||||
$this->get_object_uri( true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import SEO Title column.
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @return void
|
||||
*/
|
||||
public function import_seo_title( $value ) {
|
||||
$this->update_meta( 'title', $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import SEO Description column.
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @return void
|
||||
*/
|
||||
public function import_seo_description( $value ) {
|
||||
$this->update_meta( 'description', $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import Is Pillar Content column.
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @return void
|
||||
*/
|
||||
public function import_is_pillar_content( $value ) {
|
||||
$lcvalue = strtolower( $value );
|
||||
if ( 'yes' === $lcvalue ) {
|
||||
$this->update_meta( 'pillar_content', 'on' );
|
||||
} elseif ( 'no' === $lcvalue ) {
|
||||
$this->delete_meta( 'pillar_content' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import Focus Keyword column.
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @return void
|
||||
*/
|
||||
public function import_focus_keyword( $value ) {
|
||||
$this->update_meta( 'focus_keyword', $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import Robots column.
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @return void
|
||||
*/
|
||||
public function import_robots( $value ) {
|
||||
$this->update_meta( 'robots', Arr::from_string( $value ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import Advanced Robots column.
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @return void
|
||||
*/
|
||||
public function import_advanced_robots( $value ) {
|
||||
$robots = [];
|
||||
$robots_rules = Arr::from_string( $value );
|
||||
foreach ( $robots_rules as $robots_rule ) {
|
||||
$parts = Arr::from_string( $robots_rule, '=' );
|
||||
if ( count( $parts ) === 2 ) {
|
||||
$robots[ $parts[0] ] = $parts[1];
|
||||
}
|
||||
}
|
||||
|
||||
$this->update_meta( 'advanced_robots', $robots );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import Canonical URL column.
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @return void
|
||||
*/
|
||||
public function import_canonical_url( $value ) {
|
||||
$this->update_meta( 'canonical_url', $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import Primary Term column.
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @return void
|
||||
*/
|
||||
public function import_primary_term( $value ) {
|
||||
$term_id = Importer::get_term_id( $value );
|
||||
if ( ! $term_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->update_meta( 'primary_category', $term_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import Schema Data column. Schema data must be valid JSON.
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @return void
|
||||
*/
|
||||
public function import_schema_data( $value ) {
|
||||
$value = json_decode( $value, true );
|
||||
if ( ! $value ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $value as $key => $value ) {
|
||||
$meta_key = 'schema_' . $key;
|
||||
$this->update_meta( $meta_key, $value );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import FB Thumbnail column.
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @return void
|
||||
*/
|
||||
public function import_social_facebook_thumbnail( $value ) {
|
||||
$this->update_meta( 'facebook_image', $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import FB Title column.
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @return void
|
||||
*/
|
||||
public function import_social_facebook_title( $value ) {
|
||||
$this->update_meta( 'facebook_title', $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import FB Description column.
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @return void
|
||||
*/
|
||||
public function import_social_facebook_description( $value ) {
|
||||
$this->update_meta( 'facebook_description', $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import Twitter Thumbnail column.
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @return void
|
||||
*/
|
||||
public function import_social_twitter_thumbnail( $value ) {
|
||||
$this->update_meta( 'twitter_image', $value );
|
||||
$this->update_meta( 'twitter_use_facebook', 'off' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import Twitter Title column.
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @return void
|
||||
*/
|
||||
public function import_social_twitter_title( $value ) {
|
||||
$this->update_meta( 'twitter_title', $value );
|
||||
$this->update_meta( 'twitter_use_facebook', 'off' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import Twitter Description column.
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @return void
|
||||
*/
|
||||
public function import_social_twitter_description( $value ) {
|
||||
$this->update_meta( 'twitter_description', $value );
|
||||
$this->update_meta( 'twitter_use_facebook', 'off' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import Redirection URL column. Only if 'redirect_type' column is set, too.
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @return void
|
||||
*/
|
||||
public function import_redirect_to( $value ) {
|
||||
if ( empty( $this->data['redirect_type'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->is_empty_redirect_to() ) {
|
||||
DB::delete( $this->get_redirection()['id'] );
|
||||
}
|
||||
|
||||
$redirection = Redirection::from(
|
||||
[
|
||||
'id' => '',
|
||||
'url_to' => $this->redirect_to,
|
||||
'sources' => [
|
||||
[
|
||||
'pattern' => $this->get_object_uri(),
|
||||
'comparison' => 'exact',
|
||||
],
|
||||
],
|
||||
'header_code' => $this->redirect_type,
|
||||
]
|
||||
);
|
||||
$redirection->set_nocache( true );
|
||||
$redirection->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if empty: SEO Title
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_empty_seo_title() {
|
||||
return ! $this->get_meta( 'title' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if empty: SEO Description
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_empty_seo_description() {
|
||||
return ! $this->get_meta( 'description' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if empty: Is Pillar Content column.
|
||||
* We return true so this will always be overwritten.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_empty_is_pillar_content() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if empty: Focus Keyword column.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_empty_focus_keyword() {
|
||||
return ! $this->get_meta( 'focus_keyword' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if empty: Robots column.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_empty_robots() {
|
||||
return empty( $this->get_meta( 'robots' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if empty: Advanced Robots column.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_empty_advanced_robots() {
|
||||
return empty( $this->get_meta( 'advanced_robots' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if empty: Canonical URL column.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_empty_canonical_url() {
|
||||
return ! $this->get_meta( 'canonical_url' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if empty: Primary Term column.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_empty_primary_term() {
|
||||
return empty( $this->get_meta( 'primary_category' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if empty: Schema Data column.
|
||||
* We return true so this will always be overwritten.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_empty_schema_data() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if empty: FB Thumbnail column.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_empty_social_facebook_thumbnail() {
|
||||
return ! $this->get_meta( 'facebook_image' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if empty: FB Title column.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_empty_social_facebook_title() {
|
||||
return ! $this->get_meta( 'facebook_title' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if empty: FB Description column.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_empty_social_facebook_description() {
|
||||
return ! $this->get_meta( 'facebook_description' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if empty: Twitter Thumbnail column.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_empty_social_twitter_thumbnail() {
|
||||
return ! $this->get_meta( 'twitter_image' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if empty: Twitter Title column.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_empty_social_twitter_title() {
|
||||
return ! $this->get_meta( 'twitter_title' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if empty: Twitter Description column.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_empty_social_twitter_description() {
|
||||
return ! $this->get_meta( 'twitter_description' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if empty: Redirect URL.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_empty_redirect_to() {
|
||||
return ! (bool) $this->get_redirection()['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get redirection for object.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_redirection() {
|
||||
if ( isset( $this->redirection ) ) {
|
||||
return $this->redirection;
|
||||
}
|
||||
$object_type = $this->object_type;
|
||||
$object_id = $this->id;
|
||||
|
||||
$this->get_object_uri();
|
||||
|
||||
$redirection = Cache::get_by_object_id( $object_id, $object_type );
|
||||
$redirection = $redirection ? DB::get_redirection_by_id( $redirection->redirection_id, 'active' ) : [
|
||||
'id' => '',
|
||||
'url_to' => '',
|
||||
'header_code' => Helper::get_settings( 'general.redirections_header_code' ),
|
||||
];
|
||||
|
||||
$this->redirection = $redirection;
|
||||
|
||||
return $redirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object URI.
|
||||
*
|
||||
* @param bool $refresh Force refresh.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_object_uri( $refresh = false ) {
|
||||
if ( isset( $this->object_uri ) && ! $refresh ) {
|
||||
return $this->object_uri;
|
||||
}
|
||||
|
||||
$url = 'term' === $this->object_type ? get_term_link( (int) $this->id ) : get_permalink( $this->id );
|
||||
if ( empty( $url ) || is_wp_error( $url ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$url = wp_parse_url( $url, PHP_URL_PATH );
|
||||
|
||||
$this->object_uri = trim( $url, '/' );
|
||||
|
||||
return $this->object_uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update object meta.
|
||||
*
|
||||
* @param string $key Meta key.
|
||||
* @param mixed $value Meta value.
|
||||
* @return void
|
||||
*/
|
||||
public function update_meta( $key, $value ) {
|
||||
$update_meta = "update_{$this->object_type}_meta";
|
||||
$update_meta( $this->id, 'rank_math_' . $key, $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object meta.
|
||||
*
|
||||
* @param string $key Meta key.
|
||||
* @return bool
|
||||
*/
|
||||
public function get_meta( $key = '' ) {
|
||||
$get_meta = "get_{$this->object_type}_meta";
|
||||
return $get_meta( $this->id, $key ? 'rank_math_' . $key : '', (bool) $key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete object meta.
|
||||
*
|
||||
* @param string $key Meta key.
|
||||
* @return void
|
||||
*/
|
||||
public function delete_meta( $key ) {
|
||||
$delete_meta = "delete_{$this->object_type}_meta";
|
||||
$delete_meta( $this->id, 'rank_math_' . $key );
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,358 @@
|
||||
<?php
|
||||
/**
|
||||
* The CSV Import class.
|
||||
*
|
||||
* @since 1.0
|
||||
* @package RankMathPro
|
||||
* @subpackage RankMathPro\Admin
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMathPro\Admin\CSV_Import_Export;
|
||||
|
||||
use MyThemeShop\Helpers\Arr;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* CSV Importer class.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Importer {
|
||||
|
||||
/**
|
||||
* Term slug => ID cache.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $term_ids = [];
|
||||
|
||||
/**
|
||||
* Settings array. Default values.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $settings = [
|
||||
'not_applicable_value' => 'n/a',
|
||||
'clear_command' => 'DELETE',
|
||||
'no_overwrite' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* Lines in the CSV that could not be imported for any reason.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $failed_rows = [];
|
||||
|
||||
/**
|
||||
* Lines in the CSV that could be imported successfully.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $imported_rows = [];
|
||||
|
||||
/**
|
||||
* Error messages.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $errors = [];
|
||||
|
||||
/**
|
||||
* SplFileObject instance.
|
||||
*
|
||||
* @var \SplFileObject
|
||||
*/
|
||||
private $spl;
|
||||
|
||||
/**
|
||||
* Column headers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $column_headers = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->load_settings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load settings.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load_settings() {
|
||||
$this->settings = apply_filters( 'rank_math/admin/csv_import_settings', wp_parse_args( get_option( 'rank_math_csv_import_settings', [] ), $this->settings ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Start import from file.
|
||||
*
|
||||
* @param string $file Path to temporary CSV file.
|
||||
* @param string $settings Import settings.
|
||||
* @return void
|
||||
*/
|
||||
public function start( $file, $settings = [] ) {
|
||||
update_option( 'rank_math_csv_import', $file );
|
||||
update_option( 'rank_math_csv_import_settings', $settings );
|
||||
delete_option( 'rank_math_csv_import_status' );
|
||||
$this->load_settings();
|
||||
$lines = $this->count_lines( $file );
|
||||
update_option( 'rank_math_csv_import_total', $lines );
|
||||
Import_Background_Process::get()->start( $lines );
|
||||
}
|
||||
|
||||
/**
|
||||
* Count all lines in CSV file.
|
||||
*
|
||||
* @param mixed $file Path to CSV.
|
||||
* @return int
|
||||
*/
|
||||
public function count_lines( $file ) {
|
||||
$file = new \SplFileObject( $file );
|
||||
while ( $file->valid() ) {
|
||||
$file->fgets();
|
||||
}
|
||||
|
||||
$count = $file->key();
|
||||
|
||||
// Check if last line is empty.
|
||||
$file->seek( $count );
|
||||
$contents = $file->current();
|
||||
if ( empty( trim( $contents ) ) ) {
|
||||
$count--;
|
||||
}
|
||||
|
||||
// Unlock file.
|
||||
$file = null;
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specified line from CSV.
|
||||
*
|
||||
* @param string $file Path to file.
|
||||
* @param int $line Line number.
|
||||
* @return string
|
||||
*/
|
||||
public function get_line( $file, $line ) {
|
||||
if ( empty( $this->spl ) ) {
|
||||
$this->spl = new \SplFileObject( $file );
|
||||
}
|
||||
|
||||
if ( ! $this->spl->eof() ) {
|
||||
$this->spl->seek( $line );
|
||||
$contents = $this->spl->current();
|
||||
}
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and return column headers (first line in CSV).
|
||||
*
|
||||
* @param string $file Path to file.
|
||||
* @return array
|
||||
*/
|
||||
public function get_column_headers( $file ) {
|
||||
if ( ! empty( $this->column_headers ) ) {
|
||||
return $this->column_headers;
|
||||
}
|
||||
|
||||
if ( empty( $this->spl ) ) {
|
||||
$this->spl = new \SplFileObject( $file );
|
||||
}
|
||||
|
||||
if ( ! $this->spl->eof() ) {
|
||||
$this->spl->seek( 0 );
|
||||
$contents = $this->spl->current();
|
||||
}
|
||||
|
||||
if ( empty( $contents ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$this->column_headers = Arr::from_string( $contents, apply_filters( 'rank_math/csv_import/separator', ',' ) );
|
||||
return $this->column_headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import specified line.
|
||||
*
|
||||
* @param int $line_number Selected line number.
|
||||
* @return void
|
||||
*/
|
||||
public function import_line( $line_number ) {
|
||||
// Skip headers.
|
||||
if ( 0 === $line_number ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$file = get_option( 'rank_math_csv_import' );
|
||||
if ( ! $file ) {
|
||||
$this->add_error( esc_html__( 'Missing import file.', 'rank-math-pro' ), 'missing_file' );
|
||||
CSV_Import_Export::cancel_import( true );
|
||||
return;
|
||||
}
|
||||
|
||||
$headers = $this->get_column_headers( $file );
|
||||
if ( empty( $headers ) ) {
|
||||
$this->add_error( esc_html__( 'Missing CSV headers.', 'rank-math-pro' ), 'missing_headers' );
|
||||
return;
|
||||
}
|
||||
|
||||
$required_columns = [ 'id', 'object_type', 'slug' ];
|
||||
if ( count( array_intersect( $headers, $required_columns ) ) !== count( $required_columns ) ) {
|
||||
$this->add_error( esc_html__( 'Missing one or more required columns.', 'rank-math-pro' ), 'missing_required_columns' );
|
||||
return;
|
||||
}
|
||||
|
||||
$raw_data = $this->get_line( $file, $line_number );
|
||||
if ( empty( $raw_data ) ) {
|
||||
$total_lines = (int) get_option( 'rank_math_csv_import_total' );
|
||||
|
||||
// Last line can be empty, that is not an error.
|
||||
if ( $line_number !== $total_lines ) {
|
||||
$this->add_error( esc_html__( 'Empty column data.', 'rank-math-pro' ), 'missing_data' );
|
||||
$this->row_failed( $line_number );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$csv_separator = apply_filters( 'rank_math/csv_import/separator', ',' );
|
||||
$decoded = str_getcsv( $raw_data, $csv_separator );
|
||||
if ( count( $headers ) !== count( $decoded ) ) {
|
||||
$this->add_error( esc_html__( 'Columns number mismatch.', 'rank-math-pro' ), 'columns_number_mismatch' );
|
||||
$this->row_failed( $line_number );
|
||||
return;
|
||||
}
|
||||
|
||||
$data = array_combine( $headers, $decoded );
|
||||
if ( ! in_array( $data['object_type'], array_keys( CSV_Import_Export::get_possible_object_types() ), true ) ) {
|
||||
$this->add_error( esc_html__( 'Unknown object type.', 'rank-math-pro' ), 'unknown_object_type' );
|
||||
$this->row_failed( $line_number );
|
||||
return;
|
||||
}
|
||||
|
||||
new Import_Row( $data, $this->settings );
|
||||
$this->row_imported( $line_number );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get term ID from slug.
|
||||
*
|
||||
* @param string $term_slug Term slug.
|
||||
* @return int
|
||||
*/
|
||||
public static function get_term_id( $term_slug ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! empty( self::$term_ids[ $term_slug ] ) ) {
|
||||
return self::$term_ids[ $term_slug ];
|
||||
}
|
||||
|
||||
self::$term_ids[ $term_slug ] = $wpdb->get_var(
|
||||
$wpdb->prepare( "SELECT term_id FROM {$wpdb->terms} WHERE slug = %s", $term_slug )
|
||||
);
|
||||
|
||||
return self::$term_ids[ $term_slug ];
|
||||
}
|
||||
|
||||
/**
|
||||
* After each batch is finished.
|
||||
*
|
||||
* @param array $items Processed items.
|
||||
*/
|
||||
public function batch_done( $items ) { // phpcs:ignore
|
||||
unset( $this->spl );
|
||||
|
||||
$status = (array) get_option( 'rank_math_csv_import_status', [] );
|
||||
if ( ! isset( $status['errors'] ) || ! is_array( $status['errors'] ) ) {
|
||||
$status['errors'] = [];
|
||||
}
|
||||
if ( ! isset( $status['failed_rows'] ) || ! is_array( $status['failed_rows'] ) ) {
|
||||
$status['failed_rows'] = [];
|
||||
}
|
||||
if ( ! isset( $status['imported_rows'] ) || ! is_array( $status['imported_rows'] ) ) {
|
||||
$status['imported_rows'] = [];
|
||||
}
|
||||
|
||||
$status['imported_rows'] = array_merge( $status['imported_rows'], $this->get_imported_rows() );
|
||||
|
||||
$errors = $this->get_errors();
|
||||
if ( $errors ) {
|
||||
$status['errors'] = array_merge( $status['errors'], $errors );
|
||||
$status['failed_rows'] = array_merge( $status['failed_rows'], $this->get_failed_rows() );
|
||||
}
|
||||
|
||||
update_option( 'rank_math_csv_import_status', $status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set row import status.
|
||||
*
|
||||
* @param int $row Row index.
|
||||
*/
|
||||
private function row_failed( $row ) {
|
||||
$this->failed_rows[] = $row + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set row import status.
|
||||
*
|
||||
* @param int $row Row index.
|
||||
*/
|
||||
private function row_imported( $row ) {
|
||||
$this->imported_rows[] = $row + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get failed rows array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_failed_rows() {
|
||||
return $this->failed_rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get failed rows array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_imported_rows() {
|
||||
return $this->imported_rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all import errors.
|
||||
*
|
||||
* @return mixed Array of errors or false if there is no error.
|
||||
*/
|
||||
public function get_errors() {
|
||||
return empty( $this->errors ) ? false : $this->errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add import error.
|
||||
*
|
||||
* @param string $message Error message.
|
||||
* @param int $code Error code.
|
||||
*/
|
||||
public function add_error( $message, $code = null ) {
|
||||
if ( is_null( $code ) ) {
|
||||
$this->errors[] = $message;
|
||||
return;
|
||||
}
|
||||
$this->errors[ $code ] = $message;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user