get_results(
$wpdb->prepare(
"SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s LIMIT %d",
NullAdapter::META_KEY,
$limit
)
);
$moved = 0;
foreach ( (array) $rows as $row ) {
$post_id = (int) $row->post_id;
// Was ein Redakteur im SEO-Plugin bereits gesetzt hat, gewinnt.
if ( '' === $adapter->read( $post_id ) ) {
$adapter->write( $post_id, (string) $row->meta_value );
++$moved;
}
delete_post_meta( $post_id, NullAdapter::META_KEY );
}
return $moved;
}
/**
* Haengt die Kennzeichnung an ein einzelnes Graph-Stueck (Yoast).
*
* @param mixed $piece Graph-Stueck.
* @return mixed
*/
public static function mark_piece( $piece ) {
if ( ! is_array( $piece ) || ! self::current_post_is_ai() ) {
return $piece;
}
$piece['digitalSourceType'] = AiDisclosure::DIGITAL_SOURCE_TYPE;
return $piece;
}
/**
* Rank Math: Entitaeten liegen unter frei benannten Schluesseln.
*
* @param mixed $data JSON-LD-Daten.
* @return mixed
*/
public static function mark_rank_math( $data ) {
if ( ! is_array( $data ) || ! self::current_post_is_ai() ) {
return $data;
}
foreach ( $data as $key => $entity ) {
if ( is_array( $entity ) && self::is_creative_work( $entity ) ) {
$data[ $key ]['digitalSourceType'] = AiDisclosure::DIGITAL_SOURCE_TYPE;
}
}
return $data;
}
/**
* AIOSEO: eine Liste von Graph-Stuecken.
*
* @param mixed $graphs Graph-Stuecke.
* @return mixed
*/
public static function mark_graph_list( $graphs ) {
if ( ! is_array( $graphs ) || ! self::current_post_is_ai() ) {
return $graphs;
}
foreach ( $graphs as $index => $graph ) {
if ( is_array( $graph ) && self::is_creative_work( $graph ) ) {
$graphs[ $index ]['digitalSourceType'] = AiDisclosure::DIGITAL_SOURCE_TYPE;
}
}
return $graphs;
}
/**
* The SEO Framework: Liste wie bei AIOSEO, nur fuer die aktuelle Anfrage.
*
* @param mixed $graph Graph-Stuecke.
* @param array|null $args Null, wenn TSF die aktuelle Anfrage beschreibt.
* @return mixed
*/
public static function mark_tsf_graph( $graph, $args = null ) {
return null === $args ? self::mark_graph_list( $graph ) : $graph;
}
/**
* Head-Ausgabe, wenn kein SEO-Plugin die Aufgabe uebernimmt.
*
* Ohne SEO-Plugin gibt es die Meta-Description sonst nirgends; ohne
* passende Schema-Entitaet fehlte die maschinenlesbare Kennzeichnung.
*
* @return void
*/
public static function print_head(): void {
if ( ! self::current_post_is_ai() ) {
return;
}
$post_id = (int) get_queried_object_id();
$adapter = self::active();
if ( $adapter instanceof NullAdapter ) {
$description = $adapter->read( $post_id );
/**
* Ob das Plugin ohne SEO-Plugin selbst eine Meta-Description ausgibt.
*
* @param bool $print Standard true.
*/
if ( '' !== $description && apply_filters( 'aivor_content_print_meta_description', true ) ) {
printf( "\n", esc_attr( $description ) );
}
}
if ( $adapter instanceof NullAdapter || $adapter instanceof SeopressAdapter ) {
$data = array(
'@context' => 'https://schema.org',
'@type' => 'WebPage',
'url' => get_permalink( $post_id ),
'name' => wp_strip_all_tags( get_the_title( $post_id ) ),
'digitalSourceType' => AiDisclosure::DIGITAL_SOURCE_TYPE,
);
// JSON_HEX_TAG und JSON_HEX_AMP machen den Block sicher fuer einen
// script-Kontext; esc_html() wuerde das JSON zerstoeren.
printf(
"\n",
wp_json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- siehe oben.
);
}
}
/**
* Erkennung in fester Reihenfolge.
*
* @return SeoAdapter
*/
private static function detect(): SeoAdapter {
if ( defined( 'WPSEO_VERSION' ) ) {
return new YoastAdapter();
}
if ( defined( 'RANK_MATH_VERSION' ) && class_exists( '\\RankMath' ) ) {
return new RankMathAdapter();
}
if ( defined( 'AIOSEO_VERSION' ) ) {
return new AioseoAdapter();
}
if ( defined( 'SEOPRESS_VERSION' ) ) {
return new SeopressAdapter();
}
if ( defined( 'THE_SEO_FRAMEWORK_VERSION' ) || function_exists( 'tsf' ) ) {
return new TsfAdapter();
}
return new NullAdapter();
}
/**
* Ob die aktuelle Seite ein Beitrag von Aivor ist.
*
* @return bool
*/
private static function current_post_is_ai(): bool {
if ( is_admin() || ! is_singular() ) {
return false;
}
$post_id = (int) get_queried_object_id();
return AiDisclosure::is_ai_generated( $post_id ) && '' === (string) get_post_field( 'post_password', $post_id );
}
/**
* Ob eine Entitaet ein passender CreativeWork-Typ ist.
*
* @param array $entity Entitaet.
* @return bool
*/
private static function is_creative_work( array $entity ): bool {
$types = (array) ( $entity['@type'] ?? array() );
return array() !== array_intersect( $types, self::CREATIVE_WORK_TYPES );
}
}