Use PHP with Identi.ca, Part 2

List, search, and post microblog updates to Identi.ca with PHP and the StatusNet API

Identi.ca is a popular free microblogging service that allows users to post status messages and news. Web application developers are able to create, access, and search these messages through the Identi.ca API. This two-part article introduces the Identi.ca API and illustrates how you use it with PHP to create dynamic web applications.

Vikram Vaswani, Founder, Melonfire

Photo of Vikram VaswaniVikram Vaswani is the founder and CEO of Melonfire, a consulting services firm with special expertise in open-source tools and technologies. He is also the author of the books Zend Framework: A Beginners Guide and PHP: A Beginners Guide.



22 August 2011 (First published 16 August 2011)

Also available in Chinese Japanese

22 Aug 2011 - Per author request, revised first sentence of paragraph immediately following Listing 2.

Introduction

Other articles in this series

In the first part of this article, I introduced you to the Identi.ca web service API and showed you a few examples of using the API to read and use data from Identi.ca in a PHP-based web application. In that article, I also introduced you to the two flavors of the Identi.ca API and illustrated how you can use the Identi.ca API either by writing your own code with tools such as SimpleXML and the Zend Framework or by using a ready-made PHP library such as identica-php.

All the examples in the previous article focused on read-only access to the API. In this concluding article, I show you how to take a user's input and feed it back to Identi.ca through the API. With this approach, you improve functionality in your PHP scripts as you include capabilities to create new posts, mark posts as favorites, and set up follower relationships. So let's get going!


Searching status notices

Frequently used acronyms

  • API: Application programming interface
  • HTTP: HyperText Transfer Protocol
  • JSON: JavaScript Object Notation
  • RSS: Really Simple Syndication
  • URL: Uniform Resource Locator
  • XML: Extensible Markup Language

Search is one of the most common reasons to use the API. Like the Twitter API (on which it is based), the Identi.ca API exposes a search interface that returns results in both Atom and JSON formats. This search interface is accessible at the URL http://identi.ca/api/search.atom and is most easily processed with a tool like the Zend_Feed component, which you saw in Part 1 of this article.

Listing 1 illustrates how to search Identi.ca for posts matching a specific query term and display them using Zend_Feed.

Listing 1. Searching Identi.ca
<?php
// load Zend Gdata libraries
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Feed');

// define query term
$q = 'green lantern';

// load search results
try {
  $feed = Zend_Feed::import('http://identi.ca/api/search.atom?q=' . urlencode($q));
} catch (Zend_Feed_Exception $e) {
  echo "Failed to import feed: " . $e->getMessage();
  exit();
}

if ($feed->count() > 0) {
  echo "<h2>Search results for '$q'</h2>";
  foreach ($feed as $entry) {        
    echo '<div>';
    echo $entry->title . '<br/>';
    echo 'By: <em>' . $entry->author->name . 
      '</em> on ' . date('d M Y h:i', strtotime($entry->published)) . 
      '</div> <br/>';
  }
}
?>

As Listing 1 illustrates, an Identi.ca search is performed by appending the query term to the request URL using the q parameter. Because the request URL has a .atom suffix, the search results are returned as an Atom feed, suitable for consumption by the Zend_Feed component, which is specifically designed to parse Atom and RSS feeds.

The Zend_Feed import() method is used to import the Atom feed and turn it into an object, which can then be processed using standard object-property notation. Listing 1 illustrates how to use Zend_Feed to iterate over the entries in the feed, extracting each entry's title, author, and publication date, to produce output like that shown in Figure 1.

Figure 1. A web page listing search results
Screen capture of a web page listing search results for 'green lantern'

Example application: Interactive search utility

Listing 2 revises Listing 1 to create a more interactive search utility, allowing the user to enter a search term into a form and view Identi.ca posts matching the search term.

Listing 2. An interactive Identi.ca search utility
<html>
  <head>
    <style>
    .item {
      float:none;
      clear:both;
      margin-top:1em;
    }
    </style>
  </head>
  <body>
    <h2>Search </h2>
    <form method="get">
    Search for: <input type="text" name="q" />
    </form>
    <?php 
    if (isset($_GET['q'])) {
      // load Zend Gdata libraries
      require_once 'Zend/Loader.php';
      Zend_Loader::loadClass('Zend_Feed');

      // define query term
      $q = urlencode($_GET['q']);

      // load search results
      try {
        $feed = Zend_Feed::import(
          'http://identi.ca/api/search.atom?rpp=25&q=' . $q);
      } catch (Zend_Feed_Exception $e) {
        echo "Failed to import feed: " . $e->getMessage();
        exit();
      }

      if ($feed->count() > 0) {
        echo "<h2>Search results for '$q'</h2>";
        foreach ($feed as $entry) {        
          echo '<div class="item">';
          echo $entry->title . '<br/>';
          echo 'By: <em>' . $entry->author->name . 
            '</em> on ' . 
            date('d M Y h:i', strtotime($entry->published)) . 
            '</div>';
        }
      }
    }
    ?>
  </body>
</html>

Listing 2 is a minor revision of Listing 1, adding support for input provided by the user to the search request URL endpoint; it parses the resulting Atom feed to produce a list of matching search results. The additional rpp parameter in the request URL specifies the number of results per page. Note that the query term must be URL-encoded before it passes to the API.

Figure 2 illustrates the tool in action.

Figure 2. An interactive Identi.ca search engine
Screen capture of an interactive Identi.ca search engine with results for 'captain america'

Adding and deleting status notices

Just as you can search posts, so too can you create them afresh. The Identi.ca API includes methods to post new status messages or remove existing ones. To post a new message to a user's timeline, an authenticated client must transmit the request using POST. The Zend_Http_Client component, which you've also previously met in Part 1, supports both POST and authentication and therefore fits the need well.

Take a look at Listing 3, which illustrates the process of posting a new status message to the user timeline through the API.

Listing 3. Posting a new status message
<?php
// load Zend Gdata libraries
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Http_Client');

// define user credentials
$username = 'your-username';
$password = 'your-password';

// post notice and display unique identifier 
// if message successfully posted
try {
  $client = new Zend_Http_Client('http://identi.ca/api/statuses/update.xml');  
  $client->setAuth($username, $password);
  $client->setParameterPost('status', 'Happy hippos in the sun');
  $response = $client->request('POST');
  $xml = simplexml_load_string($response->getBody());
  if ($xml->id) {
    echo 'New notice created with id: ' . $xml->id;
  } else {
    if ($xml->error) {
      throw new Exception($xml->error);
    } else {
      throw new Exception('Unspecified error');    
    }
  }
} catch (Exception $e) {
  echo "ERROR: " . $e->getMessage();
  exit();
}
?>

Listing 3 begins by loading the necessary class libraries and defining the user's account credentials. A Zend_Http_Client object is initialized with the API endpoint URL, and the setAuth() method is used to set account credentials for authentication. The status message text is then added to the request as a POST parameter using the "status" request argument, and the entire request is transmitted to Identi.ca.

If successful, Identi.ca returns an XML response representing the newly created message, together with its unique identifier. It's possible to then parse this XML document and display a message indicating success or failure. The newly created message also appears in the user's timeline on Identi.ca.

Removing an existing message is equally simple; all you need is the message's unique identifier, which should be POST-ed to the API endpoint for message deletion. Listing 4 illustrates the process.

Listing 4. Deleting a status message
<?php
// load Zend Gdata libraries
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Http_Client');

// define user credentials
$username = 'your-username';
$password = 'your-password';

// define ID of notice to delete
$id = '0011223344';

// delete specified notice 
try {
  $client = new Zend_Http_Client(
    "http://identi.ca/api/statuses/destroy/$id.xml");
  $client->setAuth($username, $password);  
  $response = $client->request('POST');
  $xml = simplexml_load_string($response->getBody());
  if ($xml->id) {
    echo 'Notice with id: ' . $xml->id . ' successfully deleted.';
  } else {
    if ($xml->error) {
      throw new Exception($xml->error);
    } else {
      throw new Exception('Unspecified error');    
    }
  }
} catch (Exception $e) {
  echo "Failed to read API response: " . $e->getMessage();
  exit();
}
?>

Example application: User timeline editor

It's quite easy to apply the techniques shown in Listing 3 and Listing 4 to create an interactive view of a user's timeline. This view allows the user to see his or her previous messages, and it provides controls to delete existing messages or add new ones.

Listing 5 displays the complete code.

Listing 5. User timeline editor
<html>
  <head>
    <style>
    .item {
      float:none;
      clear:both;
      margin-top:1em;
    }
    .img {
      float:left;
      margin-right:1em; 
      padding-bottom: 10px;
      height: 48px;
      width: 48px;
    }
    </style>
  </head>
  <body>
    <?php
    // load Zend Gdata libraries
    require_once 'Zend/Loader.php';
    Zend_Loader::loadClass('Zend_Http_Client');

    // define user credentials
    $username = 'your-username';
    $password = 'your-password';
    
    // set up authenticated client
    $client = new Zend_Http_Client();
    $client->setAuth($username, $password);

    try {
      // post message if message text present
      if (isset($_POST['m'])) {
        $client->setUri('http://identi.ca/api/statuses/update.xml');
        $client->setParameterPost('status', $_POST['m']);
        $response = $client->request('POST');
        $xml = simplexml_load_string($response->getBody());
        if (!isset($xml->id)) {
          if ($xml->error) {
            throw new Exception($xml->error);
          } else {
            throw new Exception('Unspecified error');    
          }
        }
      // delete message if message id present
      } else if (isset($_GET['d'])) {
        $id = $_GET['d'];
        $client->setUri(
          'http://identi.ca/api/statuses/destroy/$id.xml');
        $response = $client->request('POST');
        $xml = simplexml_load_string($response->getBody());
        if (!isset($xml->id)) {
          if ($xml->error) {
            throw new Exception($xml->error);
          } else {
            throw new Exception('Unspecified error');    
          }
        }
      }

      // load user timeline
      $client->setUri(
        'http://identi.ca/api/statuses/user_timeline.xml');
      $response = $client->request('GET');
      $xml = simplexml_load_string($response->getBody());
    } catch (Exception $e) {
      echo "ERROR: " . $e->getMessage();
      exit();
    }

    // parse and display status messages
    if (count($xml->status) > 0) {
      echo '<h2>Recent status updates</h2>';
      foreach ($xml->status as $entry) {       
        echo '<div class="item">';
        echo $entry->text . '<br/>';
        echo 'By: <em>' . 
          $entry->user->name . '</em> on ' . 
          date('d M Y h:i', strtotime($entry->created_at)) . 
          '<br/>';
        echo '<small>';
        echo '<a href="?d=' . $entry->id . '">Delete</a>';
        echo '</small>';
        echo '</div>'; 
      }
    }
    ?>

    <h2>Add New Post</h2>
    <form method="post">
      Message: <input type="text" name="m" /> <br/>
      <input type="submit" name="submit" value="Post" />
    </form>
  </body>
</html>

This code might seem complex at first glance, but it's actually quite simple. Listing 5 first loads the Zend Framework class libraries and sets up an authenticated HTTP client. It also retrieves the current user timeline, using a technique discussed in Part 1, and parses the timeline XML to display a list of the user's previous messages. Each message is accompanied with a "Delete" link, which also includes the message's unique identifier as a GET variable. The code also includes a simple web form for the user to enter a new status message; on submission, the message is transmitted to the form processing script using POST.

Each time the script is requested, it checks to see whether the request method is GET or POST and uses this distinction to decide the action to take:

  • For POST requests, the script retrieves the message entered by the user and then POSTs it to the API endpoint for new message creation, as shown in Listing 3.
  • For GET requests, the script retrieves the message identifier included in the request URL and then POSTs it to the API endpoint for message deletion, as shown in Listing 4.

Figure 3 shows the script in action, displaying a list of the user's recent messages.

Figure 3. A web page listing the current user timeline
Screen capture of a web page listing the current user timeline

Figure 4 displays the result of adding a new post through the web form.

Figure 4. A web page listing the updated user timeline after posting a message
Screen capture of a web page listing the updated user timeline after posting a message

Managing user subscriptions

In addition to posting and removing messages to your own timeline through the API, you can also manage follower relationships through the API. The API includes methods to list the users following, and being followed by, the authenticated user and also includes the ability to add or remove users from the follower list.

To see this in action, consider Listing 6, which illustrates how to follow another Identi.ca user.

Listing 6. Creating a follower relationship
<?php
// load Zend Gdata libraries
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Http_Client');

// define user credentials
$username = 'your-username';
$password = 'your-password';

// define user to follow
$u = 'some-user';

// follow a user
try {
  $client = new Zend_Http_Client(
    'http://identi.ca/api/friendships/create.xml');
  $client->setAuth($username, $password);
  $client->setParameterPost('screen_name', $u);
  $response = $client->request('POST');  
  $xml = @simplexml_load_string($response->getBody());  
  if ($xml->following == 'true') {
    echo 'Now following user: ' . $u;
  } else {
    if ($xml->error) {
      throw new Exception($xml->error);
    } else {
      throw new Exception('Unspecified error');    
    }
  }  
} catch (Exception $e) {
  echo "Failed to read API response: " . $e->getMessage();
  exit();
}
?>

As Listing 6 illustrates, a POST request must be transmitted to the API endpoint by an authenticated client with the screen name of the user to be followed. If successful, the response contains an XML document with details of the specified user, with the special following property set true.

Listing 7 illustrates the process of unfollowing (that is, breaking a follower relationship).

Listing 7. Breaking a follower relationship
<?php
// load Zend Gdata libraries
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Http_Client');

// define user credentials
$username = 'your-username';
$password = 'your-password';

// define user to unfollow
$u = 'some-user';

// unfollow a user
try {
  $client = new Zend_Http_Client(
    'http://identi.ca/api/friendships/destroy.xml');
  $client->setAuth($username, $password);
  $client->setParameterPost('screen_name', $u);
  $response = $client->request('POST');
  $xml = @simplexml_load_string($response->getBody());
  if ($xml->following == 'false') {
    echo 'No longer following user: ' . $u;
  } else {
    if ($xml[0]) {
      throw new Exception($xml[0]);
    } else {
      throw new Exception('Unspecified error');
    }
  }  
} catch (Exception $e) {
  echo "Failed to read API response: " . $e->getMessage();
  exit();
}
?>

Managing user favorites

Identi.ca, like Twitter, allows users to mark certain messages as favorites. This ability is a convenient way for users to flag certain messages as particularly interesting and valuable. The Identi.ca API includes methods to retrieve a user's favorites, as well as add and delete items to and from the list.

Listing 8 provides an example of retrieving all the authenticated user's favorite messages.

Listing 8. Retrieving user favorites
<html>
  <head>
    <style>
    .item {
      float:none;
      clear:both;
      margin-top:1em;
    }
    </style>
  </head>
  <body>
  <?php
  // load Zend Gdata libraries
  require_once 'Zend/Loader.php';
  Zend_Loader::loadClass('Zend_Http_Client');

  // define user credentials
  $username = 'your-username';
  $password = 'your-password';

  // list user favorites
  try {
    $client = new Zend_Http_Client('http://identi.ca/api/favorites.xml');
    $client->setAuth($username, $password);
    $response = $client->request('GET');
    $xml = simplexml_load_string($response->getBody());
  } catch (Exception $e) {
    echo "Failed to read API response: " . $e->getMessage();
    exit();
  }

  // parse and display favorite status messages
  echo '<h2>Favorites</h2>';
  foreach ($xml->status as $entry) {
    echo '<div class="item">';
    echo $entry->text . '<br/>';
    echo 'By: <em>' . $entry->user->name . 
      '</em> on ' . date('d M Y h:i', strtotime($entry->created_at)) .
      '</div>';
  }
  ?>
  </body>
</html>

Retrieving favorites is quite simple: Send a GET request to the favorites endpoint, and the response is an XML document containing a list of the user's favorite messages. Figure 5 displays an example of the output.

Figure 5. A web page listing user favorites
Screen capture of a web page listing user favorites

Note that it's also possible to retrieve another user's favorites simply by passing the target user's screen name to the endpoint using the id parameter.

To programmatically add a new message to a user's favorites, it's necessary to first obtain the message's unique identifier. Assuming that you have this, you can send a POST request to the API referencing the id, as in Listing 9.

Listing 9. Adding a favorite message
<?php
// load Zend Gdata libraries
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Http_Client');

// define user credentials
$username = 'your-username';
$password = 'your-password';

// define ID of notice to be favored
$id = '0011223344';

// favor a notice
try {
  $client = new Zend_Http_Client(
    "http://identi.ca/api/favorites/create/$id.xml");
  $client->setAuth($username, $password);
  $response = $client->request('POST');
  $xml = simplexml_load_string($response->getBody());  
  if ($xml->favorited == 'true') {
    echo 'Successfully added favorite.';
  } else {
    if ($xml->error) {
      throw new Exception($xml->error);
    } else {
      throw new Exception('Unspecified error');    
    }
  }
} catch (Exception $e) {
  echo "ERROR: " . $e->getMessage();
  exit();
}
?>

The response to a successful POST is an XML document containing the message metadata and content, with the favorited property set to true.

As you might have guessed by now, it's also possible to programmatically remove a favorite message, again by passing the message identifier to the correct API endpoint. Listing 10 illustrates the code.

Listing 10. Deleting a favorite message
<?php
// load Zend Gdata libraries
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Http_Client');

// define user credentials
$username = 'your-username';
$password = 'your-password';

// define ID of notice to be disfavored
$id = '0011223344';

// disfavor a notice
try {
  $client = new Zend_Http_Client(
    "http://identi.ca/api/favorites/destroy/$id.xml");
  $client->setAuth($username, $password);
  $response = $client->request('POST');
  $xml = simplexml_load_string($response->getBody());  
  if ($xml->favorited == 'false') {
    echo 'Successfully removed favorite.';
  } else {
    if ($xml->error) {
      throw new Exception($xml->error);
    } else {
      throw new Exception('Unspecified error');    
    }
  }
} catch (Exception $e) {
  echo "ERROR: " . $e->getMessage();
  exit();
}
?>

Example application: dashboard

With all this information at hand, you can now build a simple browser-based management interface to Identi.ca, one that's similar to what appears on the Identi.ca home page. This interface displays the current public timeline and offers functions to follow other users or mark certain messages as favorites.

Listing 11 has the complete code.

Listing 11. The Identi.ca dashboard
<html>
  <head>
    <style>
    .item {
      float:none;
      clear:both;
      margin-top:1em;  
    }
    </style>
  </head>
  <body>
    <?php
    // load Zend Gdata libraries
    require_once 'Zend/Loader.php';
    Zend_Loader::loadClass('Zend_Http_Client');

    // define user credentials
    $username = 'your-username';
    $password = 'your-password';

    $client = new Zend_Http_Client();
    $client->setAuth($username, $password);
    
    try {
      if (isset($_GET['o'])) {
        switch ($_GET['o']) {
          case 'follow':
            $client->setUri('http://identi.ca/api/friendships/create.xml');
            $client->setParameterPost('user_id', $_GET['d']);
            $response = $client->request('POST');  
            $xml = @simplexml_load_string($response->getBody());  
            if ($xml->following != 'true') {
              if ($xml->error) {
                throw new Exception($xml->error);
              } else {
                throw new Exception('Unspecified error');
              }
            }  
            break;

          case 'unfollow':
            $client->setUri('http://identi.ca/api/friendships/destroy.xml');
            $client->setParameterPost('user_id', $_GET['d']);
            $response = $client->request('POST');  
            $xml = @simplexml_load_string($response->getBody());  
            if ($xml->following != 'false') {
              if ($xml->error) {
                throw new Exception($xml->error);
              } else {
                throw new Exception('Unspecified error');    
              }
            } 
            break;

          case 'favor':
            $client->setUri(
              'http://identi.ca/api/favorites/create/' . $_GET['d'] . '.xml');
            $response = $client->request('POST');
            $xml = simplexml_load_string($response->getBody());  
            if ($xml->favorited != 'true') {
              if ($xml->error) {
                throw new Exception($xml->error);
              } else {
                throw new Exception('Unspecified error');    
              }
            }
            break;

          case 'disfavor':
            $client->setUri(
              'http://identi.ca/api/favorites/destroy/' . $_GET['d'] . '.xml');
            $response = $client->request('POST');
            $xml = simplexml_load_string($response->getBody());  
            if ($xml->favorited != 'false') {
              if ($xml->error) {
                throw new Exception($xml->error);
              } else {
                throw new Exception('Unspecified error');    
              }
            }
            break;
        }
      }

      // load public timeline
      $client->setUri(
        'http://identi.ca/api/statuses/public_timeline.xml');
      $response = $client->request('GET');
      $xml = simplexml_load_string($response->getBody());
    } catch (Exception $e) {
      echo "ERROR: " . $e->getMessage();
      exit();
    }

    // parse and display status messages
    echo '<h2>Recent public timeline updates</h2>';
    foreach ($xml->status as $entry) {  
      echo '<div class="item">';
      echo $entry->text . '<br/>';
      echo 'By: <em>' . $entry->user->name . 
        '</em> on ' . 
        date('d M Y h:i', strtotime($entry->created_at)) . 
        '<br/>';
      echo '<small>';
      echo ($entry->user->following == 'false') ? 
        '<a href="?o=follow&d=' . $entry->user->id . 
        '">Follow user</a>' : 
        '<a href="?o=unfollow&d=' . $entry->user->id . 
        '">Unfollow user</a>';
      echo ' | ';
      echo ($entry->favorited == 'false') ? 
        '<a href="?o=favor&d=' . $entry->id . 
        '">Mark message as favorite</a>' : 
        '<a href="?o=disfavor&d=' . $entry->id . 
        '">Remove message from favorites</a>';
      echo '</small>';
      echo '</div>';
    }
    ?>
  </body>
</html>

Listing 11 is best thought of as a giant conditional statement, which executes a particular branch based on the value of the o parameter in the request URL:

  • For o=follow, it looks for a screen name and uses the API to programmatically create a follower relationship with the corresponding user.
  • For o=unfollow, it looks for a numeric user identifier and uses the API to remove the authenticated user's follower relationship with the specified user.
  • For o=favor, it looks for a numeric message identifier and uses the API to add the corresponding message to the authenticated user's favorites list.
  • For o=disfavor, it looks for a numeric message identifier and uses the API to remove the corresponding message from the authenticated user's favorites list.

In each of the above cases, after performing the specified action, the script re-requests the Identi.ca public timeline through the API, parses the XML response, and displays the messages in a list, together with the appropriate controls.

Figure 6 displays an example of Listing 11 in action.

Figure 6. A browser-based dashboard for Identi.ca
Screen capture of a browser-based dashboard for Identi.ca with posts by several users

Conclusion

Other articles in this series

In this article, you worked through a crash course on how to integrate data from the Identi.ca API into a PHP application using a combination of SimpleXML and the Zend HTTP client library. The examples in this two-part article introduced you to the two main flavors of the API, showed you how to search Identi.ca content, illustrated how to programmatically add, modify, and delete content, demonstrated how to manage user subscriptions and favorites, and built a customized interface to a user's Identi.ca data.

As these examples illustrate, the Identi.ca API is a full-fledged, flexible interface for developers ready to build creative new applications around microblogging services, content aggregation, and search. Play with it and see what you think.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML, Open source
ArticleID=751996
ArticleTitle=Use PHP with Identi.ca, Part 2
publish-date=08222011