A Popurls Clone with PHP, jQuery, Awesomeness

Popurls clonw ith PHP, Jquery, AwesomenessGood people of the Internets, I know a good deal of you frequently visit some sort of social website and/or aggregator such as Popurls, Reddit, Digg, Developer Zone, or what have you. After a deep and thorough analysis of my visitor statistics (read: pulling data out of my ass), many of you seem to like articles about PHP. This is not surprising, as PHP is a very popular (scripting) language. Then again, why anybody would want to read my incoherent ramblings is still a mystery to me. If you’re reading this, kudos! You officially have nothing better to do. Encouraging, isn’t it? Btw, Reddit, thanks for /r/nsfw/.

Since I have a crazy workload right now, I feel this is the perfect time to write a quick n’ dirty tutorial on how to build your very own Popurls. Impress your friends and/or boss with a nifty, hand made news aggregator. Yes, very buzz word friendly. This is part one of a series of articles where we start from this article’s code and later on expand it to employ a simple admin interface for the feeds as well as pulling our content and playing with it in Flash using ActionScript 3 and from there build a sweet little Flash widget. I am weirdly excited about this to the point of scaring myself, so let’s STFU and get started. If you want to see a demo, well, you’re in luck. Go there!


Abstract

What we’re going to do is use SimplePie to fetch an array of feeds of different formats, cache them and output the whole shebang. Then we’re gonna style it with some CSS and use some simple jQuery interactivity to sweeten the deal.


SimplePie is simple, not related to pi, tastes like it

We’ll start, obviously, with the back end. I should start with confessing that I’m an avid fan of the KISS and DRY principles. Instead of building our very own feed parser, we’ll use the excellent SimplePie class together with the IDNA Convert class. Don’t worry, the last one is bundled with the SimplePie 1.2 download. The IDNA Convert class helps us, amongst other things, to handle feeds where the domain name is internationalized according to RFC 3490, 3491, 3492 and 3454. IDNA is optional but as long as we already have it, why the fuck not?

First things first. Are you ready, willing and able to harness the awesomeness that is SimplePie? Assuming you have downloaded the package, unzipped/untarred it and uploaded to your web server/LAMP/WAMP, direct yourself to

http://[your_server]/[simplepie_path]/compatibility_test/sp_compatibility_test.php


Great success? Good, then let’s get started. As a side note, if you want to explore all the goodies that you get to play with , go to the SimplePie Documentation, which is a comprehensive API documentation.


Getting dirty

Before continuing, I should point out that SimplePie has automagical caching. It’s quite sweet. Just create a folder in the same directory as the SimplePie class called cache and chmod it so that the script may write to it. Let’s start by including the SimplePie library and the aforementioned IDNA Class as well ass initializing SimplePie and tell it which feeds to use. For the remainder of this article, I’ll assume that SimplePie is in the root of your folder and IDNA is in the folder idn.

require_once('simplepie.inc');
require_once('idn/idna_convert.class.php');

$popurls = new SimplePie();
$popurls->set_feed_url(
    array(
        'http://feeds.digg.com/digg/popular.rss',
        'http://www.reddit.com/.rss',
        'http://feeds.delicious.com/v2/rss/',
        'http://www.dzone.com/links/feed/frontpage/rss.xml',
        'http://feeds.wired.com/wired/index',
        'http://www.tuaw.com/rss.xml',
        'http://www.engadget.com/rss.xml'
    )
);

I twelve or so lines o’ code, we’ve done the dirty work for fetching the feeds that we want o play with. And who said dealing with code is hard? Pff. Thankfully, the SimplePie method set_feed_url accepts an array which makes our lives so much easier. Before getting down to the nitty gritty of displaying the feeds, you should start out with setting a parameter called set_item_limit. It does what you think, namely, limiting the number of items to return from each feed. If you don’t, chances are that you’ll have to wait for a long, long, looong time before actually seeing anything on your screen. That’ll make you come back here and add some snarky comment, claiming that the c0dez dun work lulz, n00b. Fair enough, the blog is called profeshunl newbie, but still. It’s about kharma. For our first proof-of-concept, a limit of three items per feed should suffice. While we’re at it, we’ll initialize the feeds and make sure that it’s served with UTF-8.

$popurls->set_item_limit(3);
$popurls->init();
$popurls->handle_content_type();

Not that hard, was it? Told you so. Now let’s set up a very simple XHTML structure below the PHP block in order to display the goods. For this part of the article, well settle for displaying the content in tables. Don’t try validating the code, though. It may seem a little bit slow the first time you runt this script, which is expected. When your browser’s done, check the cache directory. There should be seven .spc files there.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title>p0wnurls</title>
        <style type="text/css">
        body {
        font-family: 'Lucida Grande', Helvetica, Arial, sans-serif;
        font-size:10px;
    }
    table {
        border-bottom: 3px solid #eeeeee;
        margin: 20px 0 0 0;
        padding: 0 0 20px 0;
        font-size: 10px;
        width: 100%;
    }
    td {
        vertical-align: top;
        border-bottom: 1px solid #f3f3f3;
    }
    .label {
        font-weight: bold;
        width: 150px;
    }
    </style>
</head>
<body>
    <h1>p0wnurls</h1>

    <!-- Yes, you love tables as much as I do -->

    <?
    // Loop through each items
    foreach ( $popurls->get_items() as $feed_item ) {
        // Create a reference to the current item's parent feed
        $feed_parent = $feed_item->get_feed();
    ?>

        <table>
            <tr>
                <td class="label">Favicon:</td><td><?php echo $feed_parent->get_favicon(); ?> (<img src="<?php echo $feed_parent->get_favicon(); ?>">)</td>
            </tr>
            <tr>
                <td class="label">Permalink:</td><td><?php echo $feed_item->get_permalink(); ?></td>
            </tr>
            <tr>
                <td class="label">Title:</td><td><?php echo $feed_item->get_title(); ?></td>
            </tr>
            <tr>
                <td class="label">Date:</td><td><?php echo $feed_item->get_date(); ?></td>
            </tr>
            <tr>
                <td class="label">Content:</td><td><?php echo $feed_item->get_description(); ?></td>
            </tr>
            <tr>
                <td class="label">Description:</td><td><?php echo $feed_item->get_description(); ?></td>
            </tr>
            <tr>
                <td class="label">Parent Permalink:</td><td><?php echo $feed_parent->get_permalink(); ?></td>
            </tr>
            <tr>
                <td class="label">Parent Title:</td><td><?php echo $feed_parent->get_title(); ?></td>
            </tr>
        </table>

    <?php
    }
    ?>
</body>
</html>

Still here? Nice. As you can see, more often than not the description is the same as the content. Also, we need to strip away the tags and crap that we don’t need. Basically, we only want the raw text. Let’s add a row to each table and see what we can come up with, mm’kay? Let’s try something like

<tr>
    <td class="label">Description Stripped:</td><td><?php echo html_entity_decode(strip_tags($feed_item->get_description()), ENT_QUOTES, 'UTF-8'); ?></td>
</tr>

Boom. Much better. html_entity_decode will convert html entities to their should-be characters. SInce we don’t need any images or, really, any inline elements, we’ll use strip_tags. Note that strip_tags does not in any way validate the HTML it parses. If the HTML is borked, it may lead to unexpected results, which is fancy talk for “it’ll blow up in your face and have you sleeping in the shower”. If you want to, you can always roll your own stripping mechanism with a regular expression. I wouldn’t recommend it, but if you’re the adventurous type or just don’t trust strip_tags, go ahead! The last link provides you with the following regex;

$regex = "/<\/?\w+((\s+(\w|\w[\w-]*\w)(\s*=\s*(?:\".*?\"|'.*?'|[^'\">\s]+))?)+\s*|\s*)\/?>/i";

…which should suffice as a starting point for you. And if you want to just strip whitespace, how about

$regex = '/(?:(?<=\>)|(?<=\/\>))(\s+)(?=\<\/?)/';

which was found over here.

Now that we know how to fetch the information we need, there is one thing that need to be sorted out. The default action for SimplePie is to aggregate all the feeds. This means that they’re all in one big list o’ items, sorted by date. Normally this would be fine, but we want a per feed sorting mechanism, right? Right. If you don’t you can safely skip this part. Then again, if you skip it, the rest of this article will be reeeally confusing.


Are we there yet?

I like playing with arrays, so let’s expand our initial code a bit. Let’s make an associative array of it and add a key for each feed. Let’s go with a nice name of the source domain. Manually for now. If you don’t like nested foreach loops, now’s your time to look away.

// Include SP
require_once('simplepie.inc');
require_once('idn/idna_convert.class.php');

// Instantiate a source array with domain short name as key and feed URL as value
$_source = array(
    'digg'=>'http://feeds.digg.com/digg/popular.rss',
    'reddit'=>'http://www.reddit.com/.rss',
    'delicious'=>'http://feeds.delicious.com/v2/rss/',
    'dzone'=>'http://www.dzone.com/links/feed/frontpage/rss.xml',
    'wired'=>'http://feeds.wired.com/wired/index',
    'tuaw'=>'http://www.tuaw.com/rss.xml',
    'engadget'=>'http://www.engadget.com/rss.xml'
);

// Instantiate empty array
$_sorted = array();

// Loop through the source array
foreach ( $_source as $_feed_key=>$_feed_value ) {

    // Instantiate new SP
    $_feed = new SimplePie();
    $_feed->set_feed_url($_feed_value);
    $_feed->init();

    // Loop through the items of the current feed
    foreach ( $_feed->get_items(0, 3) as $__item ) {

        // Add title, favicon and permalink to feed and items to the sorted array
        $_sorted[$_feed_key]['title'] = $_feed->get_title();
        $_sorted[$_feed_key]['favicon'] = $_feed->get_favicon();
        $_sorted[$_feed_key]['permalink'] = $_feed->get_permalink();
        $_sorted[$_feed_key]['feed_items'][] = array(
            'title' => $__item->get_title(),
            'date' => $__item->get_date(),
            'permalink' => $__item->get_permalink(),
            'content' => html_entity_decode(strip_tags($__item->get_description()), ENT_QUOTES, 'UTF-8')
        );
    } 

}

Short and sweet. Try a print_r( $_sorted ); and view source. Looks neat, doesn’t it? Note that set_item_limit has been removed in favor of specifying an offset and a limit to get_items() instead. set_item_limit wouldn’t work at all. As you can see from the date values, the items are sorted in ascending order. Jut the way we want it. The content part may be way too long though. We only need something like the first 100 characters of it to function as an excerpt. Let’s truncate the content. Yes, let’s. Add some sort of truncation function such as

// Truncate string
function truncate( $string, $length=100 )
{

    // Is it shorter than out string? If so, return it as is.
    if( strlen( $string ) <= $length )
    {
        return $string;
    }

    // No? Well, regex and substr the fuck out of that string and append an ellipsis at the end.
    return preg_replace('/\s+?(\S+)?$/', '', substr($string, 0, $length)).'...';
}

After that it’s only a matter of wrapping the value for the content key with the truncate function in the inner foreach loop

$_sorted[$_feed_key]['feed_items'][] = array(
    'title' => $__item->get_title(),
    'date' => $__item->get_date(),
    'permalink' => $__item->get_permalink(),
    'content' => truncate(html_entity_decode(strip_tags( $__item->get_content() ), ENT_QUOTES, 'UTF-8'))
);


Looking good, right? As you may notice, some feeds actually don’t have any content or just have a “submitted by…” kind of string, which no fun. Let’s add an ugly conditional that checks if the content is empty or, in this case, the feed is from Reddit. In that case, we’ll use the title as excerpt instead.

// Get the content
$_content = $__item->get_content();

// If it's empty or if it's Reddit...
if ( empty( $_content ) || $_feed_key == 'reddit')
{
    // Jut use the title
    $_content = $__item->get_title();
} 

$_sorted[$_feed_key]['feed_items'][] = array(
    'title' => $__item->get_title(),
    'date' => $__item->get_date(),
    'permalink' => $__item->get_permalink(),
    'content' => truncate(html_entity_decode(strip_tags( $_content ), ENT_QUOTES, 'UTF-8'))
);

Much better. However, we need to rearrange and restyle our code a bit. And lets get rid of those horrible tables and go with unordered lists instead.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title>p0wnurls</title>
    <style type="text/css">
       body {
        font-family: 'Lucida Grande', Helvetica, Arial, sans-serif;
        font-size:10px;
    }
    ul {
        border-bottom: 3px solid #eeeeee;
        font-size: 10px;
        width: 100%;
    }
    ul li ul {
        border-bottom:0;
        margin-bottom:20px;
    }
    ul li ul li {
        border-bottom:1px solid #f3f3f3;
    }
    .label {
        font-weight: bold;
        padding: 0 10px 0 0;
    }
    .actual_feed_items {
        margin: 10px;
    }
    </style>
</head>
<body>
    <h1>p0wnurls</h1>

    <?
    foreach ( $_sorted as $feed_name=>$feed )
    {
    ?>

        <h2><?php echo $feed_name . ' | ' . $feed['title']; ?></h2>
        <ul>
            <li>
                <span class="label">Favicon:</span><span><?php echo $feed['favicon']; ?> (<img src="<?php echo $feed['favicon']; ?>">)</span>
            </li>
            <li>
                <span class="label">Permalink:</span><span><?php echo $feed['permalink']; ?></span>
            </li>
            <li>
                     <?php
                     foreach ( $feed['feed_items'] as $feed_item ) {
                     ?>

                    <ul class="actual_feed_items">
                        <li>
                            <span class="label">Title:</span><span><?php echo $feed_item['title']; ?></span>
                        </li>
                        <li>
                            <span class="label">Permalink:</span><span><?php echo $feed_item['permalink']; ?></span>
                        </li>
                        <li>
                            <span class="label">Date:</span><span><?php echo $feed_item['date']; ?></span>
                        </li>
                        <li>
                            <span class="label">Content:</span><span><?php echo $feed_item['content']; ?></span>
                        </li>
                    </ul>
                     <?php
                     }
                     ?>
            </li>
        </ul>

    <?php
    }
    ?>
</body>
</html> 

So, are we there yet?

Starting to see the resemblance to Popurls? No? You douche. Well, actually, me neither. So lets fix that. First off, let’s add a container that we center on the screen. Then we’ll divide the feeds into columns of three. Then do some hacky crap to add a div after every third column. After that, we’ll divide each feed’s items in two – the top half is shown and the other part is hidden until we press a link. After that, we should style it like Popurls and include jQuery. Let’s begin with the body. This may seem a little messy. And that’s because it is.

<body>

    <div id="container">
    <h1>p0wnurls</h1>
    <?
    $i = 1;
    foreach ( $_sorted as $feed_name=>$feed )
    {
    ?>
        <div class="feed_container">
            <div class="feed_title"><a href="<?php echo $feed['permalink']; ?>" title="<?php echo $feed['title']; ?>"><img src="<?php echo $feed['favicon']; ?>" alt="<?php echo $feed['title']; ?>" /> <?php echo $feed_name; ?></a></div>
                <ul class="feed_items">
                    <?php
                    $j = 0;
                    foreach ( $feed['feed_items'] as $feed_item )
                    {
                        // If we've reached half of our feed items, split to a new unordered list
                        if ( $j == $half_feed_items )
                        {
                        ?>

                        <li>
                            <div class="more"></div>
                            <ul class="feeds_extra">

                     <?php
                        }
                        ?>
                        <li>
                                <a href="<?php echo $feed_item['permalink']; ?>" title="<?php echo $feed_item['title']; ?>" class="tooltip"><?php echo $feed_item['title']; ?></a>
                                <span><?php echo $feed_item['content']; ?></span>
                        </li>
                    <?php
                        $j[imagine_two_plus_signs_here];
                        // When we've reached to total number of feed items, close the ul up
                        if ( $j == $total_feed_items )
                        {
                    ?>
                        </ul></li>
                    <?php
                        }
                     }
                     ?>
                </ul>
        </div>
        <?php
        // If the remainder of i divided by 3 is 0 - add a clearing div
        if ( ($i%3) == 0 ) {
        ?>
            <div class="clear"></div>
        <?php
        }
        $i[imagine_two_plus_signs_here];
        ?>
    <?php
    }
    ?>
    </div>
</body>

Don’t go copy/pasting that code – as it turns out, my code highlighting script of choice in combination with WordPress strips away any plus signs, rendering the loops somewhat… lacking… Hm. Just check that $i and $j get incremented in the end of each loop and you should be fine. I added two very stealthy clues as to where to add the plus signs. Look for the clues, eagle eye, and report back later. Notice the rearrangement of code. We use the title for the permalink anchor as, well, the title, and put the content in a span. As a courtesy for all those of you who prefer to echo the content instead of multiple opening and closing php tags, here’s the inner loop for you to play with;

$j = 0;
foreach ( $feed['feed_items'] as $feed_item )
{
    if ( $j == $half_feed_items )
    {
        echo '
    <li>
        <div class="more"></div>
        <ul class="feeds_extra">
        ';
    }
    echo '
    <li>
            <a href="' . $feed_item['permalink'] . '" title="' . $feed_item['title'] . '" class="tooltip">' . $feed_item['title'] . '</a>
            <span>' . $feed_item['content'] . '</span>
    </li>
    ';
    $j  ;

    // Have we printed the last feed item? If so, close the ul up
    if ( $j == $total_feed_items )
    {
        echo '</ul></li>';
    }
}

Next up – CSS. This is nothing special and shouldn’t be hard to follow.

body {
        font-family: 'Lucida Grande', Helvetica, Arial, sans-serif;
        font-size:12px;
        text-align:center;
        background :#000000;
        color: #f3f3f3;
    }
    a {
        text-decoration:none;
        color: #83bacf;
    }
    #container {
        width:960px;
        margin: 0 auto;
        text-align:left;
    }
    .feed_container {
        float:left;
        width:320px;
        display:inline;
    }
    ul {
        padding: 5px 5px 5px 0;
        margin:0;
    }
    ul > li {
        list-style-type: none;
        margin:0;
        padding:0;
    }
    ul li span {
        display:none;
    }
    .feed_items li {
        padding: 5px 0;
        border-top: 1px solid #505050;
    }
    .more {
        display:block;
        background:#505050;
        height:10px;
    }
    .feeds_extra {
        display:none;
    }
    .clear {
        clear:both;
        padding: 0 0 10px 0;
    }
    #tooltip {
        position:absolute;
        border:2px solid #afa06b;
        background:#e4dd9c;
        padding: 5px;
        color:#333;
        display:none;
        width: 320px;
        text-align:left;
    }

Nothing fancy. The container has a width of 960 pixels and each feed gets a third each to play with. We added a dark background and colored the anchors blue and the tooltip yellowish. It matches Popurls color scheme. I think. We’ve added the necessary code for the tooltip as well. Speaking of tooltip, let’s include jQuery and the tooltip script. I’m going with jQuery 1.4.2 and a neat little tooltip that I found on css globe. The only problem is the way that script works. We have to alter it a bit in order to have it display our content in the tooltip. No biggie. We only need to change one line of code, namely line 21 from

this.t = this.title;

to

this.t = $(this).siblings().html();

so that it looks for content in our sibling span instead of the anchor’s title. Life’s good, right? Assuming that you put your javascript files in the root of your project, add some javascript in the head of the page

<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="tooltip.js"></script>
<script type="text/javascript">
$(document).ready(function(){
    $(".more").live("click", function(){
        $(this).parent().find('.feeds_extra').toggle();
    });
});
</script>

Simple. live() adds a handler to the event for all elements that match the selector no matte rif they’re in the DOM now or are added later. bind(‘click’, function(){ … }); would also do if that’s your preference. With me so far? I thought so. Let’s add the finishing touches to our main PHP code vlock so that it actually works. Who knew, right? We need to add two variables.

$total_feed_items = 10;
$half_feed_items = round( $total_feed_items/2 );

They’re used in the inner loop in the document body. Really, they are. We also need to change the hard coded value in the inner foreach loop to use our variable. Also note that we changed the number of feed items form 3 to 10.

foreach ( $_feed->get_items(0, $total_feed_items) as $__item )

To sum up, our PHP block should look something like this (comments removed)

function truncate( $string, $length=100 )
{
    if( strlen( $string ) <= $length )
    {
        return $string;
    }
    return preg_replace('/\s ?(\S )?$/', '', substr($string, 0, $length)).' ...';
}

require_once('simplepie.inc');
require_once('idn/idna_convert.class.php');

$_source = array(
    'digg'=>'http://feeds.digg.com/digg/popular.rss',
    'reddit'=>'http://www.reddit.com/.rss',
    'delicious'=>'http://feeds.delicious.com/v2/rss/',
    'dzone'=>'http://www.dzone.com/links/feed/frontpage/rss.xml',
    'wired'=>'http://feeds.wired.com/wired/index',
    'tuaw'=>'http://www.tuaw.com/rss.xml',
    'engadget'=>'http://www.engadget.com/rss.xml'
);

$_sorted = array();

$total_feed_items = 10;
$half_feed_items = round( $total_feed_items/2 );

foreach ( $_source as $_feed_key=>$_feed_value )
{
    $_feed = new SimplePie();
    $_feed->set_feed_url($_feed_value);
    $_feed->init();

    foreach ( $_feed->get_items(0, $total_feed_items) as $__item )
    {
        $_content = $__item->get_content();

        if ( empty( $_content ) || $_feed_key == 'reddit')
        {
            $_content = $__item->get_title();
        } 

        $_sorted[$_feed_key]['title'] = $_feed->get_title();
        $_sorted[$_feed_key]['favicon'] = $_feed->get_favicon();
        $_sorted[$_feed_key]['permalink'] = $_feed->get_permalink();
        $_sorted[$_feed_key]['feed_items'][] = array(
            'title' => truncate($__item->get_title()),
            'date' => $__item->get_date(),
            'permalink' => $__item->get_permalink(),
            'content' => truncate(html_entity_decode(strip_tags( $_content ), ENT_QUOTES, 'UTF-8'))
        );
    } 

}


YMMV. And the entire HTML page should look similar to this;

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>p0wnurls</title>

    <style type="text/css">
    body {
        font-family: 'Lucida Grande', Helvetica, Arial, sans-serif;
        font-size:12px;
        text-align:center;
        background :#000000;
        color: #f3f3f3;
    }
    a {
        text-decoration:none;
        color: #83bacf;
    }
    #container {
        width:960px;
        margin: 0 auto;
        text-align:left;
    }
    .feed_container {
        float:left;
        width:320px;
        display:inline;
    }
    ul {
        padding: 5px 5px 5px 0;
        margin:0;
    }
    ul > li {
        list-style-type: none;
        margin:0;
        padding:0;
    }
    ul li span {
        display:none;
    }
    .feed_items li {
        padding: 5px 0;
        border-top: 1px solid #505050;
    }
    .more {
        display:block;
        background:#505050;
        height:10px;
    }
    .feeds_extra {
        display:none;
    }
    .clear {
        clear:both;
        padding: 0 0 10px 0;
    }
    #tooltip {
        position:absolute;
        border:2px solid #afa06b;
        background:#e4dd9c;
        padding: 5px;
        color:#333;
        display:none;
        width: 320px;
        text-align:left;
    }

    </style>
        <script type="text/javascript" src="jquery.min.js"></script>
        <script type="text/javascript" src="tooltip.js"></script>
        <script type="text/javascript">
        $(document).ready(function(){
            $(".more").live("click", function(){
                $(this).parent().find('.feeds_extra').toggle();
            });
        });
        </script>
</head>
<body>

    <div id="container">
    <h1>p0wnurls</h1>
    <?
    $i = 1;
    foreach ( $_sorted as $feed_name=>$feed )
    {
    ?>
        <div class="feed_container">
            <div class="feed_title"><a href="<?php echo $feed['permalink']; ?>" title="<?php echo $feed['title']; ?>"><img src="<?php echo $feed['favicon']; ?>" alt="<?php echo $feed['title']; ?>" /> <?php echo $feed_name; ?></a></div>
                <ul class="feed_items">
                    <?php
                    $j = 0;
                    foreach ( $feed['feed_items'] as $feed_item )
                    {
                        // If we've reached half of our feed items, split to a new unordered list
                        if ( $j == $half_feed_items )
                        {
                        ?>

                        <li>
                            <div class="more"></div>
                            <ul class="feeds_extra">

                     <?php
                        }
                        ?>
                        <li>
                                <a href="<?php echo $feed_item['permalink']; ?>" title="<?php echo $feed_item['title']; ?>" class="tooltip"><?php echo $feed_item['title']; ?></a>
                                <span><?php echo $feed_item['content']; ?></span>
                        </li>
                    <?php
                        $j[imagine_two_plus_signs_here];
                        // When we've reached to total number of feed items, close the ul up
                        if ( $j == $total_feed_items )
                        {
                    ?>
                        </ul></li>
                    <?php
                        }
                     }
                     ?>
                </ul>
        </div>
        <?php
        // If the remainder of i divided by 3 is 0 - add a clearing div
        if ( ($i%3) == 0 ) {
        ?>
            <div class="clear"></div>
        <?php
        }
        $i[imagine_two_plus_signs_here];
        ?>
    <?php
    }
    ?>
    </div>
</body>
</html>

See, that wasn’t so hard, was it? The complete page is available here for your viewing pleasure. Again, note the lack of two plus signs in the inner loop. Instead of a nifty “More” button, we have a clunky grey bar. I leave it as an exercise for the reader to do something fun with it. That’s about it for this time. Next time we’ll see if we can’t make this code OO and add an admin interface to handle the feeds. Calm down, spanky, we’ll get there. Now I need to get some sleep. I’ve been watching Dexter a whole lot lately and he’s an inspiration, to say the least. The down side is that the show takes time from quality doing-jack-shit-on-the-Internet time. I have to see what I can do about that.

Related posts:

  1. Hands-on tips for PHP security I got asked to review a fairly large piece of...
  • http://abcphp.com/story/3655/ abcphp.com

    A Popurls Clone with PHP, jQuery, Awesomeness | profeshunl newbie…

    Good people of the Internets, I know a good deal of you frequently visit some sort of social website and/or aggregator such as Popurls, Reddit, Digg, Developer Zone, or what have you. After a deep and thorough analysis of my visitor statistics (read: p…

  • http://www.ubervu.com/conversations/pronewb.com/a-popurls-clone-with-php-jquery-awesomeness uberVU – social comments

    Social comments and analytics for this post…

    This post was mentioned on Twitter by pronewb: Popurls Clone with PHP, jQuery, Awesomeness http://bit.ly/dc3dom #PHP #jQuery #popurls…

  • http://topsy.com/trackback?url=http://pronewb.com/a-popurls-clone-with-php-jquery-awesomeness Tweets that mention A Popurls Clone with PHP, jQuery, Awesomeness | profeshunl newbie — Topsy.com

    [...] This post was mentioned on Twitter by profeshunl newbie, profeshunl newbie. profeshunl newbie said: Popurls Clone with PHP, jQuery, Awesomeness http://bit.ly/dc3dom #PHP #jQuery #tutorial [...]

  • http://dzone.com/ Rick Ross

    Thanks for including DZone in your project. We appreciate it.

    Rick

  • http://blog.werconnected.info/2010-03-26-%ec%9c%a0%ec%9a%a9%ed%95%9c-%eb%a7%81%ed%81%ac/ 2010-03-26 유용한 링크 | We are connected

    [...] A Popurls Clone with PHP, jQuery, Awesomeness [...]

  • http://www.developercast.com/2010/03/26/williams-blog-a-popurls-clone-with-php-jquery-awesomeness/ William’s Blog: A Popurls Clone with PHP, jQuery, Awesomeness | Development Blog With Code Updates : Developercast.com

    [...] a new post to his blog William shows you how to create a Popurls clone with the powerful combination of PHP and jQuery. Popurls is an aggregation site with some of the [...]

  • florentins

    Hi, I've made a popurls clone in Django + jQuery :)
    You can download the sources here: http://www.pythondaddy.com/niftyurls.html

  • hasin

    SimplePie is considered comparatively bulky. And specially if you dont enable the cache feature of it, it will backfire by consuming resources @extreme. So my suggestion is that you can completely avoid the feedparsing part in your own server and use the external services for it.

    Google has an awesome feed parsing service, you can just pass your feed url to this parser and get the parsed feeds as json object. its very useful when you have thousands of feeds to parse.

    Here is the good feed parser url
    “http://www.google.com/uds/Gfeeds?num=5&hl=en&output=json&q=<encoded feed url>&v=1.0″;

  • http://allphp.com/2010/03/williams-blog-a-popurls-clone-with-php-jquery-awesomeness/ William’s Blog: A Popurls Clone with PHP, jQuery, Awesomeness | allphp.com

    [...] a new post to his blog William shows you how to create a Popurls clone with the powerful combination of PHP and jQuery. Popurls is an aggregation site with some of the [...]

  • kaola

    Great!
    Where can I download this case sources file?
    Thank you.

  • http://twitter.com/pronewb profeshunl newbie

    Hi hasin,

    Thanks for you input and I agree to a certain extent. The choice of SimplePie was mainly due to the fact that it has a quite broad user base as well as thorough api documentation, which means less of a threshold for new developers. Granted, for a serious contender to Popurls, you'd probably need to roll your own, streamlined solution. But that was way out of scope for this short tutorial. As a side note, Google feed parser is really sweet.

  • http://www.traffic-internet.net/?p=10727 Popurls | traffic-internet.net

    [...]  Tutorials PopUrls (0 visite) [...]

  • http://www.vivanno.com/aggregator/?p=57108 vivanno.com::aggregator » Archive » Popurls

    [...]  Tutorials PopUrls () [...]

  • http://twitter.com/pronewb profeshunl newbie

    Hey, that looks very nice indeed! Good work! I like the pop full-feature.

  • http://www.booto.net/?p=5329 Web Dev Tutorials 1# | Booto’Blog

    [...] A Popurls Clone with PHP, jQuery, Awesomeness By William, March 23rd, 2010 Site: Profeshunl Newbie [...]

  • http://pronewb.com/mongodb-as-in-humongous-not-retarded MongoDB as in huMONGOus, not retarded | profeshunl newbie

    [...] on the iPad. Yet. Instead, I’m still in the process of writing the upcoming parts of my Popurls Clone series and as per usual, it takes a lot more time than expected. In combination with a massive [...]

  • steve

    Any chance of putting a link to download all your code zipped up?

  • Tim

    Great tutorial… any chance of adding the second part of making the code OO and adding an admin interface to handle the feeds? … just figured that I'd ask before Dexter starts up season 5 in the fall.

    Thanks again,

  • http://www.laptopcomputerslaptops.com/ patrick jhon

    A nice piece of writing
    about particular in PHP! I’m unable to understand your some coding. Can anyone
    help me? Thanks :)

  • http://www.hogscripts.com php scripts

     always a pleasure stopping by your site thanks

Page 1 of 11
  • What is this?

    My name is William and I'm a 30 year old developer/designer from Stockholm, Sweden. I have a love/hate relationship with PHP, I'm slightly aroused by jQuery and if I had the Adobe Flash IDE as a friend on Facebook, I'd label it as "it's complicated". This is my twelfth year as a freelance monkey. I prefer the term mercenary, but someone said it had a negative ring to it. Whatever. Oh, and I'm a Mac guy who loves his BacBook Pro in a somewhat unhealthy way.


    The font used for headings is Geometry Soft Pro as found on dafont.com.