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.