getMessage() ); } } self::$running = false; } /** * @param array $old * @param array $new * @return bool */ private static function entity_input_changed( $old, $new ) { foreach ( self::$entity_keys as $key ) { $o = array_key_exists( $key, $old ) ? $old[ $key ] : null; $n = array_key_exists( $key, $new ) ? $new[ $key ] : null; if ( $o !== $n ) { return true; } } return false; } /** * Rebuild the homepage's stored entity node from current settings and * invalidate its cache. */ private static function refresh_homepage_entity() { if ( ! class_exists( 'Schema_AI_Entity_Schema_Builder' ) ) { return; } // Schema is only ever rendered on a static front page. if ( 'page' !== get_option( 'show_on_front' ) ) { return; } $home_id = (int) get_option( 'page_on_front' ); if ( $home_id <= 0 ) { return; } // Nothing configured yet → generation wouldn't inject an entity node // either, so there is nothing to keep in sync. if ( ! Schema_AI_Entity_Schema_Builder::should_inject( $home_id ) ) { return; } $fresh = Schema_AI_Entity_Schema_Builder::build( $home_id ); if ( empty( $fresh['@type'] ) || empty( $fresh['@id'] ) || empty( $fresh['name'] ) ) { // build() returns a bare stub when the homepage post has no usable // data (trashed / draft). Never overwrite a good stored node with // that. return; } global $wpdb; $table = esc_sql( $wpdb->prefix . 'schema_ai_schemas' ); $new_type = is_array( $fresh['@type'] ) ? $fresh['@type'][0] : $fresh['@type']; $new_json = wp_json_encode( $fresh, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- custom table, no WP API alternative. $rows = $wpdb->get_results( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name sanitized via esc_sql(). "SELECT id, schema_type, schema_data FROM {$table} WHERE post_id = %d", $home_id ) ); // Locate the stored entity row: the one whose decoded @id is the // site-wide entity @id. Type-independent, so it survives a retype // (e.g. Organization → WebApplication when a subtype is configured). $target = null; $entity_at_id = $fresh['@id']; $entity_types = array( 'Organization', 'LocalBusiness', 'Corporation' ); if ( class_exists( 'Schema_AI_Business_Subtype' ) ) { $entity_types[] = Schema_AI_Business_Subtype::get_type_from_settings(); } foreach ( (array) $rows as $row ) { $decoded = json_decode( $row->schema_data, true ); if ( ! is_array( $decoded ) ) { continue; } if ( isset( $decoded['@id'] ) && $decoded['@id'] === $entity_at_id ) { $target = $row; break; } } // Fallback for older rows that may predate a stable @id: match on an // entity-like @type, or the tell-tale PostalAddress. if ( null === $target ) { foreach ( (array) $rows as $row ) { $decoded = json_decode( $row->schema_data, true ); if ( ! is_array( $decoded ) ) { continue; } $type = isset( $decoded['@type'] ) ? ( is_array( $decoded['@type'] ) ? $decoded['@type'][0] : $decoded['@type'] ) : ''; if ( in_array( $type, $entity_types, true ) || ( isset( $decoded['address']['@type'] ) && 'PostalAddress' === $decoded['address']['@type'] ) ) { $target = $row; break; } } } $now = current_time( 'mysql' ); $status = Schema_AI_Entity_Schema_Builder::validation_status( $fresh ); if ( null !== $target ) { // Byte-identical → nothing to do. Don't churn the cache or nudge sync. if ( (string) $target->schema_data === (string) $new_json ) { return; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- custom table. $wpdb->update( $table, array( 'schema_type' => sanitize_text_field( $new_type ), 'schema_data' => $new_json, 'validation_status' => $status, 'updated_at' => $now, ), array( 'id' => (int) $target->id ), array( '%s', '%s', '%s', '%s' ), array( '%d' ) ); } else { // No entity row yet (business info just configured for the first // time, homepage generated before that). Add one. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- custom table. $wpdb->insert( $table, array( 'post_id' => $home_id, 'schema_type' => sanitize_text_field( $new_type ), 'schema_data' => $new_json, 'is_auto_generated' => 1, 'validation_status' => $status, 'created_at' => $now, 'updated_at' => $now, ), array( '%d', '%s', '%s', '%d', '%s', '%s', '%s' ) ); } if ( class_exists( 'Schema_AI_Schema_Cache' ) ) { $cache = new Schema_AI_Schema_Cache(); $cache->invalidate( $home_id ); } // Let the sync engine report the change to the dashboard, same as a // real generation would. do_action( 'schema_ai_schema_saved', $home_id ); } }