}> $terms Terms to create, default first. */ private static function seed( string $taxonomy, array $terms ): void { $default = RequestTerms::default_term( $taxonomy ); if ( $default && term_exists( $default, $taxonomy ) ) { return; } // Terms exist but the default was lost. Use the oldest term rather than adding new ones. if ( RequestTerms::promote_oldest_term( $taxonomy ) ) { return; } $created = []; foreach ( $terms as $term ) { $result = wp_insert_term( $term['name'], $taxonomy, [ 'slug' => $term['slug'] ] ); if ( is_wp_error( $result ) ) { continue; } foreach ( $term['meta'] as $key => $value ) { update_term_meta( (int) $result['term_id'], $key, $value ); } $created[] = (int) $result['term_id']; } if ( $created ) { RequestTerms::set_default_term( $taxonomy, $created[0] ); } } /** * Claims the install lock so that two requests never install at the same time. */ private static function acquire_lock(): bool { global $wpdb; // INSERT IGNORE is atomic: when two requests race, only one creates the row. $created = $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- The lock must bypass the options cache. $wpdb->prepare( 'INSERT IGNORE INTO %i (option_name, option_value, autoload) VALUES (%s, %s, %s)', $wpdb->options, self::LOCK_OPTION, (string) time(), 'off' ) ); if ( $created ) { return true; } $locked_at = (int) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- The lock must bypass the options cache. $wpdb->prepare( 'SELECT option_value FROM %i WHERE option_name = %s', $wpdb->options, self::LOCK_OPTION ) ); if ( $locked_at > time() - self::LOCK_TIMEOUT ) { return false; } // The request holding the lock died mid-install. Take over. $wpdb->update( $wpdb->options, [ 'option_value' => (string) time() ], [ 'option_name' => self::LOCK_OPTION ] ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- The lock must bypass the options cache. return true; } /** * Releases the install lock. */ private static function release_lock(): void { global $wpdb; $wpdb->delete( $wpdb->options, [ 'option_name' => self::LOCK_OPTION ] ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- The lock must bypass the options cache. } }