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

359 lines
14 KiB
PHP

<?php
namespace AIOSEO\Plugin\Pro\Models;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use AIOSEO\Plugin\Common\Models as CommonModels;
/**
* The Term DB Model.
*
* @since 4.0.0
*/
class Term extends CommonModels\Model {
/**
* The name of the table in the database, without the prefix.
*
* @since 4.0.0
*
* @var string
*/
protected $table = 'aioseo_terms';
/**
* Fields that should be json encoded on save and decoded on get.
*
* @since 4.0.0
*
* @var array
*/
protected $jsonFields = [
'keywords',
'og_article_tags',
'videos',
'breadcrumb_settings'
];
/**
* Fields that should be boolean values.
*
* @since 4.0.0
*
* @var array
*/
protected $booleanFields = [
'twitter_use_og',
'robots_default',
'robots_noindex',
'robots_noarchive',
'robots_nosnippet',
'robots_nofollow',
'robots_noimageindex',
'robots_noodp',
'robots_notranslate'
];
/**
* Fields that can be null when saved.
*
* @since 4.5.7
*
* @var array
*/
protected $nullFields = [
'priority'
];
/**
* Fields that should be float values.
*
* @since 4.7.3
*
* @var array
*/
protected $floatFields = [
'priority'
];
/**
* Fields that should be hidden when serialized.
*
* @since 4.0.0
*
* @var array
*/
protected $hidden = [ 'id' ];
/**
* Returns a Term with a given ID.
*
* @since 4.0.0
*
* @param int $termId The term ID.
* @return Term The Term object.
*/
public static function getTerm( $termId ) {
$term = aioseo()->core->db
->start( 'aioseo_terms' )
->where( 'term_id', $termId )
->run()
->model( 'AIOSEO\\Plugin\\Pro\\Models\\Term' );
if ( ! $term->exists() ) {
$term->term_id = $termId;
}
return apply_filters( 'aioseo_get_term', $term );
}
/**
* Saves the Term object.
*
* @since 4.0.3
*
* @param int $termId The term ID.
* @param string $data The term data to save.
* @return bool|void|string Whether the term data was saved or a DB error message.
*/
public static function saveTerm( $termId, $data ) {
if ( empty( $data ) ) {
return false;
}
$theTerm = self::getTerm( $termId );
$data = apply_filters( 'aioseo_save_term', $data, $theTerm );
// Before setting the data, we check if the title/description are the same as the defaults and clear them if so.
$data = self::checkForDefaultFormat( $termId, $theTerm, $data );
$theTerm = self::sanitizeAndSetDefaults( $termId, $theTerm, $data );
// Update traditional term meta so that it can be used by multilingual plugins.
self::updateTermMeta( $termId, $data );
$theTerm->save();
$theTerm->reset();
$lastError = aioseo()->core->db->lastError();
if ( ! empty( $lastError ) ) {
return $lastError;
}
// Fires once an AIOSEO term has been saved.
do_action( 'aioseo_insert_term', $termId );
return true;
}
/**
* Checks if the title/description is the same as their default format in Search Appearance and nulls it if this is the case.
* Doing this ensures that updates to the default title/description format also propogate to the term.
*
* @since 4.1.5
*
* @param int $termId The term ID.
* @param Term $theTerm The Term object.
* @param array $data The data.
* @return array The data.
*/
private static function checkForDefaultFormat( $termId, $theTerm, $data ) {
$data['title'] = trim( $data['title'] );
$data['description'] = trim( $data['description'] );
$term = aioseo()->helpers->getTerm( $termId );
$defaultTitleFormat = trim( aioseo()->meta->title->getTaxonomyTitle( $term->taxonomy ) );
$defaultDescriptionFormat = trim( aioseo()->meta->description->getTaxonomyDescription( $term->taxonomy ) );
if ( ! empty( $data['title'] ) && $data['title'] === $defaultTitleFormat ) {
$data['title'] = null;
}
if ( ! empty( $data['description'] ) && $data['description'] === $defaultDescriptionFormat ) {
$data['description'] = null;
}
return $data;
}
/**
* Sanitizes the term data and sets it (or the default value) to the Term object.
*
* @since 4.1.5
*
* @param int $termId The term ID.
* @param Term $theTerm The Term object.
* @param array $data The data.
* @return Term The Term object with data set.
*/
private static function sanitizeAndSetDefaults( $termId, $theTerm, $data ) {
// General
$theTerm->term_id = $termId;
$theTerm->title = ! empty( $data['title'] ) ? sanitize_text_field( $data['title'] ) : null;
$theTerm->description = ! empty( $data['description'] ) ? sanitize_text_field( $data['description'] ) : null;
$theTerm->canonical_url = ! empty( $data['canonicalUrl'] ) ? esc_url_raw( $data['canonicalUrl'] ) : null;
$theTerm->keywords = ! empty( $data['keywords'] ) ? aioseo()->helpers->sanitize( $data['keywords'] ) : null;
// Sitemap
$theTerm->priority = isset( $data['priority'] ) ? ( 'default' === sanitize_text_field( $data['priority'] ) ? null : (float) $data['priority'] ) : null;
$theTerm->frequency = ! empty( $data['frequency'] ) ? sanitize_text_field( $data['frequency'] ) : null;
// Robots Meta
$theTerm->robots_default = isset( $data['default'] ) ? rest_sanitize_boolean( $data['default'] ) : 1;
$theTerm->robots_noindex = isset( $data['noindex'] ) ? rest_sanitize_boolean( $data['noindex'] ) : 0;
$theTerm->robots_nofollow = isset( $data['nofollow'] ) ? rest_sanitize_boolean( $data['nofollow'] ) : 0;
$theTerm->robots_noarchive = isset( $data['noarchive'] ) ? rest_sanitize_boolean( $data['noarchive'] ) : 0;
$theTerm->robots_notranslate = isset( $data['notranslate'] ) ? rest_sanitize_boolean( $data['notranslate'] ) : 0;
$theTerm->robots_noimageindex = isset( $data['noimageindex'] ) ? rest_sanitize_boolean( $data['noimageindex'] ) : 0;
$theTerm->robots_nosnippet = isset( $data['nosnippet'] ) ? rest_sanitize_boolean( $data['nosnippet'] ) : 0;
$theTerm->robots_noodp = isset( $data['noodp'] ) ? rest_sanitize_boolean( $data['noodp'] ) : 0;
$theTerm->robots_max_snippet = isset( $data['maxSnippet'] ) && is_numeric( $data['maxSnippet'] ) ? (int) sanitize_text_field( $data['maxSnippet'] ) : -1;
$theTerm->robots_max_videopreview = isset( $data['maxVideoPreview'] ) && is_numeric( $data['maxVideoPreview'] ) ? (int) sanitize_text_field( $data['maxVideoPreview'] ) : -1;
$theTerm->robots_max_imagepreview = ! empty( $data['maxImagePreview'] ) ? sanitize_text_field( $data['maxImagePreview'] ) : 'large';
// Open Graph Meta
$theTerm->og_title = ! empty( $data['og_title'] ) ? sanitize_text_field( $data['og_title'] ) : null;
$theTerm->og_description = ! empty( $data['og_description'] ) ? sanitize_text_field( $data['og_description'] ) : null;
$theTerm->og_object_type = ! empty( $data['og_object_type'] ) ? sanitize_text_field( $data['og_object_type'] ) : 'default';
$theTerm->og_image_custom_url = ! empty( $data['og_image_custom_url'] ) ? esc_url_raw( $data['og_image_custom_url'] ) : null;
$theTerm->og_image_custom_fields = ! empty( $data['og_image_custom_fields'] ) ? sanitize_text_field( $data['og_image_custom_fields'] ) : null;
$theTerm->og_image_type = ! empty( $data['og_image_type'] ) ? sanitize_text_field( $data['og_image_type'] ) : 'default';
$theTerm->og_video = ! empty( $data['og_video'] ) ? sanitize_text_field( $data['og_video'] ) : '';
$theTerm->og_article_section = ! empty( $data['og_article_section'] ) ? sanitize_text_field( $data['og_article_section'] ) : null;
$theTerm->og_article_tags = ! empty( $data['og_article_tags'] ) ? aioseo()->helpers->sanitize( $data['og_article_tags'] ) : null;
// Twitter Meta
$theTerm->twitter_title = ! empty( $data['twitter_title'] ) ? sanitize_text_field( $data['twitter_title'] ) : null;
$theTerm->twitter_description = ! empty( $data['twitter_description'] ) ? sanitize_text_field( $data['twitter_description'] ) : null;
$theTerm->twitter_card = ! empty( $data['twitter_card'] ) ? sanitize_text_field( $data['twitter_card'] ) : 'default';
$theTerm->twitter_use_og = isset( $data['twitter_use_og'] ) ? rest_sanitize_boolean( $data['twitter_use_og'] ) : 0;
$theTerm->twitter_image = ! empty( $data['twitter_image'] ) ? sanitize_text_field( $data['twitter_image'] ) : null;
$theTerm->twitter_image_custom_url = ! empty( $data['twitter_image_custom_url'] ) ? esc_url_raw( $data['twitter_image_custom_url'] ) : null;
$theTerm->twitter_image_custom_fields = ! empty( $data['twitter_image_custom_fields'] ) ? sanitize_text_field( $data['twitter_image_custom_fields'] ) : null;
$theTerm->twitter_image_type = ! empty( $data['twitter_image_type'] ) ? sanitize_text_field( $data['twitter_image_type'] ) : 'default';
// Miscellaneous
$theTerm->updated = gmdate( 'Y-m-d H:i:s' ); // phpcs:ignore Generic.Formatting.MultipleStatementAlignment.IncorrectWarning
$theTerm->breadcrumb_settings = isset( $data['breadcrumb_settings']['default'] ) && false === $data['breadcrumb_settings']['default'] ? $data['breadcrumb_settings'] : null;
// Before we determine the OG/Twitter image, we need to set the meta data cache manually because the changes haven't been saved yet.
aioseo()->meta->metaData->bustTermCache( $theTerm->term_id, $theTerm );
// Set the OG/Twitter image data.
$theTerm = self::setOgTwitterImageData( $theTerm );
if ( ! $theTerm->exists() ) {
$theTerm->created = gmdate( 'Y-m-d H:i:s' );
}
return $theTerm;
}
/**
* Set the OG/Twitter image data on the term object.
*
* @since 4.1.6
*
* @param Term $theTerm The Term object to modify.
* @return Term The modified Term object.
*/
public static function setOgTwitterImageData( $theTerm ) {
// Set the OG image.
if (
in_array( $theTerm->og_image_type, [
'custom',
'custom_image'
], true )
) {
// Disable the cache.
aioseo()->social->image->useCache = false;
// Set the image details.
$ogImage = aioseo()->social->facebook->getImage();
$theTerm->og_image_url = is_array( $ogImage ) ? $ogImage[0] : $ogImage;
$theTerm->og_image_width = aioseo()->social->facebook->getImageWidth();
$theTerm->og_image_height = aioseo()->social->facebook->getImageHeight();
// Reset the cache property.
aioseo()->social->image->useCache = true;
}
// Set the Twitter image.
if (
! $theTerm->twitter_use_og &&
in_array( $theTerm->twitter_image_type, [
'custom',
'custom_image'
], true )
) {
// Disable the cache.
aioseo()->social->image->useCache = false;
// Set the image details.
$ogImage = aioseo()->social->twitter->getImage();
$theTerm->twitter_image_url = is_array( $ogImage ) ? $ogImage[0] : $ogImage;
// Reset the cache property.
aioseo()->social->image->useCache = true;
}
return $theTerm;
}
/**
* Saves some of the data as term meta so that it can be used for localization.
*
* @since 4.1.5
*
* @param int $termId The term ID.
* @param array $data The data.
* @return void
*/
public static function updateTermMeta( $termId, $data ) {
$keywords = ! empty( $data['keywords'] ) ? aioseo()->helpers->jsonTagsToCommaSeparatedList( $data['keywords'] ) : [];
$ogArticleTags = ! empty( $data['og_article_tags'] ) ? aioseo()->helpers->jsonTagsToCommaSeparatedList( $data['og_article_tags'] ) : [];
update_term_meta( $termId, '_aioseo_title', $data['title'] );
update_term_meta( $termId, '_aioseo_description', $data['description'] );
update_term_meta( $termId, '_aioseo_keywords', $keywords );
update_term_meta( $termId, '_aioseo_og_title', $data['og_title'] );
update_term_meta( $termId, '_aioseo_og_description', $data['og_description'] );
update_term_meta( $termId, '_aioseo_og_article_section', $data['og_article_section'] );
update_term_meta( $termId, '_aioseo_og_article_tags', $ogArticleTags );
update_term_meta( $termId, '_aioseo_twitter_title', $data['twitter_title'] );
update_term_meta( $termId, '_aioseo_twitter_description', $data['twitter_description'] );
}
/**
* Returns the default breadcrumb settings options.
*
* @since 4.8.3
*
* @param string $taxonomy The taxonomy name.
* @param array $existingOptions The existing options.
* @return object The default options.
*/
public static function getDefaultBreadcrumbSettingsOptions( $taxonomy, $existingOptions = [] ) {
$default = aioseo()->dynamicOptions->breadcrumbs->taxonomies->$taxonomy->useDefaultTemplate;
$showHomeCrumb = $default ? aioseo()->options->breadcrumbs->homepageLink : aioseo()->dynamicOptions->breadcrumbs->taxonomies->$taxonomy->showHomeCrumb;
$defaults = [
'default' => true,
'separator' => aioseo()->options->breadcrumbs->separator,
'breadcrumbPrefix' => aioseo()->options->breadcrumbs->breadcrumbPrefix ?? false,
'showHomeCrumb' => $showHomeCrumb,
'showTaxonomyCrumbs' => aioseo()->dynamicOptions->breadcrumbs->taxonomies->$taxonomy->showTaxonomyCrumbs ?? false,
'showParentCrumbs' => aioseo()->dynamicOptions->breadcrumbs->taxonomies->$taxonomy->showParentCrumbs ?? false,
'template' => aioseo()->helpers->encodeOutputHtml( aioseo()->breadcrumbs->frontend->getDefaultTemplate( 'taxonomy' ) ),
'parentTemplate' => aioseo()->helpers->encodeOutputHtml( aioseo()->breadcrumbs->frontend->getDefaultTemplate( 'taxonomy' ) )
];
if ( empty( $existingOptions ) ) {
return json_decode( wp_json_encode( $defaults ) );
}
$existingOptions = json_decode( wp_json_encode( $existingOptions ), true );
if ( ! is_array( $existingOptions ) ) {
return json_decode( wp_json_encode( $defaults ) );
}
$existingOptions = array_replace_recursive( $defaults, $existingOptions );
return json_decode( wp_json_encode( $existingOptions ) );
}
}