1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

[Docs] Making a Plugin - Case Study 3: Tweet This

Discussion in 'Developing Plugins' started by Nick, Nov 27, 2009.

  1. Nick

    Nick Well-Known Member

    Adding a Tweet This! button so you can send your posts to Twitter is incredibly easy. All you would have to do is add this code to the sb_list.php and sb_post.php templates:

    PHP:
    <a href="http://twitter.com/home?status=Currently reading <?php echo $h->url(array('page'=>$h->post->id)); ?>"' title="Click to send this post to Twitter!" target="_blank">Tweet This!</a>
    The problem with that method is there is no check to see if you've gone over 140 characters, and the url isn't shortened until after you click "Update" on Twitter's end. That means you have very little room for comments around the url, such as the post's title, which is not included in the above snippet.

    About this plugin

    This Hotaru plugin is going to include the post's title, shrink the url with a choice of url shortening services, and make sure it all fits in 140 characters.

    STEP 1 - The skeleton

    To get started, make a folder called tweet_this and a .php file called tweet_this.php containing the following code. Notice that we've included three plugin hooks to begin with. The first we will use for installing the plugin and the other two are both used for building our settings page. We don't actually need to write functions for those last two hooks, because we can fall back on Hotaru's default plugin functions instead.

    PHP:
    <?php
    /**
     * name: Tweet This
     * description: Send posts to Twitter
     * version: 0.1
     * folder: tweet_this
     * class: TweetThis
     * hooks: install_plugin, admin_sidebar_plugin_settings, admin_plugin_settings
     * requires: submit 1.6
     */

    class TweetThis
    {
        
    /**
         * Install Tweet This
         */
        
    public function install_plugin($h)
        {
            
    // install code to go here
        
    }
    }
    ?>
     
  2. Nick

    Nick Well-Known Member

    STEP 2 - Install function

    What options should we give site admins? For now let's give them a choice of using TinyUrl, is.gd and Bitly. If they choose the last one, they will nedd to enter their Bitly login and API key.

    Let's start with the install function. Rather than saving each item as individual settings, we'll combine them into one for efficiency. That's done with PHP's serialize function which takes our array of settings and squashes them into a string.
    PHP:
        /**
         * Install Tweet This
         */
        
    public function install_plugin($h)
        {
            
    // Plugin settings
            
    $tweet_this_settings $h->getSerializedSettings();

            if (!isset(
    $tweet_this_settings['tt_shortener'])) { $tweet_this_settings['tt_shortener'] = "is.gd"; }
            if (!isset(
    $tweet_this_settings['tt_bitly_login'])) { $tweet_this_settings['tt_bitly_login'] = ""; }
            if (!isset(
    $tweet_this_settings['tt_bitly_api_key'])) { $tweet_this_settings['tt_bitly_api_key'] = ""; }

            
    $h->updateSetting('tweet_this_settings'serialize($tweet_this_settings));
        }
    Note: the settings will only be updated if they don't already exist. Otherwise the admin would lose his choices each time he installs or upgrades the plugin.
     
  3. Nick

    Nick Well-Known Member

    STEP 3 - Admin settings preparation

    This is quite large, but hopefully quite logical.

    First, make a new .php file called tweet_this_settings.php and put a class with two functions in it like this:

    PHP:
    <?php

    class TweetThisSettings
    {
         
    /**
         * Admin settings for the Tweet This plugin
         */
        
    public function settings($h)
        {

        }

         
    /**
         * Save Tweet This settings
         */
        
    public function saveSettings($h)
        {

        }
    }
    In the first function, we will get the existing settings from the database and display them on the page. To do that, we will need to make a language file so we can label the various options.

    Create a folder called languages and put a .php file with the name tweet_this_language.php inside it.

    PHP:
    <?php
    /**
     * Language file for Tweet This plugin
     */

    /* Admin options */
    $lang['tweet_this_settings_header'] = "Tweet This Settings";
    $lang['tweet_this_settings_shortener'] = "Choose the url shortener you want to use. If you choose bit.ly, you must provide your bit.ly login and API key.";
    $lang['tweet_this_settings_tinyurl'] = "tinyurl";
    $lang['tweet_this_settings_isgd'] = "is.gd";
    $lang['tweet_this_settings_bitly'] = "bit.ly";
    $lang['tweet_this_settings_bitly_login'] = "bit.ly login";
    $lang['tweet_this_settings_bitly_api_key'] = "bit.ly API key";
    $lang['tweet_this_settings_error'] = "You must enter your bitly login and API key to use bitly";

    ?>
    Go back to your tweet_this_settings.php file and display the header:

    PHP:
        /**
         * Admin settings for the Tweet This plugin
         */
        
    public function settings($h)
        {
            
    // show header
            
    echo "<h1>" $h->lang["tweet_this_settings_header"] . "</h1>\n";
        }
    With that done, you should be able to install the plugin, click a "Tweet This" link in the Admin sidebar and view the settings page with the "Tweet This Settings" header on display.
     
  4. Nick

    Nick Well-Known Member

    STEP 4 - Admin settings form

    Before we can display the form, we need to get the existing settings from the database:

    PHP:
    /**
         * Admin settings for the Tweet This plugin
         */
        
    public function settings()
        {
            
    // show header
            
    echo "<h1>" $h->lang["tweet_this_settings_header"] . "</h1>\n";

            
    // Get settings from database if they exist...
            
    $tweet_this_settings $h->getSerializedSettings();
    Then prepare the values for our radio buttons...
    PHP:
            // set choices to blank
            
    $tinyurl "";
            
    $isgd "";
            
    $bitly "";

            
    // determine which is selected
            
    switch($tweet_this_settings['tt_shortener']) {
                case 
    'tinyurl':
                    
    $tinyurl "checked";
                    break;
                case 
    'bitly':
                    
    $bitly "checked";
                    break;
                default:
                    
    $isgd "checked";
            }
    And now we can show the whole form:
    PHP:
            // start form
            
    echo "<form name='tweet_this_settings_form' ";
            echo 
    "action='" BASEURL "admin_index.php?page=plugin_settings&amp;plugin=tweet_this' method='post'>\n";
            
            
    // instructions
            
    echo "<p>" $h->lang['tweet_this_settings_shortener'] . "</p>";
            
            
    // radio buttons
            
            // is.gd
            
    echo "<p><input type='radio' name='tt_shortener' value='isgd' " $isgd " >";
            echo 
    "&nbsp;&nbsp;" $h->lang["tweet_this_settings_isgd"] . "</p>\n"
            
            
    // tinyurl
            
    echo "<p><input type='radio' name='tt_shortener' value='tinyurl' " $tinyurl " >";
            echo 
    "&nbsp;&nbsp;" $h->lang["tweet_this_settings_tinyurl"] . "</p>\n"
            
            
    // bit.ly
            
    echo "<p><input type='radio' name='tt_shortener' value='bitly' " $bitly " >";
            echo 
    "&nbsp;&nbsp;" $h->lang["tweet_this_settings_bitly"] . "</p>\n"
            
            
    // input fields
            
            // bitly login
            
    echo "<p>" $h->lang['tweet_this_settings_bitly_login'];
            echo 
    ": <input type='text' size=30 name='tt_bitly_login' value='" $tweet_this_settings['tt_bitly_login'] . "' /></p>";
            
            
    // bit.ly api key
            
    echo "<p>" $h->lang['tweet_this_settings_bitly_api_key'];
            echo 
    ": <input type='text' size=30 name='tt_bitly_api_key' value='" $tweet_this_settings['tt_bitly_api_key'] . "' /></p>";
            
            
    // end form
            
    echo "<br />";
            echo 
    "<input type='hidden' name='submitted' value='true' />\n";
            echo 
    "<input type='submit' value='" $h->lang["main_form_save"] . "' />\n";
            echo 
    "<input type='hidden' name='csrf' value='" $h->csrfToken "' />\n";
            echo 
    "</form>\n";
        } 
    Now that the form is complete, we need to tell Hotaru when it is submitted and to call the saveSettings function. To do that, add this below the <h1> tags near the top of the settings function you've just been working on:

    PHP:
            // If the form has been submitted, go and save the data...
            
    if ($h->cage->post->getAlpha('submitted') == 'true') { 
                
    $this->saveSettings($h); 
            }
     
  5. Nick

    Nick Well-Known Member

    STEP 5 - Saving admin settings

    So far, you've stored default settings in the database, created an Admin settings page, retrieved those settings and filled in a form using them. The next step is to save those settings.

    You should still have this empty function below what you've just finished:

    PHP:
         /**
         * Save Tweet This settings
         */
        
    public function saveSettings()
        {

        }
    Let's start by including the language file and getting the settings again.

    PHP:
        /**
         * Save Tweet This Settings
         */
        
    public function saveSettings($h)
        {
            
    // Get settings from database if they exist...
            
    $tweet_this_settings $h->getSerializedSettings();
    Now let's store the user's choices in our $tweet_this_settings array:

    PHP:
            // get result of radio buttons and bitly fields
            
    $tweet_this_settings['tt_shortener'] = $h->cage->post->testAlpha('tt_shortener');
            
    $tweet_this_settings['tt_bitly_login'] = $h->cage->post->testAlnumLines('tt_bitly_login');
            
    $tweet_this_settings['tt_bitly_api_key'] = $h->cage->post->testAlnumLines('tt_bitly_api_key');
    Next we need to check whether the user has chosen bitly, and if so, did he enter both his login and api key? If not, we should show an error message and not save the settings. Otherwise, save everything and show a "Settings saved" message.

    PHP:
            // if bitly is chosen but either of the login or api key fields are empty, set error, don't save
            
    if ($tweet_this_settings['tt_shortener'] == 'bitly' &&
                (!
    $tweet_this_settings['tt_bitly_login'] || !$tweet_this_settings['tt_bitly_api_key']))
            {
                
    // error message
                
    $h->message $h->lang["tweet_this_settings_error"];
                
    $h->messageType "red";
            } 
            else 
            {
                
    // update settings and set message
                
    $h->updateSetting('tweet_this_settings'serialize($tweet_this_settings));
                
    $h->message $h->lang["main_settings_saved"];
                
    $h->messageType "green";
            }
            
            
    // show message
            
    $h->showMessage();
            
            return 
    true;
        }
    }
    That concludes the admin section. You should be able to test it in your browser now.
     
  6. Nick

    Nick Well-Known Member

    STEP 6 - The Tweet This link

    We're going to put our Tweet This link below each post description alongside the comments, tags and flag links. To do that, we need to find the same plugin hook used by Comments, Tags, and Vote Simple to place their links.

    Take a look at sb_list.php and sb_post.php in the Submit plugin. They both have the same plugin hook:

    PHP:
            <div class="show_post_extra_fields">
                <ul>
                    <?php $h>pluginHook('sb_base_show_post_extra_fields'); ?>
                </ul>
            </div>
    So all we need to do is write a function for that spot. Let's see how the Comments plugin does it:

    PHP:
        /**
         * Link to comments
         */
        
    public function sb_base_show_post_extra_fields($h)
        {
            echo 
    '<li><a class="comment_link" href="' $h->url(array('page'=>$h->post->id)) . '">' $this->hotaru->comment->countComments() . '</a></li>' "\n";
        }
    For us to use an edited version of this in our Tweet This plugin, first we need to add this hook at the top of our tweet_this.php file:

    PHP:
     hooksinstall_pluginadmin_sidebar_plugin_settingsadmin_plugin_settingssb_base_show_post_extra_fields
    Now lets copy the function from the Comments plugin and make a few changes:

    PHP:
        /**
         * Tweet This link
         */
        
    public function sb_base_show_post_extra_fields($h)
        {
            echo 
    "<li><a class='tweet_this_link' href='" $h->url(array('page'=>'tweet_this''id'=>$h->post->id)) . "'>";
            echo 
    $h->lang['tweet_this'] . "</a></li>\n";
        }
    Two things worth noting. First, we're using Hotaru's url function instead of a direct http:// link because Hotaru will automatically convert it into either a standard or friendly url, depending on the admin's settings. We're linking to a "tweet_this" page, which is actually our plugin, and we're including the ID of the post we want to tweet.

    The second point is that rather than hard code the words "Tweet This!" into the link, your should use the language file. Open /languages/tweet_this_language.php and add this:

    PHP:
    /* Tweet This link */
    $lang['tweet_this'] = "Tweet This!";
    Because you've added a new hook to the top of the file, you'll need to reinstall the Tweet This plugin. When you've done that, you should a link below post descriptions!

    That completes the presentation side of the plugin. Now it's time to get into the nitty-gritty of manipulating the url for Twitter.
     
  7. Nick

    Nick Well-Known Member

    STEP 7 - When "Tweet This!" is clicked

    When a user clicks "Tweet This", Hotaru fills the url with page=tweet_this and id=xxx. So the first thing to do is check whether page is in fact tweet_this. The best place to do that is just before HTML is output to the browser. This allows us to redirect elsewhere (in this case Twitter) without throwing any horrible "Headers already sent" errors that occur if you try redirecting after outputing HTML to the browser.

    The theme_index_top plugin hook at the top of the default theme's index.php file is the perfect spot, so let's add that to our list of plugin hooks:

    PHP:
    hooksinstall_pluginadmin_sidebar_plugin_settingsadmin_plugin_settingssb_base_show_post_extra_fieldstheme_index_top
    First, we'll set up a simple test to make sure the page parameter is being detected properly:

    PHP:
        /**
         * Determine if the user has clicked Tweet This
         */
        
    public function theme_index_top($h)
        {
            if (
    $h->isPage('tweet_this')) {
                
    $this->tweetThisPost($h);
            }
        }
        
        
    /**
         * Build the link 
         */
        
    public function tweetThisPost($h)
        {
            echo 
    "Testing. Hello, hello, testing";
        }
    When the theme_index_top hook is triggered, our plugin checks the page parameter, and if it's tweet_this, it calls a tweetThisPost function which outputs some text to the screen.

    Reinstall the plugin to register that new hook, then load the front page of your site, click "Tweet This!" and you should see "Testing. Hello, hello, testing" appear at the top of the page.

    Now let's change that last function so that it displays the page name and post id that should have been passed to the plugin:

    PHP:
        /**
         * Build the link 
         */
        
    public function tweetThisPost($h)
        {
            echo 
    "Page: " $h->cage->get->testAlnumLines('page') . "<br />";
            echo 
    "Post ID: " $h->cage->get->testInt('id') . "<br />";
        }
    Give that a test, and if it works, we can move on.
     
  8. Nick

    Nick Well-Known Member

    STEP 8 - Compressing the post's url

    Change that last function one last time to the following. This will call other functions to compress the post's url, append it to the Twitter status update link and redirect us to Twitter.

    PHP:
        /**
         * Build the link 
         */
        
    public function tweetThisPost($h)
        {
            
    // get the post's id from the url
            
    $post_id $h->cage->get->testInt('id');
            
            
    // get the compressed url for this link
            
    $shortened_url $this->getShortUrl($h$post_id);
            
            
    // add the compressed link to the Twitter status update url
            
    $twitter_url $this->getTwitterUrl($h$shortened_url); 

            
    // redirect to Twitter
            
    header("Location: " $twitter_url);
            exit;
        }
    When a link is compressed, it will get stored in the database. The postmeta table is a suitable place to put it because not every post will be tweeted and we don't want to fill up the main posts table with too many columns if they aren't absolutely necessary. We'll put a compressed address in the database later, but first we need to check if one already exists before we shrink the post's url.

    PHP:
        /**
         * Build the shortened link 
         *
         * @param int $post_id
         * @return string $url
         */
        
    public function getShortUrl($h$post_id)
        {
            
    // Check the database to see if there's already a short link there.
            
    $query "SELECT postmeta_value FROM " TABLE_POSTMETA " where postmeta_postid = %d AND postmeta_key = %s";
            
    $sql $h->db->prepare($query$post_id'compressed_url');
            
    $stored_short_link $h->db->get_var($sql);
    Immediately after that, add this:

    PHP:
            if(!$stored_short_link) {
                
    // no compressed url in the database. We need to create one...
            
    } else {
                
    // compressed url found, let's use it...
                
    $url $stored_short_link;
            }
            return 
    trim($url);
        }
    Hopefully that makes sense. Here's the full function that includes the api calls to the url shortening service to compress the url. You can see that we will need another function for bit.ly because bit.ly needs a login and api key. You should also see that we're putting the newly created url in the database.

    PHP:
        /**
         * Build the shortened link 
         *
         * @param int $post_id
         * @return string $url
         */
        
    public function getShortUrl($h$post_id)
        {
            
    // Check the database to see if there's already a short link there.
            
    $query "SELECT postmeta_value FROM " TABLE_POSTMETA " where postmeta_postid = %d AND postmeta_key = %s";
            
    $sql $h->db->prepare($query$post_id'compressed_url');
            
    $stored_short_link $h->db->get_var($sql);
            
            if(!
    $stored_short_link) {
                
    // no short link in db. We need to create one:
                
                // get the post's url and encode it:
                
    $post_url urlencode($h->url(array('page'=>$post_id)));
                
                
    // get settings so we know which shortener to use:
                
    $tweet_this_settings $h->getSerializedSettings();
                
                switch(
    $tweet_this_settings['tt_shortener']) {
                    case 
    'tinyurl':
                        
    $url file_get_contents('http://tinyurl.com/api-create.php?url=' $post_url);
                        break;
                    case 
    'bitly':
                        
    $url $this->getBitlyLink($h$post_url$tweet_this_settings);
                        break;
                    default:
                        
    $url file_get_contents('http://is.gd/api.php?longurl=' $post_url);
                }

                
    // then store it in the database:
                
    $query "INSERT INTO " TABLE_POSTMETA " (postmeta_postid, postmeta_key, postmeta_value, postmeta_updateby) VALUES(%d, %s, %s, %d)";
                
    $sql $h->db->prepare($query$post_id'compressed_url'urlencode(trim($url)), $h->current_user->id);
                
    $h->db->query($sql);

            } else {
                
    // we can use the existing one.
                
    $url $stored_short_link;
            }
            return 
    trim($url);
        }
    In the next post, we'll look at how to use bit.ly.
     
  9. Nick

    Nick Well-Known Member

    STEP 9 - Shortening urls with bit.ly

    This step should be pretty self-explanatory. We're just getting our login and api key information and including them in the api call to bit.ly.

    PHP:
        /**
         * Shorten url with bit.ly
         *
         * @param string $post_url
         * @param array $tweet_this_settings
         * @return string $url
         */
        
    public function getBitlyLink($h$post_url$tweet_this_settings)
        {
            
    // get our login and api key from the saved settings
            
    $bitly_login $tweet_this_settings['tt_bitly_login'];
            
    $bitly_apikey $tweet_this_settings['tt_bitly_api_key'];
            
            
    // build the api call:
            
    $api_call file_get_contents("http://api.bit.ly/shorten?version=2.0.1&longUrl=" $post_url "&login=" $bitly_login "&apiKey=" $bitly_apikey);
            
            
    // get the result of the api call
            
    $bitlyinfo json_decode(utf8_encode($api_call),true);

            
    // return the shortened url if no error
            
    if ($bitlyinfo['errorCode'] == 0) {
                return 
    $bitlyinfo['results'][urldecode($url)]['shortUrl'];
            } else {
                return 
    false;
            }
        }
     
  10. Nick

    Nick Well-Known Member

    STEP 10 - Include the post title and return the redirect link

    Now that we've got our shortened url. The next step is to include it as part of a larger url that we use to redirect us to Twitter. That url will look something like this:

    Code:
    http://twitter.com/home/?status=STORY TITLE AND LINK TO STORY
    This is the last function. It takes the post's title, strips it down to a maximum of 100 characters and includes it together with the compressed url. The final url is returned to the tweetThisPost function where we are redirected toTwitter.

    PHP:
        /**
         * Build the Twitter status update link
         *
         * @param int $post_id
         * @param string $shortened_url
         * @return string $url
         */
        
    public function getTwitterUrl($h$post_id$shortened_url)
        {
            
    // build an object of Post class so we can get the post's title
            
    require_once(PLUGINS 'submit/libs/Post.php');
            
    $h->readPost($post_id);
            
    $title html_entity_decode($h->post->titleENT_QUOTES"UTF-8");
            
            
    $orig_length strlen($title); // get original title length
            
    if ($orig_length 110) {
                
    $title substr($title0100); // keep only the first 100 characters
                
    $title substr($title0strrpos($title,' ')); // keep everything up to the last space
                
    $title .= "..."// adds some dots to show we've truncated it
            
    }
            
    $title $title " "// 100 chars + "..." + " " = 104 chars, leaving 36 chars for the url
            
    $title urlencode($title);
            
            
    // The final Twitter URL:
            
    $url 'http://twitter.com/home/?status=' $title '+' $shortened_url ;
            return 
    $url;
        }
    And that is nearly all, but there's one last step...
     
  11. Nick

    Nick Well-Known Member

    STEP 11 - Finishing touches

    The plugin should be fully working by now, but there are a couple of loose ends to tidy up. First, in the default theme, the links below post descriptions have an image which brightens when hovered over. We can do that with our Tweet This! link, too.

    Make a new "css" folder in the tweet_this folder and create a .css file with this code:

    Code:
    /* **************************************
     *     TWEET THIS CSS            *
     ************************************** */
     
    .tweet_this_link    { padding-left: 1.5em; background-image: url(content/plugins/tweet_this/images/tweet-this.png); }
    You can see that pulls an image from a folder we don't have yet, so lets make an images folder, too: /tweet_this/images. Attached to this post is an image you can put in that folder.

    In order to include this css file, all we have to do is add another hook, this time header_include, to the top of our tweet_this.php file:

    PHP:
     hooksinstall_pluginadmin_sidebar_plugin_settingsadmin_plugin_settingssb_base_show_post_extra_fieldstheme_index_topheader_include
    Reinstall the plugin so that hook gets registered and view the front page. You may need to hard refresh with CTRL + F5 to make sure the css file gets loaded and merged with all the other css files Hotaru uses.

    The last thing to do is make a readme.txt fileto put in your tweet_this folder. It might look something like this:

    Well, if you've made it this far, congratulations! You are well on your way to becoming a Hotaru plugin guru. If you have any questions at all about this tutorial, please open a thread in the Developing Plugins folder and I'll be happy to answer your questions.

    Note: There is one problem with this plugin. If the post submitter or a moderator edits the post's title, the url will change. That means that if anyone has already tweeted it with the old url, the compressed url will no longer match. It's probably a very rare case, but still a problem nonetheless.
     

    Attached Files:

Share This Page