0 && time() > $row['x'] ) { delete_option( self::PREFIX . $key ); return false; } return $row['v']; } /** * @param string $key * @param mixed $value * @param int $ttl Seconds; 0 = never expires. * @return bool */ public static function set( $key, $value, $ttl = 0 ) { $expires = $ttl > 0 ? time() + (int) $ttl : 0; // Never autoloaded: these are short-lived and per-key, not something // every page load needs. return update_option( self::PREFIX . $key, [ 'v' => $value, 'x' => $expires ], false ); } /** * @param string $key * @return bool */ public static function delete( $key ) { return delete_option( self::PREFIX . $key ); } /** * Sweep expired rows. Hooked to the existing daily prune-logs cron so no * new scheduled event is needed. */ public static function prune() { global $wpdb; // Our own prefixed rows only, batched so a large table can't time out // a single request; direct query because WP has no "delete options // matching a pattern" API, and this isn't a per-request path. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter $names = $wpdb->get_col( $wpdb->prepare( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s LIMIT 500", $wpdb->esc_like( self::PREFIX ) . '%' ) ); foreach ( $names as $name ) { $row = get_option( $name ); if ( ! is_array( $row ) || ! isset( $row['x'] ) || ( $row['x'] > 0 && time() > $row['x'] ) ) { delete_option( $name ); } } } }