, override_applied: bool} $ownership Ownership decision. * @return string Transient name. */ public static function key( WP_Post $post, array $ownership ) { $signature = ''; foreach ( self::key_parts( $post, $ownership ) as $name => $value ) { $signature .= $name . '=' . $value . "\n"; } return self::post_prefix( (int) $post->ID ) . substr( hash( 'sha256', $signature ), 0, 32 ); } /** * Returns the named values the cache key is derived from. * * Public because the key is the most correctness-critical string in this * module - the guarantee that no stale payload prints rests on it - and a * hash is not inspectable. Keeping the inputs readable means they can be * asserted and diagnosed rather than inferred from a hex digest. * * @since 1.3.0 * * @param WP_Post $post Post being evaluated. * @param array{state: string, reason_code: string, owner: string|null, evidence: array, override_applied: bool} $ownership Ownership decision. * @return array Key inputs, keyed by name. */ public static function key_parts( WP_Post $post, array $ownership ) { $author_id = (int) $post->post_author; return array( 'shape' => self::SHAPE_VERSION, 'build' => defined( 'AIVE_VERSION' ) ? (string) AIVE_VERSION : '', 'post' => (int) $post->ID, 'modified' => (string) $post->post_modified_gmt, 'status' => (string) $post->post_status, 'type' => (string) $post->post_type, 'content' => hash( 'sha256', (string) $post->post_content . "\0" . (string) $post->post_title . "\0" . (string) $post->post_excerpt ), 'settings' => self::settings_hash(), 'locale' => (string) get_locale(), 'home' => (string) home_url( '/' ), 'site_name' => (string) get_bloginfo( 'name' ), 'logo' => (string) get_theme_mod( 'custom_logo' ), 'icon' => (string) get_option( 'site_icon', '' ), 'theme' => (string) get_stylesheet(), 'thumbnail' => (string) get_post_thumbnail_id( $post ), 'author' => $author_id . '|' . (string) get_the_author_meta( 'display_name', $author_id ) . '|' . (string) get_author_posts_url( $author_id ), 'permalink' => (string) get_permalink( $post ), 'ownership' => SchemaOwnership::signature( $ownership ), ); } /** * Returns the transient prefix every entry for one post shares. * * @since 1.3.0 * * @param int $post_id Post id. * @return string Transient name prefix. */ public static function post_prefix( $post_id ) { return self::PREFIX . 'p' . (int) $post_id . '_'; } /** * Returns a cached payload, or null when there is none to use. * * @since 1.3.0 * * @param string $key Transient name. * @return array{objects: array>, diagnostics: array, faq_source: string, howto_source: string}|null Cached payload, or null. */ public static function get( $key ) { $cached = get_transient( (string) $key ); if ( ! is_array( $cached ) || ! isset( $cached['shape'], $cached['objects'] ) ) { return null; } if ( self::SHAPE_VERSION !== (int) $cached['shape'] || ! is_array( $cached['objects'] ) ) { return null; } return array( 'objects' => $cached['objects'], 'diagnostics' => isset( $cached['diagnostics'] ) && is_array( $cached['diagnostics'] ) ? $cached['diagnostics'] : array(), 'faq_source' => isset( $cached['faq_source'] ) ? (string) $cached['faq_source'] : 'none', 'howto_source' => isset( $cached['howto_source'] ) ? (string) $cached['howto_source'] : 'none', ); } /** * Stores a payload and returns it unchanged. * * @since 1.3.0 * * @param string $key Transient name. * @param array{objects: array>, diagnostics: array, faq_source: string, howto_source: string} $payload Generated payload. * @return array{objects: array>, diagnostics: array, faq_source: string, howto_source: string} The same payload. */ public static function set( $key, array $payload ) { $stored = $payload; $stored['shape'] = self::SHAPE_VERSION; set_transient( (string) $key, $stored, self::TTL ); return $payload; } /** * Deletes every cached payload for one post. * * Registered against the post lifecycle hooks, which pass arguments this * does not declare; PHP ignores them. * * @since 1.3.0 * * @param int|mixed $post_id Post id. * @return void */ public static function flush_post( $post_id ) { $post_id = (int) $post_id; if ( $post_id <= 0 ) { return; } self::delete_by_prefix( self::post_prefix( $post_id ) ); } /** * Deletes every cached schema payload on the site. * * Used by the site-wide invalidators: settings, site identity, theme, * locale and the activation or deactivation of a supported SEO plugin. Each * of those changes the key for every post at once, so there is nothing * narrower to delete. * * @since 1.3.0 * * @return void */ public static function flush_all() { self::delete_by_prefix( self::PREFIX ); } /** * Deletes the transients whose names start with a prefix. * * Goes through `delete_transient()` rather than a bulk DELETE so that a * persistent object cache drops its copy as well, and deletes an orphaned * timeout row under its own full option name. * * @since 1.3.0 * * @param string $prefix Transient name prefix. * @return void */ private static function delete_by_prefix( $prefix ) { global $wpdb; if ( ! isset( $wpdb ) || ! is_object( $wpdb ) ) { return; } $like = $wpdb->esc_like( '_transient_' . $prefix ) . '%'; $names = $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Transient inventory by prefix; there is no cached equivalent and no API for it. $wpdb->prepare( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s", $like ) ); foreach ( (array) $names as $name ) { delete_transient( substr( (string) $name, strlen( '_transient_' ) ) ); } $orphans = $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Orphaned timeout rows carry no transient of their own to delete. $wpdb->prepare( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like( '_transient_timeout_' . $prefix ) . '%' ) ); foreach ( (array) $orphans as $name ) { delete_option( (string) $name ); } } /** * Returns a hash of the settings that affect generated schema. * * The whole effective settings array is hashed rather than the schema keys * alone. It is one short hash either way, and hashing the whole array means * a setting introduced by a later module cannot be forgotten here. * * @since 1.3.0 * * @return string Hash of the effective settings. */ private static function settings_hash() { $encoded = wp_json_encode( Helpers::get_settings() ); return hash( 'sha256', is_string( $encoded ) ? $encoded : '' ); } }