I don't use WordPress and I don't use Google Reader. But someone who does found Mike Crute's code which incorporates Google Reader into a WordPress sidebar.
Because our web host has disabled url_fopen
for security reasons,
I added support for cURL. I also added a few other features and error
checking to it.
Update:
The plugin now purports to support "Share with note".
If you have enabled excerpts,
your note will appear before the excerpt inside a
blockquote
element.
Here it is (or download it):
<?php /* Plugin Name: Google Reader Shared Plugin URI: http://mike.crute.org/ Description: A sidebar add-in for Google Reader shared items. Version: 1.0 Author: Mike Crute Author URI: http://mike.crute.org/ */ /* Change log Author: Jeremy Michelson Author URI: http://jeremy.smallinfinity.net/ July 13, 2008 Put shared comments into a blockquote, with the "Shared by <user> comment removed. March 21, 2007 Apparently--at least this is what I think happened--Google altered the XML format of their feed, thereby destroying the link targets of the plugin. This is now fixed, at least until the XML format changes again. Thanks to Amish for alerting me to the problem! BUG FIXES: Fixed year in previous update timestamps! TO DO: The things I previously assigned myself to do. But I've been busy... February 3, 2007 BUG FIXES 1. Fixed stupid javascript bug (it's getElementById, not getDocumentById!) Now the Content Length textbox is only enabled if you want content excerpts. January 28, 2007 BUG FIXES 1. If you are only listing titles, then the close tag is </ul> not </dl>! (Thanks Amish!) January 27, 2007 NEW FEATURES 1. "Silent" failures can be made verbose by changing the below "define('READER_SHARED_DEBUG', false)" to "..., true)". It is not recommended that this be done except for debugging. 2. Added optional argument to the readerShared so that <?php readerShared('tag'); ?> extracts only those shared items that have been tagged, or given the label, 'tag'. Conversely, if you provided a specific URL for a label, then <?php readerShared('') ?> will show all items, regardless of tag. TO DO 1. Allow additional optional arguments to readerShared. These will likely have the following format: readerShared('tag1', 'tag2', '!tag3', 'tag4', '!tag5', ...) will display those shared items that match tag1, tag2, and tag4 while not matching tag3 or tag5. Similarly, readerShared('!tag') removes items tagged with 'tag' from what would otherwise be displayed; this is backwards compatible with the current readherShared('tag') which displays those items with that tag; etc.. Be warned that this is incompatible with having a tag which begins with a '!'. Posted 2007-01-20 BUG FIXES 1. removed the "class=..." from the dd tag. This is unnecessary provided the style sheet contains a ".classname dd" rule; and any "dd.classname" rule should be converted to ".classname dd". Moreover the way it was written, if there was no class, then '<dd class="">' would appear, which is just dumb. Posted 2007-01-19 NEW FEATURES 1. added support for cURL if allow_url_fopen is off 2. moved the fprintf("<ul>...") until after the xmlsimple parsing so that we can return on failure with no side effects 3. if neither cURL is enabled, nor allow_url_fopen, then this is now a NOP. Similarly if there is no SimpleXML, then this is a NOP. Added an option to determine timeout length for cURL. 4. Options to excerpt the content and if so, how much 5. Silently but cleanly failing error checking for failure of simplexml_load_(file|string). Similarly, supressed errors therefrom. 6. Added error checking of $_POST's. 7. Similarly, silently NOP if configuration has not occurred, thereby avoiding errors trying to obtain the xml from "". Similarly override an empty numdisp to the maximum while rendering. BUG FIXES 1. There was an "function readerShared_Menu redefined on line (just after where it is defined " fatal error. This was fixed by surrounding the definition with an "if ( ! function_exists(...) ) { ... }" 2. "&"'s in href's were not written as "&" resulting in invalid HTML. On closer examination, this is the effect of SimpleXML's translating character entities to their corresponding characters. So, we should selectively translate back. We need to do it selectively, so that e.g. "?" doesn't become "%3F" but so that the obviously bad characters '"', '&', '<', '>' get properly reencoded. htmlspecialchars appears to be precisely designed for this. TO DO/CHECK 1. Ensure that if get_magic_quotes_runtime(), there isn't a problem with the data that SimpleXML receives. 2. Wonder if $_POST data needs to be stripslashed if magic_quotes_gpc, or if update_option does this automatically. 3. If the order of preference (url_fopen vs cURL) is changed, update the form (function_exists instead of ini_get) as well. Also, the form could detect whether this plugin is guaranteed to fail for reasons described in Features 3&5 and give feedback accordingly. */ define('READER_SHARED_DEBUG', false); // checks to make sure the string is a good integer within valid bounds // NULL means unbounded. defaults to a min of 0 and a max of NULL // Return value codes: // FALSE: not an integer; -1: less than $min; +1: bigger than $min; 0: ok // so use === to test the return value! if ( !function_exists('readerShared_echo') ) { function readerShared_echo($s) { if ( READER_SHARED_DEBUG ) echo "<p class=\"error\">$s</p>\n"; } } if ( !function_exists('readerShared_goodInt') ) { function readerShared_goodInt($s, $min=0, $max=NULL) { $s = trim($s); if ( !ctype_digit($s) && ( ($min !== NULL && $min >=0) || $s{0} != '-' || !ctype_digits(substr($s, 1)) ) ) { return FALSE; } $i = (int)$s; if ( $min !== NULL && $i < $min ) { return -1; } if ( $max !== NULL && $i > $max ) { return 1; } return 0; } } // Format the plugin page if (is_plugin_page()) { // sanity check: don't want our "url" to e.g. be "file:///etc/passwd" // even if only an admin user is ostensibly able to get to this page // on the other hand, we should be as liberal as possible...so allow ftp $readerSharedErrors = array(); if ( isset($_POST['update_reader']) ) { if ( isset($_POST['url']) && preg_match('!^\s*(?:https?|ftp)://!', $_POST['url']) ) { if ( !preg_match('!^https?://www\.google\.com/reader/public/atom/user/\d+/state/com\.google/broadcast$!', $_POST['url']) ) { if ( preg_match('!^https?://www\.google\.com/reader/public/atom/user/\d+/label/[^/]+$!', $_POST['url']) ) { // could do something like a warning } else { $readerSharedErrors[] = "I will use the provided URL, <code>{$_POST['url']}</code>, even though it does not look like a Google Reader URL"; } } // finished checking type of google reader url update_option('GoogleReader_FeedURL', $_POST['url']); } else { $readerSharedErrors[] = 'Please '.( isset($_POST['update_reader']) ? 'provide' : 'correct' ) .' your <a href="#url">feed URL</a>.'; } // make sure numdisp is a number within the advertised bounds if ( isset($_POST['numdisp']) && ($readerSharedITest = readerShared_goodInt($_POST['numdisp'], 1, 20)) === 0 ) { update_option('GoogleReader_NumDisplay', $_POST['numdisp']); } else { $readerSharedErrorPre = '<a href="#numdisp">\"Number to Display\"</a>'; if ( $readerSharedITest === FALSE ) { $readerSharedErrors[] = $readerSharedErrorPre." must be an integer!"; } else if ( $readerSharedITest === 1 ) { $readerSharedErrors[] = $readerSharedErrorPre." must be positive."; } else { // must be -1 $readerSharedErrors[] = $readerSharedErrorPre." must be less than 20."; } } // done with numdisp // nothing to check for the class update_option('GoogleReader_CSSClass', $_POST['cssclass']); // excerpt is a checkbox update_option('GoogleReader_IncludeExcerpts', (isset($_POST['excerpt']) ? "1" : "" )); // even if !excerpt, update the content length, if there. Never an // error to omit; defaults to unlimited // similarly curl time out length foreach ( array('contentlen'=>array('GoogleReader_ContentLength', 'amount to excerpt'), 'curltime' => array('GoogleReader_curlTimeOut', 'cURL time out')) as $readerShared_id => $readerShared_stuff ) { if ( isset($_POST[$readerShared_id]) ) { $readerSharedErrorPre = "<a href=\"#$readerShared_id\">{$readerShared_stuff[1]}</a>"; if ( ($readerSharedITest = readerShared_goodInt($_POST[$readerShared_id], 0, NULL)) === 0 ) { update_option($readerShared_stuff[0], $_POST[$readerShared_id]); } else if ( trim($_POST[$readerShared_id]) === "" ) { // treat as 0 update_option($readerShared_stuff[0], '0'); } else if ( $readerSharedITest !== 1 ) { // wasn't empty $readerSharedErrors[] = $readerSharedErrorPre.' must be a nonnegative integer.'; } } // end of if isset } // end of foreach similar integer echo('<div class="updated"><p>' .(!empty($readerSharedErrors) ? 'Valid o' : 'O') .'ptions changes saved.</p></div>'); if ( !empty($readerSharedErrors) ) { echo '<div class="error"><ul>'; foreach ( $readerSharedErrors as $readerSharedError ) { echo "\n<li>$readerSharedError</li>"; } echo "\n</ul></div>\n"; } } ?> <div class="wrap"> <h2>Google Reader Shared Items Options</h2> <form method="post"> <fieldset class="options"> <p><strong>Google Reader</strong></p> <p> <label for="url">Feed URL:</label> <input name="url" type="text" id="url" value="<?php echo get_option('GoogleReader_FeedURL'); ?>" /> <label for="numdisp">Number to Display (max is 20):</label> <input name="numdisp" type="text" id="numdisp" value="<?php echo get_option('GoogleReader_NumDisplay'); ?>" /> <label for="cssclass">CSS Class:</label> <input name="cssclass" type="text" id="cssclass" value="<?php echo get_option('GoogleReader_CSSClass'); ?>" /> </p> </p><p> <input name="excerpt" type="checkbox" id="excerpt" <?php if ( get_option('GoogleReader_IncludeExcerpts') == "1" ) echo ' checked="checked"'; ?> onclick="getElementById('contentlen').disabled = !this.checked" /> <label for="excerpt">Include Content Excerpt</label> <label for="contentlen">Number of characters to excerpt (use 0 for no limit):</label> <input name="contentlen" type="text" id="contentlen" value="<?php echo get_option('GoogleReader_ContentLength'); ?>" /> <script type="text/javascript"> <!-- getElementById('contentlen').disabled = !getElementById('excerpt').checked; //--> </script> </p><p> <label for="curltime">If using cURL, (you probably are<?php if ( ini_get('allow_url_fopen') ) echo ' not'; ?>) number of seconds to wait before timing out (0 means unspecified default behavior):</label> <input name="curltime" type="text" id="curltime" value="<?php echo get_option('GoogleReader_curlTimeOut'); ?>" /> </p> </fieldset> <p><div class="submit"><input type="submit" name="update_reader" value="Save Settings" style="font-weight:bold;" /></div></p> </form> <p>If you excerpt, the Google Reader list will be inside a <dl> (descriptive list) element; the headlines will be inside a <dt> element and the excerpted content will be inside a <dd> element. Otherwise, <ul> and <li> tags will be used as usual. Please ensure your style sheet is written accordingly. </p> <p>Make sure your <code>sidebar.php</code> contains lines functionally equivalent to</p> <pre> <h2><em>Google Reader</em></h2> <?php readerShared(); ?> </pre> </div> <?php } else { function readerShared($tag = NULL) { if ( ! extension_loaded('SimpleXML') ) { readerShared_echo("SimpleXML is required and not installed."); if ( !version_compare(phpversion(), '5.0', '>=') ) { readerShared_echo("Moreover, SimpleXML requires PHP 5 or better."); } return; // no SimpleXML support! } $feedurl = trim(get_option('GoogleReader_FeedURL')); $display = trim(get_option('GoogleReader_NumDisplay')); $class = trim(get_option('GoogleReader_CSSClass')); if ( empty($feedurl) ) { // user hasn't configured yet! readerShared_echo('Reader_Shared plugin has not yet been configured.'); return; } else { // deal with tag if ( preg_match('!^http(s?)://www\.google\.com/reader/public/atom/user/(\d+)/state/com\.google/broadcast$!', $feedurl, $nonunique) && !empty($tag) ) { // turn it into a tag url $feedurl = "http{$nonunique[1]}://www.google.com/reader/public/atom/user/{$nonunique[2]}/label/".urlencode($tag); } else if ( preg_match('!^http(s?)://www\.google\.com/reader/public/atom/user/(\d+)/label/([^/]+)$!', $feedurl, $nonunique) ) { // might need to change it if ( $tag === '' ) { // need to turn it into a nontag url $feedurl = "http{$nonunique[1]}://www.google.com/reader/public/atom/user/{$nonunique[2]}/state/com.google/broadcast"; } else if ( $tag !== NULL && $tag !== $nonunique[3] ) { // replace the tag $feedurl = "http{$nonunique[1]}://www.google.com/reader/public/atom/user/{$nonunique[2]}/label/".urlencode($tag); } // end of fixing tag url } // end of fixing url } // end of checking/fixing url if ( empty($display) ) { // use max $display = 20; } if ( ini_get('allow_url_fopen') ) { // ok $feed = @simplexml_load_file($feedurl); } else if ( function_exists("curl_init") ) { $curlHandle = curl_init($feedurl); $curlTO = get_option('GoogleReader_curlTimeOut'); if ( !empty($curlTO) ) { // "0" is empty curl_setopt($curlHandle, CURLOPT_TIMEOUT, $curlTO); } // otherwise whatever the default is // make sure cURL returns a string that we can give to simplexml curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true); // make sure there is no infinite redirect loop curl_setopt($curlHandle, CURLOPT_MAXREDIRS, 10); // make sure the blog doesn't block unknown user agents curl_setopt($curlHandle, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // this is probably unnecessary but prevents broadcasting passwords curl_setopt($curlHandle, CURLOPT_UNRESTRICTED_AUTH, FALSE); $feedstring = curl_exec($curlHandle); if ( curl_errno($curlHandle) ) { // fail silently readerShared_echo("Error obtaining the Google Reader feed $feedurl."); return; } curl_close($curlHandle); $feed = @simplexml_load_string($feedstring); } else { // no curl, no url_fopen, I give up readerShared_echo('This plugin requires at least one of <code>url_fopen</code> or <code>cURL</code>.'); return; } if ( !is_a($feed, 'SimpleXMLElement') ) { // problem occurred readerShared_echo('SimpleXML failed somehow.'); return; } $loopcount = 0; if ( $excerpt = (get_option('GoogleReader_IncludeExcerpts') == '1') ) { $listType = 'dl'; $listElement = 'dt'; $excerptLen = trim(get_option('GoogleReader_ContentLength')); if ( empty($excerptLen) ) { $excerptLen = 0; } else { $excerptLen = (int)$excerptLen; } } else { $listType = 'ul'; $listElement = 'li'; } printf('<%s%s>', $listType, ($class != '') ? " class=\"$class\"" : ''); foreach ($feed->entry as $item) { if ($loopcount < $display) { printf('<%s><a href="%s" rel="nofollow">%s - %s</a></%1$s>', $listElement, htmlspecialchars($item->link['href']), $item->title, $item->source->title); $loopcount++; if ( !$excerpt ) continue; $content = ( !empty($item->summary) ? $item->summary : $item->content ); $content = readerShared_excerptContent($content, $excerptLen);//defined below echo "<dd>$content...</dd>\n"; } } echo("</$listType>"); } } if ( !function_exists('readerShared_excerptContent') ) { function readerShared_excerptContent($s, $len=0) { /* check if there is a quote */ if ( preg_match('!^<blockquote>Shared by\\s*\\w+\\s*$\\s*<br>\\s*$\\s*(.*)</blockquote>\\s*$!msu', // m allows $ to match eol; s allows . to match eol; and u is for ungreedy. $s, $matches) ) { $s = substr($s, strlen($matches[0])); /*$comment = strip_tags($matches[1]);*/ // unnecessary since complete $comment = $matches[1]; } else { $comment = ''; } // easiest to strip tags and then truncate then to truncate, strip tags, // and worry about whether there is an unfinished tag "<p", a "<" inside // an attribute (<foo onmouseover="this.innerHTML += '<p>foo</p) // and other such worries. Even though stripping all tags before // truncating is more work than strip_tags should have to do $s = strip_tags($s); if ( $len > 0 ) { $s = substr($s, 0, $len); } return strlen($comment) ?"<blockquote>$comment</blockquote>\n$s" : $s; } } if ( !function_exists('readerShared_Menu') ) { function readerShared_Menu() { add_options_page('Google Reader Shared Options', 'Google Reader', 9, basename(__FILE__)); } } add_action('admin_menu', 'readerShared_Menu'); ?>
Name it reader_shared.php
and
install it as one installs plugins, then go to the options page to configure
it.
Also, if you copy and paste it, make sure there are no extra spaces or
empty lines at the end of the file.
I have attempted to make it self-explanatory.
Unfortunately--I hope I can blame this on a style sheet over which I have
no control--the options page is now quite ugly: the check box is too big and
is not obviously associated with its "Include Content Excerpt" label.
This seems to have gone away.
But
I'll let you see an old screen shot anyway.
This page was last updated Sunday, July 13, 2008.