From ccecb903f3542f7d5d2350cc953b9c3f0166980b Mon Sep 17 00:00:00 2001
From: Paul Biron <paul@sparrowhawkcomputing.com>
Date: Sun, 22 Jul 2018 09:40:04 -0600
Subject: [PATCH] support filtering @since archive by change type

---
 functions.php         | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++
 inc/template-tags.php | 62 +++++++++++++++++++++++++++++++++++++---
 stylesheets/main.css  |  4 +++
 3 files changed, 141 insertions(+), 4 deletions(-)

diff --git a/functions.php b/functions.php
index 71f0a16..ea62765 100644
--- a/functions.php
+++ b/functions.php
@@ -108,6 +108,9 @@ if ( ! isset( $content_width ) ) {
 add_action( 'init', __NAMESPACE__ . '\\init' );
 add_action( 'widgets_init', __NAMESPACE__ . '\\widgets_init' );
 
+/**
+ * @since 1.x.y Setup to filter wp-parser-since archives by change_type
+ */
 function init() {
 
 	register_nav_menus();
@@ -132,6 +135,9 @@ function init() {
 	add_filter( 'document_title_separator', __NAMESPACE__ . '\\theme_title_separator', 10, 2 );
 
 	add_filter( 'syntaxhighlighter_htmlresult', __NAMESPACE__ . '\\syntaxhighlighter_htmlresult' );
+
+	add_filter( 'query_vars',  __NAMESPACE__ . '\\add_change_type_query_var' );
+	add_filter( 'pre_get_posts', __NAMESPACE__ . '\\since_change_type_filter', 10 );
 }
 
 /**
@@ -235,6 +241,8 @@ function widgets_init() {
 }
 
 /**
+ * @since 1.x.y initialize change_type query_var
+ *
  * @param \WP_Query $query
  */
 function pre_get_posts( $query ) {
@@ -248,9 +256,80 @@ function pre_get_posts( $query ) {
 		$query->set( 'wp-parser-source-file', str_replace( array( '.php', '/' ), array( '-php', '_' ), $query->query['wp-parser-source-file'] ) );
 	}
 
+	if ( $query->is_main_query() && $query->is_tax() && $query->get( 'wp-parser-since' ) ) {
+		$query->set( 'orderby', 'type title' );
+		$query->set( 'order', 'ASC' );
+		$query->set( 'change_type', ! empty( $_REQUEST['change_type'] ) ? $_REQUEST['change_type'] : 'introduced' );
+	}
+
 	// For search query modifications see DevHub_Search.
 }
 
+/**
+ * Add change_type to the public_query_vars.
+ *
+ * @since 1.x.y
+ *
+ * @param array $public_query_vars The array of whitelisted query variables.
+ * @return array $public_query_vars with 'change_type' added.
+ */
+function add_change_type_query_var( $public_query_vars ) {
+	$public_query_vars[] = 'change_type';
+
+	return $public_query_vars;
+}
+
+/**
+ * Filter wp-parser-since archive by the type of change.
+ *
+ * @since 1.x.y
+ *
+ * @param WP_Query $query WP_Query object.
+ * @return void
+ */
+function since_change_type_filter( $query ) {
+	global $wpdb;
+
+ 	$version = $query->get( 'wp-parser-since' );
+ 	$change_type = $query->get( 'change_type' );
+
+	if ( ! ( $query->is_archive() && $query->is_tax() && ! empty( $version ) ) ||
+			( empty( $change_type ) || 'any' === $change_type ) ) {
+		// bail, no need to filter
+		return;
+	}
+
+	$version_term = get_term_by( 'name', $version, 'wp-parser-since' );
+	$changes = get_term_meta( $version_term->term_id, '_wp_parser_changes', true );
+	if ( empty( $changes ) ) {
+		// no changes, so force a "nothing found"
+		// @see @\todo in \DevHub\taxonomy_archive_filter()
+		$query->set( 'post__in', array( -1 ) );
+
+		return;
+	}
+
+ 	$post_types = $query->get( 'post_type' );
+ 	if ( empty( $post_types ) ) {
+ 		$post_types = get_parsed_post_types();
+ 	}
+
+	$post__in = array();
+	foreach ( (array) $post_types as $post_type ) {
+		if ( ! empty( $changes[ $change_type ][ $post_type ] ) ) {
+			$post__in = array_merge( $post__in, $changes[ $change_type ][ $post_type ] );
+		}
+	}
+
+	if ( empty( $post__in ) ) {
+		// no changes, so force a "nothing found"
+		// @see @\todo in \DevHub\taxonomy_archive_filter()
+		$post__in = array( -1 );
+	}
+
+	$query->set( 'post__in', $post__in );
+}
+
 function register_nav_menus() {
 
 	\register_nav_menus( array(
diff --git a/inc/template-tags.php b/inc/template-tags.php
index 225dd81..1381469 100644
--- a/inc/template-tags.php
+++ b/inc/template-tags.php
@@ -559,6 +559,28 @@ namespace DevHub {
 	}
 
 	/**
+	 * Get an array of all change types.
+	 *
+	 * @since 1.x.y
+	 *
+	 * @param string  $labels If set to 'labels' change types with their labels are returned.
+	 * @return array
+	 */
+	function get_change_types( $labels = '' ) {
+		$change_types = array(
+			'introduced' => __( 'Introduced', 'wporg' ),
+			'modified'   => __( 'Modified', 'wporg' ),
+			'deprecated' => __( 'Deprecated', 'wporg' ),
+		);
+
+		if ( 'labels' !== $labels ) {
+			return array_keys( $post_types );
+		}
+
+		return $change_types;
+	}
+	
+	/**
 	 * Checks if given post type is one of the parsed post types.
 	 *
 	 * @param  null|string Optional. The post type. Default null.
@@ -1691,12 +1713,44 @@ namespace DevHub {
 			$form .= "<input type='hidden' name='" . esc_attr( $taxonomy ) . "' value='" . esc_attr( $term ) . "'>";
 		}
 
-		$form .= "<label for='archive-filter'>";
-		$form .= __( 'Filter by type:', 'wporg' ) . ' ';
-		$form .= '<select name="post_type[]" id="archive-filter">';
+		$form .= "<label for='post-type-archive-filter'>";
+		// @todo the "Filter by post type" wording is not ideal because most users
+		//		 won't know that classes/methods/functions/hooks are represented as
+		//		 as different CPTs, but with the addition of the "Filter by type of change"
+		//		 dropdown I felt that the original "Filter by type" wording wasn't sufficient.
+		//		 Very much open to suggestions for alternative wording!
+		$form .= __( 'Filter by post type:', 'wporg' ) . ' ';
+		$form .= '<select name="post_type[]" id="post-type-archive-filter">';
 		$form .= $options . '</select></label>';
-		$form .= "<input class='shiny-blue' type='submit' value='Filter' /></form>";
 
+		if ( 'wp-parser-since' === $taxonomy ) {
+			// @todo it might make for a better UX to detect whether there are
+			//		 any changes of a given type and not output an option for that
+			//		 change type, rather than having the user get the "Nothing Found" message
+			//		 in those cases.
+			//
+			//		 This also applies to the post type filter, e.g., in cases like
+			//		 https://developer.wordpress.org/reference/since/1.0.0/?post_type[]=wp-parser-class
+			//
+			// 		 But I'll leave this for a future iteration.
+			$change_types  = get_change_types( 'labels' );
+			$change_types  = array( 'any' => __( 'Any change', 'wporg' ) ) + $change_types;
+
+			$options = '';
+			$current_change_type = get_query_var( 'change_type' );
+			foreach ( $change_types as $change_type => $label ) {
+				$selected = selected( $change_type, $current_change_type, false );
+				$options .= "<option value='$change_type'$selected>$label</option>";
+			}
+
+			$form .= "<label for='change-type-archive-filter'>";
+			$form .= __( 'Filter by type of change:', 'wporg' ) . ' ';
+			$form .= '<select name="change_type" id="change-type-archive-filter">';
+			$form .= $options . '</select></label>';
+		}
+
+		$form .= "<input class='shiny-blue' type='submit' value='Filter' /></form>";
+		
 		echo $form;
 	}
 
diff --git a/stylesheets/main.css b/stylesheets/main.css
index 8ab9053..9a41739 100644
--- a/stylesheets/main.css
+++ b/stylesheets/main.css
@@ -1075,6 +1075,10 @@ input {
   font-size: 1.5rem;
 }
 
+.devhub-wrap .archive-filter-form label[for="change-type-archive-filter"] {
+	margin-left: 0.5em;
+}
+
 .devhub-wrap .archive-filter-form input[type="submit"] {
   margin-left: .5em;
   padding: 0.2em 0.5em;
-- 
2.7.0.windows.1

