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

310 lines
7.7 KiB
PHP

<?php
namespace AIOSEO\Plugin\Pro\Models\SearchStatistics;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use AIOSEO\Plugin\Common\Models as CommonModels;
/**
* The Keyword DB Model.
*
* @since 4.7.0
*/
class Keyword extends CommonModels\Model {
/**
* Database table name with no prefix.
*
* @since 4.7.0
*
* @var string
*/
protected $table = 'aioseo_search_statistics_keywords';
/**
* Fields that should be numeric values.
*
* @since 4.7.0
*
* @var array
*/
protected $integerFields = [ 'id' ];
/**
* Fields that should be json encoded on save and decoded on get.
*
* @since 4.7.0
*
* @var array
*/
protected $jsonFields = [];
/**
* Fields that should be boolean values.
*
* @since 4.7.0
*
* @var array
*/
protected $booleanFields = [ 'favorited' ];
/**
* Fields that should be hidden when serialized.
*
* @since 4.7.0
*
* @var array
*/
protected $hidden = [ 'id' ];
/**
* Column: Name.
*
* @since 4.7.0
*
* @var string $name
*/
public $name;
/**
* Column: Favorited.
*
* @since 4.7.0
*
* @var int $favorited
*/
public $favorited;
/**
* Class constructor.
*
* @since 4.7.0
*
* @param mixed $var This can be the primary key of the resource, or it could be an array of data to manufacture a resource without a database query.
* @return void
*/
public function __construct( $var = null ) {
parent::__construct( $var );
}
/**
* Gets the keywords from the database.
*
* @since 4.7.0
*
* @param array $args The arguments.
* @return array The keywords.
*/
public static function getKeywords( $args = [] ) {
$args = array_merge( [
'ids' => [],
'names' => [],
'filter' => 'all',
'searchTerm' => '',
'additionalFilters' => [],
], array_filter( $args ) );
$names = array_map( [ aioseo()->helpers, 'toLowerCase' ], array_unique( $args['names'] ) );
$filter = $args['filter'];
$searchTerm = sanitize_text_field( $args['searchTerm'] );
$chosenGroup = $args['additionalFilters']['group'] ?? 'all';
$query = aioseo()->core->db->start( 'aioseo_search_statistics_keywords as k' )
->select( 'k.id, k.name, k.favorited, k.created, GROUP_CONCAT(g.id) as groupIds, GROUP_CONCAT(g.name) as groupNames' )
->join( 'aioseo_search_statistics_keyword_relationships as r', 'k.id = r.keyword_id', 'LEFT' )
->join( 'aioseo_search_statistics_keyword_groups as g', 'r.keyword_group_id = g.id', 'LEFT' )
->groupBy( 'k.id' )
->limit( 100 );
if ( ! empty( $args['ids'] ) ) {
$query->whereIn( 'k.id', $args['ids'] );
}
if ( ! empty( $names ) ) {
$query->whereIn( 'k.name', $names );
}
if ( 'favorited' === $filter ) {
$query->where( 'k.favorited', 1 );
}
if ( $searchTerm ) {
$query->whereLike( 'k.name', '%' . $searchTerm . '%', true );
}
if ( 'all' !== $chosenGroup ) {
$query->where( 'g.id', $chosenGroup );
}
$rows = $query->run()->result();
foreach ( $rows as $row ) {
$row->groups = [];
if ( ! empty( $row->groupIds ) && ! empty( $row->groupNames ) ) {
// The groupIds and groupNames are comma separated strings, because of the GROUP_CONCAT function above.
$groupIds = explode( ',', $row->groupIds );
$groupNames = explode( ',', $row->groupNames );
foreach ( $groupIds as $key => $groupId ) {
$data = (object) [
'id' => $groupId,
'name' => $groupNames[ $key ],
'value' => $groupId, // Make it easier to use Vue core components in the UI.
'label' => $groupNames[ $key ], // Make it easier to use Vue core components in the UI.
];
KeywordGroup::parseGroup( $data );
$row->groups[] = json_decode( wp_json_encode( $data ), true );
}
}
unset( $row->groupIds, $row->groupNames );
}
return [
'rows' => array_values( $rows ),
];
}
/**
* Inserts multiple keywords into the database.
*
* @since 4.7.0
*
* @param array[] $keywords The keywords to insert.
* @throws \Exception
* @return array|false The newly inserted keywords. False if no keywords were inserted.
*/
public static function bulkInsert( $keywords ) {
$limit = aioseo()->searchStatistics->keywordRankTracker->getLicenseKeywordsLimit();
$currentCount = aioseo()->core->db->start( 'aioseo_search_statistics_keywords' )->count() + count( $keywords );
if ( $currentCount > $limit ) {
throw new \Exception(
// Translators: 1 - The limit of keywords allowed.
sprintf( esc_html__( 'You have reached the maximum number of %s keywords allowed.', 'aioseo-pro' ), esc_html( $limit ) )
);
}
$columns = [ 'name', 'favorited', 'created', 'updated' ];
$currentDate = gmdate( 'Y-m-d H:i:s' );
$rows = [];
foreach ( $keywords as $keyword ) {
$name = aioseo()->helpers->toLowerCase( sanitize_text_field( $keyword['name'] ) );
if ( ! empty( $name ) ) {
$rows[] = [
$name,
! empty( $keyword['favorited'] ) ? 1 : 0,
$currentDate,
$currentDate
];
}
}
if ( empty( $rows ) ) {
return false;
}
aioseo()->core->db->bulkInsert( 'aioseo_search_statistics_keywords', $columns, $rows, [
'onDuplicate' => [ 'updated' ]
] );
$tableName = aioseo()->core->db->prefix . 'aioseo_search_statistics_keywords';
return aioseo()->core->db->start( $tableName, true )
->select( 'id' )
->where( 'created', $currentDate )
->run()
->result();
}
/**
* Bulk update keywords.
*
* @since 4.7.1
*
* @param array $ids The keyword IDs.
* @param array $set The values to update.
* @return void
*/
public static function bulkUpdate( $ids, $set ) {
aioseo()->core->db
->update( 'aioseo_search_statistics_keywords' )
->whereIn( 'id', $ids )
->set( $set )
->run();
}
/**
* Bulk delete keywords.
*
* @since 4.7.0
*
* @param array $ids The keyword IDs.
* @return int The number of rows affected.
*/
public static function bulkDelete( $ids ) {
// Delete the keywords.
aioseo()->core->db
->delete( 'aioseo_search_statistics_keywords' )
->whereIn( 'id', $ids )
->run();
$rowsAffected = max( absint( aioseo()->core->db->rowsAffected() ), 0 );
// Delete the keyword relationships.
KeywordRelationship::bulkDeleteByKeyword( $ids );
return $rowsAffected;
}
/**
* Updates the "favorited" field.
* Abstract the process of favoriting to allow for the keyword to be added to our reserved group.
*
* @since 4.7.0
*
* @param boolean $value The value to update the "favorited" field to.
* @return void
*/
public function updateFavorited( $value ) {
$this->favorited = $value;
$this->save();
$favoriteGroup = KeywordGroup::getFavoriteGroup();
$groupRow = current( KeywordGroup::getByNames( [ $favoriteGroup['slug'] ] ) );
if ( empty( $groupRow ) ) {
$groupRow = new KeywordGroup();
$groupRow->name = $favoriteGroup['slug'];
$groupRow->save();
}
if ( $value ) {
KeywordRelationship::bulkInsert( [ $this->id ], [ $groupRow->id ] );
} else {
KeywordRelationship::deleteByRelationship( $this->id, $groupRow->id );
}
}
/**
* Parses a keyword row/object.
*
* @since 4.7.0
*
* @param object $row The row to format.
* @return void
*/
public static function parseKeyword( $row ) {
$row->id = intval( $row->id );
$row->favorited = (bool) $row->favorited;
$row->groups = array_values( $row->groups ?? [] );
$row->statistics = null;
$row->value = $row->id; // Make it easier to use Vue core components in the UI.
$row->label = $row->name; // Make it easier to use Vue core components in the UI.
$row->tracking = true; // Used to determine if the keyword is being tracked under the "Edit Post" page.
}
}