This entry is part 5 of 9 in the User Submitted Image Galleries Series
- User Submitting Gallery Images – Part 1
- User Submitted Gallery Images – Part 2
- User Submitted Image Gallery – Part 3
- User Submitted Image Gallery – Part 4
- User Submitted Image Gallery – Part 5
- User Submitted Image Gallery – Part 6
- User Submitted Image Gallery – Part 7
- User Submitted Image Gallery – Part 8
- User Submitted Image Gallery – Final Overview
In part five of this User Submitted Image Gallery tutorial series we build the advanced query system that allows users to filter images by categories and tags. This will be one of the most advanced sections of this series.
The filter and search functions in this section will utilize the advanced and very powerful tax_query of the WP_Query class. The tax_query parameter allows us to query items from multiple taxonomies at one time. If you need an introduction to how the tax queries work, then read my short tutorial on WP Mods.
You may recall from part four that we set up a place holder for our image category filters. This place holder is going to be replaced with a list of image categories and also a search box for image tags. When a category is clicked, or a tag is searched for, the query that we use to pull images from the database will re-run with the tax_query paramater, which will result in only images from our chosen category and tags being displayed.
The first thing we need to do is build the tax_query itself. This is a little bit complicated, but pretty easy to understand once you get a grasp on how it works.
A basic tax_query looks like this:
1 2 3 4 5 6 7 | $tax_query = array( array( 'taxonomy' => 'taxonomy_name', 'terms' => array('term-one', 'term-two'), 'field' => 'slug' ) ); |
Note that there are two arrays, one inside the other. This is not a mistake.
When using the tax_query in a get_posts() query, you would then do this:
1 | get_posts(array('tax_query' => $tax_query)); |
This query would pull all posts that are filed in the “term-two” and “term-one” terms of the “taxonomy_name” taxonomy. Our actual tax_query is going to be a bit more complex, since we are querying by both categories and tags, as well as populating it dynamically.
This code should got just below the wp_enqueue_style() function that is inside of our
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | /* tag and category tax queries */ $tax_query = array(); $category_query = array(); if(isset($_GET['image-category'])) { $categories = true; $category_query = array( 'taxonomy' => 'uig_image_category', 'terms' => array(urldecode($_GET['image-category'])), 'field' => 'slug' ); } $tag_query = array(); if(isset($_GET['image-tag'])) { $tags = true; $terms = explode(',', str_replace(', ', ',', urldecode($_GET['image-tag']))); $tag_query = array( 'taxonomy' => 'uig_image_tag', 'terms' => $terms, 'field' => 'slug' ); } if($tags && $categories) { $tax_query = array( 'relation' => 'AND', $category_query, $tag_query ); } elseif($tags) { $tax_query = array( $tag_query ); } elseif($categories) { $tax_query = array( $category_query ); } /* end tag and category tax queries */ |
There are three parts to this piece. First we set up the tax_query for the image categories. Note that we first set the $category_query variable to a blank array. We want to make sure that it is an array for when we pass it to the actual tax_query parameter in the get_posts() query, but we want to leave it blank unless a category filter request is present. And this is why we do if(isset($_GET[‘image-category’])).
The urldecode() function in the terms parameter is used to ensure all special URL characters are converted to the form that can be read by PHP / WordPress.
We do the exact same thing for the $tag_query array as for the $category_query, with just one extra piece. Since more than one tag will be allowed to be passed (it will just be a plain text input field), we use the explode() function to convert our comma delimitated string to an array. The str_replace() function is also used to remove unnecessary white space, to help ensure WordPress queries the correct tags.
Once we have both arrays setup (remember, they are blank if no $_GET variable is set), we build our master $tax_query array. We do this by first checking to see which of the tax queries we are using: tags, categories, or both, and then building the final array accordingly. Also note that I have setup an extra “relation” key in the $tax_query array if there is both a tag and category query. This passed so that get_posts() only retrieves images that are in the category AND in the tag(s) specified.
Now we need to modify our original get_posts() arguments to include our new tax_query. The modified version looks like this:
1 2 3 4 5 6 7 8 | // main image query $image_args = array( 'post_type' => 'uig_image', 'numberposts' => $per_page, 'offset' => $offset, 'tax_query' => $tax_query ); $images = get_posts($image_args); |
We have now set up our extra query parameters, so it is now time to display our actual image filters. These will be a list the available terms inside of the image category taxonomy, and also a search box where image tags can be specified. All of this code will go inside of the conditional statement we included in part four. To refresh your memory, the conditional looks like this:
1 2 3 | if($filters == 'true') { // filters will go here } |
The first part of the filters we will build is the list of categories. We will use the get_terms() function to retrieve all of the available categories, and then we’ll display them in a simple unordered list.
1 2 3 4 5 6 7 8 9 10 11 | echo '<ul id="gallery-category-filter" class="image-filters uig-clearfix">'; $categories = get_terms('uig_image_category'); if($categories) : foreach($categories as $category) : echo '<li>'; $category_link = add_query_arg('image-category', $category->slug, get_permalink(get_the_ID()) ); echo '<a class="image-category" id="' . $category->slug . '" href="' . $category_link . '">' . $category->name . '</a>'; echo '</li>'; endforeach; endif; echo '</ul>'; |
There’s nothing fancy about this code, it just retrieves all of the categories, and then loops through them. The URL for each category is generated by using the add_query_arg() function, along with get_permalink() to get the URL of the current page. After doing this, the URL for each category will look like this:
http://yoursite.com/gallery/?image-category=category-slug
When you click on a category name now, the images listed on the gallery page will refresh and show only those images in the chosen category. However, this won’t work completely. After you have chosen a category, try going to page 2 (or any other number). You will notice that the category is reset and the gallery will display all images once again. To fix this, we have to do some automatic detection for when query variables are set.
Our modified version that will take into account page variables AND tag variables being set (as well as a current class and an all link) looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | echo '<ul id="gallery-category-filter" class="image-filters uig-clearfix">'; $categories = get_terms('uig_image_category'); if($categories) : $query_base = '?' . $_SERVER['QUERY_STRING']; if(!isset($_GET['image-category'])) { echo '<li><span class="current">' . __('All', 'uig') . '</span></li>'; } else { echo '<li><a class="image-category" href="' . get_permalink(get_the_ID()) . '">' . __('All', 'uig') . '</a></li>'; } foreach($categories as $category) : echo '<li>'; if(isset($_GET['image-category']) && $category->slug == $_GET['image-category']) { echo '<span class="current">' . $category->slug . '</span>'; } else { $category_link = add_query_arg('image-category', $category->slug, $query_base); echo '<a class="image-category' . $current . '" id="' . $category->slug . '" href="' . $category_link . '">' . $category->name . '</a>'; } echo '</li>'; endforeach; endif; echo '</ul>'; |
The main part to this altered code is the $query_base variable. This variable stores all of the query vars that are set by using the $_SERVER[‘QUERY_STRING’] variable, which is set by the browser. We then pass the $query_base to the add_query_arg() function, which will result in a fully working category filter, even if a tag is also specified.
Now it’s time to build the tag filtering system. This one is actually completely different than the image categories, because we are giving the user a search box, instead of a term list. It is mostly just simple HTML:
1 2 3 4 5 6 7 8 9 10 | echo '<div id="gallery-tag-filter" class="image-filters">'; echo '<form action="" method="get">'; if(isset($_GET['image-tag'])) { $searched_tags = urldecode($_GET['image-tag']); } else { $searched_tags = ''; } if(isset($_GET['image-category'])) { echo '<input type="hidden" name="image-category" value="' . urldecode($_GET['image-category']) . '"/>'; } echo '<input type="text" id="image-tag" name="image-tag" value="' . $searched_tags . '"/>'; echo '<label for="image-tag">' . __('Search by tags. Separate each tag by a comma.', 'uig') . '</label>'; echo '</form>'; echo '</div>'; |
Notice that a conditional tag is used to check whether the image-tag query variable is set. If it is, then we pre-populate the value of the input field with the previously-searched-for terms. We also do a check to see if the image-category query variable is set. If it is, then we append a hidden input field that contains the slug of the selected category. This is so that if a user enters a tag after filtering by a category, the category filter will be retained.
Something you may notice now is that if you filter by a category and/or a tag, the pagination will not work correctly anymore. This is for a couple of reasons. First, we use wp_count_posts() to count the total number of images in the gallery; well this function does not take into account the filtering system. After filtering by a category or tag, the pagination system still things there are the same number of images in the query, even though it should be distinctly less.
To fix the image count problem, we are going to modify our original code:
1 2 3 4 5 6 7 | // get the cached image count. This is used to determine the total number of pages $image_count = get_transient('uig_image_count'); if( false === $image_count ) { $image_count = wp_count_posts('uig_image')->publish; // store count in transient with a one hour expiration set_transient('uig_image_count', $image_count, 3600); } |
The unfortunate side-effect of the changes we are going to have to make is that we will lose our caching, making the gallery slightly less efficient. Our new code looks like this:
1 2 3 4 5 6 7 | // setup the image args for retrieving the total number of images $image_count_args = array( 'post_type' => 'uig_image', 'posts_per_page' => -1, 'tax_query' => $tax_query ); $image_count = count(get_posts($image_count_args)); |
This is nothing more than another get_posts() query. It has a tax_query parameter included to ensure that we get the correct image count for our filtered query.
What this altered code means is that we are now doing two nearly identical queries. Unfortunately, this is the only way I was able to find to do this. If you happen to know of a better method, please share it.
We are not quite done updating our pagination system. Much like we did with the tag and category filters, we need to add a couple of options to the paginate_links() function to make sure it detects the query arguments that are passed so that the filter does not get messed up when changing pages. To do this we will add the optional add_args parameter to the paginate_links() function, like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | echo paginate_links( array( 'base' => $base, 'format' => '?page=%#%', 'prev_text' => __('Previous', 'uig'), 'next_text' => __('Next', 'uig'), 'total' => $total_pages, 'current' => $page, 'end_size' => 1, 'mid_size' => 5, 'add_args' => array( 'image-category' => $_GET['image-category'], 'image-tag' => $_GET['image-tag'] ) )); |
The add_args option tags an array of query args. We simply pass the name of each query variable, along with the values for each. This will preserve our query when switching pages.
There is just one more thing to do to finish our image filters, and that is add a little CSS. I’ve just done some very simple CSS to make things line up a little better, so feel free to elaborate as much as you wish.
1 2 | ul#gallery-category-filter { margin: 0; padding: 0; } #gallery-category-filter li { display: block; float: left; margin: 0 10px 0 0; } |
Once all together, your gallery should look about like this:
That’s it! Now for all of the code together:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | // displays the user submitted gallery function uig_show_gallery($atts, $content = null ) { extract( shortcode_atts( array( 'filters' => 'true', 'columns' => 3, 'per_page' => 6 ), $atts ) ); // load the necessary CSS -- this is loaded inline and only work with WP 3.3+ wp_enqueue_style('uig-gallery', UIG_PLUGIN_URL . 'includes/css/gallery.css'); /* tag and category tax queries */ $tax_query = array(); $category_query = array(); if(isset($_GET['image-category'])) { $categories = true; $category_query = array( 'taxonomy' => 'uig_image_category', 'terms' => array(urldecode($_GET['image-category'])), 'field' => 'slug' ); } $tag_query = array(); if(isset($_GET['image-tag'])) { $tags = true; $terms = explode(',', str_replace(', ', ',', urldecode($_GET['image-tag']))); $tag_query = array( 'taxonomy' => 'uig_image_tag', 'terms' => $terms, 'field' => 'slug' ); } if($tags && $categories) { $tax_query = array( 'relation' => 'AND', $category_query, $tag_query ); } elseif($tags) { $tax_query = array( $tag_query ); } elseif($categories) { $tax_query = array( $category_query ); } /* end tag and category tax queries */ /* pagination parameters */ // check what page we are on if (isset($_GET['page'])) $page = $_GET['page']; else $page = 1; // default number of pages $total_pages = 1; // image offset. Used in the get_posts() query to show only images for the current page $offset = $per_page * ($page-1); // setup the image args for retrieving the total number of images $image_count_args = array( 'post_type' => 'uig_image', 'posts_per_page' => -1, 'tax_query' => $tax_query ); $image_count = count(get_posts($image_count_args)); // calculate the total number of pages $total_pages = ceil($image_count/$per_page); /* end pagination parameters */ // main image query $image_args = array( 'post_type' => 'uig_image', 'numberposts' => $per_page, 'offset' => $offset, 'tax_query' => $tax_query ); $images = get_posts($image_args); // start our output buffer ob_start(); if($filters == 'true') { echo '<ul id="gallery-category-filter" class="image-filters uig-clearfix">'; $categories = get_terms('uig_image_category'); if($categories) : $query_base = '?' . $_SERVER['QUERY_STRING']; if(!isset($_GET['image-category'])) { echo '<li><span class="current">' . __('All', 'uig') . '</span></li>'; } else { echo '<li><a class="image-category" href="' . get_permalink(get_the_ID()) . '">' . __('All', 'uig') . '</a></li>'; } foreach($categories as $category) : echo '<li>'; if(isset($_GET['image-category']) && $category->slug == $_GET['image-category']) { echo '<span class="current">' . $category->slug . '</span>'; } else { $category_link = add_query_arg('image-category', $category->slug, $query_base); echo '<a class="image-category' . $current . '" id="' . $category->slug . '" href="' . $category_link . '">' . $category->name . '</a>'; } echo '</li>'; endforeach; endif; echo '</ul>'; echo '<div id="gallery-tag-filter" class="image-filters">'; echo '<form action="" method="get">'; if(isset($_GET['image-tag'])) { $searched_tags = urldecode($_GET['image-tag']); } else { $searched_tags = ''; } if(isset($_GET['image-category'])) { echo '<input type="hidden" name="image-category" value="' . urldecode($_GET['image-category']) . '"/>'; } echo '<input type="text" id="image-tag" name="image-tag" value="' . $searched_tags . '"/>'; echo '<label for="image-tag">' . __('Search by tags. Separate each tag by a comma.', 'uig') . '</label>'; echo '</form>'; echo '</div>'; } if($images) : /*** main image loop ***/ $counter = 1; echo '<ul id="gallery-images" class="uig-clearfix">'; foreach($images as $image) : if($counter % $columns == 0) { $last = ' last'; } else { $last = ''; } if($new_row || $counter == 1) { $first = ' first'; } else { $first = ''; } echo '<li class="gallery-image image-' . $counter . $last . $first . '">'; echo '<a href="' . get_permalink($image->ID) . '">'; echo get_the_post_thumbnail($image->ID, 'uig-gallery-image'); echo '</a>'; echo '</li>'; if($counter % $columns == 0) { $new_row = true; } else { $new_row = false; } $counter++; endforeach; echo '</ul>'; /*** end main image loop ***/ /*** display pagination ***/ // pagination base echo '<div id="gallery-pagination">'; $base = get_permalink(get_the_ID()) . '%_%'; echo paginate_links( array( 'base' => $base, 'format' => '?page=%#%', 'prev_text' => __('Previous', 'uig'), 'next_text' => __('Next', 'uig'), 'total' => $total_pages, 'current' => $page, 'end_size' => 1, 'mid_size' => 5, 'add_args' => array( 'image-category' => $_GET['image-category'], 'image-tag' => $_GET['image-tag'] ) )); echo '</div>'; /*** end pagination display ***/ else : echo '<p>' . __('No images found.', 'uig') . '</p>'; endif; // end if($images) return ob_get_clean(); } add_shortcode('user_gallery', 'uig_show_gallery'); |