booted ) { return; } $this->booted = true; add_action( 'plugins_loaded', array( $this, 'boot_features' ) ); } /** * Construct and boot the enabled features. * * @return void */ public function boot_features() { // Order matters: the rename brings 1.1.0's settings under the current option name, and only // then can the v1 cleanup see them. LegacyNames::maybe_migrate(); Settings::maybe_migrate(); $this->registry()->boot(); if ( is_admin() ) { $this->attach_surface(); $this->settings_page()->boot(); } // Registered unconditionally: REST requests do not run through the admin. $rest = new Admin\RestController( $this->registry(), $this->settings_page() ); $rest->boot(); // Same reasoning: an ability can be invoked by anything, not only by the admin. The // registrar itself decides whether this core has the API at all. $this->abilities()->boot(); } /** * The ability registrar. * * @return Abilities */ public function abilities() { if ( null === $this->abilities ) { $this->abilities = new Abilities( $this->registry() ); } return $this->abilities; } /** * Hand the admin surface to every booted feature that draws on a list screen. * * If no such feature is enabled the surface is never booted, so it registers nothing — the * zero-hook guarantee holds for the surface exactly as it does for a feature. * * @return void */ private function attach_surface() { $surface = $this->surface(); $attached = false; foreach ( $this->registry()->booted() as $feature_id ) { $feature = $this->registry()->get( $feature_id ); if ( $feature instanceof Admin\SurfaceAware ) { $feature->register_surface( $surface ); $attached = true; } } if ( $attached ) { $surface->boot(); } } /** * The admin surface. * * One implementation today. When core moves the posts screen to DataViews, this method picks * the other one and no feature notices. * * @return Admin\AdminSurface */ public function surface() { if ( null === $this->surface ) { $this->surface = new Admin\ClassicListTable(); } return $this->surface; } /** * The settings screen. * * Not a Feature: it must load even when every feature is off, or nothing could ever be * switched on. It is also the way back out of Installation Lockdown. * * @return Admin\SettingsPage */ public function settings_page() { if ( null === $this->settings_page ) { $this->settings_page = new Admin\SettingsPage( $this->registry() ); } return $this->settings_page; } /** * Get the feature registry, populating it on first access. * * @return FeatureRegistry */ public function registry() { if ( null === $this->registry ) { $this->registry = new FeatureRegistry(); foreach ( $this->feature_classes() as $group => $class_names ) { foreach ( $class_names as $class_name ) { if ( class_exists( $class_name ) ) { $this->registry->register( new $class_name(), $group ); } } } /** * Lets an add-on register its own features alongside the core's. * * Fires once, when the registry is first populated, after every core feature is * registered and before anything boots. Register with the group key the feature * belongs under, e.g. `$registry->register( $feature, 'site_lock' )`. * * @param FeatureRegistry $registry The feature registry. */ do_action( 'adminkeep_register_features', $this->registry ); } return $this->registry; } /** * The features this build ships. * * Two groups under one idea — you decide what changes on this site. Site Lock stops things * changing behind your back; the content features make deliberate change safe. * * **The group keys are data, not decoration.** The settings screen renders one nav heading * per group from this array. When Group A shipped in 2.2.0 the split lived in a code comment * here while the screen had a single hardcoded "Site Lock" heading, so all five content * features were listed under it. Keep the grouping in one place and that cannot recur. * * @return array Group key => feature class names. */ private function feature_classes() { return array( 'site_lock' => array( 'Adminkeep\\Feature\\DisableComments', 'Adminkeep\\Feature\\RegistrationLock', 'Adminkeep\\Feature\\DisableXmlRpc', 'Adminkeep\\Feature\\DisableFileEdit', 'Adminkeep\\Feature\\InstallLock', ), 'content' => array( 'Adminkeep\\Feature\\Duplicate', 'Adminkeep\\Feature\\LiveDraft', 'Adminkeep\\Feature\\KeepUrl', 'Adminkeep\\Feature\\Order', 'Adminkeep\\Feature\\ReplaceMedia', ), 'appearance' => array( 'Adminkeep\\Feature\\CustomCss', ), 'admin' => array( 'Adminkeep\\Feature\\UserColumns', ), ); } /** * Translated heading for each feature group, in display order. * * Runs at render time, so translated strings are safe here — unlike settings_schema(). * * @return array Group key => heading. */ public static function group_labels() { return array( 'site_lock' => __( 'Site Lock', 'adminkeep' ), 'content' => __( 'Content', 'adminkeep' ), 'appearance' => __( 'Appearance', 'adminkeep' ), 'admin' => __( 'Admin', 'adminkeep' ), ); } }