Tag Archives: wordpress

Using one category page to show multiple categories in WordPress

[Update: There is probably never a good reason to do this. Instead, create a new category to hold the posts.]

Trying to show multiple categories in one loop is easily the hardest thing I’ve done in WordPress.

  1. First, create a container category where you want your multiple categories to be shown. Let’s call it the MultiCat category and give it the multicat slug. No posts are required to belong to this category, and if they do, it will have no benefit.
  2. Next, add this bit of code to functions.php of your theme. This is where we create a query variable which enables us to identify the multi-category page properly. Update the category slugs below to match the slugs of the categories you want to show together.
    function multi_cat_handler( $query ) {
        if ( $query->is_main_query() && $query->query["category_name"] == 'cat1-slug,cat2-slug,cat3-slug,cat4-slug' ) {
         $query->set("allish",'yes');
        }
    }
    add_action( 'pre_get_posts', 'multi_cat_handler' );
  3. Next, add this code to functions.php. Update multicat to the slug of your multiple categories category. Also update the other slugs as in the previous step.
    function alter_the_query_for_me( $request ) {
        $dummy_query = new WP_Query(); 
        $dummy_query->parse_query( $request );
    	  if($dummy_query->query['category_name'] == 'multicat') {
    		$request['category_name'] = 'cat1-slug,cat2-slug,cat3-slug,cat4-slug';
    	  }
        return $request;
    }
    add_filter( 'request', 'alter_the_query_for_me' );
  4. To display the h1 tag of the MultiCat category page properly, we use the following code:
    if(get_query_var('allish') == 'yes') {
    echo 'Title of the Multiple Categories Page';
    }
    else {
    echo 'Normal code that outputs category title';
    }

    If you do not do the above, when people go to the MultiCat category page, they will see a random title from one of the multiple categories you want to show on the page, which is not the behavior you want.

  5. Below is the main code that outputs your posts. The if clause at the top allows us to know we are on the multiple categories page (we cannot use other methods such as checking category ID, since that will return a random category’s ID from the multiple categories we want to show).
    <!--?php if(get_query_var('allish')=='yes') : ?-->
    
    <!--?php 
    $paged = (get_query_var('paged')) ? get_query_var('paged') : 1;  //necessary for proper pagination
    
    parse_str($get_string, $get_array);
    
    
    <?php $args = array( 'paged' => $paged, 'cat' => '3,4,671,672' );
    ?-->
    
    <!--?php query_posts($args); ?-->
    <!--?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?-->
    
    Here lies the code that outputs your post content
    
    <!--?php endwhile ?-->
    
    <!--?php else : ?-->
    
    Here is the loop that outputs your normal categories
    
    <!--?php endif ?-->
    

    The $args array contains the query we use to pull posts from the database. We are pulling posts from the categories with the IDs of 3, 4, 671 and 672. Notice that in Step 2 we used category slugs, while in this step we are using category IDs. They have to match, and order may matter.

That’s all.

Caveats

The RSS feed of the category page will be the RSS feed of one of the categories shown on the MultiCat page. This may be fixable through using RSS-specific filters, but in my case I had no need for RSS and did not try to find a fix.

How to moderate bbPress submissions that contain links

The most common trait of forum spam submissions is that they contain links. The code below (add it to your main wordpress install’s functions.php theme file) filters new bbPress topics and replies and if it detects a link, it marks the submission as “pending”, allowing moderators to review the submission in the back end before publishing it. The code is working on bbPress version 2.5.4.

The code, however, creates front end issues. If it is a new topic, the user is redirected to a page that contains the topic title but not the topic content. If it is a new reply, the page reloads with no indication of that the reply has been saved. These issues may be solvable with query variables and some jQuery, but in my case, almost all submissions that contain links are guaranteed to be spam, therefore user experience is not a big concern.

function bb_filter_handler($data , $postarr) {
    
   
   //If the post date and post_modified are the same, it is a new reply/topic. But if they are different,
   //it is a moderater editing the reply/topic (such as changing from pending to published status, 
   //therefore we let the data through without filtering. Without this admins/moderators won't be able to
   //change a reply/topic from "pending" status to "published".
if(  strtotime($data["post_date"]) != strtotime($data["post_modified"]    )  ) {
    
    return $data;
}
    
if(   ($data["post_type"] == 'reply' || $data["post_type"] == 'topic') && $data["post_status"] == 'publish'    ) {  

        $text= $data["post_content"];
        
        
        $regex = "((https?|ftp)\:\/\/)?"; // SCHEME 
        $regex .= "([a-z0-9+!*(),;?&=\$_.-]+(\:[a-z0-9+!*(),;?&=\$_.-]+)?@)?"; // User and Pass 
        $regex .= "([a-z0-9-.]*)\.([a-z]{2,3})"; // Host or IP 
        $regex .= "(\:[0-9]{2,5})?"; // Port 
        $regex .= "(\/([a-z0-9+\$_-]\.?)+)*\/?"; // Path 
        $regex .= "(\?[a-z+&\$_.-][a-z0-9;:@&%=+\/\$_.-]*)?"; // GET Query 
        $regex .= "(#[a-z_.-][a-z0-9+\$_.-]*)?"; // Anchor 
        
        
        
           if(preg_match("/$regex/", $text))  { 
                   $data["post_status"] = 'pending';
           } else {
                  //do nothing
           }    
    
    
}

 return $data;
 
}
add_filter( 'wp_insert_post_data', 'bb_filter_handler', '99', 2 );

How to automate and throttle Relevanssi indexing on large websites

First of all, update Relevanssi to the latest version. This significantly increased indexing performance on my 80,000+ page website.

Next, I created the following hacky solution for a problem that shouldn’t exist; the fact that Relevanssi cannot silently index everything without hogging all server resources. First find out the number of pages Relevanssi can index in one go without overloading your server, say 500. Then use the following Tampermonkey script on the Relevanssi settings page. You need Chrome’s Tampermonkey extension. Here’s what the script does:

  1. It enables jQuery on the Relevanssi dashboard.
  2. It waits 15 seconds, then clicks the “Continue indexing” button. Once the indexing is done and the page reloads, it waits 15 seconds, then clicks it again, and so on.
  3. Leave this running in a tab until all pages are indexed, then turn the script off and close the tab.

Below is the code:

// ==UserScript==
// @name       Relevanssi Index Button Clicker
// @namespace  http://hawramani.com
// @version    0.1
// @description  Click click click
// @match      http://mywordpressite.com/wp-admin/options-general.php?page=relevanssi/relevanssi.php
// @copyright  2014 jQuery, Ikram Hawramani
// ==/UserScript==


(function () {
 
    function loadScript(url, callback) {
 
        var script = document.createElement("script")
        script.type = "text/javascript";
 
        if (script.readyState) { //IE
            script.onreadystatechange = function () {
                if (script.readyState == "loaded" || script.readyState == "complete") {
                    script.onreadystatechange = null;
                    callback();
                }
            };
        } else { //Others
            script.onload = function () {
                callback();
            };
        }
 
        script.src = url;
        document.getElementsByTagName("head")[0].appendChild(script);
    }
 
    loadScript("https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js", function () {
 
         //jQuery loaded
         console.log('jquery loaded');

        setTimeout(function(){$('[name="index_extend"]').click();},15000);
 
    });
 
 
})();

Using query_posts() as if it is get_posts()

Some filters work only with query_posts(); but what if you wanted to use one of these filters in a situation where you would normally use get_posts()? Below is the translation:

Original get_posts() query:

$args = array('orderby'=> 'title', 'order' => 'ASC','fields' =>'ids');

$posts_array = get_posts($args);

Translated to query_posts():

$args = array('posts_per_page'=>-1, 'orderby'=> 'title', 'order' => 'ASC','fields' =>'ids');
// the -1 means return all posts, without it you will get the
// number of posts you've set your blog to show per page

$posts_array = query_posts($args);

// do your thing here

wp_reset_query(); // this stops your get_posts() query from affecting other functions;
                  // without it functions like is_single() will break

How to ignore accents and other diacritics in WordPress/MySQL search (Arabic, French, etc.)

On my new Asmaa.org website, which is an Arabic-language baby name resource, I use a simple loop to show the posts in alphabetical order. Each post title is a baby name:

$args = array( 'paged' => $paged, 'orderby'=> 'title', 'order' => 'ASC',  'cat' => $cat_id);
query_posts($args); ?>
while ( have_posts() ) : the_post()

Since the Arabic alphabet is an abjad, most vowels are added to a word as diacritical marks. This has the unfortunate consequence of causing علم and عَلَم, two words that should be shown very close next to each other, to be shown miles apart in an alphabetical sort.

I solved the issue with this WordPress filter:

add_filter('posts_orderby', 'cleanse_diacritics');

function cleanse_diacritics($d) { //$d is this string: 'wp_posts.post_title ASC' (or sth similar) in a default WordPress install
                          //assuming you are sorting alphabetically ascending
    if(strpos($d,'title') !== false) { //if the string 'title' is in the orderby query, we know that
                                       //we are dealing with an alphabetical sort.
                                       //no need to mess with other queries like order by post_date

// below we replace the default order query WordPress passes to MySQL by
// using a whole bunch of replaces to remove diacritics from the sorting
        $d = 'REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(HEX(REPLACE(
wp_posts.post_title, "-", "")), "D98E", ""), "D98B", ""), "D98F", ""), "D98C", 
""),"D991",""),"D992",""),"D990",""),"D98D","") ASC';
    }
    return $d;
}

I got the nested MySQL replace() functions from this StackOverflow answer. I am not sure why I used that function name; it had something to do with the fact that I was logging the $d variable when I first created it, just to see what it was.</>

Explanation: When you run a query_posts(array('orderby' => 'title') function or something similar, the posts_orderby filter can be used to modify the order by part of the MySQL query. We wrap the name of the relevant MySQL column in replace() functions to remove all diacritics using their hex UTF-8 code units, which results in a diacritic-insensitive sort.

If you are dealing with a language other than Arabic, you may need to replace a code with another code (é [C3A9] to e [65] for example) instead of replacing with an empty string.

Considerations

The filter posts_orderby does not seem to work with get_posts(). There is a workaround however; see: Using query_posts() as if it is get_posts().