features[ $feature->id() ] = $feature; $this->groups[ $feature->id() ] = $group; } /** * Registered features split into their groups, in registration order. * * Groups with no registered features are omitted, so a build that ships only the locks * renders one heading rather than an empty second one. * * @return array Group key => features keyed by id. */ public function grouped() { $grouped = array(); foreach ( $this->features as $id => $feature ) { $group = isset( $this->groups[ $id ] ) ? $this->groups[ $id ] : 'site_lock'; $grouped[ $group ][ $id ] = $feature; } return $grouped; } /** * All registered features. * * @return Feature[] */ public function all() { return $this->features; } /** * Get one registered feature. * * @param string $feature_id Feature id. * @return Feature|null */ public function get( $feature_id ) { return isset( $this->features[ $feature_id ] ) ? $this->features[ $feature_id ] : null; } /** * Ids of features that were booted. * * @return string[] */ public function booted() { return $this->booted; } /** * Boot every enabled feature. * * Unknown feature ids in stored settings are ignored rather than fatal — a downgrade or a * removed feature must not take the site down. * * @return void */ public function boot() { foreach ( $this->features as $id => $feature ) { if ( ! Settings::is_enabled( $id ) ) { continue; } $feature->boot( $this->resolve_settings( $feature ) ); $this->booted[] = $id; } } /** * Merge stored values over the feature's schema defaults. * * A stored value of the wrong shape falls back to the default instead of persisting. * * @param Feature $feature Feature instance. * @return array */ public function resolve_settings( Feature $feature ) { $stored = Settings::get( $feature->id() ); $resolved = array(); foreach ( $feature->settings_schema() as $key => $spec ) { $default = isset( $spec['default'] ) ? $spec['default'] : null; $type = isset( $spec['type'] ) ? $spec['type'] : 'string'; if ( ! array_key_exists( $key, $stored ) ) { $resolved[ $key ] = $default; continue; } $resolved[ $key ] = $this->coerce( $stored[ $key ], $type, $default ); } return $resolved; } /** * Coerce a stored value to its declared type, falling back to the default. * * @param mixed $value Stored value. * @param string $type Declared type. * @param mixed $default Declared default. * @return mixed */ private function coerce( $value, $type, $default ) { switch ( $type ) { case 'bool': return (bool) $value; case 'int': return is_numeric( $value ) ? (int) $value : $default; case 'array': return is_array( $value ) ? $value : $default; case 'string': default: return is_scalar( $value ) ? (string) $value : $default; } } }