getButterfly Logo getButterfly

Our WordPress tutorials allow you to extend the power of WordPress. Our step by step WordPress tutorials are easy to understand and follow the WordPress best practices. These WordPress tutorials contain real-life examples, tips, and hacks that allow you to learn WordPress faster.

How to display a list of WordPress plugins from the official repository

Note: This function has been updated to use one transient only. Scroll down to see the updated version.

This code will retrieve a list of plugins from the WordPress plugin repository and it will cache the results for 24 hours, using the plugin API. Use the {wordpress-items slugs="quick-event-calendar, youtube-playlist-player, wp-perfect-plugin, mobilize, ecards-lite, marketplace-items"} shortcode to display them. Note that some CSS styling might be necessary.

<?php
/*
 * WordPress Plugins Shortcode
 * 
 * Displays your WordPress plugins inside a post or a page
 * 
 * @author Ciprian Popescu
 * @copyright 2016, 2017 Ciprian Popescu
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * 
 */

function whiskey_wordpress_items($atts) {
    extract(shortcode_atts(array(
        'slugs' => '',
    ), $atts));

    $url = 'https://api.wordpress.org/plugins/info/1.0/';

    $data = '';

    $slugs = explode(',', $slugs);
    $slugCount = count($slugs);
    if ($slugCount > 0) {
        $data .= '<ul class="columnar">';
        for ($i = 0; $i < $slugCount; $i++) {
            $args = (object) array('slug' => trim($slugs[$i]));
            $request = array('action' => 'plugin_information', 'timeout' => 15, 'request' => serialize($args));

            /*
             * Set plugin data transient if it does not exist.
             * If it exists, retrieve it from the database
             */
            $slug_transient = get_transient('wp-plugin-' . strtolower(trim($slugs[$i])));

            if($slug_transient === false) {
                $response = wp_remote_post($url, array('body' => $request));
                $plugin_info = unserialize($response['body']);

                $slug = $plugin_info->slug;
                $name = $plugin_info->name;
                $downloaded = $plugin_info->downloaded;
                $author = $plugin_info->author;
                $version = $plugin_info->version;

                set_transient (
                    'wp-plugin-' . strtolower(trim($slugs[$i])),
                    $slug . '|' . $name . '|' . $downloaded . '|' . $author . '|' . $version,
                    24 * HOUR_IN_SECONDS
                );
            } else {
                $plugin_info = explode('|', $slug_transient);

                $slug = $plugin_info[0];
                $name = $plugin_info[1];
                $downloaded = $plugin_info[2];
                $author = $plugin_info[3];
                $version = $plugin_info[4];
            }
            //

            $data .= '<li>
                <div class="package-widget package-wordpress">
                    <div class="package-details">
                        <h3><a class="name" href="https://wordpress.org/plugins/' . $slug . '/">' . $name . '</a></h3>
                        <p class="description"><code>' . $version . '</code></p>
                        <p class="author quiet">downloaded ' . number_format($downloaded) . ' times | ' . $author . '</p>
                    </div>
                </div>
            </li>';
        }
        $data .= '</ul>';
    }

    return $data;
}

add_shortcode('wordpress-items', 'whiskey_wordpress_items');

This is how it looks on my website:

WordPress Plugins - API

UPDATE #1

I have updated the code to use one transient instead of multiple ones, based on the number of plugins. See the updated code below. It will safely replace the one above.

function whiskey_wordpress_items($atts) {
    extract(shortcode_atts(array(
        'slugs' => '',
    ), $atts));

    $data = get_transient('whiskey_plugins');
    if ($data === false) {
        $url = 'https://api.wordpress.org/plugins/info/1.0/';

        $data = '';

        $slugs = explode(',', $slugs);
        $slugCount = count($slugs);
        if ($slugCount > 0) {
            $data .= '<ul class="columnar">';
                for ($i = 0; $i < $slugCount; $i++) {
                    $args = (object) array('slug' => trim($slugs[$i]));
                    $request = array('action' => 'plugin_information', 'timeout' => 15, 'request' => serialize($args));

                    $response = wp_remote_post($url, array('body' => $request));

                    $plugin_info = unserialize($response['body']);

                    $slug = $plugin_info->slug;
                    $name = $plugin_info->name;
                    $downloaded = $plugin_info->downloaded;
                    $author = $plugin_info->author;
                    $version = $plugin_info->version;

                    $data .= '<li>
                        <div class="package-widget package-wordpress">
                            <div class="package-details">
                                <h3><a class="name" href="https://wordpress.org/plugins/' . $slug . '/">' . $name . '</a></h3>
                                <p class="description"><code>' . $version . '</code></p>
                                <p class="author quiet">downloaded ' . number_format($downloaded) . ' times | ' . $author . '</p>
                            </div>
                        </div>
                    </li>';
                }
            $data .= '</ul>';
        }

        set_transient('whiskey_plugins', $data, 24 * HOUR_IN_SECONDS);
    }

    return $data;
}

UPDATE #2

I have been using the WordPress plugin API on my homepage to get my WordPress repository plugins and live data about the number of downloads. Everything was live and the API was really fast, but the page still seemed sluggish. So I have implemented transients and the speed increased by 59.54% on the homepage and by 23.91% on the dedicated plugins page.

Transients and page speed

How to add pagination to custom WordPress tables

First of all, we need to write a class to handle pagination. Second, we need to display the pagination in our custom WordPress table (in the back-end section, for a plugin or for theme settings).

Here is the pagination class and here below is the sample usage:

include 'classes/Pagination.php';

<?php
$items = $wpdb->get_results("SELECT field1, field2, field3 FROM wp_custom_table");
$items = $wpdb->num_rows;

if ($items > 0) {
    $p = new Pagination;
    $p->items($items);
    $p->target('admin.php?page=mypage');
    $p->calculate();

    if (!isset($_GET['paging'])) {
        $p->page = 1;
    } else {
        $p->page = (int) $_GET['paging'];
    }

    $limit = 'LIMIT ' . ($p->page - 1) * $p->limit . ', ' . $p->limit;
} else {
    echo '<p>No records found!</p>';
}

$results = $wpdb->get_results("SELECT field1, field2, field3 FROM wp_custom_table $limit", ARRAY_A);
?>
<div class="tablenav"><?php echo $p->show(); ?></div>
<table class="wp-list-table widefat striped posts">
    <thead>
        <tr>
            <th scope="col">Field 1</th>
            <th scope="col">Field 2</th>
            <th scope="col">Field 2</th>
        </tr>
    </thead>
    <?php
    foreach($results as $row) {
        echo '<tr>';
            echo '<td>' . $row['field1'] . '</td>';
            echo '<td>' . $row['field2'] . '</td>';
            echo '<td>' . $row['field3'] . '</td>';
        echo '</tr>';
    }
    ?>
</table>

And that is all. The pagination class is compatible with WordPress 4.7+.

How to add custom columns to custom post types

Here’s how to add additional custom columns to any post type in WordPress. This applies to the admin editing screen. Note that I am using ACF in the example below, but you can easily switch get_field() with get_post_meta($post->ID, 'key_name', true). Also, I am using event as the post type slug.

function gb1475228224_page_columns($columns) {
    $columns = array(
        'cb'    => '<input type="checkbox" />',
        'title' => 'Title',
        'date'  => 'Date',
        'cdate' => 'Event Date',
        'ctime' => 'Event Time',
    );

    return $columns;
}

function gb1475228224_custom_columns($column) {
    global $post;

    if($column == 'thumbnail') {
        echo wp_get_attachment_image( get_field('page_image', $post->ID), array(200,200) );
    } else if($column == 'cdate') {
        echo 'Event Date:<br>' . get_field('booking_date', $post->ID);
    } else if($column == 'ctime') {
        echo 'Event Time:<br>' . get_field('booking_time', $post->ID);
    }
}

add_action("manage_posts_custom_column", "gb1475228224_custom_columns");
add_filter("manage_edit-event_columns", "gb1475228224_page_columns");

That is all. Enjoy.

How to create a taxonomy dropdown

Create a taxonomy dropdown and hide empty terms:

<select class="filter-categories">
    <option value="">Category...</option>
    <?php
    $terms = get_terms(array(
        'taxonomy' => 'event-category',
        'hide_empty' => true
    ));
    if (!empty($terms) && !is_wp_error($terms)) {
        foreach($terms as $term) {
            echo '<option value="' . $term->slug . '">' . $term->name . ' (' . $term->count . ')</option>';
        }
    }
    ?>
</select>

Modify the event-category taxonomy based on your requirements.

How to generate an expandable archive list

WordPress Archives

WordPress can generate a yearly archive or a monthly archive. But not both. The code below uses a filter and a small jQuery code to create an expandable/collapsible archive list.

Here’s the archive filter:

/*
 * Add filter to query archives by year
 */
function whiskey_getarchives_filter($where, $args) {
    if(isset($args['year'])) {
        $where .= ' AND YEAR(post_date) = ' . intval($args['year']);
    }

    return $where;
}

add_filter('getarchives_where', 'whiskey_getarchives_filter', 10, 2);

Here’s the archive display code:

<dl class="tree-accordion">
    <?php
    $currentyear = date("Y");
    $years = range($currentyear, 1950); // Set the starting year
    foreach($years as $year) { ?>
        <dt><a href=""><i class="fa fa-fw fa-plus-square-o" aria-hidden="true"></i> <?php echo $year; ?></a></dt>
        <?php
        $archive = wp_get_archives('echo=0&show_post_count=1&type=monthly&year=' . $year);
        $archive = explode('</li>', $archive);
        $links = array();

        foreach($archive as $link) {
            $link = str_replace(array('<li>', "\n", "\t", "\s"), '' , $link);
            if('' != $link)
                $links[] = $link;
            else
                continue;
        }

        $fliplinks = array_reverse($links);
        if(!empty($fliplinks)) {
            echo '<dd>';
                foreach($fliplinks as $link) {
                    echo '<span>' . $link . '</span>';
                }
            echo '</dd>';
        } else {
            echo '<dd class="tree-accordion-empty"></dd>';
        }
        ?>
    <?php } ?>
</dl>

Here’s the jQuery code:

jQuery(document).ready(function(){
    var allPanels = jQuery('.tree-accordion > dd').hide();

    jQuery('.tree-accordion > dt > a').click(function() {
        $target = jQuery(this).parent().next();

        if(!$target.hasClass('active')) {
            allPanels.removeClass('active').slideUp();
            $target.addClass('active').slideDown(100);
        }

        return false;
    });

    jQuery('.tree-accordion-empty').prev().hide();
});

And, finally, here’s the CSS code:

.tree-accordion {
    line-height: 1.5;
}
.tree-accordion dt, .tree-accordion dd {}
.tree-accordion dt a {
    display: block;
}
.tree-accordion .fa {
    color: #666666;
}
.tree-accordion dd a {}
.tree-accordion dd span {
    display: block;
}
.tree-accordion dd {
    margin: 0 0 0 20px;
}

Note that I’m using FontAwesome for the plus icon.

WordPress performance tutorial: How to cache your mega menu

Navigation menus in WordPress are not performing very well in case of lots of submenus, sections, multilevel items or the well-known mega-menus. Here’s a function that allows you to cache the output of your navigation menu.

Prerequisites:

  1. WordPress Lighthouse plugin
  2. A modern WordPress theme using wp_nav_menu() function

Usage:

In order for this tutorial to work, activate the Lighthouse plugin and replace wp_nav_menu() with lhf_nav_menu() in your header.php and footer.php templates. All the other parameters stay the same. See sample usage below.

<?php
if(function_exists('lhf_nav_menu')) {
    lhf_nav_menu(array('theme_location' => 'primary', 'menu_class' => '', 'container' => 'div', 'container_class' => ''));
} else {
    wp_nav_menu(array('theme_location' => 'primary', 'menu_class' => '', 'container' => 'div', 'container_class' => ''));
}
?>

Enjoy your faster menu!

WordPress numbered siblings (child page navigation)

The other day, I had to create a numbered navigation section for child pages. The parent page would hold 15 child pages which needed to be navigated through using a previous/next system and have the current index displayed (i.e. 3/15).

Numbered siblings (outcome)

The previous/next arrows use FontAwesome and the index is styles using CSS. The GitHub code doesn’t style the navigation in any way. Use CSS or modify the second function (numbered_navigation()) in order to achieve a similar result.

How to rename all WordPress attachments to lowercase

I have recently had a situation on my hands, where attached PDFs had names like My-Document.pdf, and my-document.pdf would not work. While waiting for hosting support to fix it by activating the mod_speling module, here’s what I did:

First, I created a PHP file, called _renamer.php, and added this code:

<?php
$directory = 'wp-content/uploads/2014/02';

echo '<div>starting...</div>';

$files = scandir($directory);

foreach ($files as $key => $name) {
    $oldName = $name;
    $newName = strtolower($name);
    echo '<div>checking... ';
    rename("$directory/$oldName", "$directory/$newName");
    echo '' . $directory/$oldName . ' renamed to ' . $directory/$newName . '</div>';
}
?>

The code above would rename all the files in the selected directory to lowercase. I only needed to change the directory path several times for each directory. Then, I ran mydomain.eu/_renamer.php.

After the above operation was complete for all selected directories, I needed to update the post links and the post metas. The following SQL queries, run inside phpMyAdmin, will finish the process.


UPDATE wp_posts SET `guid` = LOWER(`guid`) WHERE post_type = 'attachment';
UPDATE wp_postmeta SET `meta_value` = LOWER(`meta_value`) WHERE meta_key = '_wp_attached_file';

Don’t forget to clear the cache, if using any plugin or CDN, and you’re done!

WordPress video submission – A case study and a code snippet

You have often wondered how to create a small form and allow users to submit/upload videos on your WordPress based website. To be honest, this module allows the user to upload any file format, such as archives, documents, audio files or graphic files.

I will not be using a custom post format for this module, as the case study is based on a real life example. All video submissions will go into a separate category called “Submissions” and details will be submitted as post content.

Video Submit - Case Study Screenshot

The entire code consists of a main function – videowizard_main() – which adds the form engine to the page. It also processes the form, combines all form information and uploads the attachment. The post gets published in a special category, called “Submissions” (which can be changed to any name for that matter, just change the ID – in this case 3), as draft/private, so only an administrator can see it and publish it or further process it.

The videodjin_ functions and actions allow the posts listing to show the attached files in a separate column.

Finally, the add_shortcode('videowizard', 'videowizard_main') function creates a shortcode which can be placed on any post or page, like this [videowizard].

For brevity (and speed) purposes, I have placed this chunk of code at the end of the functions.php file. It can also be placed in a drop-in plugin. The form has no options, but it can be modified/amended/customized directly from the main function.

Show page content inside another page

Gist is available here.

<?php
function my_show_page($atts) {
    extract(shortcode_atts(array('page_id' => 0), $atts));
    $page = get_page($page_id);

    return apply_filters('the_content', $page->post_content);
}

add_shortcode('my_show_page', 'my_show_page');

// [my_show_page page_id="999"]
?>

Show category posts in another page

Gist is available here.

<?php
function pp_custom_content($atts) {
    extract(shortcode_atts(array(
        'type' => '',
        'cat' => '',
    ), $atts));
    $am_display = '';
    $loop = new WP_Query(array('post_type' => $type, 'posts_per_page' => 10, 'category_name' => $cat));
    while ($loop->have_posts()) : $loop->the_post();
        $am_display .= '<div style="float: left; width: 47%; margin-right: 16px; height: 200px; overflow: hidden;">';
        $am_display .= '<div style="float: left; margin-right: 8px;"><a href="' . get_permalink() . '">' . get_the_post_thumbnail(get_the_ID(), 'thumbnail') . '</a></div>';
        $am_display .= '<div><strong>' . get_the_title() . '</strong> (<a href="' . get_permalink() . '">Details</a>)<br>' . get_the_excerpt() . '</div>';
        $am_display .= '</div>';
    endwhile;
    $am_display .= '<br clear="all">';
    $am_display .= '<hr>';

    return $am_display;
}

// usage: [pp type="post" cat="category"]
add_shortcode('pp', 'pp_custom_content'); // shortcode, function
?>

How to add a custom post type to your WordPress site

I had to create a custom post type for 2 clients, coincidentally testimonials. The code is pretty simple and straightforward and can be reused by changing post type and name. The code below illustrates the registration of a custom post type with 2 hierarchical taxonomies and one non-hierarchical taxonomy.

Gist is available here.

How to create a tabbed interface for your WordPress plugin

Have you ever wanted to create a tabbed interface for your plugin? Have you ever wanted to rearrange your plugin’s options and provide tabs instead of subpages and submenus? In the following tutorial, I’ll show you how to create a simple plugin with multiple tabs.

We’ll start with the usual page structure:

<div class="wrap">
<h2>Sample Plugin</h2>

<?php
$active_tab = isset($_GET['tab']) ? $_GET['tab'] : 'sample_tab_1';
if(isset($_GET['tab'])) $active_tab = $_GET['tab'];
?>
<h2 class="nav-tab-wrapper">
<a href="?page=sample&amp;tab=sample_tab_1" class="nav-tab <?php echo $active_tab == 'sample_tab_1' ? 'nav-tab-active' : ''; ?>"><?php _e('Sample Tab 1', 'sample'); ?></a>
<a href="?page=sample&amp;tab=sample_tab_2" class="nav-tab <?php echo $active_tab == 'sample_tab_2' ? 'nav-tab-active' : ''; ?>"><?php _e('Sample Tab 2', 'sample'); ?></a>
<a href="?page=sample&amp;tab=sample_tab_3" class="nav-tab <?php echo $active_tab == 'sample_tab_3' ? 'nav-tab-active' : ''; ?>"><?php _e('Sample Tab 3', 'sample'); ?></a>
<a href="?page=sample&amp;tab=sample_tab_4" class="nav-tab <?php echo $active_tab == 'sample_tab_4' ? 'nav-tab-active' : ''; ?>"><?php _e('Sample Tab 4', 'sample'); ?></a>
<a href="?page=sample&amp;tab=sample_tab_5" class="nav-tab <?php echo $active_tab == 'sample_tab_5' ? 'nav-tab-active' : ''; ?>"><?php _e('Sample Tab 5', 'sample'); ?></a>
</h2>

Notice the bolded code is where active tab checking is performed. That’s where the magic happens.

We’ll then add the tabs:

<?php if ($active_tab == 'sample_tab_1') { ?>
    <div id="poststuff" class="ui-sortable meta-box-sortables">
        <div class="postbox">
            <h3><?php _e('Sample Settings', 'sample'); ?></h3>
            <div class="inside">
                <p><?php _e('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 'sample'); ?></p>
            </div>
        </div>
    </div>
<?php } if($active_tab == 'sample_tab_2') { ?>
    <div id="poststuff" class="ui-sortable meta-box-sortables">
        <div class="postbox">
            <h3><?php _e('Sample Settings', 'sample'); ?></h3>
            <div class="inside">
                <p><?php _e('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 'sample'); ?></p>
            </div>
        </div>
    </div>
<?php } if($active_tab == 'sample_tab_3') { ?>

And so on. Notice you can add how many tabs you want.

You close the function by ending the current loop and the main div.

<?php } ?>
</div>

Enjoy!

==


Subscribe to getButterfly Blog

Once a week or so we send an email with our best content. We never bug you, we just send you our latest piece of content.



If you found any value in this post, agree, disagree, or have anything to add - please do. I use comments as my #1 signal for what to write about. Read our comment policy before commenting! Comments such as "Thank you!", "Awesome!", "You're the man!" are either marked as spam or stripped from URL.

One thought on “WordPress Tutorials

Leave a Reply

Your email address will not be published. Required fields are marked *