Initial commit: Atomaste website
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Models;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use AIOSEO\Plugin\Common\Models as CommonModels;
|
||||
/**
|
||||
* The Crawl Cleanup Blocked Arg DB Model.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*/
|
||||
class CrawlCleanupBlockedArg extends CommonModels\Model {
|
||||
/**
|
||||
* The name of the table in the database, without the prefix.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'aioseo_crawl_cleanup_blocked_args';
|
||||
|
||||
/**
|
||||
* Fields that should be hidden when serialized.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = [ 'id' ];
|
||||
|
||||
/**
|
||||
* Fields that should be numeric values.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $integerFields = [ 'id', 'hits' ];
|
||||
|
||||
/**
|
||||
* Field to count hits.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $hits = 0;
|
||||
|
||||
/**
|
||||
* Field for Regex.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $regex = null;
|
||||
|
||||
/**
|
||||
* Field that contains the hash for key+value
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $key_value_hash = null;
|
||||
|
||||
/**
|
||||
* Separator used to merge key and value string.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $keyValueSeparator = '=';
|
||||
|
||||
/**
|
||||
* Separator used to merge key and value string.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @var CrawlCleanupBlockedArg|null
|
||||
*/
|
||||
private static $regexBlockedArgs = null;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public function __construct( $var = null ) {
|
||||
parent::__construct( $var );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Blocked row using Key and Value.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @param string $key The key to search.
|
||||
* @param string $value The value to search.
|
||||
* @return CrawlCleanupBlockedArg The CrawlCleanupBlockedArg object.
|
||||
*/
|
||||
public static function getByKeyValue( $key, $value ) {
|
||||
$keyValue = self::getKeyValueString( $key, $value );
|
||||
|
||||
return aioseo()->core->db
|
||||
->start( 'aioseo_crawl_cleanup_blocked_args' )
|
||||
->where( 'key_value_hash', sha1( $keyValue ) )
|
||||
->run()
|
||||
->model( 'AIOSEO\\Plugin\\Common\\Models\\CrawlCleanupBlockedArg' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Blocked row using Regex Value.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @param string $regex The regex value to search.
|
||||
* @return CrawlCleanupBlockedArg The CrawlCleanupBlockedArg object.
|
||||
*/
|
||||
public static function getByRegex( $regex ) {
|
||||
return aioseo()->core->db
|
||||
->start( 'aioseo_crawl_cleanup_blocked_args' )
|
||||
->where( 'regex', $regex )
|
||||
->run()
|
||||
->model( 'AIOSEO\\Plugin\\Common\\Models\\CrawlCleanupBlockedArg' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for regex match by key and value.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @param string $key The key to search.
|
||||
* @param string $value The value to search.
|
||||
* @return CrawlCleanupBlockedArg The CrawlCleanupBlockedArg object.
|
||||
*/
|
||||
public static function matchRegex( $key, $value ) {
|
||||
$keyValue = self::getKeyValueString( $key, $value );
|
||||
$regexBlockedArgs = self::getRegexBlockedArgs();
|
||||
|
||||
foreach ( $regexBlockedArgs as $regexQueryArg ) {
|
||||
$escapedRegex = str_replace( '@', '\@', $regexQueryArg->regex );
|
||||
if ( preg_match( "@{$escapedRegex}@", (string) $keyValue ) ) {
|
||||
return new CrawlCleanupBlockedArg( $regexQueryArg->id );
|
||||
}
|
||||
}
|
||||
|
||||
return new CrawlCleanupBlockedArg();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Regex rows.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @return CrawlCleanupBlockedArg The CrawlCleanupBlockedArg object.
|
||||
*/
|
||||
public static function getRegexBlockedArgs() {
|
||||
if ( null === self::$regexBlockedArgs ) {
|
||||
self::$regexBlockedArgs = aioseo()->core->db
|
||||
->start( 'aioseo_crawl_cleanup_blocked_args' )
|
||||
->select( 'id, regex' )
|
||||
->whereRaw( 'regex IS NOT NULL' )
|
||||
->run()
|
||||
->result();
|
||||
}
|
||||
|
||||
return self::$regexBlockedArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms data as needed.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @param array $data The data array to transform.
|
||||
* @return array The transformed data.
|
||||
*/
|
||||
protected function transform( $data, $set = false ) {
|
||||
$data = parent::transform( $data, $set );
|
||||
|
||||
// Create key+value hash.
|
||||
if ( ! empty( $data['key'] ) ) {
|
||||
$keyValue = self::getKeyValueString( $data['key'], $data['value'] );
|
||||
$data['key_value_hash'] = sha1( $keyValue );
|
||||
}
|
||||
|
||||
// Case hits number are empty start with 0.
|
||||
if ( empty( $data['hits'] ) ) {
|
||||
$data['hits'] = 0;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase hits and save.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
*/
|
||||
public function addHit() {
|
||||
if ( $this->id ) {
|
||||
$this->hits++;
|
||||
parent::save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return string with key and value with pattern model defined.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @param string $key The key to merge.
|
||||
* @param string $value The value to merge.
|
||||
* @return string The result string merging key and value (case not empty).
|
||||
*/
|
||||
public static function getKeyValueString( $key, $value ) {
|
||||
return $key . ( $value ? self::getKeyValueSeparator() . $value : '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return string to separate key and value.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @return string The separator for key and value.
|
||||
*/
|
||||
public static function getKeyValueSeparator() {
|
||||
return self::$keyValueSeparator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Models;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use AIOSEO\Plugin\Common\Models as CommonModels;
|
||||
|
||||
/**
|
||||
* The Crawl Cleanup Log DB Model.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*/
|
||||
class CrawlCleanupLog extends CommonModels\Model {
|
||||
/**
|
||||
* The name of the table in the database, without the prefix.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'aioseo_crawl_cleanup_logs';
|
||||
|
||||
/**
|
||||
* Fields that should be hidden when serialized.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = [ 'id' ];
|
||||
|
||||
/**
|
||||
* Fields that should be numeric values.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $integerFields = [ 'id', 'hits' ];
|
||||
|
||||
/**
|
||||
* Field to count hits.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $hits = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Create a Log in case it doesn't exist.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function create() {
|
||||
if ( null !== $this->id ) {
|
||||
$this->hits++;
|
||||
}
|
||||
|
||||
parent::save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Crawl Cleanup passing Slug
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @param string $slug The Slug to search.
|
||||
* @return CrawlCleanupLog The CrawlCleanupLog object.
|
||||
*/
|
||||
public static function getBySlug( $slug ) {
|
||||
return aioseo()->core->db
|
||||
->start( 'aioseo_crawl_cleanup_logs' )
|
||||
->where( 'hash', sha1( $slug ) )
|
||||
->run()
|
||||
->model( 'AIOSEO\\Plugin\\Common\\Models\\CrawlCleanupLog' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms data as needed.
|
||||
*
|
||||
* @since 4.5.8
|
||||
*
|
||||
* @param array $data The data array to transform.
|
||||
* @return array The transformed data.
|
||||
*/
|
||||
protected function transform( $data, $set = false ) {
|
||||
$data = parent::transform( $data, $set );
|
||||
|
||||
// Create slug hash.
|
||||
if ( ! empty( $data['slug'] ) ) {
|
||||
$data['hash'] = sha1( $data['slug'] );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,493 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Models;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base Model class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
#[\AllowDynamicProperties]
|
||||
class Model implements \JsonSerializable {
|
||||
/**
|
||||
* Fields that can be null when saving to the database.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $nullFields = [];
|
||||
|
||||
/**
|
||||
* Fields that should be encoded/decoded on save/get.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $jsonFields = [];
|
||||
|
||||
/**
|
||||
* Fields that should be boolean values.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $booleanFields = [];
|
||||
|
||||
/**
|
||||
* Fields that should be integer values.
|
||||
*
|
||||
* @since 4.1.0
|
||||
* @version 4.7.3 Renamed from numericFields to integerFields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $integerFields = [];
|
||||
|
||||
/**
|
||||
* Fields that should be float values.
|
||||
*
|
||||
* @since 4.7.3
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $floatFields = [];
|
||||
|
||||
/**
|
||||
* Fields that should be hidden when serialized.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = [];
|
||||
|
||||
/**
|
||||
* The table used in the SQL query.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = '';
|
||||
|
||||
/**
|
||||
* The primary key retrieved from the table.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pk = 'id';
|
||||
|
||||
/**
|
||||
* The ID of the model.
|
||||
* This needs to be null in order for MySQL to auto-increment correctly if the NO_AUTO_VALUE_ON_ZERO SQL mode is enabled.
|
||||
*
|
||||
* @since 4.2.7
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
public $id = null;
|
||||
|
||||
/**
|
||||
* An array of columns from the DB that we can use.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $columns = [];
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @since 4.0.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.
|
||||
*/
|
||||
public function __construct( $var = null ) {
|
||||
$skip = [ 'id', 'created', 'updated' ];
|
||||
$fields = [];
|
||||
foreach ( $this->getColumns() as $column => $value ) {
|
||||
if ( ! in_array( $column, $skip, true ) ) {
|
||||
$fields[ $column ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$this->applyKeys( $fields );
|
||||
|
||||
// Process straight through if we were given a valid array.
|
||||
if ( is_array( $var ) || is_object( $var ) ) {
|
||||
// Apply keys to object.
|
||||
$this->applyKeys( $var );
|
||||
|
||||
if ( $this->exists() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->loadData( $var );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the data from the database!
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param mixed $var Generally the primary key to load up the model from the DB.
|
||||
* @return Model|bool Returns the current object.
|
||||
*/
|
||||
protected function loadData( $var = null ) {
|
||||
// Return false if var is invalid or not supplied.
|
||||
if ( null === $var ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = aioseo()->core->db
|
||||
->start( $this->table )
|
||||
->where( $this->pk, $var )
|
||||
->limit( 1 )
|
||||
->output( 'ARRAY_A' );
|
||||
|
||||
$result = $query->run();
|
||||
if ( ! $result || $result->nullSet() ) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Apply keys to object.
|
||||
$this->applyKeys( $result->result()[0] );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the keys from the result array and add them to the Model.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param array $array The array of keys and values to add to the Model.
|
||||
* @return void
|
||||
*/
|
||||
protected function applyKeys( $array ) {
|
||||
if ( ! is_object( $array ) && ! is_array( $array ) ) {
|
||||
throw new \Exception( '$array must either be an object or an array.' );
|
||||
}
|
||||
|
||||
foreach ( (array) $array as $key => $value ) {
|
||||
$key = trim( $key );
|
||||
$this->$key = $value;
|
||||
|
||||
if ( null === $value && in_array( $key, $this->nullFields, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( in_array( $key, $this->jsonFields, true ) ) {
|
||||
if ( $value ) {
|
||||
$this->$key = is_string( $value ) ? json_decode( $value ) : $value;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( in_array( $key, $this->booleanFields, true ) ) {
|
||||
$this->$key = (bool) $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( in_array( $key, $this->integerFields, true ) ) {
|
||||
$this->$key = (int) $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( in_array( $key, $this->floatFields, true ) ) {
|
||||
$this->$key = (float) $value;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Let's filter out any properties we cannot save to the database.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param string $key The table column.
|
||||
* @return array The array of valid columns for the database query.
|
||||
*/
|
||||
protected function filter( $key ) {
|
||||
$table = aioseo()->core->db->prefix . $this->table;
|
||||
$results = aioseo()->core->db->execute( 'SHOW COLUMNS FROM `' . $table . '`', true );
|
||||
$fields = [];
|
||||
$skip = [ 'created', 'updated' ];
|
||||
$columns = $results->result();
|
||||
|
||||
foreach ( $columns as $col ) {
|
||||
if ( ! in_array( $col->Field, $skip, true ) && array_key_exists( $col->Field, $key ) ) {
|
||||
$fields[ $col->Field ] = $key[ $col->Field ];
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the data to be null if it exists in the nullFields variables.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param array $data The data array to transform.
|
||||
* @return array The transformed data.
|
||||
*/
|
||||
protected function transform( $data, $set = false ) {
|
||||
foreach ( $this->nullFields as $field ) {
|
||||
if ( isset( $data[ $field ] ) && empty( $data[ $field ] ) ) {
|
||||
// Because sitemap prio can both be 0 and null, we need to make sure it's 0 if it's set.
|
||||
if ( 'priority' === $field && 0.0 === $data[ $field ] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data[ $field ] = null;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $this->booleanFields as $field ) {
|
||||
if ( isset( $data[ $field ] ) && '' === $data[ $field ] ) {
|
||||
unset( $data[ $field ] );
|
||||
} elseif ( isset( $data[ $field ] ) ) {
|
||||
$data[ $field ] = (bool) $data[ $field ] ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $set ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ( $this->integerFields as $field ) {
|
||||
if ( isset( $data[ $field ] ) ) {
|
||||
$data[ $field ] = (int) $data[ $field ];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $this->jsonFields as $field ) {
|
||||
if ( isset( $data[ $field ] ) && ! aioseo()->helpers->isJsonString( $data[ $field ] ) ) {
|
||||
if ( is_array( $data[ $field ] ) && aioseo()->helpers->isArrayNumeric( $data[ $field ] ) ) {
|
||||
$data[ $field ] = array_values( $data[ $field ] );
|
||||
}
|
||||
$data[ $field ] = wp_json_encode( $data[ $field ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a piece of data to the requested resource.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function set() {
|
||||
$args = func_get_args();
|
||||
$count = func_num_args();
|
||||
|
||||
if ( ! is_array( $args[0] ) && $count < 2 ) {
|
||||
throw new \Exception( 'The set method must contain at least 2 arguments: key and value. Or an array of data. Only one argument was passed and it was not an array.' );
|
||||
}
|
||||
|
||||
$key = $args[0];
|
||||
$value = ! empty( $args[1] ) ? $args[1] : null;
|
||||
|
||||
// Make sure we have a key.
|
||||
if ( false === $key ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If it's not an array, make it one.
|
||||
if ( ! is_array( $key ) ) {
|
||||
$key = [ $key => $value ];
|
||||
}
|
||||
|
||||
// Preprocess data.
|
||||
$key = $this->transform( $key, true );
|
||||
|
||||
// Save the items in this object.
|
||||
foreach ( $key as $k => $v ) {
|
||||
if ( ! empty( $k ) ) {
|
||||
$this->$k = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the Model Resource itself.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function delete() {
|
||||
if ( ! $this->exists() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
aioseo()->core->db
|
||||
->delete( $this->table )
|
||||
->where( $this->pk, $this->id )
|
||||
->run();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves data to the requested resource.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function save() {
|
||||
$fields = $this->transform( $this->filter( (array) get_object_vars( $this ) ) );
|
||||
|
||||
$id = null;
|
||||
if ( count( $fields ) > 0 ) {
|
||||
$pk = $this->pk;
|
||||
|
||||
if ( isset( $this->$pk ) && '' !== $this->$pk ) {
|
||||
// PK specified.
|
||||
$pkv = $this->$pk;
|
||||
$query = aioseo()->core->db
|
||||
->start( $this->table )
|
||||
->where( [ $pk => $pkv ] )
|
||||
->run();
|
||||
|
||||
if ( ! $query->nullSet() ) {
|
||||
// Row exists in database.
|
||||
$fields['updated'] = gmdate( 'Y-m-d H:i:s' );
|
||||
aioseo()->core->db
|
||||
->update( $this->table )
|
||||
->set( $fields )
|
||||
->where( [ $pk => $pkv ] )
|
||||
->run();
|
||||
$id = $this->$pk;
|
||||
} else {
|
||||
// Row does not exist.
|
||||
$fields[ $pk ] = $pkv;
|
||||
$fields['created'] = gmdate( 'Y-m-d H:i:s' );
|
||||
$fields['updated'] = gmdate( 'Y-m-d H:i:s' );
|
||||
|
||||
$id = aioseo()->core->db
|
||||
->insert( $this->table )
|
||||
->set( $fields )
|
||||
->run()
|
||||
->insertId();
|
||||
|
||||
if ( $id ) {
|
||||
$this->$pk = $id;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$fields['created'] = gmdate( 'Y-m-d H:i:s' );
|
||||
$fields['updated'] = gmdate( 'Y-m-d H:i:s' );
|
||||
|
||||
$id = aioseo()->core->db
|
||||
->insert( $this->table )
|
||||
->set( $fields )
|
||||
->run()
|
||||
->insertId();
|
||||
|
||||
if ( $id ) {
|
||||
$this->$pk = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the resource.
|
||||
$this->reset( $id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the model exists in the database.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return bool If the model exists, true otherwise false.
|
||||
*/
|
||||
public function exists() {
|
||||
return ( ! empty( $this->{$this->pk} ) ) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets a resource by forcing internal updates to be applied.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param string $id The resource ID.
|
||||
* @return void
|
||||
*/
|
||||
public function reset( $id = null ) {
|
||||
$id = ! empty( $id ) ? $id : $this->{$this->pk};
|
||||
$this->__construct( $id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to remove data we don't want serialized.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array An array of data that we are okay with serializing.
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
// The attribute above omits a deprecation notice from PHP 8.1 that is thrown because the return type of jsonSerialize() isn't "mixed".
|
||||
// Once PHP 7.x is our minimum supported version, this can be removed in favour of overriding the return type in the method signature like this -
|
||||
// public function jsonSerialize() : array
|
||||
public function jsonSerialize() {
|
||||
$array = [];
|
||||
|
||||
foreach ( $this->getColumns() as $column => $value ) {
|
||||
if ( in_array( $column, $this->hidden, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$array[ $column ] = isset( $this->$column ) ? $this->$column : null;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the columns for the model.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array An array of columns.
|
||||
*/
|
||||
public function getColumns() {
|
||||
if ( empty( self::$columns[ get_called_class() ] ) ) {
|
||||
self::$columns[ get_called_class() ] = [];
|
||||
|
||||
// Let's set the columns that are available by default.
|
||||
$table = aioseo()->core->db->prefix . $this->table;
|
||||
$results = aioseo()->core->db->start( $table )
|
||||
->output( 'OBJECT' )
|
||||
->execute( 'SHOW COLUMNS FROM `' . $table . '`', true )
|
||||
->result();
|
||||
|
||||
foreach ( $results as $col ) {
|
||||
self::$columns[ get_called_class() ][ $col->Field ] = $col->Default;
|
||||
}
|
||||
|
||||
if ( ! empty( $this->appends ) ) {
|
||||
foreach ( $this->appends as $append ) {
|
||||
self::$columns[ get_called_class() ][ $append ] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::$columns[ get_called_class() ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Models;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Notification DB Model.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class Notification extends Model {
|
||||
/**
|
||||
* The name of the table in the database, without the prefix.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'aioseo_notifications';
|
||||
|
||||
/**
|
||||
* An array of fields to set to null if already empty when saving to the database.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $nullFields = [
|
||||
'start',
|
||||
'end',
|
||||
'notification_id',
|
||||
'notification_name',
|
||||
'button1_label',
|
||||
'button1_action',
|
||||
'button2_label',
|
||||
'button2_action'
|
||||
];
|
||||
|
||||
/**
|
||||
* Fields that should be json encoded on save and decoded on get.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $jsonFields = [ 'level' ];
|
||||
|
||||
/**
|
||||
* Fields that should be boolean values.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $booleanFields = [ 'dismissed' ];
|
||||
|
||||
/**
|
||||
* Fields that should be hidden when serialized.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = [ 'id' ];
|
||||
|
||||
/**
|
||||
* An array of fields attached to this resource.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columns = [
|
||||
'id',
|
||||
'slug',
|
||||
'addon',
|
||||
'title',
|
||||
'content',
|
||||
'type',
|
||||
'level',
|
||||
'notification_id',
|
||||
'notification_name',
|
||||
'start',
|
||||
'end',
|
||||
'button1_label',
|
||||
'button1_action',
|
||||
'button2_label',
|
||||
'button2_action',
|
||||
'dismissed',
|
||||
'new',
|
||||
'created',
|
||||
'updated'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the list of notifications.
|
||||
*
|
||||
* @since 4.1.3
|
||||
*
|
||||
* @param boolean $reset Whether or not to reset the new notifications.
|
||||
* @return array An array of notifications.
|
||||
*/
|
||||
public static function getNotifications( $reset = true ) {
|
||||
return [
|
||||
'active' => self::getAllActiveNotifications(),
|
||||
'new' => self::getNewNotifications( $reset ),
|
||||
'dismissed' => self::getAllDismissedNotifications()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of active notifications.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array An array of active notifications.
|
||||
*/
|
||||
public static function getAllActiveNotifications() {
|
||||
$staticNotifications = self::getStaticNotifications();
|
||||
$notifications = array_values( json_decode( wp_json_encode( self::getActiveNotifications() ), true ) );
|
||||
|
||||
return ! empty( $staticNotifications ) ? array_merge( $staticNotifications, $notifications ) : $notifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all new notifications. After retrieving them, this will reset them.
|
||||
* This means that calling this method twice will result in no results
|
||||
* the second time. The only exception is to pass false as a reset variable to prevent it.
|
||||
*
|
||||
* @since 4.1.3
|
||||
*
|
||||
* @param boolean $reset Whether or not to reset the new notifications.
|
||||
* @return array An array of new notifications if any exist.
|
||||
*/
|
||||
public static function getNewNotifications( $reset = true ) {
|
||||
$notifications = self::filterNotifications(
|
||||
aioseo()->core->db
|
||||
->start( 'aioseo_notifications' )
|
||||
->where( 'dismissed', 0 )
|
||||
->where( 'new', 1 )
|
||||
->whereRaw( "(start <= '" . gmdate( 'Y-m-d H:i:s' ) . "' OR start IS NULL)" )
|
||||
->whereRaw( "(end >= '" . gmdate( 'Y-m-d H:i:s' ) . "' OR end IS NULL)" )
|
||||
->orderBy( 'start DESC, created DESC' )
|
||||
->run()
|
||||
->models( 'AIOSEO\\Plugin\\Common\\Models\\Notification' )
|
||||
);
|
||||
|
||||
if ( $reset ) {
|
||||
self::resetNewNotifications();
|
||||
}
|
||||
|
||||
return $notifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all new notifications.
|
||||
*
|
||||
* @since 4.1.3
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function resetNewNotifications() {
|
||||
aioseo()->core->db
|
||||
->update( 'aioseo_notifications' )
|
||||
->where( 'new', 1 )
|
||||
->set( 'new', 0 )
|
||||
->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all static notifications.
|
||||
*
|
||||
* @since 4.1.2
|
||||
*
|
||||
* @return array An array of static notifications.
|
||||
*/
|
||||
public static function getStaticNotifications() {
|
||||
$staticNotifications = [];
|
||||
$notifications = [
|
||||
'unlicensed-addons',
|
||||
'review'
|
||||
];
|
||||
|
||||
foreach ( $notifications as $notification ) {
|
||||
switch ( $notification ) {
|
||||
case 'review':
|
||||
// If they intentionally dismissed the main notification, we don't show the repeat one.
|
||||
$originalDismissed = get_user_meta( get_current_user_id(), '_aioseo_plugin_review_dismissed', true );
|
||||
if ( '4' !== $originalDismissed ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$dismissed = get_user_meta( get_current_user_id(), '_aioseo_notification_plugin_review_dismissed', true );
|
||||
if ( '3' === $dismissed ) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ( ! empty( $dismissed ) && $dismissed > time() ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$activated = aioseo()->internalOptions->internal->firstActivated( time() );
|
||||
if ( $activated > strtotime( '-20 days' ) ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$isV3 = get_option( 'aioseop_options' ) || get_option( 'aioseo_options_v3' );
|
||||
$staticNotifications[] = [
|
||||
'slug' => 'notification-' . $notification,
|
||||
'component' => 'notifications-' . $notification . ( $isV3 ? '' : '2' )
|
||||
];
|
||||
break;
|
||||
case 'unlicensed-addons':
|
||||
$unlicensedAddons = aioseo()->addons->unlicensedAddons();
|
||||
if ( empty( $unlicensedAddons['addons'] ) ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$staticNotifications[] = [
|
||||
'slug' => 'notification-' . $notification,
|
||||
'component' => 'notifications-' . $notification,
|
||||
'addons' => $unlicensedAddons['addons'],
|
||||
'message' => $unlicensedAddons['message']
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $staticNotifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve active notifications.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array An array of active notifications or empty.
|
||||
*/
|
||||
public static function getActiveNotifications() {
|
||||
return self::filterNotifications(
|
||||
aioseo()->core->db
|
||||
->start( 'aioseo_notifications' )
|
||||
->where( 'dismissed', 0 )
|
||||
->whereRaw( "(start <= '" . gmdate( 'Y-m-d H:i:s' ) . "' OR start IS NULL)" )
|
||||
->whereRaw( "(end >= '" . gmdate( 'Y-m-d H:i:s' ) . "' OR end IS NULL)" )
|
||||
->orderBy( 'start DESC, created DESC' )
|
||||
->run()
|
||||
->models( 'AIOSEO\\Plugin\\Common\\Models\\Notification' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of dismissed notifications.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array An array of dismissed notifications.
|
||||
*/
|
||||
public static function getAllDismissedNotifications() {
|
||||
return array_values( json_decode( wp_json_encode( self::getDismissedNotifications() ), true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve dismissed notifications.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array An array of dismissed notifications or empty.
|
||||
*/
|
||||
public static function getDismissedNotifications() {
|
||||
return self::filterNotifications(
|
||||
aioseo()->core->db
|
||||
->start( 'aioseo_notifications' )
|
||||
->where( 'dismissed', 1 )
|
||||
->orderBy( 'updated DESC' )
|
||||
->run()
|
||||
->models( 'AIOSEO\\Plugin\\Common\\Models\\Notification' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a notification by its name.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param string $name The notification name.
|
||||
* @return Notification The notification.
|
||||
*/
|
||||
public static function getNotificationByName( $name ) {
|
||||
return aioseo()->core->db
|
||||
->start( 'aioseo_notifications' )
|
||||
->where( 'notification_name', $name )
|
||||
->run()
|
||||
->model( 'AIOSEO\\Plugin\\Common\\Models\\Notification' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a new notification in the DB.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param array $fields The fields.
|
||||
* @return Notification $notification The notification.
|
||||
*/
|
||||
public static function addNotification( $fields ) {
|
||||
// Set the dismissed status to false.
|
||||
$fields['dismissed'] = 0;
|
||||
|
||||
$notification = new self();
|
||||
$notification->set( $fields );
|
||||
$notification->save();
|
||||
|
||||
return $notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a notification by its name.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param string $name The notification name.
|
||||
* @return void
|
||||
*/
|
||||
public static function deleteNotificationByName( $name ) {
|
||||
aioseo()->core->db
|
||||
->delete( 'aioseo_notifications' )
|
||||
->where( 'notification_name', $name )
|
||||
->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the notifications based on the targeted plan levels.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param array $notifications The notifications
|
||||
* @return array $remainingNotifications The remaining notifications.
|
||||
*/
|
||||
public static function filterNotifications( $notifications ) {
|
||||
$remainingNotifications = [];
|
||||
foreach ( $notifications as $notification ) {
|
||||
// If announcements are disabled and this is an announcement, skip adding it and move on.
|
||||
if (
|
||||
! aioseo()->options->advanced->announcements &&
|
||||
'success' === $notification->type
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this is an addon notification and the addon is disabled, skip adding it and move on.
|
||||
if ( ! empty( $notification->addon ) && ! aioseo()->addons->getLoadedAddon( $notification->addon ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$levels = $notification->level;
|
||||
if ( ! is_array( $levels ) ) {
|
||||
$levels = empty( $notification->level ) ? [ 'all' ] : [ $notification->level ];
|
||||
}
|
||||
|
||||
foreach ( $levels as $level ) {
|
||||
if ( ! aioseo()->notices->validateType( $level ) ) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$remainingNotifications[] = $notification;
|
||||
}
|
||||
|
||||
return $remainingNotifications;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,905 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Models;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Post DB Model.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class Post extends Model {
|
||||
/**
|
||||
* The name of the table in the database, without the prefix.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'aioseo_posts';
|
||||
|
||||
/**
|
||||
* Fields that should be json encoded on save and decoded on get.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $jsonFields = [
|
||||
'keywords',
|
||||
'keyphrases',
|
||||
'page_analysis',
|
||||
'schema',
|
||||
// 'schema_type_options',
|
||||
'images',
|
||||
'videos',
|
||||
'open_ai',
|
||||
'options',
|
||||
'local_seo',
|
||||
'primary_term',
|
||||
'og_article_tags'
|
||||
];
|
||||
|
||||
/**
|
||||
* Fields that should be hidden when serialized.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = [ 'id' ];
|
||||
|
||||
/**
|
||||
* Fields that should be boolean values.
|
||||
*
|
||||
* @since 4.0.13
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $booleanFields = [
|
||||
'twitter_use_og',
|
||||
'pillar_content',
|
||||
'robots_default',
|
||||
'robots_noindex',
|
||||
'robots_noarchive',
|
||||
'robots_nosnippet',
|
||||
'robots_nofollow',
|
||||
'robots_noimageindex',
|
||||
'robots_noodp',
|
||||
'robots_notranslate',
|
||||
'limit_modified_date',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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'
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns a Post with a given ID.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param int $postId The post ID.
|
||||
* @return Post The Post object.
|
||||
*/
|
||||
public static function getPost( $postId ) {
|
||||
// This is needed to prevent an error when upgrading from 4.1.8 to 4.1.9.
|
||||
// WordPress deletes the attachment .zip file for the new plugin version after installing it, which triggers the "delete_post" hook.
|
||||
// In-between the 4.1.8 to 4.1.9 update, the new Core class does not exist yet, causing the PHP error.
|
||||
// TODO: Delete this in a future release.
|
||||
$post = new self();
|
||||
if ( ! property_exists( aioseo(), 'core' ) ) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
$post = aioseo()->core->db->start( 'aioseo_posts' )
|
||||
->where( 'post_id', $postId )
|
||||
->run()
|
||||
->model( 'AIOSEO\\Plugin\\Common\\Models\\Post' );
|
||||
|
||||
if ( ! $post->exists() ) {
|
||||
$post->post_id = $postId;
|
||||
$post = self::setDynamicDefaults( $post, $postId );
|
||||
} else {
|
||||
$post = self::runDynamicMigrations( $post );
|
||||
}
|
||||
|
||||
// Set options object.
|
||||
$post = self::setOptionsDefaults( $post );
|
||||
|
||||
return apply_filters( 'aioseo_get_post', $post );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dynamic defaults on the post object if it doesn't exist in the DB yet.
|
||||
*
|
||||
* @since 4.1.4
|
||||
*
|
||||
* @param Post $post The Post object.
|
||||
* @param int $postId The post ID.
|
||||
* @return Post The modified Post object.
|
||||
*/
|
||||
private static function setDynamicDefaults( $post, $postId ) {
|
||||
if ( 'page' === get_post_type( $postId ) ) { // This check cannot be deleted and is required to prevent errors after WordPress cleans up the attachment it creates when a plugin is updated.
|
||||
$isWooCommerceCheckoutPage = aioseo()->helpers->isWooCommerceCheckoutPage( $postId );
|
||||
if (
|
||||
$isWooCommerceCheckoutPage ||
|
||||
aioseo()->helpers->isWooCommerceCartPage( $postId ) ||
|
||||
aioseo()->helpers->isWooCommerceAccountPage( $postId )
|
||||
) {
|
||||
$post->robots_default = false;
|
||||
$post->robots_noindex = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( aioseo()->helpers->isStaticHomePage( $postId ) ) {
|
||||
$post->og_object_type = 'website';
|
||||
}
|
||||
|
||||
$post->twitter_use_og = aioseo()->options->social->twitter->general->useOgData;
|
||||
|
||||
if ( property_exists( $post, 'schema' ) && null === $post->schema ) {
|
||||
$post->schema = self::getDefaultSchemaOptions();
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates removed QAPage schema on-the-fly when the post is loaded.
|
||||
*
|
||||
* @since 4.1.8
|
||||
*
|
||||
* @param Post $aioseoPost The post object.
|
||||
* @return Post The modified post object.
|
||||
*/
|
||||
private static function migrateRemovedQaSchema( $aioseoPost ) {
|
||||
if ( ! $aioseoPost->schema_type || 'webpage' !== strtolower( $aioseoPost->schema_type ) ) {
|
||||
return $aioseoPost;
|
||||
}
|
||||
|
||||
$schemaTypeOptions = json_decode( $aioseoPost->schema_type_options );
|
||||
if ( 'qapage' !== strtolower( $schemaTypeOptions->webPage->webPageType ) ) {
|
||||
return $aioseoPost;
|
||||
}
|
||||
|
||||
$schemaTypeOptions->webPage->webPageType = 'WebPage';
|
||||
$aioseoPost->schema_type_options = wp_json_encode( $schemaTypeOptions );
|
||||
$aioseoPost->save();
|
||||
|
||||
return $aioseoPost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs dynamic migrations whenever the post object is loaded.
|
||||
*
|
||||
* @since 4.1.7
|
||||
*
|
||||
* @param Post $post The Post object.
|
||||
* @return Post The modified Post object.
|
||||
*/
|
||||
private static function runDynamicMigrations( $post ) {
|
||||
$post = self::migrateRemovedQaSchema( $post );
|
||||
$post = self::migrateImageTypes( $post );
|
||||
$post = self::runDynamicSchemaMigration( $post );
|
||||
$post = self::migrateKoreaCountryCodeSchemas( $post );
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Migrates the post's schema data when it is loaded.
|
||||
*
|
||||
* @since 4.2.5
|
||||
*
|
||||
* @param Post $post The Post object.
|
||||
* @return Post The modified Post object.
|
||||
*/
|
||||
private static function runDynamicSchemaMigration( $post ) {
|
||||
if ( ! property_exists( $post, 'schema' ) ) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
if ( null === $post->schema ) {
|
||||
$post = aioseo()->updates->migratePostSchemaHelper( $post );
|
||||
}
|
||||
|
||||
// If the schema prop isn't set yet, we want to set it here.
|
||||
// We also want to run this regardless of whether it is already set to make sure the default schema graph
|
||||
// is correctly propagated on the frontend after changing it.
|
||||
$post->schema = self::getDefaultSchemaOptions( $post->schema );
|
||||
|
||||
// Filter out null or empty graphs.
|
||||
$post->schema->graphs = array_filter( $post->schema->graphs, function( $graph ) {
|
||||
return ! empty( $graph );
|
||||
} );
|
||||
|
||||
foreach ( $post->schema->graphs as $graph ) {
|
||||
// If the first character of the graph ID isn't a pound, add one.
|
||||
// We have to do this because the schema migration in 4.2.5 didn't add the pound for custom graphs.
|
||||
if ( property_exists( $graph, 'id' ) && '#' !== substr( $graph->id, 0, 1 ) ) {
|
||||
$graph->id = '#' . $graph->id;
|
||||
}
|
||||
|
||||
// If the graph has an old rating value, we need to migrate it to the review.
|
||||
if (
|
||||
property_exists( $graph, 'id' ) &&
|
||||
preg_match( '/(movie|software-application)/', (string) $graph->id ) &&
|
||||
property_exists( $graph->properties, 'rating' ) &&
|
||||
property_exists( $graph->properties->rating, 'value' )
|
||||
) {
|
||||
$graph->properties->review->rating = $graph->properties->rating->value;
|
||||
unset( $graph->properties->rating->value );
|
||||
}
|
||||
|
||||
// If the graph has audience data, we need to migrate it to the correct one.
|
||||
if (
|
||||
property_exists( $graph, 'id' ) &&
|
||||
preg_match( '/(product|product-review)/', $graph->id ) &&
|
||||
property_exists( $graph->properties, 'audience' )
|
||||
) {
|
||||
$graph->properties->audience = self::migratePostAudienceAgeSchema( $graph->properties->audience );
|
||||
}
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates the post's image types when it is loaded.
|
||||
*
|
||||
* @since 4.2.5
|
||||
*
|
||||
* @param Post $post The Post object.
|
||||
* @return Post The modified Post object.
|
||||
*/
|
||||
private static function migrateImageTypes( $post ) {
|
||||
$pageBuilder = aioseo()->helpers->getPostPageBuilderName( $post->post_id );
|
||||
if ( ! $pageBuilder ) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
$deprecatedImageSources = 'seedprod' === strtolower( $pageBuilder )
|
||||
? [ 'auto', 'custom', 'featured' ]
|
||||
: [ 'auto' ];
|
||||
|
||||
if ( ! empty( $post->og_image_type ) && in_array( $post->og_image_type, $deprecatedImageSources, true ) ) {
|
||||
$post->og_image_type = 'default';
|
||||
}
|
||||
|
||||
if ( ! empty( $post->twitter_image_type ) && in_array( $post->twitter_image_type, $deprecatedImageSources, true ) ) {
|
||||
$post->twitter_image_type = 'default';
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the Post object.
|
||||
*
|
||||
* @since 4.0.3
|
||||
*
|
||||
* @param int $postId The Post ID.
|
||||
* @param array $data The post data to save.
|
||||
* @return bool|void|string Whether the post data was saved or a DB error message.
|
||||
*/
|
||||
public static function savePost( $postId, $data ) {
|
||||
if ( empty( $data ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$thePost = self::getPost( $postId );
|
||||
$data = apply_filters( 'aioseo_save_post', $data, $thePost );
|
||||
|
||||
// Before setting the data, we check if the title/description are the same as the defaults and clear them if so.
|
||||
$data = self::checkForDefaultFormat( $postId, $thePost, $data );
|
||||
$thePost = self::sanitizeAndSetDefaults( $postId, $thePost, $data );
|
||||
|
||||
// Update traditional post meta so that it can be used by multilingual plugins.
|
||||
self::updatePostMeta( $postId, $data );
|
||||
|
||||
$thePost->save();
|
||||
$thePost->reset();
|
||||
|
||||
$lastError = aioseo()->core->db->lastError();
|
||||
if ( ! empty( $lastError ) ) {
|
||||
return $lastError;
|
||||
}
|
||||
|
||||
// Fires once an AIOSEO post has been saved.
|
||||
do_action( 'aioseo_insert_post', $postId );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 post.
|
||||
*
|
||||
* @since 4.1.5
|
||||
*
|
||||
* @param int $postId The post ID.
|
||||
* @param Post $thePost The Post object.
|
||||
* @param array $data The data.
|
||||
* @return array The data.
|
||||
*/
|
||||
private static function checkForDefaultFormat( $postId, $thePost, $data ) {
|
||||
$data['title'] = trim( (string) $data['title'] );
|
||||
$data['description'] = trim( (string) $data['description'] );
|
||||
|
||||
$post = aioseo()->helpers->getPost( $postId );
|
||||
$defaultTitleFormat = trim( aioseo()->meta->title->getPostTypeTitle( $post->post_type ) );
|
||||
$defaultDescriptionFormat = trim( aioseo()->meta->description->getPostTypeDescription( $post->post_type ) );
|
||||
if ( ! empty( $data['title'] ) && $data['title'] === $defaultTitleFormat ) {
|
||||
$data['title'] = null;
|
||||
}
|
||||
|
||||
if ( ! empty( $data['description'] ) && $data['description'] === $defaultDescriptionFormat ) {
|
||||
$data['description'] = null;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the keyphrases posted data.
|
||||
*
|
||||
* @since 4.2.8
|
||||
*
|
||||
* @param array $data An array containing the keyphrases field data.
|
||||
* @return array The sanitized data.
|
||||
*/
|
||||
private static function sanitizeKeyphrases( $data ) {
|
||||
if (
|
||||
! empty( $data['focus']['analysis'] ) &&
|
||||
is_array( $data['focus']['analysis'] )
|
||||
) {
|
||||
foreach ( $data['focus']['analysis'] as &$analysis ) {
|
||||
// Remove unnecessary 'title' and 'description'.
|
||||
unset( $analysis['title'] );
|
||||
unset( $analysis['description'] );
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
! empty( $data['additional'] ) &&
|
||||
is_array( $data['additional'] )
|
||||
) {
|
||||
foreach ( $data['additional'] as &$additional ) {
|
||||
if (
|
||||
! empty( $additional['analysis'] ) &&
|
||||
is_array( $additional['analysis'] )
|
||||
) {
|
||||
foreach ( $additional['analysis'] as &$additionalAnalysis ) {
|
||||
// Remove unnecessary 'title' and 'description'.
|
||||
unset( $additionalAnalysis['title'] );
|
||||
unset( $additionalAnalysis['description'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the page_analysis posted data.
|
||||
*
|
||||
* @since 4.2.7
|
||||
*
|
||||
* @param array $data An array containing the page_analysis field data.
|
||||
* @return array The sanitized data.
|
||||
*/
|
||||
private static function sanitizePageAnalysis( $data ) {
|
||||
if (
|
||||
empty( $data['analysis'] ) ||
|
||||
! is_array( $data['analysis'] )
|
||||
) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ( $data['analysis'] as &$analysis ) {
|
||||
foreach ( $analysis as $key => $result ) {
|
||||
// Remove unnecessary data.
|
||||
foreach ( [ 'title', 'description', 'highlightSentences' ] as $keyToRemove ) {
|
||||
if ( isset( $analysis[ $key ][ $keyToRemove ] ) ) {
|
||||
unset( $analysis[ $key ][ $keyToRemove ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the post data and sets it (or the default value) to the Post object.
|
||||
*
|
||||
* @since 4.1.5
|
||||
*
|
||||
* @param int $postId The post ID.
|
||||
* @param Post $thePost The Post object.
|
||||
* @param array $data The data.
|
||||
* @return Post The Post object with data set.
|
||||
*/
|
||||
protected static function sanitizeAndSetDefaults( $postId, $thePost, $data ) {
|
||||
// General
|
||||
$thePost->post_id = $postId;
|
||||
$thePost->title = ! empty( $data['title'] ) ? sanitize_text_field( $data['title'] ) : null;
|
||||
$thePost->description = ! empty( $data['description'] ) ? sanitize_text_field( $data['description'] ) : null;
|
||||
$thePost->canonical_url = ! empty( $data['canonicalUrl'] ) ? sanitize_text_field( $data['canonicalUrl'] ) : null;
|
||||
$thePost->keywords = ! empty( $data['keywords'] ) ? aioseo()->helpers->sanitize( $data['keywords'] ) : null;
|
||||
$thePost->pillar_content = isset( $data['pillar_content'] ) ? rest_sanitize_boolean( $data['pillar_content'] ) : 0;
|
||||
// TruSEO
|
||||
$thePost->keyphrases = ! empty( $data['keyphrases'] ) ? self::sanitizeKeyphrases( $data['keyphrases'] ) : null;
|
||||
$thePost->page_analysis = ! empty( $data['page_analysis'] ) ? self::sanitizePageAnalysis( $data['page_analysis'] ) : null;
|
||||
$thePost->seo_score = ! empty( $data['seo_score'] ) ? sanitize_text_field( $data['seo_score'] ) : 0;
|
||||
// Sitemap
|
||||
$thePost->priority = isset( $data['priority'] ) ? ( 'default' === sanitize_text_field( $data['priority'] ) ? null : (float) $data['priority'] ) : null;
|
||||
$thePost->frequency = ! empty( $data['frequency'] ) ? sanitize_text_field( $data['frequency'] ) : 'default';
|
||||
// Robots Meta
|
||||
$thePost->robots_default = isset( $data['default'] ) ? rest_sanitize_boolean( $data['default'] ) : 1;
|
||||
$thePost->robots_noindex = isset( $data['noindex'] ) ? rest_sanitize_boolean( $data['noindex'] ) : 0;
|
||||
$thePost->robots_nofollow = isset( $data['nofollow'] ) ? rest_sanitize_boolean( $data['nofollow'] ) : 0;
|
||||
$thePost->robots_noarchive = isset( $data['noarchive'] ) ? rest_sanitize_boolean( $data['noarchive'] ) : 0;
|
||||
$thePost->robots_notranslate = isset( $data['notranslate'] ) ? rest_sanitize_boolean( $data['notranslate'] ) : 0;
|
||||
$thePost->robots_noimageindex = isset( $data['noimageindex'] ) ? rest_sanitize_boolean( $data['noimageindex'] ) : 0;
|
||||
$thePost->robots_nosnippet = isset( $data['nosnippet'] ) ? rest_sanitize_boolean( $data['nosnippet'] ) : 0;
|
||||
$thePost->robots_noodp = isset( $data['noodp'] ) ? rest_sanitize_boolean( $data['noodp'] ) : 0;
|
||||
$thePost->robots_max_snippet = isset( $data['maxSnippet'] ) && is_numeric( $data['maxSnippet'] ) ? (int) sanitize_text_field( $data['maxSnippet'] ) : -1;
|
||||
$thePost->robots_max_videopreview = isset( $data['maxVideoPreview'] ) && is_numeric( $data['maxVideoPreview'] ) ? (int) sanitize_text_field( $data['maxVideoPreview'] ) : -1;
|
||||
$thePost->robots_max_imagepreview = ! empty( $data['maxImagePreview'] ) ? sanitize_text_field( $data['maxImagePreview'] ) : 'large';
|
||||
// Open Graph Meta
|
||||
$thePost->og_title = ! empty( $data['og_title'] ) ? sanitize_text_field( $data['og_title'] ) : null;
|
||||
$thePost->og_description = ! empty( $data['og_description'] ) ? sanitize_text_field( $data['og_description'] ) : null;
|
||||
$thePost->og_object_type = ! empty( $data['og_object_type'] ) ? sanitize_text_field( $data['og_object_type'] ) : 'default';
|
||||
$thePost->og_image_type = ! empty( $data['og_image_type'] ) ? sanitize_text_field( $data['og_image_type'] ) : 'default';
|
||||
$thePost->og_image_url = null; // We'll reset this below.
|
||||
$thePost->og_image_width = null; // We'll reset this below.
|
||||
$thePost->og_image_height = null; // We'll reset this below.
|
||||
$thePost->og_image_custom_url = ! empty( $data['og_image_custom_url'] ) ? esc_url_raw( $data['og_image_custom_url'] ) : null;
|
||||
$thePost->og_image_custom_fields = ! empty( $data['og_image_custom_fields'] ) ? sanitize_text_field( $data['og_image_custom_fields'] ) : null;
|
||||
$thePost->og_video = ! empty( $data['og_video'] ) ? sanitize_text_field( $data['og_video'] ) : '';
|
||||
$thePost->og_article_section = ! empty( $data['og_article_section'] ) ? sanitize_text_field( $data['og_article_section'] ) : null;
|
||||
$thePost->og_article_tags = ! empty( $data['og_article_tags'] ) ? aioseo()->helpers->sanitize( $data['og_article_tags'] ) : null;
|
||||
// Twitter Meta
|
||||
$thePost->twitter_title = ! empty( $data['twitter_title'] ) ? sanitize_text_field( $data['twitter_title'] ) : null;
|
||||
$thePost->twitter_description = ! empty( $data['twitter_description'] ) ? sanitize_text_field( $data['twitter_description'] ) : null;
|
||||
$thePost->twitter_use_og = isset( $data['twitter_use_og'] ) ? rest_sanitize_boolean( $data['twitter_use_og'] ) : 0;
|
||||
$thePost->twitter_card = ! empty( $data['twitter_card'] ) ? sanitize_text_field( $data['twitter_card'] ) : 'default';
|
||||
$thePost->twitter_image_type = ! empty( $data['twitter_image_type'] ) ? sanitize_text_field( $data['twitter_image_type'] ) : 'default';
|
||||
$thePost->twitter_image_url = null; // We'll reset this below.
|
||||
$thePost->twitter_image_custom_url = ! empty( $data['twitter_image_custom_url'] ) ? esc_url_raw( $data['twitter_image_custom_url'] ) : null;
|
||||
$thePost->twitter_image_custom_fields = ! empty( $data['twitter_image_custom_fields'] ) ? sanitize_text_field( $data['twitter_image_custom_fields'] ) : null;
|
||||
// Schema
|
||||
$thePost->schema = ! empty( $data['schema'] ) ? self::getDefaultSchemaOptions( $data['schema'] ) : null;
|
||||
$thePost->local_seo = ! empty( $data['local_seo'] ) ? $data['local_seo'] : null;
|
||||
$thePost->limit_modified_date = isset( $data['limit_modified_date'] ) ? rest_sanitize_boolean( $data['limit_modified_date'] ) : 0;
|
||||
$thePost->open_ai = ! empty( $data['open_ai'] ) ? self::getDefaultOpenAiOptions( $data['open_ai'] ) : null;
|
||||
$thePost->updated = gmdate( 'Y-m-d H:i:s' );
|
||||
$thePost->primary_term = ! empty( $data['primary_term'] ) ? $data['primary_term'] : 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->bustPostCache( $thePost->post_id, $thePost );
|
||||
|
||||
// Set the OG/Twitter image data.
|
||||
$thePost = self::setOgTwitterImageData( $thePost );
|
||||
|
||||
if ( ! $thePost->exists() ) {
|
||||
$thePost->created = gmdate( 'Y-m-d H:i:s' );
|
||||
}
|
||||
|
||||
// Update defaults from addons.
|
||||
foreach ( aioseo()->addons->getLoadedAddons() as $addon ) {
|
||||
if ( isset( $addon->postModel ) && method_exists( $addon->postModel, 'sanitizeAndSetDefaults' ) ) {
|
||||
$thePost = $addon->postModel->sanitizeAndSetDefaults( $postId, $thePost, $data );
|
||||
}
|
||||
}
|
||||
|
||||
return $thePost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the OG/Twitter image data on the post object.
|
||||
*
|
||||
* @since 4.1.6
|
||||
*
|
||||
* @param Post $thePost The Post object to modify.
|
||||
* @return Post The modified Post object.
|
||||
*/
|
||||
public static function setOgTwitterImageData( $thePost ) {
|
||||
// Set the OG image.
|
||||
if (
|
||||
in_array( $thePost->og_image_type, [
|
||||
'featured',
|
||||
'content',
|
||||
'attach',
|
||||
'custom',
|
||||
'custom_image'
|
||||
], true )
|
||||
) {
|
||||
// Disable the cache.
|
||||
aioseo()->social->image->useCache = false;
|
||||
|
||||
// Set the image details.
|
||||
$ogImage = aioseo()->social->facebook->getImage( $thePost->post_id );
|
||||
$thePost->og_image_url = is_array( $ogImage ) ? $ogImage[0] : $ogImage;
|
||||
$thePost->og_image_width = aioseo()->social->facebook->getImageWidth();
|
||||
$thePost->og_image_height = aioseo()->social->facebook->getImageHeight();
|
||||
|
||||
// Reset the cache property.
|
||||
aioseo()->social->image->useCache = true;
|
||||
}
|
||||
|
||||
// Set the Twitter image.
|
||||
if (
|
||||
! $thePost->twitter_use_og &&
|
||||
in_array( $thePost->twitter_image_type, [
|
||||
'featured',
|
||||
'content',
|
||||
'attach',
|
||||
'custom',
|
||||
'custom_image'
|
||||
], true )
|
||||
) {
|
||||
// Disable the cache.
|
||||
aioseo()->social->image->useCache = false;
|
||||
|
||||
// Set the image details.
|
||||
$ogImage = aioseo()->social->twitter->getImage( $thePost->post_id );
|
||||
$thePost->twitter_image_url = is_array( $ogImage ) ? $ogImage[0] : $ogImage;
|
||||
|
||||
// Reset the cache property.
|
||||
aioseo()->social->image->useCache = true;
|
||||
}
|
||||
|
||||
return $thePost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves some of the data as post meta so that it can be used for localization.
|
||||
*
|
||||
* @since 4.1.5
|
||||
*
|
||||
* @param int $postId The post ID.
|
||||
* @param array $data The data.
|
||||
* @return void
|
||||
*/
|
||||
public static function updatePostMeta( $postId, $data ) {
|
||||
// Update the post meta as well for localization.
|
||||
$keywords = ! empty( $data['keywords'] ) ? aioseo()->helpers->jsonTagsToCommaSeparatedList( $data['keywords'] ) : [];
|
||||
$ogArticleTags = ! empty( $data['og_article_tags'] ) ? aioseo()->helpers->jsonTagsToCommaSeparatedList( $data['og_article_tags'] ) : [];
|
||||
|
||||
update_post_meta( $postId, '_aioseo_title', $data['title'] );
|
||||
update_post_meta( $postId, '_aioseo_description', $data['description'] );
|
||||
update_post_meta( $postId, '_aioseo_keywords', $keywords );
|
||||
update_post_meta( $postId, '_aioseo_og_title', $data['og_title'] );
|
||||
update_post_meta( $postId, '_aioseo_og_description', $data['og_description'] );
|
||||
update_post_meta( $postId, '_aioseo_og_article_section', $data['og_article_section'] );
|
||||
update_post_meta( $postId, '_aioseo_og_article_tags', $ogArticleTags );
|
||||
update_post_meta( $postId, '_aioseo_twitter_title', $data['twitter_title'] );
|
||||
update_post_meta( $postId, '_aioseo_twitter_description', $data['twitter_description'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default values for the TruSEO page analysis.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param object|null $pageAnalysis The page analysis object.
|
||||
* @return object The default values.
|
||||
*/
|
||||
public static function getPageAnalysisDefaults( $pageAnalysis = null ) {
|
||||
$defaults = [
|
||||
'analysis' => [
|
||||
'basic' => [
|
||||
'lengthContent' => [
|
||||
'error' => 1,
|
||||
'maxScore' => 9,
|
||||
'score' => 6,
|
||||
],
|
||||
],
|
||||
'title' => [
|
||||
'titleLength' => [
|
||||
'error' => 1,
|
||||
'maxScore' => 9,
|
||||
'score' => 1,
|
||||
],
|
||||
],
|
||||
'readability' => [
|
||||
'contentHasAssets' => [
|
||||
'error' => 1,
|
||||
'maxScore' => 5,
|
||||
'score' => 0,
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
if ( empty( $pageAnalysis ) ) {
|
||||
return json_decode( wp_json_encode( $defaults ) );
|
||||
}
|
||||
|
||||
return $pageAnalysis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON object with default schema options.
|
||||
*
|
||||
* @since 4.2.5
|
||||
*
|
||||
* @param string $existingOptions The existing options in JSON.
|
||||
* @param null|\WP_Post $post The post object.
|
||||
* @return object The existing options with defaults added in JSON.
|
||||
*/
|
||||
public static function getDefaultSchemaOptions( $existingOptions = '', $post = null ) {
|
||||
$defaultGraphName = aioseo()->schema->getDefaultPostTypeGraph( $post );
|
||||
|
||||
$defaults = [
|
||||
'blockGraphs' => [],
|
||||
'customGraphs' => [],
|
||||
'default' => [
|
||||
'data' => [
|
||||
'Article' => [],
|
||||
'Course' => [],
|
||||
'Dataset' => [],
|
||||
'FAQPage' => [],
|
||||
'Movie' => [],
|
||||
'Person' => [],
|
||||
'Product' => [],
|
||||
'ProductReview' => [],
|
||||
'Car' => [],
|
||||
'Recipe' => [],
|
||||
'Service' => [],
|
||||
'SoftwareApplication' => [],
|
||||
'WebPage' => []
|
||||
],
|
||||
'graphName' => $defaultGraphName,
|
||||
'isEnabled' => true,
|
||||
],
|
||||
'graphs' => []
|
||||
];
|
||||
|
||||
if ( empty( $existingOptions ) ) {
|
||||
return json_decode( wp_json_encode( $defaults ) );
|
||||
}
|
||||
|
||||
$existingOptions = json_decode( wp_json_encode( $existingOptions ), true );
|
||||
$existingOptions = array_replace_recursive( $defaults, $existingOptions );
|
||||
|
||||
if ( isset( $existingOptions['defaultGraph'] ) && ! empty( $existingOptions['defaultPostTypeGraph'] ) ) {
|
||||
$existingOptions['default']['isEnabled'] = ! empty( $existingOptions['defaultGraph'] );
|
||||
|
||||
unset( $existingOptions['defaultGraph'] );
|
||||
unset( $existingOptions['defaultPostTypeGraph'] );
|
||||
}
|
||||
|
||||
// Reset the default graph type to make sure it's accurate.
|
||||
if ( $defaultGraphName ) {
|
||||
$existingOptions['default']['graphName'] = $defaultGraphName;
|
||||
}
|
||||
|
||||
return json_decode( wp_json_encode( $existingOptions ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the defaults for the keyphrases column.
|
||||
*
|
||||
* @since 4.1.7
|
||||
*
|
||||
* @param null|object $keyphrases The database keyphrases.
|
||||
* @return object The defaults.
|
||||
*/
|
||||
public static function getKeyphrasesDefaults( $keyphrases = null ) {
|
||||
$defaults = [
|
||||
'focus' => [
|
||||
'keyphrase' => '',
|
||||
'score' => 0,
|
||||
'analysis' => [
|
||||
'keyphraseInTitle' => [
|
||||
'score' => 0,
|
||||
'maxScore' => 9,
|
||||
'error' => 1
|
||||
]
|
||||
]
|
||||
],
|
||||
'additional' => []
|
||||
];
|
||||
|
||||
if ( empty( $keyphrases ) ) {
|
||||
return json_decode( wp_json_encode( $defaults ) );
|
||||
}
|
||||
|
||||
if ( empty( $keyphrases->focus ) ) {
|
||||
$keyphrases->focus = $defaults['focus'];
|
||||
}
|
||||
|
||||
if ( empty( $keyphrases->additional ) ) {
|
||||
$keyphrases->additional = $defaults['additional'];
|
||||
}
|
||||
|
||||
return $keyphrases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the defaults for the options column.
|
||||
*
|
||||
* @since 4.2.2
|
||||
* @version 4.2.9
|
||||
*
|
||||
* @param Post $post The Post object.
|
||||
* @return Post The modified Post object.
|
||||
*/
|
||||
public static function setOptionsDefaults( $post ) {
|
||||
$defaults = [
|
||||
'linkFormat' => [
|
||||
'internalLinkCount' => 0,
|
||||
'linkAssistantDismissed' => false
|
||||
],
|
||||
'primaryTerm' => [
|
||||
'productEducationDismissed' => false
|
||||
]
|
||||
];
|
||||
|
||||
if ( empty( $post->options ) ) {
|
||||
$post->options = json_decode( wp_json_encode( $defaults ) );
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
$post->options = json_decode( wp_json_encode( $post->options ), true );
|
||||
$post->options = array_replace_recursive( $defaults, $post->options );
|
||||
$post->options = json_decode( wp_json_encode( $post->options ) );
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default Open AI options.
|
||||
*
|
||||
* @since 4.3.2
|
||||
*
|
||||
* @param array $existingOptions The existing options.
|
||||
* @return object The default options.
|
||||
*/
|
||||
public static function getDefaultOpenAiOptions( $existingOptions = [] ) {
|
||||
$defaults = [
|
||||
'title' => [
|
||||
'suggestions' => [],
|
||||
'usage' => 0
|
||||
],
|
||||
'description' => [
|
||||
'suggestions' => [],
|
||||
'usage' => 0
|
||||
]
|
||||
];
|
||||
|
||||
if ( empty( $existingOptions ) ) {
|
||||
return json_decode( wp_json_encode( $defaults ) );
|
||||
}
|
||||
|
||||
$existingOptions = json_decode( wp_json_encode( $existingOptions ), true );
|
||||
$existingOptions = array_replace_recursive( $defaults, $existingOptions );
|
||||
|
||||
return json_decode( wp_json_encode( $existingOptions ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates the post's audience age schema data when it is loaded.
|
||||
* Min age: [0 => newborns, 0.25 => infants, 1 => toddlers, 5 => kids, 13 => adults]
|
||||
* Max age: [0.25 => newborns, 1 => infants, 5 => toddlers, 13 => kids]
|
||||
*
|
||||
* @since 4.7.9
|
||||
*
|
||||
* @param object $audience The audience data.
|
||||
* @return object
|
||||
*/
|
||||
public static function migratePostAudienceAgeSchema( $audience ) {
|
||||
$ages = [ 0, 0.25, 1, 5, 13 ];
|
||||
|
||||
// converts variable to integer if it's a number otherwise is null.
|
||||
$parsedMinAge = filter_var( $audience->minimumAge, FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE );
|
||||
$parsedMaxAge = filter_var( $audience->maximumAge, FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE );
|
||||
|
||||
if ( null === $parsedMinAge && null === $parsedMaxAge ) {
|
||||
return $audience;
|
||||
}
|
||||
|
||||
$minAge = is_numeric( $parsedMinAge ) ? $parsedMinAge : 0;
|
||||
$maxAge = is_numeric( $parsedMaxAge ) ? $parsedMaxAge : null;
|
||||
|
||||
// get the minimumAge if available or the nearest bigger one.
|
||||
foreach ( $ages as $age ) {
|
||||
if ( $age >= $minAge ) {
|
||||
$audience->minimumAge = $age;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// get the maximumAge if available or the nearest bigger one.
|
||||
foreach ( $ages as $age ) {
|
||||
if ( $age >= $maxAge ) {
|
||||
$maxAge = $age;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// makes sure the maximumAge is 13 below
|
||||
if ( null !== $maxAge ) {
|
||||
$audience->maximumAge = 13 < $maxAge ? 13 : $maxAge;
|
||||
}
|
||||
|
||||
// Minimum age 13 is for adults.
|
||||
// If minimumAge is still higher or equal 13 then it's for adults and maximumAge should be empty.
|
||||
if ( 13 <= $audience->minimumAge ) {
|
||||
$audience->minimumAge = 13;
|
||||
$audience->maximumAge = null;
|
||||
}
|
||||
|
||||
return $audience;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates update Korea country code for Person, Product, Event, and JobsPosting schemas.
|
||||
*
|
||||
* @since 4.7.1
|
||||
*
|
||||
* @param Post $aioseoPost The post object.
|
||||
* @return Post The modified post object.
|
||||
*/
|
||||
private static function migrateKoreaCountryCodeSchemas( $aioseoPost ) {
|
||||
if ( empty( $aioseoPost->schema ) || empty( $aioseoPost->schema->graphs ) ) {
|
||||
return $aioseoPost;
|
||||
}
|
||||
|
||||
foreach ( $aioseoPost->schema->graphs as $key => $graph ) {
|
||||
if ( isset( $aioseoPost->schema->graphs[ $key ]->properties->location->country ) ) {
|
||||
$aioseoPost->schema->graphs[ $key ]->properties->location->country = self::invertKoreaCode( $graph->properties->location->country );
|
||||
}
|
||||
|
||||
if ( isset( $aioseoPost->schema->graphs[ $key ]->properties->shippingDestinations ) ) {
|
||||
$aioseoPost->schema->graphs[ $key ]->properties->shippingDestinations = array_map( function( $item ) {
|
||||
$item->country = self::invertKoreaCode( $item->country );
|
||||
|
||||
return $item;
|
||||
}, $graph->properties->shippingDestinations );
|
||||
}
|
||||
}
|
||||
|
||||
$aioseoPost->save();
|
||||
|
||||
return $aioseoPost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to invert the country code for Korea.
|
||||
*
|
||||
* @since 4.7.1
|
||||
*
|
||||
* @param string $code country code.
|
||||
* @return string country code.
|
||||
*/
|
||||
public static function invertKoreaCode( $code ) {
|
||||
return 'KP' === $code ? 'KR' : $code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Models;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Keyword.
|
||||
*
|
||||
* @since 4.7.4
|
||||
*/
|
||||
class WritingAssistantKeyword extends Model {
|
||||
/**
|
||||
* The name of the table in the database, without the prefix.
|
||||
*
|
||||
* @since 4.7.4
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'aioseo_writing_assistant_keywords';
|
||||
|
||||
/**
|
||||
* Fields that should be numeric values.
|
||||
*
|
||||
* @since 4.7.4
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $integerFields = [ 'id', 'progress' ];
|
||||
|
||||
/**
|
||||
* Fields that should be boolean values.
|
||||
*
|
||||
* @since 4.7.4
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $booleanFields = [];
|
||||
|
||||
/**
|
||||
* Fields that should be encoded/decoded on save/get.
|
||||
*
|
||||
* @since 4.7.4
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $jsonFields = [ 'keywords', 'competitors', 'content_analysis' ];
|
||||
|
||||
/**
|
||||
* Gets a keyword.
|
||||
*
|
||||
* @since 4.7.4
|
||||
*
|
||||
* @param string $keyword A keyword.
|
||||
* @param string $country The country code.
|
||||
* @param string $language The language code.
|
||||
* @return object A keyword found.
|
||||
*/
|
||||
public static function getKeyword( $keyword, $country, $language ) {
|
||||
$dbKeyword = aioseo()->core->db->start( 'aioseo_writing_assistant_keywords' )
|
||||
->where( 'keyword', $keyword )
|
||||
->where( 'country', $country )
|
||||
->where( 'language', $language )
|
||||
->run()
|
||||
->model( 'AIOSEO\Plugin\Common\Models\WritingAssistantKeyword' );
|
||||
|
||||
if ( ! $dbKeyword->exists() ) {
|
||||
$dbKeyword->keyword = $keyword;
|
||||
$dbKeyword->country = $country;
|
||||
$dbKeyword->language = $language;
|
||||
}
|
||||
|
||||
return $dbKeyword;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Models;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Posts.
|
||||
*
|
||||
* @since 4.7.4
|
||||
*/
|
||||
class WritingAssistantPost extends Model {
|
||||
/**
|
||||
* The name of the table in the database, without the prefix.
|
||||
*
|
||||
* @since 4.7.4
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'aioseo_writing_assistant_posts';
|
||||
|
||||
/**
|
||||
* Fields that should be integer values.
|
||||
*
|
||||
* @since 4.7.4
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $integerFields = [ 'id', 'post_id', 'keyword_id' ];
|
||||
|
||||
/**
|
||||
* Fields that should be boolean values.
|
||||
*
|
||||
* @since 4.7.4
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $booleanFields = [];
|
||||
|
||||
/**
|
||||
* Fields that should be encoded/decoded on save/get.
|
||||
*
|
||||
* @since 4.7.4
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $jsonFields = [ 'content_analysis' ];
|
||||
|
||||
/**
|
||||
* Gets a post's content analysis.
|
||||
*
|
||||
* @since 4.7.4
|
||||
*
|
||||
* @param int $postId A post ID.
|
||||
* @return array The post content's analysis.
|
||||
*/
|
||||
public static function getContentAnalysis( $postId ) {
|
||||
$post = self::getPost( $postId );
|
||||
|
||||
return ! empty( $post->content_analysis ) && is_object( $post->content_analysis ) ? (array) $post->content_analysis : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a writing assistant post.
|
||||
*
|
||||
* @since 4.7.4
|
||||
*
|
||||
* @param int $postId A post ID.
|
||||
* @return WritingAssistantPost The post object.
|
||||
*/
|
||||
public static function getPost( $postId ) {
|
||||
$post = aioseo()->core->db->start( 'aioseo_writing_assistant_posts' )
|
||||
->where( 'post_id', $postId )
|
||||
->run()
|
||||
->model( 'AIOSEO\Plugin\Common\Models\WritingAssistantPost' );
|
||||
|
||||
if ( ! $post->exists() ) {
|
||||
$post->post_id = $postId;
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a post's current keyword.
|
||||
*
|
||||
* @since 4.7.4
|
||||
*
|
||||
* @param int $postId A post ID.
|
||||
* @return WritingAssistantKeyword|bool An attached keyword.
|
||||
*/
|
||||
public static function getKeyword( $postId ) {
|
||||
$post = self::getPost( $postId );
|
||||
if ( ! $post->exists() || empty( $post->keyword_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$keyword = aioseo()->core->db->start( 'aioseo_writing_assistant_keywords' )
|
||||
->where( 'id', $post->keyword_id )
|
||||
->run()
|
||||
->model( 'AIOSEO\Plugin\Common\Models\WritingAssistantKeyword' );
|
||||
|
||||
// This is here so this property is reactive in the frontend.
|
||||
if ( ! empty( $keyword->keywords ) ) {
|
||||
foreach ( $keyword->keywords as &$keyph ) {
|
||||
$keyph->contentCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Help sorting in the frontend.
|
||||
if ( ! empty( $keyword->competitors->competitors ) ) {
|
||||
foreach ( $keyword->competitors->competitors as &$competitor ) {
|
||||
$competitor->wasAnalyzed = true;
|
||||
if ( 0 >= $competitor->wordCount ) {
|
||||
$competitor->wordCount = 0;
|
||||
$competitor->readabilityScore = 999;
|
||||
$competitor->readabilityGrade = '';
|
||||
$competitor->gradeScore = 0;
|
||||
$competitor->grade = '';
|
||||
$competitor->wasAnalyzed = false;
|
||||
}
|
||||
|
||||
$competitor->readabilityScore = (float) $competitor->readabilityScore;
|
||||
}
|
||||
}
|
||||
|
||||
return $keyword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if a post has a keyword.
|
||||
*
|
||||
* @since 4.7.4
|
||||
*
|
||||
* @param int $postId A post ID.
|
||||
* @return boolean Has a keyword.
|
||||
*/
|
||||
public static function hasKeyword( $postId ) {
|
||||
$post = self::getPost( $postId );
|
||||
|
||||
return (bool) $post->keyword_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a keyword to a post.
|
||||
*
|
||||
* @since 4.7.4
|
||||
*
|
||||
* @param int $keywordId The keyword ID.
|
||||
* @return void
|
||||
*/
|
||||
public function attachKeyword( $keywordId ) {
|
||||
$this->keyword_id = $keywordId;
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user