plugin = $plugin; $this->importer = $importer; } /** * Registers hooks. */ public function register() { add_action( 'admin_menu', array( $this, 'menu' ), 20 ); add_action( 'admin_post_' . self::ACTION, array( $this, 'handle' ) ); } /** * Adds the submenu page. */ public function menu() { add_submenu_page( Admin::SLUG, __( 'Import', 'orbitstem-seo' ), __( 'Import', 'orbitstem-seo' ), Admin::capability(), self::SLUG, array( $this, 'render' ) ); } /** * Handles start, process and rollback requests. */ public function handle() { if ( ! current_user_can( Admin::capability() ) ) { wp_die( esc_html__( 'You do not have permission to do this.', 'orbitstem-seo' ), 403 ); } check_admin_referer( self::ACTION ); $do = isset( $_POST['do'] ) ? sanitize_key( wp_unslash( $_POST['do'] ) ) : ''; $source = isset( $_POST['source'] ) ? sanitize_key( wp_unslash( $_POST['source'] ) ) : ''; $notice = 'error'; if ( 'start' === $do && $this->importer->start( $source ) ) { $notice = 'started'; $this->importer->import_templates( $source ); } elseif ( 'rollback' === $do && $this->importer->start_rollback() ) { $notice = 'rollback'; } elseif ( 'process' === $do ) { $this->plugin->jobs->tick( 15 ); $notice = 'processed'; } wp_safe_redirect( add_query_arg( 'notice', $notice, admin_url( 'admin.php?page=' . self::SLUG ) ) ); exit; } /** * Renders the page. */ public function render() { if ( ! current_user_can( Admin::capability() ) ) { wp_die( esc_html__( 'You do not have permission to view this page.', 'orbitstem-seo' ) ); } // phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only view state. $preview = isset( $_GET['preview'] ) ? sanitize_key( wp_unslash( $_GET['preview'] ) ) : ''; $notice = isset( $_GET['notice'] ) ? sanitize_key( wp_unslash( $_GET['notice'] ) ) : ''; // phpcs:enable $messages = array( 'started' => __( 'Import started. It runs in the background; this page shows progress.', 'orbitstem-seo' ), 'rollback' => __( 'Rollback started.', 'orbitstem-seo' ), 'processed' => __( 'Processed one batch.', 'orbitstem-seo' ), 'error' => __( 'That could not be started. Another import or rollback may be running.', 'orbitstem-seo' ), ); $busy = $this->importer->busy(); Ui::open( self::SLUG, __( 'Import SEO data', 'orbitstem-seo' ), __( 'Copy SEO data from another SEO plugin. Its data is only read, never changed.', 'orbitstem-seo' ) ); ?>

importer->sources() as $id => $source ) : ?> counts(); ?>
name() ); ?> 0 ) : ?> button( 'start', __( 'Import', 'orbitstem-seo' ), $busy, $id ); ?>
importer->source( $compare ) ) { $this->render_compare( $compare ); } ?> importer->source( $preview ) ) : ?>

importer->source( $preview )->name() ) ); ?>

importer->preview( $preview ) as $row ) : ?>
()
importer->template_changes( $preview ); ?>

History and can be undone there.', 'orbitstem-seo' ); ?>

$change ) : ?>
importer->preview_dropped ) : ?>

importer->preview_dropped, 0, 20 ) ) ) ); ?>

plugin->jobs->status( Importer::JOB_IMPORT ) || $this->plugin->jobs->status( Importer::JOB_ROLLBACK ) ) : ?>

status_table(); ?>

button( 'process', __( 'Process now', 'orbitstem-seo' ), false ); } if ( $this->importer->has_imported() ) { $this->button( 'rollback', __( 'Roll back imported data', 'orbitstem-seo' ), $busy ); } ?>

importer->source( $source_id ); $ids = array_slice( $source->post_ids_after( 0, 10 ), 0, 10 ); $rows = array(); $posts = array(); foreach ( $ids as $id ) { $post = get_post( $id ); if ( $post && 'publish' === $post->post_status ) { $posts[ $id ] = $post; } } // All pages are fetched at the same time, so the screen opens in about the time of the slowest page. $pages = $this->live_tags_many( array_map( function ( $post ) { return (string) get_permalink( $post ); }, $posts ) ); foreach ( $posts as $id => $post ) { $live = $pages[ $id ]; $imported = $source->read_post( $id ); // Orbitstem's own output with the imported values, exactly as the resolver will build it. $busy = false; $inject = function ( $value, $object_id, $meta_key, $single ) use ( $id, $imported, &$busy ) { $field = 0 === strpos( (string) $meta_key, \Orbitstem\SEO\Content\MetaRepository::PREFIX ) ? substr( $meta_key, strlen( \Orbitstem\SEO\Content\MetaRepository::PREFIX ) ) : ''; if ( $busy || (int) $object_id !== (int) $id || '' === $field || ! isset( $imported[ $field ] ) || '' === $imported[ $field ] || false === $imported[ $field ] ) { return $value; } $busy = true; // Read the stored value without this filter answering itself. $own = get_post_meta( $id, $meta_key, true ); $busy = false; if ( '' !== $own && false !== $own ) { return $value; // Orbitstem fields already set are kept by the import. } return $single ? $imported[ $field ] : array( $imported[ $field ] ); }; add_filter( 'get_post_metadata', $inject, 10, 4 ); $ours = $this->plugin->resolver->for_post( $post ); remove_filter( 'get_post_metadata', $inject, 10 ); $rows[] = array( 'title' => get_the_title( $post ), 'live' => $live, 'ours' => $ours ? $ours : array(), ); } ?>

name() ) ); ?>

' . esc_html__( '(the page did not answer in time; open it to check)', 'orbitstem-seo' ) . ''; } else { echo esc_html( '' !== $now['title'] ? $now['title'] : __( '(no title)', 'orbitstem-seo' ) ); } ?>
' . Ui::badge( 'warning', __( 'noindex', 'orbitstem-seo' ) ) : ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped in Ui::badge(). ?>

' . Ui::badge( 'warning', __( 'noindex', 'orbitstem-seo' ) ) : ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped in Ui::badge(). ?>
$urls Page URLs keyed by post ID. * @return array Same keys. */ private function live_tags_many( array $urls ) { // In parallel, the screen waits about as long as the slowest page, so each may take up to 20 seconds. $args = \Orbitstem\SEO\Diagnostics\UrlChecker::request_args( 20 ); $out = array(); if ( $urls && class_exists( '\WpOrg\Requests\Requests' ) ) { $requests = array(); foreach ( $urls as $id => $url ) { $requests[ $id ] = array( 'url' => $url, 'headers' => $args['headers'], 'type' => 'GET', ); } try { $responses = \WpOrg\Requests\Requests::request_multiple( $requests, array( 'timeout' => $args['timeout'], 'connect_timeout' => $args['timeout'], 'follow_redirects' => false, 'verify' => (bool) $args['sslverify'], ) ); } catch ( \Exception $e ) { $responses = array(); } foreach ( $urls as $id => $url ) { $response = isset( $responses[ $id ] ) ? $responses[ $id ] : null; $out[ $id ] = $this->parse_tags( $response instanceof \WpOrg\Requests\Response && $response->success ? (string) $response->body : '' ); } return $out; } foreach ( $urls as $id => $url ) { $response = wp_remote_get( $url, $args ); $out[ $id ] = $this->parse_tags( is_wp_error( $response ) ? '' : (string) wp_remote_retrieve_body( $response ) ); } return $out; } /** * Title, description and noindex from a page's HTML. * * @param string $html Page HTML ('' when it could not be fetched). * @return array{title:string,description:string,noindex:bool,failed:bool} */ private function parse_tags( $html ) { $head = false !== stripos( $html, '' ) ? substr( $html, 0, stripos( $html, '' ) ) : $html; $decode = function ( $text ) { return trim( html_entity_decode( wp_strip_all_tags( (string) $text ), ENT_QUOTES | ENT_HTML5, 'UTF-8' ) ); }; return array( 'title' => preg_match( '#]*>(.*?)#is', $head, $m ) ? $decode( $m[1] ) : '', 'description' => preg_match( '#]*\bname=["\']description["\'][^>]*\bcontent=["\']([^"\']*)["\']#i', $head, $d ) ? $decode( $d[1] ) : '', 'noindex' => (bool) preg_match( '#]*\bname=["\']robots["\'][^>]*\bcontent=["\'][^"\']*noindex#i', $head ), 'failed' => '' === $html, ); } /** * Status of the import and rollback jobs. */ private function status_table() { $rows = array( Importer::JOB_IMPORT => __( 'Import', 'orbitstem-seo' ), Importer::JOB_ROLLBACK => __( 'Rollback', 'orbitstem-seo' ), ); echo ''; foreach ( $rows as $job => $label ) { $state = $this->plugin->jobs->status( $job ); if ( ! $state ) { continue; } $cursor = is_array( $state['cursor'] ) ? $state['cursor'] : array(); $details = array(); foreach ( array( 'items', 'fields', 'skipped', 'removed', 'kept' ) as $key ) { if ( isset( $cursor[ $key ] ) ) { $details[] = $key . ': ' . (int) $cursor[ $key ]; } } if ( '' !== $state['last_error'] ) { $details[] = __( 'last error:', 'orbitstem-seo' ) . ' ' . $state['last_error']; } printf( '', esc_html( $label ), esc_html( $state['status'] ), esc_html( implode( ', ', $details ) ), esc_html( wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), (int) $state['updated'] ) ) ); } echo '
%s%s%s%s
'; } /** * A POST button for an action. * * @param string $task Task: start, process or rollback. * @param string $label Button label. * @param bool $disabled Whether disabled. * @param string $source Source id. */ private function button( $task, $label, $disabled, $source = '' ) { ?>