Files
2026-04-28 15:13:50 +02:00

404 lines
11 KiB
PHP

<?php
namespace AIOSEO\Plugin\Pro\Migration;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// phpcs:disable WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound
use AIOSEO\Plugin\Common\Migration as CommonMigration;
use AIOSEO\Plugin\Pro\Models;
use AIOSEO\Plugin\Addon\VideoSitemap;
/**
* Migrates the term meta from V3.
*
* @since 4.0.0
*/
class Meta extends CommonMigration\Meta {
/**
* Instance of the Video class of the Video Sitemap.
*
* @since 4.0.2
*
* @var VideoSitemap\VideoSitemap\Video
*/
private $videoSitemap = null;
/**
* Instantiates the Video Sitemap video class if the addon is installed and active.
*
* @since 4.0.2
*
* @return void
*/
public function instantiateVideoSitemap() {
if ( null !== $this->videoSitemap ) {
return;
}
$videoSitemap = aioseo()->addons->getAddon( 'aioseo-video-sitemap' );
if (
! empty( $videoSitemap ) &&
is_plugin_active( $videoSitemap->basename ) &&
aioseo()->license->isAddonAllowed( 'aioseo-video-sitemap' ) &&
class_exists( 'AIOSEO\\Plugin\\Addon\\VideoSitemap\\VideoSitemap' ) &&
function_exists( 'aioseoVideoSitemap' )
) {
// The video sitemap has already been instantiated, let's grab it.
$this->videoSitemap = property_exists( aioseoVideoSitemap(), 'video' ) ? aioseoVideoSitemap()->video : new VideoSitemap\VideoSitemap\Video();
}
}
/**
* Migrates additional post meta data.
*
* @since 4.0.2
*
* @param int $postId The post ID.
* @return void
*/
public function migrateAdditionalPostMeta( $postId ) {
parent::migrateAdditionalPostMeta( $postId );
$this->instantiateVideoSitemap();
if ( $this->videoSitemap ) {
$post = get_post( $postId );
$this->videoSitemap->scanPost( $post );
}
}
/**
* Migrates the plugin meta data.
*
* @since 4.0.0
*
* @return void
*/
public function migrateMeta() {
parent::migrateMeta();
try {
if ( as_next_scheduled_action( 'aioseo_migrate_term_meta' ) ) {
return;
}
as_schedule_single_action( time() + 30, 'aioseo_migrate_term_meta', [], 'aioseo' );
} catch ( \Exception $e ) {
// Do nothing.
}
}
/**
* Migrates the term meta data from V3.
*
* @since 4.0.0
*
* @return void
*/
public function migrateTermMeta() {
if ( aioseo()->core->cache->get( 'v3_migration_in_progress_settings' ) ) {
aioseo()->actionScheduler->scheduleSingle( 'aioseo_migrate_term_meta', 30, [], true );
return;
}
$termsPerAction = 50;
$publicTaxonomies = implode( "', '", aioseo()->helpers->getPublicTaxonomies( true ) );
$timeStarted = gmdate( 'Y-m-d H:i:s', aioseo()->core->cache->get( 'v3_migration_in_progress_terms' ) );
$termsToMigrate = aioseo()->core->db
->start( 'terms' . ' as t' )
->select( 't.term_id' )
->leftJoin( 'aioseo_terms as at', '`t`.`term_id` = `at`.`term_id`' )
->leftJoin( 'term_taxonomy as tt', '`t`.`term_id` = `tt`.`term_id`' )
->whereRaw( "( at.term_id IS NULL OR at.updated < '$timeStarted' )" )
->whereRaw( "( tt.taxonomy IN ( '$publicTaxonomies' ) )" )
->orderBy( 't.term_id DESC' )
->limit( $termsPerAction )
->run()
->result();
if ( ! $termsToMigrate || ! count( $termsToMigrate ) ) {
aioseo()->core->cache->delete( 'v3_migration_in_progress_terms' );
return;
}
foreach ( $termsToMigrate as $term ) {
$newTermMeta = $this->getMigratedTermMeta( $term->term_id );
$term = Models\Term::getTerm( $term->term_id );
$term->set( $newTermMeta );
$term->save();
$this->updateLocalizedTermMeta( $term->term_id, $newTermMeta );
if ( $this->videoSitemap ) {
$term = get_term( $term->term_id );
$this->videoSitemap->scanTerm( $term );
}
}
if ( count( $termsToMigrate ) === $termsPerAction ) {
try {
as_schedule_single_action( time() + 30, 'aioseo_migrate_term_meta', [], 'aioseo' );
} catch ( \Exception $e ) {
// Do nothing.
}
} else {
aioseo()->core->cache->delete( 'v3_migration_in_progress_terms' );
}
}
/**
* Returns the migrated term meta for a given term.
*
* @since 4.0.3
*
* @param int $termId The term ID.
* @return array $meta The term meta.
*/
public function getMigratedTermMeta( $termId ) {
if ( ! is_numeric( $termId ) ) {
return [];
}
if ( null === self::$oldOptions ) {
self::$oldOptions = get_option( 'aioseop_options' );
}
if ( empty( self::$oldOptions ) ) {
return [];
}
$termMeta = aioseo()->core->db
->start( 'termmeta' . ' as tm' )
->select( '`tm`.`meta_key`, `tm`.`meta_value`' )
->where( 'tm.term_id', $termId )
->whereLike( 'tm.meta_key', '_aioseop_%', true )
->run()
->result();
$mappedMeta = [
'_aioseop_title' => 'title',
'_aioseop_description' => 'description',
'_aioseop_custom_link' => 'canonical_url',
'_aioseop_sitemap_exclude' => '',
'_aioseop_disable' => '',
'_aioseop_noindex' => 'robots_noindex',
'_aioseop_nofollow' => 'robots_nofollow',
'_aioseop_sitemap_priority' => 'priority',
'_aioseop_sitemap_frequency' => 'frequency',
'_aioseop_keywords' => 'keywords',
'_aioseop_opengraph_settings' => ''
];
$meta = [
'term_id' => $termId,
];
if ( ! $termMeta || ! count( $termMeta ) ) {
return $meta;
}
foreach ( $termMeta as $record ) {
$name = $record->meta_key;
$value = $record->meta_value;
if ( ! in_array( $name, array_keys( $mappedMeta ), true ) ) {
continue;
}
switch ( $name ) {
case '_aioseop_description':
$meta[ $mappedMeta[ $name ] ] = aioseo()->helpers->sanitizeOption( aioseo()->migration->helpers->macrosToSmartTags( $value ) );
break;
case '_aioseop_title':
if ( ! empty( $value ) ) {
$meta[ $mappedMeta[ $name ] ] = $this->getTermTitle( $termId, $value );
}
break;
case '_aioseop_sitemap_exclude':
if ( empty( $value ) ) {
break;
}
$this->migrateSitemapExcludedTerm( $termId );
break;
case '_aioseop_disable':
if ( empty( $value ) ) {
break;
}
$this->migrateExcludedTerm( $termId );
break;
case '_aioseop_noindex':
case '_aioseop_nofollow':
if ( 'on' === (string) $value ) {
$meta['robots_default'] = false;
$meta[ $mappedMeta[ $name ] ] = true;
} elseif ( 'off' === (string) $value ) {
$meta['robots_default'] = false;
}
break;
case '_aioseop_keywords':
$meta[ $mappedMeta[ $name ] ] = aioseo()->migration->helpers->oldKeywordsToNewKeywords( $value );
break;
case '_aioseop_opengraph_settings':
$meta += $this->convertOpenGraphMeta( $value );
break;
case '_aioseop_sitemap_priority':
case '_aioseop_sitemap_frequency':
if ( empty( $value ) ) {
$meta[ $mappedMeta[ $name ] ] = 'default';
break;
}
$meta[ $mappedMeta[ $name ] ] = $value;
break;
default:
$meta[ $mappedMeta[ $name ] ] = esc_html( wp_strip_all_tags( strval( $value ) ) );
break;
}
}
return $meta;
}
/**
* Migrates a given sitemap excluded term from V3.
*
* @since 4.0.3
*
* @param int $termId The term ID.
* @return void
*/
private function migrateSitemapExcludedTerm( $termId ) {
$term = get_term( $termId );
if ( ! is_object( $term ) ) {
return;
}
aioseo()->options->sitemap->general->advancedSettings->enable = true;
$excludedTerms = aioseo()->options->sitemap->general->advancedSettings->excludeTerms;
foreach ( $excludedTerms as $excludedTerm ) {
$excludedTerm = json_decode( $excludedTerm );
if ( $excludedTerm->value === $termId ) {
return;
}
}
$excludedTerm = [
'value' => $term->term_id,
'type' => $term->taxonomy,
'label' => $term->name,
'link' => get_term_link( $term, $term->taxonomy )
];
$excludedTerms[] = wp_json_encode( $excludedTerm );
aioseo()->options->sitemap->general->advancedSettings->excludeTerms = $excludedTerms;
}
/**
* Migrates a given disabled term from V3.
*
* @since 4.0.3
*
* @param int $termId The term ID.
* @return void
*/
private function migrateExcludedTerm( $termId ) {
$term = get_term( $termId );
if ( ! is_object( $term ) ) {
return;
}
$excludedTerms = aioseo()->options->deprecated->searchAppearance->advanced->excludeTerms;
foreach ( $excludedTerms as $excludedTerm ) {
$excludedTerm = json_decode( $excludedTerm );
if ( $excludedTerm->value === $termId ) {
return;
}
}
$excludedTerm = [
'value' => $term->term_id,
'type' => $term->taxonomy,
'label' => $term->name,
'link' => get_term_link( $term, $term->taxonomy )
];
$excludedTerms[] = wp_json_encode( $excludedTerm );
aioseo()->options->deprecated->searchAppearance->advanced->excludeTerms = $excludedTerms;
$deprecatedOptions = aioseo()->internalOptions->internal->deprecatedOptions;
if ( ! in_array( 'excludeTerms', $deprecatedOptions, true ) ) {
array_push( $deprecatedOptions, 'excludeTerms' );
aioseo()->internalOptions->internal->deprecatedOptions = $deprecatedOptions;
}
}
/**
* Updates the traditional term meta table with the new data.
*
* @since 4.1.0
*
* @param int $termId The term ID.
* @param array $newMeta The new meta data.
* @return void
*/
protected function updateLocalizedTermMeta( $termId, $newMeta ) {
$localizedFields = [
'title',
'description',
'keywords',
'og_title',
'og_description',
'og_article_section',
'og_article_tags',
'twitter_title',
'twitter_description'
];
foreach ( $newMeta as $k => $v ) {
if ( ! in_array( $k, $localizedFields, true ) ) {
continue;
}
if ( in_array( $k, [ 'keywords', 'og_article_tags' ], true ) ) {
$v = ! empty( $v ) ? aioseo()->helpers->jsonTagsToCommaSeparatedList( $v ) : '';
}
update_term_meta( $termId, "_aioseo_{$k}", $v );
}
}
/**
* Returns the title as it was in V3.
*
* @since 4.0.0
*
* @param int $termId The term ID.
* @param string $seoTitle The old SEO title.
* @return string The title.
*/
protected function getTermTitle( $termId, $seoTitle = '' ) {
$term = get_term( $termId );
if ( ! isset( $term->term_id ) ) {
return '';
}
$taxonomy = 'post_tag' !== $term->taxonomy ? $term->taxonomy : 'tag';
$oldOptions = get_option( 'aioseo_options_v3' );
$titleFormat = isset( $oldOptions[ "aiosp_{$taxonomy}_title_format" ] ) ? $oldOptions[ "aiosp_{$taxonomy}_title_format" ] : '';
if ( ! $titleFormat ) {
$titleFormat = isset( $oldOptions[ "aiosp_{$taxonomy}_tax_title_format" ] ) ? $oldOptions[ "aiosp_{$taxonomy}_tax_title_format" ] : '';
}
$seoTitle = aioseo()->helpers->pregReplace( '/(%category_title%|%tag_title%|%taxonomy_title%|%tag%)/', $seoTitle, $titleFormat );
return aioseo()->migration->helpers->macrosToSmartTags( $seoTitle );
}
}