<?php
/* LJGetLatest - return the latest (partial) data from the given user's livejournal
 * 
 * Usage: 
 *  require('ljgetlatest.php');
 *  $latest = ljgetlatest('myusername');
 *  print "<h2>{$latest['title']}</h2><div>{$latest['content']}</div>";
 *
 * Returns:
 *  array('title' => 'Title',
 *        'link'  => 'http://...',
 *        'content' => 'lorem ipsum dolor sit amet...'
 *       );
 *
 * Requirements:
 *  - PHP 4.3 or above 
 *  - PEAR Module: HTTP_Request
 *  - PEAR Module: Net_URL
 *
 * If you don't have access to manage PEAR modules, but PEAR is installed (it ought to be, it's default since 
 * PHP 4.2-ish), then you can still download them both from http://pear.php.net/packages.php
 * Unpack the tgz files somewhere, and make two folders (HTTP and Net) then put the main 
 * scripts and any sub-folders they were into them. (i.e., put Request.php and the Request/ folder into HTTP/)
 * Then upload these folders to the same folder as this script, and you shouldn't have any problems. 
 *
 * Explanation:
 * This feeds off the Atom XML feed (like RSS) that Livejournal automatically produces for each user. 
 * First the function checks for a cache file - if it's less than a minute old, it returns it no matter what.
 * If the cache file is old or doesn't exist, it checks the Atom feed's HTTP modified date, and if the feed
 * hasn't been modified since the cache file was written, it returns the cache file. Only if the atom feed is
 * newer than the cache file (or the file doesn't exist) do we download it. 
 * If there's an error along the way, ljgetlatest() returns either an old cache file if it exists, or a fake
 * entry with the title 'Error'. 
 * If the content contains the trace of an <lj-cut> tag (<a name="cutid1">) then the content is trimmed and 
 * a link inserted to the whole entry on LJ.
 */





/* DEBUG
  error_reporting(E_ALL);
  ini_set('display_errors',true);
*/

function ljgetlatest($ljuser) {
  
$cache_dir 'cache';
  
$cache_file "{$cache_dir}/lj-latest-{$ljuser}.inc";
  
$check_file "{$cache_dir}/last-polled-{$ljuser}";
  
$cache_age 60// 60 is good
  
  
$feedurl "http://{$ljuser}.livejournal.com/data/atom";
  
  
// Check if there's a local file. If modified within the last minute, use it, no questions asked.
  
$cache_exists is_readable($cache_file);
  if (!
is_dir($cache_dir)) @mkdir($cache_dir,0777); // Try to make the folder if it doesn't exist
  
if ($cache_exists) {
    
$cache_date filemtime($cache_file);
    if ( (
$cache_date $cache_age) > time() ) {
      return 
loadljcache($cache_file);
    }
  }

  
  
// More than a minute old? Then let's check if we've checked recently (or are currently checking) - look at the mod-date on the checkfile
  
if (is_file($check_file) && ((filemtime($check_file) + $cache_age) > time()) ) {
    
// The check file exists and it's been touched recently. Assume that another process has just updated or is updating.
    // Return the cache if it exists, else wait and try again. Die after a 4 seconds.
    
$tries 8;
    while(
$tries--) {
      if (
is_readable($cache_file)) {
        return 
loadljcache($cache_file);
      }
      
usleep(500000); // 0.5 seconds
    
}
    return array(
'title'=>'Error',
                 
'link'=>'#',
                 
'content'=>"Oh dear, I'm afraid I can't get the latest journal entry. Please mail me and let me know something's wrong!"
                
);
  }
  

  
  
// Checkfile hasn't changed, let's poll the LJ server
  
touch($check_file); // Touch the check_file to make sure other processes don't interfere or spam the LJ server
  
@include_once('HTTP/Request.php');
  if (!
class_exists('HTTP_Request')) {
    return array(
'title'=>'Error',
                 
'link'=>'#',
                 
'content'=>"I'm afraid something's missing that stops me getting the latest journal entry. Please mail me and let me know something's wrong!"
                
);
  }


  
$req = new HTTP_Request($feedurl);
  
$req->addHeader("User-agent""LJGetLatest/1.0 (http://{$_SERVER['HTTP_HOST']}{$_SERVER['SCRIPT_NAME']}; matt@lazycat.org)");
  
$req->sendRequest();
  
  
// TODO: Handle redirections and fatal HTTP errors here.
  
$status $req->getResponseCode();
  if ((
$status >= 300) || ($status 200)) {
    return 
$cache_exists loadljcache($cache_file) : array('title'=>'Error''link'=>'#''content'=>"I'm afraid livejournal's having a problem right now so I can't get the latest journal entry. Please mail me and let me know something's wrong!");
  }
  
  
// Check the modified time on the feed - is it newer than modified date on our local file? 
  
$remote_modified strtotime$req->getResponseHeader('last-modified') );
  if (
$cache_exists && ($cache_date $remote_modified)) {
    return 
loadljcache($cache_file);
  }


  
// Download the newer version
  
$content $req->getResponseBody();
  
$content ltrim(preg_replace("/<!--[^>]+>/m",'',$content)); // Kill useless comments.

/*PHP5
  // Make sure it's valid XML
  $xml = simplexml_load_string($content);
  if (!$xml && $cache_exists) {
    return loadljcache($cache_file);
  } elseif (!$xml) {
    return $cache_exists ? loadljcache($cache_file) : array('title'=>'Error',
                 'link'=>'#',
                 'content'=>"There was an error in the Livejournal feed, so I'm afraid I can't show the latest entry."
                );
  }
  
  // Get title, content, link from the first <entry> tag
  $link = (string) $xml->entry[0]->link['href'];
  $title = (string) $xml->entry[0]->title;
  $content = (string) $xml->entry[0]->content;

*/

  // Get first entry
  
$entry false;
  
$start_pos = ((int) strpos($content,'<entry>')) + 7;
  
$end_pos strpos($content,'</entry>',$start_pos);
  
$entry substr($content,$start_pos,$end_pos $start_pos);
  
  
// Extract title, link and content
  
$matches = array();
  
$link preg_match('<link rel="alternate" type="text/html" href="([^"]+)"/>',$entry,$matches) ? $matches[1] : '#';

  
$start_pos = ((int) strpos($entry,'<title>')) + 7;
  
$end_pos strpos($entry,'</title>',$start_pos);
  
$title = @substr($entry,$start_pos,$end_pos $start_pos);
  if (!
$title$title '';
  
  
$start_pos = ((int) strpos($entry,'<content type="html">')) + 21;
  
$end_pos strpos($entry,'</content>',$start_pos);
  
$content = @substr($entry,$start_pos,$end_pos $start_pos);
  
$content html_entity_decode($content);
  if (!
$content$content '';
  
  
//    return $cache_exists ? loadljcache($cache_file) : array('title'=>'Error', 'link'=>'#', 'content'=>"I'm afraid livejournal's having a problem right now so I can't get the latest journal entry. Please mail me and let me know something's wrong!");
  


  // Drop everything after an lj-cut if it exists, replace it with a "read more" link.
  
$content preg_replace('/<a name="cutid1">.*/','<a href="'.$link.'">Read more</a>',$content);


  
$out = array('link' => &$link,
               
'title' => &$title,
               
'content' => &$content
              
);

  
// Save as the new local file
  //PHP5  file_put_contents($cache_file,"{$link}\n{$title}\n{$content}");
  
$fp = @fopen($cache_file,'w');
  @
fwrite($fp,"{$link}\n{$title}\n{$content}");
  @
fclose($fp);
  
  
// Return content.
  
return $out;
}



function 
loadljcache($file) {
  
$content file($file);
  
$out = array();
  
  if (
count($content) < 3) {
    
$out['link'] = '#';
    
$out['title'] = '';
    
$out['content'] = join('',$content);
    return 
$out;
  }
  
  
$out['link'] = array_shift($content);
  
$out['title'] = array_shift($content);
  
$out['content'] = join('',$content);
  return 
$out;
}

if (!
function_exists('html_entity_decode')) {
  function 
html_entity_decode($string) {
     
// replace numeric entities
     
$string preg_replace('~&#x([0-9a-f]+);~ei''chr(hexdec("\\1"))'$string);
     
$string preg_replace('~&#([0-9]+);~e''chr("\\1")'$string);
     
// replace literal entities
     
$trans_tbl get_html_translation_table(HTML_ENTITIES);
     
$trans_tbl array_flip($trans_tbl);
     return 
strtr($string$trans_tbl);
  }
}