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 ); } }