diff --git a/docs/site.md b/docs/site.md index 6c1a030..4da10fb 100644 --- a/docs/site.md +++ b/docs/site.md @@ -68,7 +68,7 @@ $site->configure(function() { }); ``` -This approach of passing a function to `conifigure()` has a couple advantages: +This approach of passing a function to `configure()` has a couple advantages: - You don't have to define your own `Site` subclass. - Wrapping your config code in a closure avoids polluting the global namespace, but also allows it to remain in the familiar functions.php rather than in a random library class, where newcomers to your codebase may not know to look. @@ -101,7 +101,7 @@ $site->configure(); ## Disabling defaults -If you want to *only* run your custom config callback without funning Conifer's default config code, you can pass `false` as the second argument to `configure()`, which tells Conifer to disable its defaults: +If you want to *only* run your custom config callback without running Conifer's default configuration code, pass `false` as the second argument to `configure()`: ```php $site->configure(function() { /* ... */ }, false); @@ -119,6 +119,32 @@ $site->configure(function() { This closes all comments on the frontend, hides all existing comments on all posts on the frontend and within WP Admin, and removes comment management pages from WP Admin. +## Disabling Tags + +Many sites do not need tag support on every post type. Use `disable_tags_for_post_types()` to remove the built-in tag taxonomy from the post types you specify: + +```php +$site->configure(function() { + $postTypes = ['robot', 'android', 'cyborg']; + + $this->disable_tags_for_post_types($postTypes); +}); +``` + +This removes tag support and the `post_tag` taxonomy association only from the listed post types. Tags remain available on all other post types. + +You can also remove tags for all post types using the included `disable_tags` function. + +```php +$site->configure(function() { + //... + + // Tags will be disabled for all registered post types + $this->disable_tags(); + + //... +}); + ## Directory Cascades A **Directory Cascade** is an ordered list of directories where Conifer looks for Twig views (`*.twig` files), JS files, or stylesheets. Each type of asset (views, JS, CSS) has its own set of directories - its own *cascade* - where Conifer looks for that type of file. Each cascade has its own getter and setter on the `Site` class: @@ -158,7 +184,7 @@ $site->configure(function() { ### Finding generic file paths -Under the hood, the script and style cascades call the generic `file_file()` method, which simply traverses a list of directories looking for a relative file path within each. It returns the full path of the first existing file it finds: +Under the hood, the script and style cascades call the generic `find_file()` method, which traverses a list of directories looking for a relative file path within each. It returns the full path of the first existing file it finds: ```php // directory tree: diff --git a/lib/Conifer/Site.php b/lib/Conifer/Site.php index d9cbab3..e22e155 100644 --- a/lib/Conifer/Site.php +++ b/lib/Conifer/Site.php @@ -157,10 +157,13 @@ public function configure_defaults() Button::register('button'); Integrations\YoastIntegration::demote_metabox(); + // TODO moar integrations! } /** * Register default Post Class Maps for default Conifer classes + * + * @todo Terms/Users */ public function configure_default_classmaps() { @@ -530,6 +533,7 @@ public function add_default_twig_helpers() public function configure_default_admin_dashboard_widgets() { add_action('wp_dashboard_setup', function () { + // TODO widget API? wp_add_dashboard_widget( 'conifer_guide', __('Welcome to Conifer'), @@ -546,6 +550,7 @@ function () { public function remove_conifer_widget() { add_action('wp_dashboard_setup', function () { + // TODO widget API? remove_meta_box('conifer_guide', 'dashboard', 'normal'); }); } @@ -826,4 +831,74 @@ public function disable_comments() add_filter('comments_open', '__return_false', 20); add_filter('pings_open', '__return_false', 20); } + + /** + * Removes the tag taxonomy from the specified post types. + * + * @param array $postTypes Post types to remove the tag taxonomy from. + * @return void + */ + public function disable_tags_for_post_types(array $postTypes): void + { + // Attempt to disable tags for already registered taxonomies. + foreach ($postTypes as $postType) { + $this->disable_tags_for_post_type($postType); + } + + // If called before init, core taxonomies may not be registered yet; retry after init. + if (did_action('init') && !doing_action('init')) { + return; + } + + add_action('init', function () use ($postTypes) { + foreach ($postTypes as $postType) { + $this->disable_tags_for_post_type($postType); + } + }, PHP_INT_MAX); + } + + /** + * Removes the tag taxonomy from all post types except the provided post types. + * + * @param array $excludedPostTypes Post types to exclude from tag removal. + * Defaults to ['tribe_events']. + * @return void + */ + private function disable_tags(array $excludedPostTypes = ['tribe_events']): void + { + $types = get_post_types([], 'names'); + + $filteredPostTypes = array_filter($types, function ($type) use ($excludedPostTypes) { + return !in_array($type, $excludedPostTypes, true); + }); + + // Hide Tags from the admin menu. + // ? This is intended to be a universal solution, but if we provide a public facing function to disable tag support for specific post types, we should + // ? probably reconsider globally removing the tags menu item. If downstream someone explicitly calls disable_tags_for_post_types(['post']) + // ? they may not expect the menu item to be removed for all post types. + add_action('admin_menu', function () { + remove_submenu_page('edit.php', 'edit-tags.php?taxonomy=post_tag'); + }); + + // Taxonomies are registered during init, so remove associations after it runs. + if (did_action('init')) { + $this->disable_tags_for_post_types($filteredPostTypes); + return; + } + + add_action('init', function () use ($filteredPostTypes) { + $this->disable_tags_for_post_types($filteredPostTypes); + }); + } + + /** + * Removes the tag taxonomy from a single post type. + * + * @param string $postType Post type to remove the tag taxonomy from. + * @return bool True if the taxonomy association was removed, false otherwise. + */ + private function disable_tags_for_post_type(string $postType): bool + { + return unregister_taxonomy_for_object_type('post_tag', $postType); + } } diff --git a/test/integration/SiteTagRemovalTest.php b/test/integration/SiteTagRemovalTest.php new file mode 100644 index 0000000..44b89d9 --- /dev/null +++ b/test/integration/SiteTagRemovalTest.php @@ -0,0 +1,91 @@ +postTypesWithTags = array_filter( + get_post_types([], 'names'), + function (string $postType): bool { + return is_object_in_taxonomy($postType, 'post_tag'); + } + ); + + register_post_type(self::POST_TYPE_ONE, [ + 'public' => true, + 'taxonomies' => ['post_tag'], + ]); + register_post_type(self::POST_TYPE_TWO, [ + 'public' => true, + 'taxonomies' => ['post_tag'], + ]); + register_post_type(self::EXCLUDED_POST_TYPE, [ + 'public' => true, + 'taxonomies' => ['post_tag'], + ]); + } + + public function tearDown(): void + { + foreach ([self::POST_TYPE_ONE, self::POST_TYPE_TWO, self::EXCLUDED_POST_TYPE] as $postType) { + unregister_post_type($postType); + } + + foreach ($this->postTypesWithTags as $postType) { + register_taxonomy_for_object_type('post_tag', $postType); + } + + parent::tearDown(); + } + + public function test_disable_tags_for_post_types_removes_tags_only_from_specified_post_types() + { + $this->site->disable_tags_for_post_types([self::POST_TYPE_ONE]); + + $this->assertFalse(is_object_in_taxonomy(self::POST_TYPE_ONE, 'post_tag')); + $this->assertTrue(is_object_in_taxonomy(self::POST_TYPE_TWO, 'post_tag')); + } + + public function test_disable_tags_excludes_tribe_events_by_default() + { + $this->site->configure(function () { + $this->disable_tags(); + }, false); + + $this->assertFalse(is_object_in_taxonomy(self::POST_TYPE_ONE, 'post_tag')); + $this->assertTrue(is_object_in_taxonomy(self::EXCLUDED_POST_TYPE, 'post_tag')); + } + + public function test_disable_tags_honors_custom_excluded_post_types() + { + $excludedPostTypes = [self::POST_TYPE_ONE]; + $this->site->configure(function () use ($excludedPostTypes) { + $this->disable_tags($excludedPostTypes); + }, false); + + $this->assertTrue(is_object_in_taxonomy(self::POST_TYPE_ONE, 'post_tag')); + $this->assertFalse(is_object_in_taxonomy(self::POST_TYPE_TWO, 'post_tag')); + } +}