summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Readme.md77
-rw-r--r--composer.json15
-rw-r--r--src/Columns.php234
-rw-r--r--src/PostType.php535
-rw-r--r--src/Taxonomy.php376
6 files changed, 1238 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..61ead86
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/vendor
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..372edc1
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,77 @@
+# PostTypes
+
+> Simple WordPress custom post types.
+
+## Requirements
+
+* PHP >=7.2
+* [Composer](https://getcomposer.org/)
+* [WordPress](https://wordpress.org) >=5.1
+
+## Installation
+
+#### Install with composer
+
+Run the following in your terminal to install PostTypes with [Composer](https://getcomposer.org/).
+
+```
+$ composer require dweipert/posttypes
+```
+
+PostTypes uses [PSR-4](https://www.php-fig.org/psr/psr-4/) autoloading and can be used with the Composer's autoloader. Below is a basic example of getting started, though your setup may be different depending on how you are using Composer.
+
+```php
+require __DIR__ . '/vendor/autoload.php';
+
+use PostTypes\PostType;
+
+$books = new PostType( 'book' );
+
+$books->register();
+```
+
+See Composer's [basic usage](https://getcomposer.org/doc/01-basic-usage.md#autoloading) guide for details on working with Composer and autoloading.
+
+## Basic Usage
+
+Below is a basic example of setting up a simple book post type with a genre taxonomy.
+
+```php
+// Require the Composer autoloader.
+require __DIR__ . '/vendor/autoload.php';
+
+// Import PostTypes.
+use PostTypes\PostType;
+use PostTypes\Taxonomy;
+
+// Create a book post type.
+$books = new PostType( 'book' );
+
+// Attach the genre taxonomy (which is created below).
+$books->taxonomy( 'genre' );
+
+// Hide the date and author columns.
+$books->columns()->hide( [ 'date', 'author' ] );
+
+// Set the Books menu icon.
+$books->icon( 'dashicons-book-alt' );
+
+// Register the post type to WordPress.
+$books->register();
+
+// Create a genre taxonomy.
+$genres = new Taxonomy( 'genre' );
+
+// Set options for the taxonomy.
+$genres->options( [
+ 'hierarchical' => false,
+] );
+
+// Register the taxonomy to WordPress.
+$genres->register();
+```
+
+## Notes
+
+* The class has no methods for making custom fields for post types, use [Advanced Custom Fields](https://advancedcustomfields.com)
+* Maintained under the [Semantic Versioning Guide](https://semver.org)
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..a20a7ef
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,15 @@
+{
+ "name": "dweipert/posttypes",
+ "description": "Simple WordPress custom post types.",
+ "keywords": ["wordpress", "post-types"],
+ "require": {
+ "php": ">=7.2"
+ },
+ "autoload": {
+ "psr-4": {
+ "PostTypes\\": "src/"
+ }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true
+}
diff --git a/src/Columns.php b/src/Columns.php
new file mode 100644
index 0000000..51f0cd7
--- /dev/null
+++ b/src/Columns.php
@@ -0,0 +1,234 @@
+<?php
+
+namespace PostTypes;
+
+/**
+ * Columns
+ *
+ * Used to help manage a post types columns in the admin table
+ */
+class Columns
+{
+ /**
+ * Holds an array of all the defined columns.
+ *
+ * @var array
+ */
+ public $items = [];
+
+ /**
+ * An array of columns to add.
+ *
+ * @var array
+ */
+ public $add = [];
+
+ /**
+ * An array of columns to hide.
+ *
+ * @var array
+ */
+ public $hide = [];
+
+ /**
+ * An array of columns to reposition.
+ *
+ * @var array
+ */
+ public $positions = [];
+
+ /**
+ * An array of custom populate callbacks.
+ *
+ * @var array
+ */
+ public $populate = [];
+
+ /**
+ * An array of columns that are sortable.
+ *
+ * @var array
+ */
+ public $sortable = [];
+
+ /**
+ * Set the all columns
+ * @param array $columns an array of all the columns to replace
+ */
+ public function set($columns)
+ {
+ $this->items = $columns;
+ }
+
+ /**
+ * Add a new column
+ * @param string $column the slug of the column
+ * @param string $label the label for the column
+ */
+ public function add($columns, $label = null)
+ {
+
+ if (!is_array($columns)) {
+ $columns = [$columns => $label];
+ }
+
+ foreach ($columns as $column => $label) {
+ if (is_null($label)) {
+ $label = str_replace(['_', '-'], ' ', ucfirst($column));
+ }
+
+ $this->add[$column] = $label;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a column to hide
+ * @param string $column the slug of the column to hdie
+ */
+ public function hide($columns)
+ {
+ if (!is_array($columns)) {
+ $columns = [$columns];
+ }
+
+ foreach ($columns as $column) {
+ $this->hide[] = $column;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set a custom callback to populate a column
+ * @param string $column the column slug
+ * @param mixed $callback callback function
+ */
+ public function populate($column, $callback)
+ {
+ $this->populate[$column] = $callback;
+
+ return $this;
+ }
+
+ /**
+ * Define the postion for a columns
+ * @param string $columns an array of columns
+ */
+ public function order($columns)
+ {
+ foreach ($columns as $column => $position) {
+ $this->positions[$column] = $position;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set columns that are sortable
+ * @param string $column the slug of the column
+ * @param string $meta_value the meta_value to orderby
+ * @param boolean $is_num whether to order by string/number
+ */
+ public function sortable($sortable)
+ {
+ foreach ($sortable as $column => $options) {
+ $this->sortable[$column] = $options;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Check if an orderby field is a custom sort option.
+ * @param string $orderby the orderby value from query params
+ */
+ public function isSortable($orderby)
+ {
+ if (is_string($orderby) && array_key_exists($orderby, $this->sortable)) {
+ return true;
+ }
+
+ foreach ($this->sortable as $column => $options) {
+ if (is_string($options) && $options === $orderby) {
+ return true;
+ }
+ if (is_array($options) && isset($options[0]) && $options[0] === $orderby) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get meta key for an orderby.
+ * @param string $orderby the orderby value from query params
+ */
+ public function sortableMeta($orderby)
+ {
+ if (array_key_exists($orderby, $this->sortable)) {
+ return $this->sortable[$orderby];
+ }
+
+ foreach ($this->sortable as $column => $options) {
+ if (is_string($options) && $options === $orderby) {
+ return $options;
+ }
+ if (is_array($options) && isset($options[0]) && $options[0] === $orderby) {
+ return $options;
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * Modify the columns for the object
+ * @param array $columns WordPress default columns
+ * @return array The modified columns
+ */
+ public function modifyColumns($columns)
+ {
+ // if user defined set columns, return those
+ if (!empty($this->items)) {
+ return $this->items;
+ }
+
+ // add additional columns
+ if (!empty($this->add)) {
+ foreach ($this->add as $key => $label) {
+ $columns[$key] = $label;
+ }
+ }
+
+ // unset hidden columns
+ if (!empty($this->hide)) {
+ foreach ($this->hide as $key) {
+ unset($columns[$key]);
+ }
+ }
+
+ // if user has made added custom columns
+ if (!empty($this->positions)) {
+ foreach ($this->positions as $key => $position) {
+ // find index of the element in the array
+ $index = array_search($key, array_keys($columns));
+ // retrieve the element in the array of columns
+ $item = array_slice($columns, $index, 1);
+ // remove item from the array
+ unset($columns[$key]);
+
+ // split columns array into two at the desired position
+ $start = array_slice($columns, 0, $position, true);
+ $end = array_slice($columns, $position, count($columns) - 1, true);
+
+ // insert column into position
+ $columns = $start + $item + $end;
+ }
+ }
+
+ return $columns;
+ }
+}
diff --git a/src/PostType.php b/src/PostType.php
new file mode 100644
index 0000000..97edc02
--- /dev/null
+++ b/src/PostType.php
@@ -0,0 +1,535 @@
+<?php
+
+namespace PostTypes;
+
+use PostTypes\Columns;
+
+/**
+ * PostType
+ */
+class PostType
+{
+ /**
+ * The names passed to the PostType
+ * @var array
+ */
+ public $names;
+
+ /**
+ * The name for the PostType
+ * @var string
+ */
+ public $name;
+
+ /**
+ * The singular for the PostType
+ * @var string
+ */
+ public $singular;
+
+ /**
+ * The plural name for the PostType
+ * @var string
+ */
+ public $plural;
+
+ /**
+ * The slug for the PostType
+ * @var string
+ */
+ public $slug;
+
+ /**
+ * Options for the PostType
+ * @var array
+ */
+ public $options;
+
+ /**
+ * Labels for the PostType
+ * @var array
+ */
+ public $labels;
+
+ /**
+ * Taxonomies for the PostType
+ * @var array
+ */
+ public $taxonomies = [];
+
+ /**
+ * Filters for the PostType
+ * @var mixed
+ */
+ public $filters;
+
+ /**
+ * The menu icon for the PostType
+ * @var string
+ */
+ public $icon;
+
+ /**
+ * The column manager for the PostType
+ * @var mixed
+ */
+ public $columns;
+
+ /**
+ * Create a PostType
+ * @param mixed $names A string for the name, or an array of names
+ * @param array $options An array of options for the PostType
+ */
+ public function __construct($names, $options = [], $labels = [])
+ {
+ // assign names to the PostType
+ $this->names($names);
+
+ // assign custom options to the PostType
+ $this->options($options);
+
+ // assign labels to the PostType
+ $this->labels($labels);
+ }
+
+ /**
+ * Set the names for the PostType
+ * @param mixed $names A string for the name, or an array of names
+ * @return $this
+ */
+ public function names($names)
+ {
+ // only the post type name is passed
+ if (is_string($names)) {
+ $names = ['name' => $names];
+ }
+
+ // set the names array
+ $this->names = $names;
+
+ // create names for the PostType
+ $this->createNames();
+
+ return $this;
+ }
+
+ /**
+ * Set the options for the PostType
+ * @param array $options An array of options for the PostType
+ * @return $this
+ */
+ public function options(array $options)
+ {
+ $this->options = $options;
+
+ return $this;
+ }
+
+ /**
+ * Set the labels for the PostType
+ * @param array $labels An array of labels for the PostType
+ * @return $this
+ */
+ public function labels(array $labels)
+ {
+ $this->labels = $labels;
+
+ return $this;
+ }
+
+ /**
+ * Add a Taxonomy to the PostType
+ * @param mixed $taxonomies The Taxonomy name(s) to add
+ * @return $this
+ */
+ public function taxonomy($taxonomies)
+ {
+ $taxonomies = is_string($taxonomies) ? [$taxonomies] : $taxonomies;
+
+ foreach ($taxonomies as $taxonomy) {
+ $this->taxonomies[] = $taxonomy;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add filters to the PostType
+ * @param array $filters An array of Taxonomy filters
+ * @return $this
+ */
+ public function filters(array $filters)
+ {
+ $this->filters = $filters;
+
+ return $this;
+ }
+
+ /**
+ * Set the menu icon for the PostType
+ * @param string $icon A dashicon class for the menu icon
+ * @return $this
+ */
+ public function icon($icon)
+ {
+ $this->icon = $icon;
+
+ return $this;
+ }
+
+ /**
+ * Flush rewrite rules
+ * @link https://codex.wordpress.org/Function_Reference/flush_rewrite_rules
+ * @param boolean $hard
+ * @return void
+ */
+ public function flush($hard = true)
+ {
+ flush_rewrite_rules($hard);
+ }
+
+ /**
+ * Get the Column Manager for the PostType
+ * @return PostTypes\Columns
+ */
+ public function columns()
+ {
+ if (!isset($this->columns)) {
+ $this->columns = new Columns;
+ }
+
+ return $this->columns;
+ }
+
+ /**
+ * Register the PostType to WordPress
+ * @return void
+ */
+ public function register()
+ {
+ // register the PostType
+ if (!post_type_exists($this->name)) {
+ add_action('init', [$this, 'registerPostType']);
+ } else {
+ add_filter('register_post_type_args', [$this, 'modifyPostType'], 10, 2);
+ }
+
+ // register Taxonomies to the PostType
+ add_action('init', [$this, 'registerTaxonomies']);
+
+ // modify filters on the admin edit screen
+ add_action('restrict_manage_posts', [$this, 'modifyFilters']);
+
+ if (isset($this->columns)) {
+ // modify the admin edit columns.
+ add_filter("manage_{$this->name}_posts_columns", [$this, 'modifyColumns'], 10, 1);
+
+ // populate custom columns
+ add_filter("manage_{$this->name}_posts_custom_column", [$this, 'populateColumns'], 10, 2);
+
+ // run filter to make columns sortable.
+ add_filter('manage_edit-'.$this->name.'_sortable_columns', [$this, 'setSortableColumns']);
+
+ // run action that sorts columns on request.
+ add_action('pre_get_posts', [$this, 'sortSortableColumns']);
+ }
+ }
+
+ /**
+ * Register the PostType
+ * @return void
+ */
+ public function registerPostType()
+ {
+ // create options for the PostType
+ $options = $this->createOptions();
+
+ // check that the post type doesn't already exist
+ if (!post_type_exists($this->name)) {
+ // register the post type
+ register_post_type($this->name, $options);
+ }
+ }
+
+ /**
+ * Modify the existing Post Type.
+ *
+ * @return array
+ */
+ public function modifyPostType(array $args, string $posttype)
+ {
+ if ($posttype !== $this->name) {
+ return $args;
+ }
+
+ // create options for the PostType
+ $options = $this->createOptions();
+
+ $args = array_replace_recursive($args, $options);
+
+ return $args;
+ }
+
+ /**
+ * Create the required names for the PostType
+ * @return void
+ */
+ public function createNames()
+ {
+ // names required for the PostType
+ $required = [
+ 'name',
+ 'singular',
+ 'plural',
+ 'slug',
+ ];
+
+ foreach ($required as $key) {
+ // if the name is set, assign it
+ if (isset($this->names[$key])) {
+ $this->$key = $this->names[$key];
+ continue;
+ }
+
+ // if the key is not set and is singular or plural
+ if (in_array($key, ['singular', 'plural'])) {
+ // create a human friendly name
+ $name = ucwords(strtolower(str_replace(['-', '_'], ' ', $this->names['name'])));
+ }
+
+ if ($key === 'slug') {
+ // create a slug friendly name
+ $name = strtolower(str_replace([' ', '_'], '-', $this->names['name']));
+ }
+
+ // if is plural or slug, append an 's'
+ if (in_array($key, ['plural', 'slug'])) {
+ if (substr($name, strlen($name) - 1, 1) == "y") {
+ $name = substr($name, 0, strlen($name) - 1) . "ies";
+ } else {
+ $name .= 's';
+ }
+ }
+
+ // asign the name to the PostType property
+ $this->$key = $name;
+ }
+ }
+
+ /**
+ * Create options for PostType
+ * @return array Options to pass to register_post_type
+ */
+ public function createOptions()
+ {
+ // default options
+ $options = [
+ 'public' => true,
+ 'rewrite' => [
+ 'slug' => $this->slug
+ ]
+ ];
+
+ // replace defaults with the options passed
+ $options = array_replace_recursive($options, $this->options);
+
+ // create and set labels
+ if (!isset($options['labels'])) {
+ $options['labels'] = $this->createLabels();
+ }
+
+ // set the menu icon
+ if (!isset($options['menu_icon']) && isset($this->icon)) {
+ $options['menu_icon'] = $this->icon;
+ }
+
+ return $options;
+ }
+
+ /**
+ * Create the labels for the PostType
+ * @return array
+ */
+ public function createLabels()
+ {
+ // default labels
+ $labels = [
+ 'name' => $this->plural,
+ 'singular_name' => $this->singular,
+ 'menu_name' => $this->plural,
+ 'all_items' => $this->plural,
+ 'add_new' => "Erstellen",
+ 'add_new_item' => "{$this->singular} erstellen",
+ 'edit_item' => "{$this->singular} bearbeiten",
+ 'new_item' => "{$this->singular} erstellen",
+ 'view_item' => "{$this->singular} anschauen",
+ 'search_items' => "{$this->plural} suchen",
+ 'not_found' => "Kein {$this->plural} gefunden",
+ 'not_found_in_trash' => "Es wurden keine {$this->plural} im Papierkorb gefunden.",
+ 'parent_item_colon' => "Eltern-{$this->singular}:",
+ ];
+
+ return array_replace_recursive($labels, $this->labels);
+ }
+
+ /**
+ * Register Taxonomies to the PostType
+ * @return void
+ */
+ public function registerTaxonomies()
+ {
+ if (!empty($this->taxonomies)) {
+ foreach ($this->taxonomies as $taxonomy) {
+ register_taxonomy_for_object_type($taxonomy, $this->name);
+ }
+ }
+ }
+
+ /**
+ * Modify and display filters on the admin edit screen
+ * @param string $posttype The current screen post type
+ * @return void
+ */
+ public function modifyFilters($posttype)
+ {
+ // first check we are working with the this PostType
+ if ($posttype === $this->name) {
+ // calculate what filters to add
+ $filters = $this->getFilters();
+
+ foreach ($filters as $taxonomy) {
+ // if the taxonomy doesn't exist, ignore it
+ if (!taxonomy_exists($taxonomy)) {
+ continue;
+ }
+
+ // If the taxonomy is not registered to the post type, continue.
+ if (!is_object_in_taxonomy($this->name, $taxonomy)) {
+ continue;
+ }
+
+ // get the taxonomy object
+ $tax = get_taxonomy($taxonomy);
+
+ // start the html for the filter dropdown
+ $selected = null;
+
+ if (isset($_GET[$taxonomy])) {
+ $selected = sanitize_title($_GET[$taxonomy]);
+ }
+
+ $dropdown_args = [
+ 'name' => $taxonomy,
+ 'value_field' => 'slug',
+ 'taxonomy' => $tax->name,
+ 'show_option_all' => $tax->labels->all_items,
+ 'hierarchical' => $tax->hierarchical,
+ 'selected' => $selected,
+ 'orderby' => 'name',
+ 'hide_empty' => 0,
+ 'show_count' => 0,
+ ];
+
+ // Output screen reader label.
+ echo '<label class="screen-reader-text" for="cat">' . $tax->labels->filter_by_item . '</label>';
+
+ // Output dropdown for taxonomy.
+ wp_dropdown_categories($dropdown_args);
+ }
+ }
+ }
+
+ /**
+ * Calculate the filters for the PostType
+ * @return array
+ */
+ public function getFilters()
+ {
+ // default filters are empty
+ $filters = [];
+
+ // if custom filters have been set, use them
+ if (!is_null($this->filters)) {
+ return $this->filters;
+ }
+
+ // if no custom filters have been set, and there are
+ // Taxonomies assigned to the PostType
+ if (is_null($this->filters) && !empty($this->taxonomies)) {
+ // create filters for each taxonomy assigned to the PostType
+ return $this->taxonomies;
+ }
+
+ return $filters;
+ }
+
+ /**
+ * Modify the columns for the PostType
+ * @param array $columns Default WordPress columns
+ * @return array The modified columns
+ */
+ public function modifyColumns($columns)
+ {
+ $columns = $this->columns->modifyColumns($columns);
+
+ return $columns;
+ }
+
+ /**
+ * Populate custom columns for the PostType
+ * @param string $column The column slug
+ * @param int $post_id The post ID
+ */
+ public function populateColumns($column, $post_id)
+ {
+ if (isset($this->columns->populate[$column])) {
+ call_user_func_array($this->columns()->populate[$column], [$column, $post_id]);
+ }
+ }
+
+ /**
+ * Make custom columns sortable
+ * @param array $columns Default WordPress sortable columns
+ */
+ public function setSortableColumns($columns)
+ {
+ if (!empty($this->columns()->sortable)) {
+ $columns = array_merge($columns, $this->columns()->sortable);
+ }
+
+ return $columns;
+ }
+
+ /**
+ * Set query to sort custom columns
+ * @param WP_Query $query
+ */
+ public function sortSortableColumns($query)
+ {
+ // don't modify the query if we're not in the post type admin
+ if (!is_admin() || $query->get('post_type') !== $this->name) {
+ return;
+ }
+
+ $orderby = $query->get('orderby');
+
+ // if the sorting a custom column
+ if ($this->columns()->isSortable($orderby)) {
+ // get the custom column options
+ $meta = $this->columns()->sortableMeta($orderby);
+
+ // determine type of ordering
+ if (is_string($meta) or !$meta[1]) {
+ $meta_key = $meta;
+ $meta_value = 'meta_value';
+ } else {
+ $meta_key = $meta[0];
+ $meta_value = 'meta_value_num';
+ }
+
+ // set the custom order
+ $query->set('meta_key', $meta_key);
+ $query->set('orderby', $meta_value);
+ }
+ }
+}
diff --git a/src/Taxonomy.php b/src/Taxonomy.php
new file mode 100644
index 0000000..a7aca5b
--- /dev/null
+++ b/src/Taxonomy.php
@@ -0,0 +1,376 @@
+<?php
+
+namespace PostTypes;
+
+use PostTypes\Columns;
+
+/**
+ * Taxonomy
+ */
+class Taxonomy
+{
+ /**
+ * The names passed to the Taxonomy
+ * @var mixed
+ */
+ public $names;
+
+ /**
+ * The Taxonomy name
+ * @var string
+ */
+ public $name;
+
+ /**
+ * The singular label for the Taxonomy
+ * @var string
+ */
+ public $singular;
+
+ /**
+ * The plural label for the Taxonomy
+ * @var string
+ */
+ public $plural;
+
+ /**
+ * The Taxonomy slug
+ * @var string
+ */
+ public $slug;
+
+ /**
+ * Custom options for the Taxonomy
+ * @var array
+ */
+ public $options;
+
+ /**
+ * Custom labels for the Taxonomy
+ * @var array
+ */
+ public $labels;
+
+ /**
+ * PostTypes to register the Taxonomy to
+ * @var array
+ */
+ public $posttypes = [];
+
+ /**
+ * The column manager for the Taxonomy
+ * @var mixed
+ */
+ public $columns;
+
+ /**
+ * Create a Taxonomy
+ * @param mixed $names The name(s) for the Taxonomy
+ */
+ public function __construct($names, $options = [], $labels = [])
+ {
+ $this->names($names);
+
+ $this->options($options);
+
+ $this->labels($labels);
+ }
+
+ /**
+ * Set the names for the Taxonomy
+ * @param mixed $names The name(s) for the Taxonomy
+ * @return $this
+ */
+ public function names($names)
+ {
+ if (is_string($names)) {
+ $names = ['name' => $names];
+ }
+
+ $this->names = $names;
+
+ // create names for the Taxonomy
+ $this->createNames();
+
+ return $this;
+ }
+
+ /**
+ * Set options for the Taxonomy
+ * @param array $options
+ * @return $this
+ */
+ public function options(array $options = [])
+ {
+ $this->options = $options;
+
+ return $this;
+ }
+
+ /**
+ * Set the Taxonomy labels
+ * @param array $labels
+ * @return $this
+ */
+ public function labels(array $labels = [])
+ {
+ $this->labels = $labels;
+
+ return $this;
+ }
+
+ /**
+ * Assign a PostType to register the Taxonomy to
+ * @param mixed $posttypes
+ * @return $this
+ */
+ public function posttype($posttypes)
+ {
+ $posttypes = is_string($posttypes) ? [$posttypes] : $posttypes;
+
+ foreach ($posttypes as $posttype) {
+ $this->posttypes[] = $posttype;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the Column Manager for the Taxonomy
+ * @return Columns
+ */
+ public function columns()
+ {
+ if (!isset($this->columns)) {
+ $this->columns = new Columns;
+ }
+
+ return $this->columns;
+ }
+
+ /**
+ * Register the Taxonomy to WordPress
+ * @return void
+ */
+ public function register()
+ {
+ // register the taxonomy, set priority to 9
+ // so taxonomies are registered before PostTypes
+ add_action('init', [$this, 'registerTaxonomy'], 9);
+
+ // assign taxonomy to post type objects
+ add_action('init', [$this, 'registerTaxonomyToObjects']);
+
+ if (isset($this->columns)) {
+ // modify the columns for the Taxonomy
+ add_filter("manage_edit-{$this->name}_columns", [$this, 'modifyColumns']);
+
+ // populate the columns for the Taxonomy
+ add_filter("manage_{$this->name}_custom_column", [$this, 'populateColumns'], 10, 3);
+
+ // set custom sortable columns
+ add_filter("manage_edit-{$this->name}_sortable_columns", [$this, 'setSortableColumns']);
+
+ // run action that sorts columns on request
+ add_action('parse_term_query', [$this, 'sortSortableColumns']);
+ }
+ }
+
+ /**
+ * Register the Taxonomy to WordPress
+ * @return void
+ */
+ public function registerTaxonomy()
+ {
+ // Get the existing taxonomy options if it exists.
+ $options = (taxonomy_exists($this->name)) ? (array) get_taxonomy($this->name) : [];
+
+ // create options for the Taxonomy.
+ $options = array_replace_recursive($options, $this->createOptions());
+
+ // register the Taxonomy with WordPress.
+ register_taxonomy($this->name, null, $options);
+ }
+
+ /**
+ * Register the Taxonomy to PostTypes
+ * @return void
+ */
+ public function registerTaxonomyToObjects()
+ {
+ // register Taxonomy to each of the PostTypes assigned
+ if (!empty($this->posttypes)) {
+ foreach ($this->posttypes as $posttype) {
+ register_taxonomy_for_object_type($this->name, $posttype);
+ }
+ }
+ }
+
+ /**
+ * Create names for the Taxonomy
+ * @return void
+ */
+ public function createNames()
+ {
+ $required = [
+ 'name',
+ 'singular',
+ 'plural',
+ 'slug',
+ ];
+
+ foreach ($required as $key) {
+ // if the name is set, assign it
+ if (isset($this->names[$key])) {
+ $this->$key = $this->names[$key];
+ continue;
+ }
+
+ // if the key is not set and is singular or plural
+ if (in_array($key, ['singular', 'plural'])) {
+ // create a human friendly name
+ $name = ucwords(strtolower(str_replace(['-', '_'], ' ', $this->names['name'])));
+ }
+
+ if ($key === 'slug') {
+ // create a slug friendly name
+ $name = strtolower(str_replace([' ', '_'], '-', $this->names['name']));
+ }
+
+ // if is plural or slug, append an 's'
+ if (in_array($key, ['plural', 'slug'])) {
+ $name .= 's';
+ }
+
+ // asign the name to the PostType property
+ $this->$key = $name;
+ }
+ }
+
+ /**
+ * Create options for Taxonomy
+ * @return array Options to pass to register_taxonomy
+ */
+ public function createOptions()
+ {
+ // default options
+ $options = [
+ 'hierarchical' => true,
+ 'show_admin_column' => true,
+ 'rewrite' => [
+ 'slug' => $this->slug,
+ ],
+ ];
+
+ // replace defaults with the options passed
+ $options = array_replace_recursive($options, $this->options);
+
+ // create and set labels
+ if (!isset($options['labels'])) {
+ $options['labels'] = $this->createLabels();
+ }
+
+ return $options;
+ }
+
+ /**
+ * Create labels for the Taxonomy
+ * @return array
+ */
+ public function createLabels()
+ {
+ // default labels
+ $labels = [
+ 'name' => $this->plural,
+ 'singular_name' => $this->singular,
+ 'menu_name' => $this->plural,
+ 'all_items' => "Alle {$this->plural}",
+ 'edit_item' => "{$this->singular} bearbeiten",
+ 'view_item' => "{$this->singular} anschauen",
+ 'update_item' => "{$this->singular} aktualisieren",
+ 'add_new_item' => "{$this->singular} hinzufügen",
+ 'new_item_name' => "Neuer {$this->singular} Name",
+ 'parent_item' => "Übergeordnete {$this->plural}",
+ 'parent_item_colon' => "Übergeordnete {$this->plural}:",
+ 'search_items' => "{$this->plural} suchen",
+ 'popular_items' => "Häufig genutzte {$this->plural}",
+ 'separate_items_with_commas' => "Trenne {$this->plural} mit Komma",
+ 'add_or_remove_items' => "{$this->plural} hinzufügen oder entfernen",
+ 'choose_from_most_used' => "Wählen aus den meistgenutzten {$this->plural}",
+ 'not_found' => "Keine {$this->plural} gefunden",
+ ];
+
+ return array_replace($labels, $this->labels);
+ }
+
+ /**
+ * Modify the columns for the Taxonomy
+ * @param array $columns The WordPress default columns
+ * @return array
+ */
+ public function modifyColumns($columns)
+ {
+ $columns = $this->columns->modifyColumns($columns);
+
+ return $columns;
+ }
+
+ /**
+ * Populate custom columns for the Taxonomy
+ * @param string $content
+ * @param string $column
+ * @param int $term_id
+ */
+ public function populateColumns($content, $column, $term_id)
+ {
+ if (isset($this->columns->populate[$column])) {
+ $content = call_user_func_array($this->columns()->populate[$column], [$content, $column, $term_id]);
+ }
+
+ return $content;
+ }
+
+ /**
+ * Make custom columns sortable
+ * @param array $columns Default WordPress sortable columns
+ */
+ public function setSortableColumns($columns)
+ {
+ if (!empty($this->columns()->sortable)) {
+ $columns = array_merge($columns, $this->columns()->sortable);
+ }
+
+ return $columns;
+ }
+
+ /**
+ * Set query to sort custom columns
+ * @param WP_Term_Query $query
+ */
+ public function sortSortableColumns($query)
+ {
+ // don't modify the query if we're not in the post type admin
+ if (!is_admin() || !in_array($this->name, $query->query_vars['taxonomy'] ?? [])) {
+ return;
+ }
+
+ // check the orderby is a custom ordering
+ if (isset($_GET['orderby']) && array_key_exists($_GET['orderby'], $this->columns()->sortable)) {
+ // get the custom sorting options
+ $meta = $this->columns()->sortable[$_GET['orderby']];
+
+ // check ordering is not numeric
+ if (is_string($meta)) {
+ $meta_key = $meta;
+ $orderby = 'meta_value';
+ } else {
+ $meta_key = $meta[0];
+ $orderby = 'meta_value_num';
+ }
+
+ // set the sort order
+ $query->query_vars['orderby'] = $orderby;
+ $query->query_vars['meta_key'] = $meta_key;
+ }
+ }
+}