Changeset 3179979
- Timestamp:
- 11/01/2024 01:56:37 PM (17 months ago)
- Location:
- ac-custom-loop-shortcode/trunk
- Files:
-
- 2 edited
-
ac-wp-custom-loop-sc.php (modified) (3 diffs)
-
readme.txt (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
ac-custom-loop-shortcode/trunk/ac-wp-custom-loop-sc.php
r2509295 r3179979 2 2 /* 3 3 Plugin Name: AC Custom Loop Shortcode 4 Plugin URI: https:// github.com/ambercouch/ac-wp-custom-loop-shortcode4 Plugin URI: https://ambercouch.co.uk 5 5 Description: Shortcode ( [ac_custom_loop] ) that allows you to easily list post, pages or custom posts with the WordPress content editor or in any widget that supports short code. A typical use would be to show your latest post on your homepage. 6 6 Version: 1.5 … … 8 8 Author URI: http://ambercouch.co.uk 9 9 Author Email: richard@ambercouch.co.uk 10 Text Domain: ac-wp-custom-loop-shortcode 11 Domain Path: /lang/ 12 License: 13 Copyright 2018 AmberCouch 14 This program is free software; you can redistribute it and/or modify 15 it under the terms of the GNU General Public License as published by 16 the Free Software Foundation; either version 2 of the License, or 17 (at your option) any later version. 18 19 This program is distributed in the hope that it will be useful, 20 but WITHOUT ANY WARRANTY; without even the implied warranty of 21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 GNU General Public License for more details. 23 24 You should have received a copy of the GNU General Public License 25 along with this program; if not, write to the Free Software 26 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 10 Text Domain: ac-custom-loop-shortcode 11 License: GPL-2.0+ 12 License URI: http://www.gnu.org/licenses/gpl-2.0.txt 27 13 */ 28 14 29 use Timber\PostQuery;30 31 15 defined('ABSPATH') or die('You do not have the required permissions'); 32 16 33 if (!function_exists('ac_wp_custom_loop_short_code')) 34 { 35 36 function ac_wp_custom_loop_short_code($atts) 17 function acclsc_get_template($timber, $template_path, $template_type , $template){ 18 $theme_directory = $template_path; 19 20 $twig_template_folder = false; 21 if ($timber != false){ 22 $twig_template_folder = $theme_directory . 'templates/'; 23 $template = (substr($template, -5) === '.twig') ? substr_replace($template ,"",-5) : $template; 24 $theme_template = $template . '.twig'; 25 $theme_template_type = $template . '-' . $template_type . '.twig'; 26 }else{ 27 28 //$theme_extention = (substr($template, -4) === '.php' || substr($template, -5) === '.twig' ) ? '' : '.php'; 29 $template = (substr($template, -4) === '.php') ? substr_replace($template ,"",-4) : $template; 30 $theme_template = $theme_directory . $template . '.php'; 31 $theme_template_type = $theme_directory . $template . '-' . $template_type . '.php'; 32 } 33 if($timber != false){ 34 35 if (file_exists($twig_template_folder.$theme_template_type)) 36 { 37 $template = $theme_template_type; 38 39 }elseif (file_exists($twig_template_folder.$theme_template )) 40 { 41 $template = $theme_template; 42 }else{ 43 $template = "loop-template.twig"; 44 } 45 }else{ 46 47 if (file_exists($theme_template_type)) 48 { 49 $template = $theme_template_type; 50 51 }elseif (file_exists( $theme_template )) 52 { 53 $template = $theme_template; 54 }else{ 55 $template = "loop-template.php"; 56 } 57 } 58 return $template; 59 } 60 61 function acclsc_get_orderby($ids, $type){ 62 63 if($ids){ 64 $orderby = 'post__in'; 65 } 66 elseif ($type == 'post') 37 67 { 38 39 68 $orderby = 'date'; 69 } 70 else 71 { 72 $orderby = 'menu_order'; 73 } 74 75 return $orderby; 76 77 } 78 79 // Function to validate the post type 80 function acclsc_valid_post_type($type) { 81 $post_types = get_post_types(array('public' => true), 'names'); 82 return in_array($type, $post_types) || $type == 'any'; 83 } 84 85 // Function to return an error message for invalid post types 86 function acclsc_invalid_post_type_message($type) { 87 $post_types = get_post_types(array('public' => true), 'names'); 88 $output = '<p><strong>' . $type . '</strong> ' . __('is not a public post type on this website.') . '</p>'; 89 $output .= '<ul>'; 90 foreach ($post_types as $cpt) { 91 $output .= '<li>' . $cpt . '</li>'; 92 } 93 $output .= '</ul>'; 94 $output .= '<p>'; 95 $output .= __('Please edit the short code to use one of the available post types.', 'ac-wp-custom-loop-shortcode'); 96 $output .= '</p>'; 97 $output .= '<code>[ ac_custom_loop type="post" show="4"]</code>'; 98 return $output; 99 } 100 101 // Function to enqueue CSS 102 function acclsc_enqueue_styles() { 103 $handle = 'ac_wp_custom_loop_styles'; 104 if (!wp_script_is($handle, 'enqueued')) { 105 wp_register_style('ac_wp_custom_loop_styles', plugin_dir_url(__FILE__) . 'assets/css/ac_wp_custom_loop_styles.css', array(), '20181016'); 106 wp_enqueue_style('ac_wp_custom_loop_styles'); 107 } 108 } 109 110 // Function to build WP_Query arguments with support for multiple terms and exclusion terms 111 function acclsc_build_query_args($type, $show, $orderby, $order, $ignore_sticky_posts, $tax, $term, $exclude, $ids) { 112 $args = array( 113 'post_type' => $type, 114 'posts_per_page' => $show, 115 'orderby' => $orderby, 116 'order' => $order, 117 'ignore_sticky_posts' => $ignore_sticky_posts 118 ); 119 120 // Initialize the tax_query array 121 $args['tax_query'] = array('relation' => 'AND'); 122 123 // Add included terms if `tax` and `term` are provided 124 if (!empty($tax) && !empty($term)) { 125 $terms = explode(',', $term); // Split terms by comma 126 $args['tax_query'][] = array( 127 'taxonomy' => $tax, 128 'field' => 'slug', 129 'terms' => $terms, 130 'operator' => 'AND' // Ensures posts match all terms in the array 131 ); 132 } 133 134 // Add excluded terms if `exclude` is provided 135 if (!empty($tax) && !empty($exclude)) { 136 $exclude_terms = explode(',', $exclude); // Split exclude terms by comma 137 $args['tax_query'][] = array( 138 'taxonomy' => $tax, 139 'field' => 'slug', 140 'terms' => $exclude_terms, 141 'operator' => 'NOT IN' // Excludes posts with any of these terms 142 ); 143 } 144 145 // Include specific post IDs if provided 146 if (!empty($ids)) { 147 $args['post__in'] = explode(',', $ids); 148 } 149 150 return $args; 151 } 152 153 // Function to render PHP template 154 function acclsc_render_php_template($query, $template) { 155 $output = ''; 156 while ($query->have_posts()) { 157 $query->the_post(); 158 ob_start(); 159 include($template); 160 $output .= ob_get_clean(); 161 } 162 wp_reset_postdata(); 163 return $output; 164 } 165 166 // Function to render Timber template 167 function acclsc_render_timber_template($query, $template) { 168 $context = Timber::get_context(); 169 $context['posts'] = new Timber\PostQuery($query); 170 ob_start(); 171 Timber::render($template, $context); 172 return ob_get_clean(); 173 } 174 175 176 // Function to handle queries with one or more subtax terms and group by term combinations 177 function acclsc_handle_subtax_query($query_args, $subtaxes, $timber, $template, $wrapper, $class) { 178 $output = ''; 179 $subtaxonomies = explode(',', $subtaxes); // Split subtaxonomies by comma 180 $grouped_posts = []; // Initialize grouped posts array 181 182 // Get terms for each subtaxonomy 183 $terms_by_taxonomy = []; 184 foreach ($subtaxonomies as $subtax) { 185 $terms = get_terms(array( 186 'taxonomy' => $subtax, 187 'hide_empty' => true 188 )); 189 if (!empty($terms) && !is_wp_error($terms)) { 190 $terms_by_taxonomy[$subtax] = $terms; 191 } 192 } 193 194 // Handle single subtax case 195 if (count($subtaxonomies) == 1) { 196 foreach ($terms_by_taxonomy[$subtaxonomies[0]] as $term) { 197 $subtax_query_args = $query_args; 198 $subtax_query_args['tax_query'][] = array( 199 'taxonomy' => $subtaxonomies[0], 200 'field' => 'slug', 201 'terms' => $term->slug 202 ); 203 204 $query = new WP_Query($subtax_query_args); 205 206 if ($query->have_posts()) { 207 $grouped_posts[$term->name] = []; 208 while ($query->have_posts()) { 209 $query->the_post(); 210 $grouped_posts[$term->name][] = get_post(get_the_ID()); 211 } 212 } 213 wp_reset_postdata(); 214 } 215 216 } else { 217 // Multiple subtaxonomies case with nested grouping 218 foreach ($terms_by_taxonomy[$subtaxonomies[0]] as $term_1) { 219 foreach ($terms_by_taxonomy[$subtaxonomies[1]] as $term_2) { 220 $subtax_query_args = $query_args; 221 $subtax_query_args['tax_query'] = array('relation' => 'AND', 222 array( 223 'taxonomy' => $subtaxonomies[0], 224 'field' => 'slug', 225 'terms' => $term_1->slug 226 ), 227 array( 228 'taxonomy' => $subtaxonomies[1], 229 'field' => 'slug', 230 'terms' => $term_2->slug 231 ), 232 array( 233 'taxonomy' => $query_args['tax_query'][0]['taxonomy'], 234 'field' => 'slug', 235 'terms' => $query_args['tax_query'][0]['terms'] 236 ) 237 ); 238 239 $query = new WP_Query($subtax_query_args); 240 241 if ($query->have_posts()) { 242 // Nest posts under [fooTerm][barTerm] structure 243 if (!isset($grouped_posts[$term_1->name])) { 244 $grouped_posts[$term_1->name] = []; 245 } 246 $grouped_posts[$term_1->name][$term_2->name] = []; 247 248 while ($query->have_posts()) { 249 $query->the_post(); 250 $grouped_posts[$term_1->name][$term_2->name][] = get_post(get_the_ID()); 251 } 252 } 253 wp_reset_postdata(); 254 } 255 } 256 } 257 258 // Render grouped posts using either Timber or PHP templates 259 if ($wrapper == 'true') { 260 $output .= '<div class="' . esc_attr($class) . '">'; 261 } 262 263 if ($timber && class_exists('Timber')) { 264 $output .= acclsc_render_grouped_timber_template($grouped_posts, $template); 265 } else { 266 $output .= acclsc_render_grouped_php_template($grouped_posts, $template); 267 } 268 269 if ($wrapper == 'true') { 270 $output .= '</div>'; 271 } 272 273 return $output; 274 } 275 276 // Function to render grouped posts using PHP template 277 function acclsc_render_grouped_php_template($grouped_posts, $template) { 278 279 error_log(print_r('acclsc_render_grouped_php_template', true)); 280 error_log(print_r('grouped_post', true)); 281 error_log(print_r($grouped_posts, true)); 282 $output = ''; 283 ob_start(); 284 include($template); 285 $output .= ob_get_clean(); 286 wp_reset_postdata(); 287 return $output; 288 } 289 290 // Function to render grouped posts using Timber template 291 function acclsc_render_grouped_timber_template($grouped_posts, $template) { 292 $context = Timber::get_context(); 293 $context['grouped_posts'] = $grouped_posts; 294 ob_start(); 295 Timber::render($template, $context); 296 return ob_get_clean(); 297 } 298 299 if (!function_exists('acclsc_sc')) { 300 301 function acclsc_sc($atts) { 40 302 extract(shortcode_atts(array( 41 303 'type' => 'post', 42 'show' => 4,304 'show' => '-1', 43 305 'template_path' => get_stylesheet_directory() . '/', 44 306 'template' => 'loop-template', … … 51 313 'tax' => '', 52 314 'term' => '', 315 'subtax' => '', // New subtax parameter 53 316 'timber' => false, 317 'exclude' => '', 54 318 'ids' => '' 55 56 319 ), $atts)); 57 320 321 // Validate post type 322 if (!acclsc_valid_post_type($type)) { 323 return acclsc_invalid_post_type_message($type); 324 } 325 326 $output = ''; 58 327 $template_type = $type; 59 328 60 //default orderby 61 if ($type == 'post' && $orderby == '') 62 { 63 $orderby = 'date'; 64 } 65 elseif($orderby == '') 66 { 67 $orderby = 'menu_order'; 68 } 69 70 if($ids != ''){ 329 // Handle IDs 330 if ($ids != '') { 71 331 $ids = explode(',', $ids); 72 332 $type = 'any'; 73 $orderby = 'post__in'; 74 75 } 76 77 $args = [ 78 'public' => true 79 ]; 80 $output = ''; 81 $post_types = get_post_types($args, 'names'); 82 83 $theme_directory = $template_path; 84 85 86 if ($timber != false){ 87 $twig_template_folder = $theme_directory . 'templates/'; 88 $template = (substr($template, -5) === '.twig') ? substr_replace($template ,"",-5) : $template; 89 $theme_template = $template . '.twig'; 90 $theme_template_type = $template . '-' . $template_type . '.twig'; 91 }else{ 92 93 //$theme_extention = (substr($template, -4) === '.php' || substr($template, -5) === '.twig' ) ? '' : '.php'; 94 $template = (substr($template, -4) === '.php') ? substr_replace($template ,"",-4) : $template; 95 $theme_template = $theme_directory . $template . '.php'; 96 $theme_template_type = $theme_directory . $template . '-' . $template_type . '.php'; 97 } 98 99 $wrapperOpen = ($wrapper == 'true') ? '<div class="'.$class.'" >' : ''; 100 $wrapperClose = ($wrapper == 'true') ? '</div>' : ''; 101 102 if ($css == 'true') 103 { 104 $handle = 'ac_wp_custom_loop_styles'; 105 $list = 'enqueued'; 106 107 if (!wp_script_is($handle, $list)) 108 { 109 wp_register_style('ac_wp_custom_loop_styles', plugin_dir_url(__FILE__) . 'assets/css/ac_wp_custom_loop_styles.css', array(), '20181016'); 110 wp_enqueue_style('ac_wp_custom_loop_styles'); 333 } 334 335 // Get the template path 336 $template = acclsc_get_template($timber, $template_path, $template_type, $template); 337 338 // Check if the template exists 339 if (!file_exists($template)) { 340 return '<p>Template not found: ' . $template . '</p>'; 341 } 342 343 // Get the correct orderby 344 $orderby = acclsc_get_orderby($ids, $type); 345 346 // Enqueue CSS if required 347 if ($css == 'true') { 348 acclsc_enqueue_styles(); 349 } 350 351 // Main Query Arguments 352 $query_args = acclsc_build_query_args($type, $show, $orderby, $order, $ignore_sticky_posts, $tax, $term, $exclude, $ids); 353 354 // If no subtax is provided, use the default query and rendering behavior 355 if (empty($subtax)) { 356 // Execute the query 357 $query = new WP_Query($query_args); 358 359 // Check if there are posts and render accordingly 360 if ($query->have_posts()) { 361 if ($wrapper == 'true') { 362 $output .= '<div class="' . esc_attr($class) . '">'; 363 } 364 365 // Use Timber or PHP template rendering 366 if ($timber && class_exists('Timber')) { 367 $output .= acclsc_render_timber_template($query, $template); 368 } else { 369 $output .= acclsc_render_php_template($query, $template); 370 } 371 372 if ($wrapper == 'true') { 373 $output .= '</div>'; 374 } 111 375 } 112 } 113 114 if($timber != false){ 115 116 if (file_exists($twig_template_folder.$theme_template_type)) 117 { 118 $template = $theme_template_type; 119 120 }elseif (file_exists($twig_template_folder.$theme_template )) 121 { 122 $template = $theme_template; 123 }else{ 124 $template = "loop-template.twig"; 125 } 126 }else{ 127 128 if (file_exists($theme_template_type)) 129 { 130 $template = $theme_template_type; 131 132 }elseif (file_exists( $theme_template )) 133 { 134 $template = $theme_template; 135 }else{ 136 $template = "loop-template.php"; 137 } 138 } 139 140 if (!in_array($type, $post_types) && $type != 'any') 141 { 142 $output .= '<p>'; 143 $output .= '<strong>' . $type . '</strong> '; 144 $output .= __('is not a public post type on this website. The following post type are available: -', 'ac-wp-custom-loop-shortcode'); 145 $output .= '</p>'; 146 $output .= '<ul>'; 147 148 foreach ($post_types as $key => $cpt) 149 { 150 $output .= '<li>' . $cpt . '</li>'; 151 } 152 $output .= '</ul>'; 153 $output .= '<p>'; 154 $output .= __('Please edit the short code to use one of the available post types.', 'ac-wp-custom-loop-shortcode'); 155 $output .= '</p>'; 156 $output .= '<code>[ ac_custom_loop type="post" show="4"]</code>'; 157 158 return $output; 159 } 160 161 global $wp_query; 162 $temp_q = $wp_query; 163 $wp_query = null; 164 $wp_query = new WP_Query(); 165 $wp_query->query(array( 166 'post_type' => $type, 167 'showposts' => $show, 168 'orderby' => $orderby, 169 'order' => $order, 170 'ignore_sticky_posts' => $ignore_sticky_posts, 171 'taxonomy' => $tax, 172 'term' => $term, 173 'post__in' => $ids 174 )); 175 176 177 if (have_posts()) : 178 $output .= $wrapperOpen; 179 if($timber === false){ 180 181 while (have_posts()): 182 the_post(); 183 ob_start(); 184 ?> 185 <?php include("$template"); ?> 186 <?php 187 $output .= ob_get_contents(); 188 ob_end_clean(); 189 endwhile; 190 191 }else{ 192 if(class_exists('Timber')){ 193 194 $context = Timber::get_context(); 195 $context['posts'] = new Timber\PostQuery(); 196 $templates = array( $template); 197 ob_start(); 198 Timber::render( $templates, $context ); 199 $output .= ob_get_contents(); 200 ob_end_clean(); 201 }else{ 202 ob_start(); 203 ?> 204 <?php echo "<p>The Timber plugin is not active.<br> Activate Timber or set <code>timber='false'</code> in the short code</p>" ?> 205 <?php 206 $output .= ob_get_contents(); 207 ob_end_clean(); 208 } 209 210 } 211 $output .= $wrapperClose; 212 endif; 213 214 if (have_posts()) : 215 216 endif; 217 218 $wp_query = $temp_q; 376 377 } else { 378 // If subtax is provided, query the terms and group the results by subtax term 379 $output .= acclsc_handle_subtax_query($query_args, $subtax, $timber, $template, $wrapper, $class); 380 } 219 381 220 382 return $output; 221 222 } 223 224 add_shortcode('ac_custom_loop', 'ac_wp_custom_loop_short_code'); 225 226 } 383 } 384 385 add_shortcode('ac_custom_loop', 'acclsc_sc'); 386 } -
ac-custom-loop-shortcode/trunk/readme.txt
r2509295 r3179979 3 3 Donate link: http://ambercouch.co.uk/ 4 4 Tags: shortcode, list post, list custom posts, timber, twig, custom post type 5 Requires at least: 4.66 Tested up to: 5.77 Stable tag: 1.5 5 Requires at least: 5.2 6 Tested up to: 6.6 7 Stable tag: 1.5.1 8 8 Requires PHP: 5.2.4 9 9 License: GPLv2 or later … … 28 28 == Frequently Asked Questions == 29 29 30 = How do I display a certain post type using the short code 31 32 [ac_custom_loop type="foo"] 33 this displays foo custom post types 34 35 = How do I display a post that have a certain tag = 36 37 [ac_custom_loop type="post" tax="tag" term="foo" ] 38 this displays posts that have been tagged foo 39 40 = How do I group post in to tags = 41 42 [ac_custom_loop type="post" subtax="category,tag"] 43 this displays posts grouped into category and sub-grouped in to tags 44 45 Cat Foo 46 Tag bar 47 post1, post2 48 tag baz 49 post3, post4 50 Cat Foo2 51 Tag bar2 52 post5, post6 53 30 54 = Can I use my own template to display the looped posts? = 31 55 … … 39 63 40 64 == Upgrade Notice == 65 66 = 1.5.1 = 67 Added support for grouping posts by taxonomies. 41 68 42 69 = 1.5 = … … 62 89 63 90 == Changelog == 91 92 = 1.5.1 (2024-11-01) = 93 * Added support for grouping posts by taxonomies. 94 * Refactored all the code 64 95 65 96 = 1.5 (2021-04-04) =
Note: See TracChangeset
for help on using the changeset viewer.