From dc90e90fd2748f953a2d4750105817b41cafe43a Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Thu, 19 Nov 2009 19:12:58 -0500 Subject: Load notice data using javascript from the html on the page instead of writing json representations of the notices on each page Clicking on a geo link pops up a map --- plugins/Mapstraction/MapstractionPlugin.php | 66 ------------------------- plugins/Mapstraction/usermap.js | 76 +++++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 70 deletions(-) (limited to 'plugins') diff --git a/plugins/Mapstraction/MapstractionPlugin.php b/plugins/Mapstraction/MapstractionPlugin.php index eabd0d0f0..a0dbf5204 100644 --- a/plugins/Mapstraction/MapstractionPlugin.php +++ b/plugins/Mapstraction/MapstractionPlugin.php @@ -110,11 +110,6 @@ class MapstractionPlugin extends Plugin function onEndShowScripts($action) { $actionName = $action->trimmed('action'); - // These are the ones that have maps on 'em - if (!in_array($actionName, - array('showstream', 'all', 'allmap', 'usermap'))) { - return true; - } switch ($this->provider) { @@ -151,37 +146,6 @@ class MapstractionPlugin extends Plugin $action->raw(sprintf('var _provider = "%s";', $this->provider)); $action->elementEnd('script'); - switch ($actionName) { - case 'usermap': - case 'showstream': - $notice = empty($action->tag) - ? $action->user->getNotices(($action->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1) - : $action->user->getTaggedNotices($action->tag, ($action->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1, 0, 0, null); - break; - case 'all': - case 'allmap': - $cur = common_current_user(); - if (!empty($cur) && $cur->id == $action->user->id) { - $notice = $action->user->noticeInbox(($action->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1); - } else { - $notice = $action->user->noticesWithFriends(($action->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1); - } - break; - } - - $jsonArray = array(); - - while ($notice->fetch()) { - if (!empty($notice->lat) && !empty($notice->lon)) { - $jsonNotice = $this->noticeAsJson($notice); - $jsonArray[] = $jsonNotice; - } - } - - $action->elementStart('script', array('type' => 'text/javascript')); - $action->raw('var _notices = ' . json_encode($jsonArray)); - $action->elementEnd('script'); - return true; } @@ -212,34 +176,4 @@ class MapstractionPlugin extends Plugin $action->elementEnd('div'); } - - function noticeAsJson($notice) - { - // FIXME: this code should be abstracted to a neutral third - // party, like Notice::asJson(). I'm not sure of the ethics - // of refactoring from within a plugin, so I'm just abusing - // the ApiAction method. Don't do this unless you're me! - - require_once(INSTALLDIR.'/lib/api.php'); - - $act = new ApiAction('/dev/null'); - - $arr = $act->twitterStatusArray($notice, true); - $arr['url'] = $notice->bestUrl(); - $arr['html'] = htmlspecialchars($notice->rendered); - $arr['source'] = htmlspecialchars($arr['source']); - - if (!empty($notice->reply_to)) { - $reply_to = Notice::staticGet('id', $notice->reply_to); - if (!empty($reply_to)) { - $arr['in_reply_to_status_url'] = $reply_to->bestUrl(); - } - $reply_to = null; - } - - $profile = $notice->getProfile(); - $arr['user']['profile_url'] = $profile->profileurl; - - return $arr; - } } diff --git a/plugins/Mapstraction/usermap.js b/plugins/Mapstraction/usermap.js index 270b7efea..7fb73fa88 100644 --- a/plugins/Mapstraction/usermap.js +++ b/plugins/Mapstraction/usermap.js @@ -1,14 +1,82 @@ $(document).ready(function() { - var mapstraction = new mxn.Mapstraction("map_canvas", _provider); + notices = []; + $(".notice").each(function(){ + notice = getNoticeFromElement($(this)); + if(notice['geo']) + notices.push(notice); + }); + if($("#map_canvas") && notices.length>0) + { + showMapstraction($("#map_canvas"), notices); + } + + $('a.geo').click(function(){ + noticeElement = $(this).closest(".notice"); + notice = getNoticeFromElement(noticeElement); + + $.fn.jOverlay.options = { + color : '#000', + opacity : '0.6', + zIndex : 99, + center : false, + bgClickToClose : true, + autoHide : true, + css : {'max-width':'542px', 'top':'5%', 'left':'32.5%'} + }; + html="
"; + html+=""; + html+=$("
").append($(this).clone()).html(); + $().jOverlay({ "html": html }); + $('#jOverlayContent').show(); + $('#jOverlayContent button').click($.closeOverlay); + + showMapstraction($("#map_canvas_popup"), notice); + + return false; + }); +}); + +function getMicroformatValue(element) +{ + if(element[0].tagName.toLowerCase() == 'abbr'){ + return element.attr('title'); + }else{ + return element.text(); + } +} + +function getNoticeFromElement(noticeElement) +{ + notice = {}; + if(noticeElement.find(".latitude").length){ + notice['geo']={'coordinates': [ + parseFloat(getMicroformatValue(noticeElement.find(".latitude"))), + parseFloat(getMicroformatValue(noticeElement.find(".longitude")))] }; + } + notice['user']={ + 'profile_image_url': noticeElement.find("img.avatar").attr('src'), + 'profile_url': noticeElement.find(".author a.url").attr('href'), + 'screen_name': noticeElement.find(".author .nickname").text() + }; + notice['html']=noticeElement.find(".entry-content").html(); + notice['url']=noticeElement.find("a.timestamp").attr('href'); + notice['created_at']=noticeElement.find("abbr.published").text(); + return notice; +} + +function showMapstraction(element, notices) { + if(element instanceof jQuery) element = element[0]; + if(! $.isArray(notices)) notices = [notices]; + var mapstraction = new mxn.Mapstraction(element, _provider); var minLat = 181.0; var maxLat = -181.0; var minLon = 181.0; var maxLon = -181.0; - for (var i in _notices) + for (var i in notices) { - var n = _notices[i]; + var n = notices[i]; var lat = n['geo']['coordinates'][0]; var lon = n['geo']['coordinates'][1]; @@ -42,4 +110,4 @@ $(document).ready(function() { bounds = new mxn.BoundingBox(minLat, minLon, maxLat, maxLon); mapstraction.setBounds(bounds); -}); +} -- cgit v1.2.3-54-g00ecf From dd36c2de23dbdb30f333be1f9d5292d62363b0c8 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 19 Nov 2009 20:55:38 -0800 Subject: Initial functional version of feed subscription plugin, currently supporting only PuSH-enabled feeds. --- db/08to09_pg.sql | 6 +- db/statusnet_pg.sql | 6 +- plugins/FeedSub/FeedSubPlugin.php | 117 ++++++ plugins/FeedSub/README | 24 ++ plugins/FeedSub/actions/feedsubcallback.php | 100 +++++ plugins/FeedSub/actions/feedsubsettings.php | 257 ++++++++++++ plugins/FeedSub/extlib/README | 9 + plugins/FeedSub/extlib/XML/Feed/Parser.php | 351 ++++++++++++++++ plugins/FeedSub/extlib/XML/Feed/Parser/Atom.php | 365 ++++++++++++++++ .../FeedSub/extlib/XML/Feed/Parser/AtomElement.php | 261 ++++++++++++ .../FeedSub/extlib/XML/Feed/Parser/Exception.php | 42 ++ plugins/FeedSub/extlib/XML/Feed/Parser/RSS09.php | 214 ++++++++++ .../extlib/XML/Feed/Parser/RSS09Element.php | 62 +++ plugins/FeedSub/extlib/XML/Feed/Parser/RSS1.php | 277 ++++++++++++ plugins/FeedSub/extlib/XML/Feed/Parser/RSS11.php | 276 ++++++++++++ .../extlib/XML/Feed/Parser/RSS11Element.php | 151 +++++++ .../FeedSub/extlib/XML/Feed/Parser/RSS1Element.php | 116 +++++ plugins/FeedSub/extlib/XML/Feed/Parser/RSS2.php | 335 +++++++++++++++ .../FeedSub/extlib/XML/Feed/Parser/RSS2Element.php | 171 ++++++++ plugins/FeedSub/extlib/XML/Feed/Parser/Type.php | 467 +++++++++++++++++++++ .../extlib/XML/Feed/samples/atom10-entryonly.xml | 28 ++ .../extlib/XML/Feed/samples/atom10-example1.xml | 20 + .../extlib/XML/Feed/samples/atom10-example2.xml | 45 ++ .../FeedSub/extlib/XML/Feed/samples/delicious.feed | 177 ++++++++ .../FeedSub/extlib/XML/Feed/samples/flickr.feed | 184 ++++++++ .../extlib/XML/Feed/samples/grwifi-atom.xml | 7 + plugins/FeedSub/extlib/XML/Feed/samples/hoder.xml | 102 +++++ .../extlib/XML/Feed/samples/illformed_atom10.xml | 13 + .../extlib/XML/Feed/samples/rss091-complete.xml | 47 +++ .../XML/Feed/samples/rss091-international.xml | 30 ++ .../extlib/XML/Feed/samples/rss091-simple.xml | 15 + .../extlib/XML/Feed/samples/rss092-sample.xml | 103 +++++ .../extlib/XML/Feed/samples/rss10-example1.xml | 62 +++ .../extlib/XML/Feed/samples/rss10-example2.xml | 67 +++ .../FeedSub/extlib/XML/Feed/samples/rss2sample.xml | 42 ++ .../extlib/XML/Feed/samples/sixapart-jp.xml | 226 ++++++++++ .../extlib/XML/Feed/samples/technorati.feed | 54 +++ plugins/FeedSub/extlib/XML/Feed/schemas/atom.rnc | 338 +++++++++++++++ plugins/FeedSub/extlib/XML/Feed/schemas/rss10.rnc | 113 +++++ plugins/FeedSub/extlib/XML/Feed/schemas/rss11.rnc | 218 ++++++++++ .../FeedSub/extlib/xml-feed-parser-bug-16416.patch | 14 + plugins/FeedSub/feeddiscovery.php | 209 +++++++++ plugins/FeedSub/feedinfo.php | 212 ++++++++++ plugins/FeedSub/feedinfo.sql | 14 + plugins/FeedSub/feedmunger.php | 238 +++++++++++ plugins/FeedSub/images/24px-Feed-icon.svg.png | Bin 0 -> 1204 bytes plugins/FeedSub/images/48px-Feed-icon.svg.png | Bin 0 -> 2434 bytes plugins/FeedSub/images/96px-Feed-icon.svg.png | Bin 0 -> 5440 bytes plugins/FeedSub/images/README | 5 + plugins/FeedSub/tests/FeedDiscoveryTest.php | 111 +++++ plugins/FeedSub/tests/FeedMungerTest.php | 147 +++++++ 51 files changed, 6442 insertions(+), 6 deletions(-) create mode 100644 plugins/FeedSub/FeedSubPlugin.php create mode 100644 plugins/FeedSub/README create mode 100644 plugins/FeedSub/actions/feedsubcallback.php create mode 100644 plugins/FeedSub/actions/feedsubsettings.php create mode 100644 plugins/FeedSub/extlib/README create mode 100755 plugins/FeedSub/extlib/XML/Feed/Parser.php create mode 100644 plugins/FeedSub/extlib/XML/Feed/Parser/Atom.php create mode 100755 plugins/FeedSub/extlib/XML/Feed/Parser/AtomElement.php create mode 100755 plugins/FeedSub/extlib/XML/Feed/Parser/Exception.php create mode 100755 plugins/FeedSub/extlib/XML/Feed/Parser/RSS09.php create mode 100755 plugins/FeedSub/extlib/XML/Feed/Parser/RSS09Element.php create mode 100755 plugins/FeedSub/extlib/XML/Feed/Parser/RSS1.php create mode 100755 plugins/FeedSub/extlib/XML/Feed/Parser/RSS11.php create mode 100755 plugins/FeedSub/extlib/XML/Feed/Parser/RSS11Element.php create mode 100755 plugins/FeedSub/extlib/XML/Feed/Parser/RSS1Element.php create mode 100644 plugins/FeedSub/extlib/XML/Feed/Parser/RSS2.php create mode 100755 plugins/FeedSub/extlib/XML/Feed/Parser/RSS2Element.php create mode 100644 plugins/FeedSub/extlib/XML/Feed/Parser/Type.php create mode 100755 plugins/FeedSub/extlib/XML/Feed/samples/atom10-entryonly.xml create mode 100755 plugins/FeedSub/extlib/XML/Feed/samples/atom10-example1.xml create mode 100755 plugins/FeedSub/extlib/XML/Feed/samples/atom10-example2.xml create mode 100755 plugins/FeedSub/extlib/XML/Feed/samples/delicious.feed create mode 100755 plugins/FeedSub/extlib/XML/Feed/samples/flickr.feed create mode 100755 plugins/FeedSub/extlib/XML/Feed/samples/grwifi-atom.xml create mode 100755 plugins/FeedSub/extlib/XML/Feed/samples/hoder.xml create mode 100755 plugins/FeedSub/extlib/XML/Feed/samples/illformed_atom10.xml create mode 100755 plugins/FeedSub/extlib/XML/Feed/samples/rss091-complete.xml create mode 100755 plugins/FeedSub/extlib/XML/Feed/samples/rss091-international.xml create mode 100755 plugins/FeedSub/extlib/XML/Feed/samples/rss091-simple.xml create mode 100755 plugins/FeedSub/extlib/XML/Feed/samples/rss092-sample.xml create mode 100755 plugins/FeedSub/extlib/XML/Feed/samples/rss10-example1.xml create mode 100755 plugins/FeedSub/extlib/XML/Feed/samples/rss10-example2.xml create mode 100755 plugins/FeedSub/extlib/XML/Feed/samples/rss2sample.xml create mode 100755 plugins/FeedSub/extlib/XML/Feed/samples/sixapart-jp.xml create mode 100755 plugins/FeedSub/extlib/XML/Feed/samples/technorati.feed create mode 100755 plugins/FeedSub/extlib/XML/Feed/schemas/atom.rnc create mode 100755 plugins/FeedSub/extlib/XML/Feed/schemas/rss10.rnc create mode 100755 plugins/FeedSub/extlib/XML/Feed/schemas/rss11.rnc create mode 100644 plugins/FeedSub/extlib/xml-feed-parser-bug-16416.patch create mode 100644 plugins/FeedSub/feeddiscovery.php create mode 100644 plugins/FeedSub/feedinfo.php create mode 100644 plugins/FeedSub/feedinfo.sql create mode 100644 plugins/FeedSub/feedmunger.php create mode 100644 plugins/FeedSub/images/24px-Feed-icon.svg.png create mode 100644 plugins/FeedSub/images/48px-Feed-icon.svg.png create mode 100644 plugins/FeedSub/images/96px-Feed-icon.svg.png create mode 100644 plugins/FeedSub/images/README create mode 100644 plugins/FeedSub/tests/FeedDiscoveryTest.php create mode 100644 plugins/FeedSub/tests/FeedMungerTest.php (limited to 'plugins') diff --git a/db/08to09_pg.sql b/db/08to09_pg.sql index d9b57fb84..7aaf3a7a0 100644 --- a/db/08to09_pg.sql +++ b/db/08to09_pg.sql @@ -29,13 +29,13 @@ create table config ( ); -create table user_role ( +create table profile_role ( - user_id integer not null /* comment 'user having the role'*/ references "user" (id), + profile_id integer not null /* comment 'account having the role'*/ references profile (id), role varchar(32) not null /* comment 'string representing the role'*/, created timestamp /* not null comment 'date the role was granted'*/, - primary key (user_id, role) + primary key (profile_id, role) ); diff --git a/db/statusnet_pg.sql b/db/statusnet_pg.sql index 8dbaf8598..927925753 100644 --- a/db/statusnet_pg.sql +++ b/db/statusnet_pg.sql @@ -560,13 +560,13 @@ create table config ( ); -create table user_role ( +create table profile_role ( - user_id integer not null /* comment 'user having the role'*/ references "user" (id), + profile_id integer not null /* comment 'account having the role'*/ references profile (id), role varchar(32) not null /* comment 'string representing the role'*/, created timestamp /* not null comment 'date the role was granted'*/, - primary key (user_id, role) + primary key (profile_id, role) ); diff --git a/plugins/FeedSub/FeedSubPlugin.php b/plugins/FeedSub/FeedSubPlugin.php new file mode 100644 index 000000000..36d4e7802 --- /dev/null +++ b/plugins/FeedSub/FeedSubPlugin.php @@ -0,0 +1,117 @@ + +Author URI: http://status.net/ +*/ + +/* + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2009, StatusNet, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @package FeedSubPlugin + * @maintainer Brion Vibber + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +define('FEEDSUB_SERVICE', 100); // fixme -- avoid hardcoding these? + +// We bundle the XML_Parse_Feed library... +set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib'); + +class FeedSubException extends Exception +{ +} + +class FeedSubPlugin extends Plugin +{ + /** + * Hook for RouterInitialized event. + * + * @param Net_URL_Mapper $m path-to-action mapper + * @return boolean hook return + */ + + function onRouterInitialized($m) + { + $m->connect('feedsub/callback/:feed', + array('action' => 'feedsubcallback'), + array('feed' => '[0-9]+')); + $m->connect('settings/feedsub', + array('action' => 'feedsubsettings')); + return true; + } + + /** + * Add the feed settings page to the Connect Settings menu + * + * @param Action &$action The calling page + * + * @return boolean hook return + */ + function onEndConnectSettingsNav(&$action) + { + $action_name = $action->trimmed('action'); + + $action->menuItem(common_local_url('feedsubsettings'), + dgettext('FeebSubPlugin', 'Feeds'), + dgettext('FeedSubPlugin', 'Feed subscription options'), + $action_name === 'feedsubsettings'); + + return true; + } + + /** + * Automatically load the actions and libraries used by the plugin + * + * @param Class $cls the class + * + * @return boolean hook return + * + */ + function onAutoload($cls) + { + $base = dirname(__FILE__); + $lower = strtolower($cls); + $files = array("$base/$lower.php"); + if (substr($lower, -6) == 'action') { + $files[] = "$base/actions/" . substr($lower, 0, -6) . ".php"; + } + foreach ($files as $file) { + if (file_exists($file)) { + include_once $file; + return false; + } + } + return true; + } + + /* + // auto increment seems to be broken + function onCheckSchema() { + $schema = Schema::get(); + $schema->ensureDataObject('Feedinfo'); + return true; + } + */ +} diff --git a/plugins/FeedSub/README b/plugins/FeedSub/README new file mode 100644 index 000000000..cbf3adbb9 --- /dev/null +++ b/plugins/FeedSub/README @@ -0,0 +1,24 @@ +Plugin to support importing updates from external RSS and Atom feeds into your timeline. + +Uses PubSubHubbub for push feed updates; currently non-PuSH feeds cannot be subscribed. + +Todo: +* set feed icon avatar for actual profiles as well as for preview +* use channel image and/or favicon for avatar? +* garbage-collect subscriptions that are no longer being used +* administrative way to kill feeds? +* functional l10n +* clean up subscription form look and workflow +* use ajax for test/preview in subscription form +* rssCloud support? (Does anything use it that doesn't support PuSH as well?) +* possibly a polling daemon to support non-PuSH feeds? +* likely problems with multiple feeds from the same site, such as category feeds on a blog + (currently each feed would publish a separate notice on a separate profile, but pointing to the same post URI.) + (could use the local URI I guess, but that's so icky!) +* problems with Atom feeds that list but don't have the type + (such as http://atomgen.appspot.com/feed/5 demo feed); currently it's not recognized and we end up with the feed's master URI +* make it easier to see what you're subscribed to and unsub from things +* saner treatment of fullname/nickname? +* make use of tags/categories from feeds +* update feed profile data when it changes +* XML_Feed_Parser has major problems with category and link tags; consider replacing? diff --git a/plugins/FeedSub/actions/feedsubcallback.php b/plugins/FeedSub/actions/feedsubcallback.php new file mode 100644 index 000000000..0c4280c1f --- /dev/null +++ b/plugins/FeedSub/actions/feedsubcallback.php @@ -0,0 +1,100 @@ +. + */ + +/** + * @package FeedSubPlugin + * @maintainer Brion Vibber + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + + +class FeedSubCallbackAction extends Action +{ + function handle() + { + parent::handle(); + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $this->handlePost(); + } else { + $this->handleGet(); + } + } + + /** + * Handler for POST content updates from the hub + */ + function handlePost() + { + $feedid = $this->arg('feed'); + common_log(LOG_INFO, "POST for feed id $feedid"); + if (!$feedid) { + throw new ServerException('Empty or invalid feed id', 400); + } + + $feedinfo = Feedinfo::staticGet('id', $feedid); + if (!$feedinfo) { + throw new ServerException('Unknown feed id ' . $feedid, 400); + } + + $post = file_get_contents('php://input'); + $feedinfo->postUpdates($post); + } + + /** + * Handler for GET verification requests from the hub + */ + function handleGet() + { + $mode = $this->arg('hub_mode'); + $topic = $this->arg('hub_topic'); + $challenge = $this->arg('hub_challenge'); + $lease_seconds = $this->arg('hub_lease_seconds'); + $verify_token = $this->arg('hub_verify_token'); + + if ($mode != 'subscribe' && $mode != 'unsubscribe') { + common_log(LOG_WARNING, __METHOD__ . ": bogus hub callback with mode \"$mode\""); + throw new ServerException("Bogus hub callback: bad mode", 404); + } + + $feedinfo = Feedinfo::staticGet('feeduri', $topic); + if (!$feedinfo) { + common_log(LOG_WARNING, __METHOD__ . ": bogus hub callback for unknown feed $topic"); + throw new ServerException("Bogus hub callback: unknown feed", 404); + } + + # Can't currently set the token in our sub api + #if ($feedinfo->verify_token !== $verify_token) { + # common_log(LOG_WARNING, __METHOD__ . ": bogus hub callback with bad token \"$verify_token\" for feed $topic"); + # throw new ServerError("Bogus hub callback: bad token", 404); + #} + + // OK! + common_log(LOG_INFO, __METHOD__ . ': sub confirmed'); + $feedinfo->sub_start = common_sql_date(time()); + if ($lease_seconds > 0) { + $feedinfo->sub_end = common_sql_date(time() + $lease_seconds); + } else { + $feedinfo->sub_end = null; + } + $feedinfo->update(); + + print $challenge; + } +} diff --git a/plugins/FeedSub/actions/feedsubsettings.php b/plugins/FeedSub/actions/feedsubsettings.php new file mode 100644 index 000000000..242224fac --- /dev/null +++ b/plugins/FeedSub/actions/feedsubsettings.php @@ -0,0 +1,257 @@ +. + */ + +/** + * @package FeedSubPlugin + * @maintainer Brion Vibber + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +class FeedSubSettingsAction extends ConnectSettingsAction +{ + protected $feedurl; + protected $preview; + protected $munger; + + /** + * Title of the page + * + * @return string Title of the page + */ + + function title() + { + return dgettext('FeedSubPlugin', 'Feed subscriptions'); + } + + /** + * Instructions for use + * + * @return instructions for use + */ + + function getInstructions() + { + return dgettext('FeedSubPlugin', + 'You can subscribe to feeds from other sites; ' . + 'updates will appear in your personal timeline.'); + } + + /** + * Content area of the page + * + * Shows a form for associating a Twitter account with this + * StatusNet account. Also lets the user set preferences. + * + * @return void + */ + + function showContent() + { + $user = common_current_user(); + + $profile = $user->getProfile(); + + $fuser = null; + + $flink = Foreign_link::getByUserID($user->id, FEEDSUB_SERVICE); + + if (!empty($flink)) { + $fuser = $flink->getForeignUser(); + } + + $this->elementStart('form', array('method' => 'post', + 'id' => 'form_settings_feedsub', + 'class' => 'form_settings', + 'action' => + common_local_url('feedsubsettings'))); + + $this->hidden('token', common_session_token()); + + $this->elementStart('fieldset', array('id' => 'settings_feeds')); + + $this->elementStart('ul', 'form_data'); + $this->elementStart('li', array('id' => 'settings_twitter_login_button')); + $this->input('feedurl', _('Feed URL'), $this->feedurl, _('Enter the URL of a PubSubHubbub-enabled feed')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + + if ($this->preview) { + $this->submit('subscribe', dgettext('FeedSubPlugin', 'Subscribe')); + } else { + $this->submit('validate', dgettext('FeedSubPlugin', 'Continue')); + } + + $this->elementEnd('fieldset'); + + $this->elementEnd('form'); + + if ($this->preview) { + $this->previewFeed(); + } + } + + /** + * Handle posts to this form + * + * Based on the button that was pressed, muxes out to other functions + * to do the actual task requested. + * + * All sub-functions reload the form with a message -- success or failure. + * + * @return void + */ + + function handlePost() + { + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. '. + 'Try again, please.')); + return; + } + + if ($this->arg('validate')) { + $this->validateAndPreview(); + } else if ($this->arg('subscribe')) { + $this->saveFeed(); + } else { + $this->showForm(_('Unexpected form submission.')); + } + } + + /** + * Set up and add a feed + * + * @return boolean true if feed successfully read + * Sends you back to input form if not. + */ + function validateFeed() + { + $feedurl = trim($this->arg('feedurl')); + + if ($feedurl == '') { + $this->showForm(dgettext('FeedSubPlugin', + 'Empty feed URL!')); + return; + } + $this->feedurl = $feedurl; + + // Get the canonical feed URI and check it + try { + $discover = new FeedDiscovery(); + $uri = $discover->discoverFromURL($feedurl); + } catch (FeedSubBadURLException $e) { + $this->showForm(dgettext('FeedSubPlugin', 'Invalid URL or could not reach server.')); + return false; + } catch (FeedSubBadResponseException $e) { + $this->showForm(dgettext('FeedSubPlugin', 'Cannot read feed; server returned error.')); + return false; + } catch (FeedSubEmptyException $e) { + $this->showForm(dgettext('FeedSubPlugin', 'Cannot read feed; server returned an empty page.')); + return false; + } catch (FeedSubBadHTMLException $e) { + $this->showForm(dgettext('FeedSubPlugin', 'Bad HTML, could not find feed link.')); + return false; + } catch (FeedSubNoFeedException $e) { + $this->showForm(dgettext('FeedSubPlugin', 'Could not find a feed linked from this URL.')); + return false; + } catch (FeedSubUnrecognizedTypeException $e) { + $this->showForm(dgettext('FeedSubPlugin', 'Not a recognized feed type.')); + return false; + } catch (FeedSubException $e) { + // Any new ones we forgot about + $this->showForm(dgettext('FeedSubPlugin', 'Bad feed URL.')); + return false; + } + + $this->munger = $discover->feedMunger(); + $this->feedinfo = $this->munger->feedInfo(); + + if ($this->feedinfo->huburi == '') { + $this->showForm(dgettext('FeedSubPlugin', 'Feed is not PuSH-enabled; cannot subscribe.')); + return false; + } + + return true; + } + + function saveFeed() + { + if ($this->validateFeed()) { + $this->preview = true; + $this->feedinfo = Feedinfo::ensureProfile($this->munger); + + // If not already in use, subscribe to updates via the hub + if ($this->feedinfo->sub_start) { + common_log(LOG_INFO, __METHOD__ . ": double the fun! new sub for {$this->feedinfo->feeduri} last subbed {$this->feedinfo->sub_start}"); + } else { + $ok = $this->feedinfo->subscribe(); + common_log(LOG_INFO, __METHOD__ . ": sub was $ok"); + if (!$ok) { + $this->showForm(dgettext('FeedSubPlugin', 'Feed subscription failed! Bad response from hub.')); + return; + } + } + + // And subscribe the current user to the local profile + $user = common_current_user(); + $profile = $this->feedinfo->getProfile(); + + if ($user->isSubscribed($profile)) { + $this->showForm(dgettext('FeedSubPlugin', 'Already subscribed!')); + } elseif ($user->subscribeTo($profile)) { + $this->showForm(dgettext('FeedSubPlugin', 'Feed subscribed!')); + } else { + $this->showForm(dgettext('FeedSubPlugin', 'Feed subscription failed!')); + } + } + } + + function validateAndPreview() + { + if ($this->validateFeed()) { + $this->preview = true; + $this->showForm(dgettext('FeedSubPlugin', 'Previewing feed:')); + } + } + + function previewFeed() + { + $feedinfo = $this->munger->feedinfo(); + $notice = $this->munger->notice(0, true); // preview + + if ($notice) { + $this->element('b', null, 'Preview of latest post from this feed:'); + + $item = new NoticeList($notice, $this); + $item->show(); + } else { + $this->element('b', null, 'No posts in this feed yet.'); + } + } + + function showScripts() + { + parent::showScripts(); + $this->autofocus('feedurl'); + } +} diff --git a/plugins/FeedSub/extlib/README b/plugins/FeedSub/extlib/README new file mode 100644 index 000000000..799b40c47 --- /dev/null +++ b/plugins/FeedSub/extlib/README @@ -0,0 +1,9 @@ +XML_Feed_Parser 1.0.3 is not currently actively maintained, and has +a nasty bug which breaks getting the feed target link from WordPress +feeds and possibly others that are RSS2-formatted but include an + self-link element as well. + +Patch from this bug report is included: +http://pear.php.net/bugs/bug.php?id=16416 + +If upgrading, be sure that fix is included with the future upgrade! diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser.php b/plugins/FeedSub/extlib/XML/Feed/Parser.php new file mode 100755 index 000000000..ffe8220a5 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser.php @@ -0,0 +1,351 @@ + + * @copyright 2005 James Stewart + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL + * @version CVS: $Id: Parser.php,v 1.24 2006/08/15 13:04:00 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/** + * XML_Feed_Parser_Type is an abstract class required by all of our + * feed types. It makes sense to load it here to keep the other files + * clean. + */ +require_once 'XML/Feed/Parser/Type.php'; + +/** + * We will throw exceptions when errors occur. + */ +require_once 'XML/Feed/Parser/Exception.php'; + +/** + * This is the core of the XML_Feed_Parser package. It identifies feed types + * and abstracts access to them. It is an iterator, allowing for easy access + * to the entire feed. + * + * @author James Stewart + * @version Release: 1.0.3 + * @package XML_Feed_Parser + */ +class XML_Feed_Parser implements Iterator +{ + /** + * This is where we hold the feed object + * @var Object + */ + private $feed; + + /** + * To allow for extensions, we make a public reference to the feed model + * @var DOMDocument + */ + public $model; + + /** + * A map between entry ID and offset + * @var array + */ + protected $idMappings = array(); + + /** + * A storage space for Namespace URIs. + * @var array + */ + private $feedNamespaces = array( + 'rss2' => array( + 'http://backend.userland.com/rss', + 'http://backend.userland.com/rss2', + 'http://blogs.law.harvard.edu/tech/rss')); + /** + * Detects feed types and instantiate appropriate objects. + * + * Our constructor takes care of detecting feed types and instantiating + * appropriate classes. For now we're going to treat Atom 0.3 as Atom 1.0 + * but raise a warning. I do not intend to introduce full support for + * Atom 0.3 as it has been deprecated, but others are welcome to. + * + * @param string $feed XML serialization of the feed + * @param bool $strict Whether or not to validate the feed + * @param bool $suppressWarnings Trigger errors for deprecated feed types? + * @param bool $tidy Whether or not to try and use the tidy library on input + */ + function __construct($feed, $strict = false, $suppressWarnings = false, $tidy = false) + { + $this->model = new DOMDocument; + if (! $this->model->loadXML($feed)) { + if (extension_loaded('tidy') && $tidy) { + $tidy = new tidy; + $tidy->parseString($feed, + array('input-xml' => true, 'output-xml' => true)); + $tidy->cleanRepair(); + if (! $this->model->loadXML((string) $tidy)) { + throw new XML_Feed_Parser_Exception('Invalid input: this is not ' . + 'valid XML'); + } + } else { + throw new XML_Feed_Parser_Exception('Invalid input: this is not valid XML'); + } + + } + + /* detect feed type */ + $doc_element = $this->model->documentElement; + $error = false; + + switch (true) { + case ($doc_element->namespaceURI == 'http://www.w3.org/2005/Atom'): + require_once 'XML/Feed/Parser/Atom.php'; + require_once 'XML/Feed/Parser/AtomElement.php'; + $class = 'XML_Feed_Parser_Atom'; + break; + case ($doc_element->namespaceURI == 'http://purl.org/atom/ns#'): + require_once 'XML/Feed/Parser/Atom.php'; + require_once 'XML/Feed/Parser/AtomElement.php'; + $class = 'XML_Feed_Parser_Atom'; + $error = 'Atom 0.3 deprecated, using 1.0 parser which won\'t provide ' . + 'all options'; + break; + case ($doc_element->namespaceURI == 'http://purl.org/rss/1.0/' || + ($doc_element->hasChildNodes() && $doc_element->childNodes->length > 1 + && $doc_element->childNodes->item(1)->namespaceURI == + 'http://purl.org/rss/1.0/')): + require_once 'XML/Feed/Parser/RSS1.php'; + require_once 'XML/Feed/Parser/RSS1Element.php'; + $class = 'XML_Feed_Parser_RSS1'; + break; + case ($doc_element->namespaceURI == 'http://purl.org/rss/1.1/' || + ($doc_element->hasChildNodes() && $doc_element->childNodes->length > 1 + && $doc_element->childNodes->item(1)->namespaceURI == + 'http://purl.org/rss/1.1/')): + require_once 'XML/Feed/Parser/RSS11.php'; + require_once 'XML/Feed/Parser/RSS11Element.php'; + $class = 'XML_Feed_Parser_RSS11'; + break; + case (($doc_element->hasChildNodes() && $doc_element->childNodes->length > 1 + && $doc_element->childNodes->item(1)->namespaceURI == + 'http://my.netscape.com/rdf/simple/0.9/') || + $doc_element->namespaceURI == 'http://my.netscape.com/rdf/simple/0.9/'): + require_once 'XML/Feed/Parser/RSS09.php'; + require_once 'XML/Feed/Parser/RSS09Element.php'; + $class = 'XML_Feed_Parser_RSS09'; + break; + case ($doc_element->tagName == 'rss' and + $doc_element->hasAttribute('version') && + $doc_element->getAttribute('version') == 0.91): + $error = 'RSS 0.91 has been superceded by RSS2.0. Using RSS2.0 parser.'; + require_once 'XML/Feed/Parser/RSS2.php'; + require_once 'XML/Feed/Parser/RSS2Element.php'; + $class = 'XML_Feed_Parser_RSS2'; + break; + case ($doc_element->tagName == 'rss' and + $doc_element->hasAttribute('version') && + $doc_element->getAttribute('version') == 0.92): + $error = 'RSS 0.92 has been superceded by RSS2.0. Using RSS2.0 parser.'; + require_once 'XML/Feed/Parser/RSS2.php'; + require_once 'XML/Feed/Parser/RSS2Element.php'; + $class = 'XML_Feed_Parser_RSS2'; + break; + case (in_array($doc_element->namespaceURI, $this->feedNamespaces['rss2']) + || $doc_element->tagName == 'rss'): + if (! $doc_element->hasAttribute('version') || + $doc_element->getAttribute('version') != 2) { + $error = 'RSS version not specified. Parsing as RSS2.0'; + } + require_once 'XML/Feed/Parser/RSS2.php'; + require_once 'XML/Feed/Parser/RSS2Element.php'; + $class = 'XML_Feed_Parser_RSS2'; + break; + default: + throw new XML_Feed_Parser_Exception('Feed type unknown'); + break; + } + + if (! $suppressWarnings && ! empty($error)) { + trigger_error($error, E_USER_WARNING); + } + + /* Instantiate feed object */ + $this->feed = new $class($this->model, $strict); + } + + /** + * Proxy to allow feed element names to be used as method names + * + * For top-level feed elements we will provide access using methods or + * attributes. This function simply passes on a request to the appropriate + * feed type object. + * + * @param string $call - the method being called + * @param array $attributes + */ + function __call($call, $attributes) + { + $attributes = array_pad($attributes, 5, false); + list($a, $b, $c, $d, $e) = $attributes; + return $this->feed->$call($a, $b, $c, $d, $e); + } + + /** + * Proxy to allow feed element names to be used as attribute names + * + * To allow variable-like access to feed-level data we use this + * method. It simply passes along to __call() which in turn passes + * along to the relevant object. + * + * @param string $val - the name of the variable required + */ + function __get($val) + { + return $this->feed->$val; + } + + /** + * Provides iteration functionality. + * + * Of course we must be able to iterate... This function simply increases + * our internal counter. + */ + function next() + { + if (isset($this->current_item) && + $this->current_item <= $this->feed->numberEntries - 1) { + ++$this->current_item; + } else if (! isset($this->current_item)) { + $this->current_item = 0; + } else { + return false; + } + } + + /** + * Return XML_Feed_Type object for current element + * + * @return XML_Feed_Parser_Type Object + */ + function current() + { + return $this->getEntryByOffset($this->current_item); + } + + /** + * For iteration -- returns the key for the current stage in the array. + * + * @return int + */ + function key() + { + return $this->current_item; + } + + /** + * For iteration -- tells whether we have reached the + * end. + * + * @return bool + */ + function valid() + { + return $this->current_item < $this->feed->numberEntries; + } + + /** + * For iteration -- resets the internal counter to the beginning. + */ + function rewind() + { + $this->current_item = 0; + } + + /** + * Provides access to entries by ID if one is specified in the source feed. + * + * As well as allowing the items to be iterated over we want to allow + * users to be able to access a specific entry. This is one of two ways of + * doing that, the other being by offset. This method can be quite slow + * if dealing with a large feed that hasn't yet been processed as it + * instantiates objects for every entry until it finds the one needed. + * + * @param string $id Valid ID for the given feed format + * @return XML_Feed_Parser_Type|false + */ + function getEntryById($id) + { + if (isset($this->idMappings[$id])) { + return $this->getEntryByOffset($this->idMappings[$id]); + } + + /* + * Since we have not yet encountered that ID, let's go through all the + * remaining entries in order till we find it. + * This is a fairly slow implementation, but it should work. + */ + return $this->feed->getEntryById($id); + } + + /** + * Retrieve entry by numeric offset, starting from zero. + * + * As well as allowing the items to be iterated over we want to allow + * users to be able to access a specific entry. This is one of two ways of + * doing that, the other being by ID. + * + * @param int $offset The position of the entry within the feed, starting from 0 + * @return XML_Feed_Parser_Type|false + */ + function getEntryByOffset($offset) + { + if ($offset < $this->feed->numberEntries) { + if (isset($this->feed->entries[$offset])) { + return $this->feed->entries[$offset]; + } else { + try { + $this->feed->getEntryByOffset($offset); + } catch (Exception $e) { + return false; + } + $id = $this->feed->entries[$offset]->getID(); + $this->idMappings[$id] = $offset; + return $this->feed->entries[$offset]; + } + } else { + return false; + } + } + + /** + * Retrieve version details from feed type class. + * + * @return void + * @author James Stewart + */ + function version() + { + return $this->feed->version; + } + + /** + * Returns a string representation of the feed. + * + * @return String + **/ + function __toString() + { + return $this->feed->__toString(); + } +} +?> \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/Atom.php b/plugins/FeedSub/extlib/XML/Feed/Parser/Atom.php new file mode 100644 index 000000000..c7e218a1e --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/Atom.php @@ -0,0 +1,365 @@ + + * @copyright 2005 James Stewart + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: Atom.php,v 1.29 2008/03/30 22:00:36 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ +*/ + +/** + * This is the class that determines how we manage Atom 1.0 feeds + * + * How we deal with constructs: + * date - return as unix datetime for use with the 'date' function unless specified otherwise + * text - return as is. optional parameter will give access to attributes + * person - defaults to name, but parameter based access + * + * @author James Stewart + * @version Release: 1.0.3 + * @package XML_Feed_Parser + */ +class XML_Feed_Parser_Atom extends XML_Feed_Parser_Type +{ + /** + * The URI of the RelaxNG schema used to (optionally) validate the feed + * @var string + */ + private $relax = 'atom.rnc'; + + /** + * We're likely to use XPath, so let's keep it global + * @var DOMXPath + */ + public $xpath; + + /** + * When performing XPath queries we will use this prefix + * @var string + */ + private $xpathPrefix = '//'; + + /** + * The feed type we are parsing + * @var string + */ + public $version = 'Atom 1.0'; + + /** + * The class used to represent individual items + * @var string + */ + protected $itemClass = 'XML_Feed_Parser_AtomElement'; + + /** + * The element containing entries + * @var string + */ + protected $itemElement = 'entry'; + + /** + * Here we map those elements we're not going to handle individually + * to the constructs they are. The optional second parameter in the array + * tells the parser whether to 'fall back' (not apt. at the feed level) or + * fail if the element is missing. If the parameter is not set, the function + * will simply return false and leave it to the client to decide what to do. + * @var array + */ + protected $map = array( + 'author' => array('Person'), + 'contributor' => array('Person'), + 'icon' => array('Text'), + 'logo' => array('Text'), + 'id' => array('Text', 'fail'), + 'rights' => array('Text'), + 'subtitle' => array('Text'), + 'title' => array('Text', 'fail'), + 'updated' => array('Date', 'fail'), + 'link' => array('Link'), + 'generator' => array('Text'), + 'category' => array('Category')); + + /** + * Here we provide a few mappings for those very special circumstances in + * which it makes sense to map back to the RSS2 spec. Key is RSS2 version + * value is an array consisting of the equivalent in atom and any attributes + * needed to make the mapping. + * @var array + */ + protected $compatMap = array( + 'guid' => array('id'), + 'links' => array('link'), + 'tags' => array('category'), + 'contributors' => array('contributor')); + + /** + * Our constructor does nothing more than its parent. + * + * @param DOMDocument $xml A DOM object representing the feed + * @param bool (optional) $string Whether or not to validate this feed + */ + function __construct(DOMDocument $model, $strict = false) + { + $this->model = $model; + + if ($strict) { + if (! $this->model->relaxNGValidateSource($this->relax)) { + throw new XML_Feed_Parser_Exception('Failed required validation'); + } + } + + $this->xpath = new DOMXPath($this->model); + $this->xpath->registerNamespace('atom', 'http://www.w3.org/2005/Atom'); + $this->numberEntries = $this->count('entry'); + } + + /** + * Implement retrieval of an entry based on its ID for atom feeds. + * + * This function uses XPath to get the entry based on its ID. If DOMXPath::evaluate + * is available, we also use that to store a reference to the entry in the array + * used by getEntryByOffset so that method does not have to seek out the entry + * if it's requested that way. + * + * @param string $id any valid Atom ID. + * @return XML_Feed_Parser_AtomElement + */ + function getEntryById($id) + { + if (isset($this->idMappings[$id])) { + return $this->entries[$this->idMappings[$id]]; + } + + $entries = $this->xpath->query("//atom:entry[atom:id='$id']"); + + if ($entries->length > 0) { + $xmlBase = $entries->item(0)->baseURI; + $entry = new $this->itemClass($entries->item(0), $this, $xmlBase); + + if (in_array('evaluate', get_class_methods($this->xpath))) { + $offset = $this->xpath->evaluate("count(preceding-sibling::atom:entry)", $entries->item(0)); + $this->entries[$offset] = $entry; + } + + $this->idMappings[$id] = $entry; + + return $entry; + } + + } + + /** + * Retrieves data from a person construct. + * + * Get a person construct. We default to the 'name' element but allow + * access to any of the elements. + * + * @param string $method The name of the person construct we want + * @param array $arguments An array which we hope gives a 'param' + * @return string|false + */ + protected function getPerson($method, $arguments) + { + $offset = empty($arguments[0]) ? 0 : $arguments[0]; + $parameter = empty($arguments[1]['param']) ? 'name' : $arguments[1]['param']; + $section = $this->model->getElementsByTagName($method); + + if ($parameter == 'url') { + $parameter = 'uri'; + } + + if ($section->length <= $offset) { + return false; + } + + $param = $section->item($offset)->getElementsByTagName($parameter); + if ($param->length == 0) { + return false; + } + return $param->item(0)->nodeValue; + } + + /** + * Retrieves an element's content where that content is a text construct. + * + * Get a text construct. When calling this method, the two arguments + * allowed are 'offset' and 'attribute', so $parser->subtitle() would + * return the content of the element, while $parser->subtitle(false, 'type') + * would return the value of the type attribute. + * + * @todo Clarify overlap with getContent() + * @param string $method The name of the text construct we want + * @param array $arguments An array which we hope gives a 'param' + * @return string + */ + protected function getText($method, $arguments) + { + $offset = empty($arguments[0]) ? 0: $arguments[0]; + $attribute = empty($arguments[1]) ? false : $arguments[1]; + $tags = $this->model->getElementsByTagName($method); + + if ($tags->length <= $offset) { + return false; + } + + $content = $tags->item($offset); + + if (! $content->hasAttribute('type')) { + $content->setAttribute('type', 'text'); + } + $type = $content->getAttribute('type'); + + if (! empty($attribute) and + ! ($method == 'generator' and $attribute == 'name')) { + if ($content->hasAttribute($attribute)) { + return $content->getAttribute($attribute); + } else if ($attribute == 'href' and $content->hasAttribute('uri')) { + return $content->getAttribute('uri'); + } + return false; + } + + return $this->parseTextConstruct($content); + } + + /** + * Extract content appropriately from atom text constructs + * + * Because of different rules applied to the content element and other text + * constructs, they are deployed as separate functions, but they share quite + * a bit of processing. This method performs the core common process, which is + * to apply the rules for different mime types in order to extract the content. + * + * @param DOMNode $content the text construct node to be parsed + * @return String + * @author James Stewart + **/ + protected function parseTextConstruct(DOMNode $content) + { + if ($content->hasAttribute('type')) { + $type = $content->getAttribute('type'); + } else { + $type = 'text'; + } + + if (strpos($type, 'text/') === 0) { + $type = 'text'; + } + + switch ($type) { + case 'text': + case 'html': + return $content->textContent; + break; + case 'xhtml': + $container = $content->getElementsByTagName('div'); + if ($container->length == 0) { + return false; + } + $contents = $container->item(0); + if ($contents->hasChildNodes()) { + /* Iterate through, applying xml:base and store the result */ + $result = ''; + foreach ($contents->childNodes as $node) { + $result .= $this->traverseNode($node); + } + return $result; + } + break; + case preg_match('@^[a-zA-Z]+/[a-zA-Z+]*xml@i', $type) > 0: + return $content; + break; + case 'application/octet-stream': + default: + return base64_decode(trim($content->nodeValue)); + break; + } + return false; + } + /** + * Get a category from the entry. + * + * A feed or entry can have any number of categories. A category can have the + * attributes term, scheme and label. + * + * @param string $method The name of the text construct we want + * @param array $arguments An array which we hope gives a 'param' + * @return string + */ + function getCategory($method, $arguments) + { + $offset = empty($arguments[0]) ? 0: $arguments[0]; + $attribute = empty($arguments[1]) ? 'term' : $arguments[1]; + $categories = $this->model->getElementsByTagName('category'); + if ($categories->length <= $offset) { + $category = $categories->item($offset); + if ($category->hasAttribute($attribute)) { + return $category->getAttribute($attribute); + } + } + return false; + } + + /** + * This element must be present at least once with rel="feed". This element may be + * present any number of further times so long as there is no clash. If no 'rel' is + * present and we're asked for one, we follow the example of the Universal Feed + * Parser and presume 'alternate'. + * + * @param int $offset the position of the link within the container + * @param string $attribute the attribute name required + * @param array an array of attributes to search by + * @return string the value of the attribute + */ + function getLink($offset = 0, $attribute = 'href', $params = false) + { + if (is_array($params) and !empty($params)) { + $terms = array(); + $alt_predicate = ''; + $other_predicate = ''; + + foreach ($params as $key => $value) { + if ($key == 'rel' && $value == 'alternate') { + $alt_predicate = '[not(@rel) or @rel="alternate"]'; + } else { + $terms[] = "@$key='$value'"; + } + } + if (!empty($terms)) { + $other_predicate = '[' . join(' and ', $terms) . ']'; + } + $query = $this->xpathPrefix . 'atom:link' . $alt_predicate . $other_predicate; + $links = $this->xpath->query($query); + } else { + $links = $this->model->getElementsByTagName('link'); + } + if ($links->length > $offset) { + if ($links->item($offset)->hasAttribute($attribute)) { + $value = $links->item($offset)->getAttribute($attribute); + if ($attribute == 'href') { + $value = $this->addBase($value, $links->item($offset)); + } + return $value; + } else if ($attribute == 'rel') { + return 'alternate'; + } + } + return false; + } +} + +?> \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/AtomElement.php b/plugins/FeedSub/extlib/XML/Feed/Parser/AtomElement.php new file mode 100755 index 000000000..063ecb617 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/AtomElement.php @@ -0,0 +1,261 @@ + + * @copyright 2005 James Stewart + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: AtomElement.php,v 1.19 2007/03/26 12:43:11 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/** + * This class provides support for atom entries. It will usually be called by + * XML_Feed_Parser_Atom with which it shares many methods. + * + * @author James Stewart + * @version Release: 1.0.3 + * @package XML_Feed_Parser + */ +class XML_Feed_Parser_AtomElement extends XML_Feed_Parser_Atom +{ + /** + * This will be a reference to the parent object for when we want + * to use a 'fallback' rule + * @var XML_Feed_Parser_Atom + */ + protected $parent; + + /** + * When performing XPath queries we will use this prefix + * @var string + */ + private $xpathPrefix = ''; + + /** + * xml:base values inherited by the element + * @var string + */ + protected $xmlBase; + + /** + * Here we provide a few mappings for those very special circumstances in + * which it makes sense to map back to the RSS2 spec or to manage other + * compatibilities (eg. with the Univeral Feed Parser). Key is the other version's + * name for the command, value is an array consisting of the equivalent in our atom + * api and any attributes needed to make the mapping. + * @var array + */ + protected $compatMap = array( + 'guid' => array('id'), + 'links' => array('link'), + 'tags' => array('category'), + 'contributors' => array('contributor')); + + /** + * Our specific element map + * @var array + */ + protected $map = array( + 'author' => array('Person', 'fallback'), + 'contributor' => array('Person'), + 'id' => array('Text', 'fail'), + 'published' => array('Date'), + 'updated' => array('Date', 'fail'), + 'title' => array('Text', 'fail'), + 'rights' => array('Text', 'fallback'), + 'summary' => array('Text'), + 'content' => array('Content'), + 'link' => array('Link'), + 'enclosure' => array('Enclosure'), + 'category' => array('Category')); + + /** + * Store useful information for later. + * + * @param DOMElement $element - this item as a DOM element + * @param XML_Feed_Parser_Atom $parent - the feed of which this is a member + */ + function __construct(DOMElement $element, $parent, $xmlBase = '') + { + $this->model = $element; + $this->parent = $parent; + $this->xmlBase = $xmlBase; + $this->xpathPrefix = "//atom:entry[atom:id='" . $this->id . "']/"; + $this->xpath = $this->parent->xpath; + } + + /** + * Provides access to specific aspects of the author data for an atom entry + * + * Author data at the entry level is more complex than at the feed level. + * If atom:author is not present for the entry we need to look for it in + * an atom:source child of the atom:entry. If it's not there either, then + * we look to the parent for data. + * + * @param array + * @return string + */ + function getAuthor($arguments) + { + /* Find out which part of the author data we're looking for */ + if (isset($arguments['param'])) { + $parameter = $arguments['param']; + } else { + $parameter = 'name'; + } + + $test = $this->model->getElementsByTagName('author'); + if ($test->length > 0) { + $item = $test->item(0); + return $item->getElementsByTagName($parameter)->item(0)->nodeValue; + } + + $source = $this->model->getElementsByTagName('source'); + if ($source->length > 0) { + $test = $this->model->getElementsByTagName('author'); + if ($test->length > 0) { + $item = $test->item(0); + return $item->getElementsByTagName($parameter)->item(0)->nodeValue; + } + } + return $this->parent->getAuthor($arguments); + } + + /** + * Returns the content of the content element or info on a specific attribute + * + * This element may or may not be present. It cannot be present more than + * once. It may have a 'src' attribute, in which case there's no content + * If not present, then the entry must have link with rel="alternate". + * If there is content we return it, if not and there's a 'src' attribute + * we return the value of that instead. The method can take an 'attribute' + * argument, in which case we return the value of that attribute if present. + * eg. $item->content("type") will return the type of the content. It is + * recommended that all users check the type before getting the content to + * ensure that their script is capable of handling the type of returned data. + * (data carried in the content element can be either 'text', 'html', 'xhtml', + * or any standard MIME type). + * + * @return string|false + */ + protected function getContent($method, $arguments = array()) + { + $attribute = empty($arguments[0]) ? false : $arguments[0]; + $tags = $this->model->getElementsByTagName('content'); + + if ($tags->length == 0) { + return false; + } + + $content = $tags->item(0); + + if (! $content->hasAttribute('type')) { + $content->setAttribute('type', 'text'); + } + if (! empty($attribute)) { + return $content->getAttribute($attribute); + } + + $type = $content->getAttribute('type'); + + if (! empty($attribute)) { + if ($content->hasAttribute($attribute)) + { + return $content->getAttribute($attribute); + } + return false; + } + + if ($content->hasAttribute('src')) { + return $content->getAttribute('src'); + } + + return $this->parseTextConstruct($content); + } + + /** + * For compatibility, this method provides a mapping to access enclosures. + * + * The Atom spec doesn't provide for an enclosure element, but it is + * generally supported using the link element with rel='enclosure'. + * + * @param string $method - for compatibility with our __call usage + * @param array $arguments - for compatibility with our __call usage + * @return array|false + */ + function getEnclosure($method, $arguments = array()) + { + $offset = isset($arguments[0]) ? $arguments[0] : 0; + $query = "//atom:entry[atom:id='" . $this->getText('id', false) . + "']/atom:link[@rel='enclosure']"; + + $encs = $this->parent->xpath->query($query); + if ($encs->length > $offset) { + try { + if (! $encs->item($offset)->hasAttribute('href')) { + return false; + } + $attrs = $encs->item($offset)->attributes; + $length = $encs->item($offset)->hasAttribute('length') ? + $encs->item($offset)->getAttribute('length') : false; + return array( + 'url' => $attrs->getNamedItem('href')->value, + 'type' => $attrs->getNamedItem('type')->value, + 'length' => $length); + } catch (Exception $e) { + return false; + } + } + return false; + } + + /** + * Get details of this entry's source, if available/relevant + * + * Where an atom:entry is taken from another feed then the aggregator + * is supposed to include an atom:source element which replicates at least + * the atom:id, atom:title, and atom:updated metadata from the original + * feed. Atom:source therefore has a very similar structure to atom:feed + * and if we find it we will return it as an XML_Feed_Parser_Atom object. + * + * @return XML_Feed_Parser_Atom|false + */ + function getSource() + { + $test = $this->model->getElementsByTagName('source'); + if ($test->length == 0) { + return false; + } + $source = new XML_Feed_Parser_Atom($test->item(0)); + } + + /** + * Get the entry as an XML string + * + * Return an XML serialization of the feed, should it be required. Most + * users however, will already have a serialization that they used when + * instantiating the object. + * + * @return string XML serialization of element + */ + function __toString() + { + $simple = simplexml_import_dom($this->model); + return $simple->asXML(); + } +} + +?> \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/Exception.php b/plugins/FeedSub/extlib/XML/Feed/Parser/Exception.php new file mode 100755 index 000000000..1e76e3f85 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/Exception.php @@ -0,0 +1,42 @@ + + * @copyright 2005 James Stewart + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL + * @version CVS: $Id: Exception.php,v 1.3 2005/11/07 01:52:35 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/** + * We are extending PEAR_Exception + */ +require_once 'PEAR/Exception.php'; + +/** + * XML_Feed_Parser_Exception is a simple extension of PEAR_Exception, existing + * to help with identification of the source of exceptions. + * + * @author James Stewart + * @version Release: 1.0.3 + * @package XML_Feed_Parser + */ +class XML_Feed_Parser_Exception extends PEAR_Exception +{ + +} + +?> \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/RSS09.php b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS09.php new file mode 100755 index 000000000..07f38f911 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS09.php @@ -0,0 +1,214 @@ + + * @copyright 2005 James Stewart + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: RSS09.php,v 1.5 2006/07/26 21:18:46 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/** + * This class handles RSS0.9 feeds. + * + * @author James Stewart + * @version Release: 1.0.3 + * @package XML_Feed_Parser + * @todo Find a Relax NG URI we can use + */ +class XML_Feed_Parser_RSS09 extends XML_Feed_Parser_Type +{ + /** + * The URI of the RelaxNG schema used to (optionally) validate the feed + * @var string + */ + private $relax = ''; + + /** + * We're likely to use XPath, so let's keep it global + * @var DOMXPath + */ + protected $xpath; + + /** + * The feed type we are parsing + * @var string + */ + public $version = 'RSS 0.9'; + + /** + * The class used to represent individual items + * @var string + */ + protected $itemClass = 'XML_Feed_Parser_RSS09Element'; + + /** + * The element containing entries + * @var string + */ + protected $itemElement = 'item'; + + /** + * Here we map those elements we're not going to handle individually + * to the constructs they are. The optional second parameter in the array + * tells the parser whether to 'fall back' (not apt. at the feed level) or + * fail if the element is missing. If the parameter is not set, the function + * will simply return false and leave it to the client to decide what to do. + * @var array + */ + protected $map = array( + 'title' => array('Text'), + 'link' => array('Text'), + 'description' => array('Text'), + 'image' => array('Image'), + 'textinput' => array('TextInput')); + + /** + * Here we map some elements to their atom equivalents. This is going to be + * quite tricky to pull off effectively (and some users' methods may vary) + * but is worth trying. The key is the atom version, the value is RSS2. + * @var array + */ + protected $compatMap = array( + 'title' => array('title'), + 'link' => array('link'), + 'subtitle' => array('description')); + + /** + * We will be working with multiple namespaces and it is useful to + * keep them together + * @var array + */ + protected $namespaces = array( + 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'); + + /** + * Our constructor does nothing more than its parent. + * + * @todo RelaxNG validation + * @param DOMDocument $xml A DOM object representing the feed + * @param bool (optional) $string Whether or not to validate this feed + */ + function __construct(DOMDocument $model, $strict = false) + { + $this->model = $model; + + $this->xpath = new DOMXPath($model); + foreach ($this->namespaces as $key => $value) { + $this->xpath->registerNamespace($key, $value); + } + $this->numberEntries = $this->count('item'); + } + + /** + * Included for compatibility -- will not work with RSS 0.9 + * + * This is not something that will work with RSS0.9 as it does not have + * clear restrictions on the global uniqueness of IDs. + * + * @param string $id any valid ID. + * @return false + */ + function getEntryById($id) + { + return false; + } + + /** + * Get details of the image associated with the feed. + * + * @return array|false an array simply containing the child elements + */ + protected function getImage() + { + $images = $this->model->getElementsByTagName('image'); + if ($images->length > 0) { + $image = $images->item(0); + $details = array(); + if ($image->hasChildNodes()) { + $details = array( + 'title' => $image->getElementsByTagName('title')->item(0)->value, + 'link' => $image->getElementsByTagName('link')->item(0)->value, + 'url' => $image->getElementsByTagName('url')->item(0)->value); + } else { + $details = array('title' => false, + 'link' => false, + 'url' => $image->attributes->getNamedItem('resource')->nodeValue); + } + $details = array_merge($details, + array('description' => false, 'height' => false, 'width' => false)); + if (! empty($details)) { + return $details; + } + } + return false; + } + + /** + * The textinput element is little used, but in the interests of + * completeness we will support it. + * + * @return array|false + */ + protected function getTextInput() + { + $inputs = $this->model->getElementsByTagName('textinput'); + if ($inputs->length > 0) { + $input = $inputs->item(0); + $results = array(); + $results['title'] = isset( + $input->getElementsByTagName('title')->item(0)->value) ? + $input->getElementsByTagName('title')->item(0)->value : null; + $results['description'] = isset( + $input->getElementsByTagName('description')->item(0)->value) ? + $input->getElementsByTagName('description')->item(0)->value : null; + $results['name'] = isset( + $input->getElementsByTagName('name')->item(0)->value) ? + $input->getElementsByTagName('name')->item(0)->value : null; + $results['link'] = isset( + $input->getElementsByTagName('link')->item(0)->value) ? + $input->getElementsByTagName('link')->item(0)->value : null; + if (empty($results['link']) && + $input->attributes->getNamedItem('resource')) { + $results['link'] = $input->attributes->getNamedItem('resource')->nodeValue; + } + if (! empty($results)) { + return $results; + } + } + return false; + } + + /** + * Get details of a link from the feed. + * + * In RSS1 a link is a text element but in order to ensure that we resolve + * URLs properly we have a special function for them. + * + * @return string + */ + function getLink($offset = 0, $attribute = 'href', $params = false) + { + $links = $this->model->getElementsByTagName('link'); + if ($links->length <= $offset) { + return false; + } + $link = $links->item($offset); + return $this->addBase($link->nodeValue, $link); + } +} + +?> \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/RSS09Element.php b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS09Element.php new file mode 100755 index 000000000..d41f36e8d --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS09Element.php @@ -0,0 +1,62 @@ + + * @copyright 2005 James Stewart + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: RSS09Element.php,v 1.4 2006/06/30 17:41:56 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/* + * This class provides support for RSS 0.9 entries. It will usually be called by + * XML_Feed_Parser_RSS09 with which it shares many methods. + * + * @author James Stewart + * @version Release: 1.0.3 + * @package XML_Feed_Parser + */ +class XML_Feed_Parser_RSS09Element extends XML_Feed_Parser_RSS09 +{ + /** + * This will be a reference to the parent object for when we want + * to use a 'fallback' rule + * @var XML_Feed_Parser_RSS09 + */ + protected $parent; + + /** + * Our specific element map + * @var array + */ + protected $map = array( + 'title' => array('Text'), + 'link' => array('Link')); + + /** + * Store useful information for later. + * + * @param DOMElement $element - this item as a DOM element + * @param XML_Feed_Parser_RSS1 $parent - the feed of which this is a member + */ + function __construct(DOMElement $element, $parent, $xmlBase = '') + { + $this->model = $element; + $this->parent = $parent; + } +} + +?> \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/RSS1.php b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS1.php new file mode 100755 index 000000000..60c9938ba --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS1.php @@ -0,0 +1,277 @@ + + * @copyright 2005 James Stewart + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: RSS1.php,v 1.10 2006/07/27 13:52:05 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/** + * This class handles RSS1.0 feeds. + * + * @author James Stewart + * @version Release: 1.0.3 + * @package XML_Feed_Parser + * @todo Find a Relax NG URI we can use + */ +class XML_Feed_Parser_RSS1 extends XML_Feed_Parser_Type +{ + /** + * The URI of the RelaxNG schema used to (optionally) validate the feed + * @var string + */ + private $relax = 'rss10.rnc'; + + /** + * We're likely to use XPath, so let's keep it global + * @var DOMXPath + */ + protected $xpath; + + /** + * The feed type we are parsing + * @var string + */ + public $version = 'RSS 1.0'; + + /** + * The class used to represent individual items + * @var string + */ + protected $itemClass = 'XML_Feed_Parser_RSS1Element'; + + /** + * The element containing entries + * @var string + */ + protected $itemElement = 'item'; + + /** + * Here we map those elements we're not going to handle individually + * to the constructs they are. The optional second parameter in the array + * tells the parser whether to 'fall back' (not apt. at the feed level) or + * fail if the element is missing. If the parameter is not set, the function + * will simply return false and leave it to the client to decide what to do. + * @var array + */ + protected $map = array( + 'title' => array('Text'), + 'link' => array('Text'), + 'description' => array('Text'), + 'image' => array('Image'), + 'textinput' => array('TextInput'), + 'updatePeriod' => array('Text'), + 'updateFrequency' => array('Text'), + 'updateBase' => array('Date'), + 'rights' => array('Text'), # dc:rights + 'description' => array('Text'), # dc:description + 'creator' => array('Text'), # dc:creator + 'publisher' => array('Text'), # dc:publisher + 'contributor' => array('Text'), # dc:contributor + 'date' => array('Date') # dc:contributor + ); + + /** + * Here we map some elements to their atom equivalents. This is going to be + * quite tricky to pull off effectively (and some users' methods may vary) + * but is worth trying. The key is the atom version, the value is RSS2. + * @var array + */ + protected $compatMap = array( + 'title' => array('title'), + 'link' => array('link'), + 'subtitle' => array('description'), + 'author' => array('creator'), + 'updated' => array('date')); + + /** + * We will be working with multiple namespaces and it is useful to + * keep them together + * @var array + */ + protected $namespaces = array( + 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'rss' => 'http://purl.org/rss/1.0/', + 'dc' => 'http://purl.org/rss/1.0/modules/dc/', + 'content' => 'http://purl.org/rss/1.0/modules/content/', + 'sy' => 'http://web.resource.org/rss/1.0/modules/syndication/'); + + /** + * Our constructor does nothing more than its parent. + * + * @param DOMDocument $xml A DOM object representing the feed + * @param bool (optional) $string Whether or not to validate this feed + */ + function __construct(DOMDocument $model, $strict = false) + { + $this->model = $model; + if ($strict) { + $validate = $this->model->relaxNGValidate(self::getSchemaDir . + DIRECTORY_SEPARATOR . $this->relax); + if (! $validate) { + throw new XML_Feed_Parser_Exception('Failed required validation'); + } + } + + $this->xpath = new DOMXPath($model); + foreach ($this->namespaces as $key => $value) { + $this->xpath->registerNamespace($key, $value); + } + $this->numberEntries = $this->count('item'); + } + + /** + * Allows retrieval of an entry by ID where the rdf:about attribute is used + * + * This is not really something that will work with RSS1 as it does not have + * clear restrictions on the global uniqueness of IDs. We will employ the + * _very_ hit and miss method of selecting entries based on the rdf:about + * attribute. If DOMXPath::evaluate is available, we also use that to store + * a reference to the entry in the array used by getEntryByOffset so that + * method does not have to seek out the entry if it's requested that way. + * + * @param string $id any valid ID. + * @return XML_Feed_Parser_RSS1Element + */ + function getEntryById($id) + { + if (isset($this->idMappings[$id])) { + return $this->entries[$this->idMappings[$id]]; + } + + $entries = $this->xpath->query("//rss:item[@rdf:about='$id']"); + if ($entries->length > 0) { + $classname = $this->itemClass; + $entry = new $classname($entries->item(0), $this); + if (in_array('evaluate', get_class_methods($this->xpath))) { + $offset = $this->xpath->evaluate("count(preceding-sibling::rss:item)", $entries->item(0)); + $this->entries[$offset] = $entry; + } + $this->idMappings[$id] = $entry; + return $entry; + } + return false; + } + + /** + * Get details of the image associated with the feed. + * + * @return array|false an array simply containing the child elements + */ + protected function getImage() + { + $images = $this->model->getElementsByTagName('image'); + if ($images->length > 0) { + $image = $images->item(0); + $details = array(); + if ($image->hasChildNodes()) { + $details = array( + 'title' => $image->getElementsByTagName('title')->item(0)->value, + 'link' => $image->getElementsByTagName('link')->item(0)->value, + 'url' => $image->getElementsByTagName('url')->item(0)->value); + } else { + $details = array('title' => false, + 'link' => false, + 'url' => $image->attributes->getNamedItem('resource')->nodeValue); + } + $details = array_merge($details, array('description' => false, 'height' => false, 'width' => false)); + if (! empty($details)) { + return $details; + } + } + return false; + } + + /** + * The textinput element is little used, but in the interests of + * completeness we will support it. + * + * @return array|false + */ + protected function getTextInput() + { + $inputs = $this->model->getElementsByTagName('textinput'); + if ($inputs->length > 0) { + $input = $inputs->item(0); + $results = array(); + $results['title'] = isset( + $input->getElementsByTagName('title')->item(0)->value) ? + $input->getElementsByTagName('title')->item(0)->value : null; + $results['description'] = isset( + $input->getElementsByTagName('description')->item(0)->value) ? + $input->getElementsByTagName('description')->item(0)->value : null; + $results['name'] = isset( + $input->getElementsByTagName('name')->item(0)->value) ? + $input->getElementsByTagName('name')->item(0)->value : null; + $results['link'] = isset( + $input->getElementsByTagName('link')->item(0)->value) ? + $input->getElementsByTagName('link')->item(0)->value : null; + if (empty($results['link']) and + $input->attributes->getNamedItem('resource')) { + $results['link'] = + $input->attributes->getNamedItem('resource')->nodeValue; + } + if (! empty($results)) { + return $results; + } + } + return false; + } + + /** + * Employs various techniques to identify the author + * + * Dublin Core provides the dc:creator, dc:contributor, and dc:publisher + * elements for defining authorship in RSS1. We will try each of those in + * turn in order to simulate the atom author element and will return it + * as text. + * + * @return array|false + */ + function getAuthor() + { + $options = array('creator', 'contributor', 'publisher'); + foreach ($options as $element) { + $test = $this->model->getElementsByTagName($element); + if ($test->length > 0) { + return $test->item(0)->value; + } + } + return false; + } + + /** + * Retrieve a link + * + * In RSS1 a link is a text element but in order to ensure that we resolve + * URLs properly we have a special function for them. + * + * @return string + */ + function getLink($offset = 0, $attribute = 'href', $params = false) + { + $links = $this->model->getElementsByTagName('link'); + if ($links->length <= $offset) { + return false; + } + $link = $links->item($offset); + return $this->addBase($link->nodeValue, $link); + } +} + +?> \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/RSS11.php b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS11.php new file mode 100755 index 000000000..3cd1ef15d --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS11.php @@ -0,0 +1,276 @@ + + * @copyright 2005 James Stewart + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: RSS11.php,v 1.6 2006/07/27 13:52:05 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/** + * This class handles RSS1.1 feeds. RSS1.1 is documented at: + * http://inamidst.com/rss1.1/ + * + * @author James Stewart + * @version Release: 1.0.3 + * @package XML_Feed_Parser + * @todo Support for RDF:List + * @todo Ensure xml:lang is accessible to users + */ +class XML_Feed_Parser_RSS11 extends XML_Feed_Parser_Type +{ + /** + * The URI of the RelaxNG schema used to (optionally) validate the feed + * @var string + */ + private $relax = 'rss11.rnc'; + + /** + * We're likely to use XPath, so let's keep it global + * @var DOMXPath + */ + protected $xpath; + + /** + * The feed type we are parsing + * @var string + */ + public $version = 'RSS 1.0'; + + /** + * The class used to represent individual items + * @var string + */ + protected $itemClass = 'XML_Feed_Parser_RSS1Element'; + + /** + * The element containing entries + * @var string + */ + protected $itemElement = 'item'; + + /** + * Here we map those elements we're not going to handle individually + * to the constructs they are. The optional second parameter in the array + * tells the parser whether to 'fall back' (not apt. at the feed level) or + * fail if the element is missing. If the parameter is not set, the function + * will simply return false and leave it to the client to decide what to do. + * @var array + */ + protected $map = array( + 'title' => array('Text'), + 'link' => array('Text'), + 'description' => array('Text'), + 'image' => array('Image'), + 'updatePeriod' => array('Text'), + 'updateFrequency' => array('Text'), + 'updateBase' => array('Date'), + 'rights' => array('Text'), # dc:rights + 'description' => array('Text'), # dc:description + 'creator' => array('Text'), # dc:creator + 'publisher' => array('Text'), # dc:publisher + 'contributor' => array('Text'), # dc:contributor + 'date' => array('Date') # dc:contributor + ); + + /** + * Here we map some elements to their atom equivalents. This is going to be + * quite tricky to pull off effectively (and some users' methods may vary) + * but is worth trying. The key is the atom version, the value is RSS2. + * @var array + */ + protected $compatMap = array( + 'title' => array('title'), + 'link' => array('link'), + 'subtitle' => array('description'), + 'author' => array('creator'), + 'updated' => array('date')); + + /** + * We will be working with multiple namespaces and it is useful to + * keep them together. We will retain support for some common RSS1.0 modules + * @var array + */ + protected $namespaces = array( + 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'rss' => 'http://purl.org/net/rss1.1#', + 'dc' => 'http://purl.org/rss/1.0/modules/dc/', + 'content' => 'http://purl.org/rss/1.0/modules/content/', + 'sy' => 'http://web.resource.org/rss/1.0/modules/syndication/'); + + /** + * Our constructor does nothing more than its parent. + * + * @param DOMDocument $xml A DOM object representing the feed + * @param bool (optional) $string Whether or not to validate this feed + */ + function __construct(DOMDocument $model, $strict = false) + { + $this->model = $model; + + if ($strict) { + $validate = $this->model->relaxNGValidate(self::getSchemaDir . + DIRECTORY_SEPARATOR . $this->relax); + if (! $validate) { + throw new XML_Feed_Parser_Exception('Failed required validation'); + } + } + + $this->xpath = new DOMXPath($model); + foreach ($this->namespaces as $key => $value) { + $this->xpath->registerNamespace($key, $value); + } + $this->numberEntries = $this->count('item'); + } + + /** + * Attempts to identify an element by ID given by the rdf:about attribute + * + * This is not really something that will work with RSS1.1 as it does not have + * clear restrictions on the global uniqueness of IDs. We will employ the + * _very_ hit and miss method of selecting entries based on the rdf:about + * attribute. Please note that this is even more hit and miss with RSS1.1 than + * with RSS1.0 since RSS1.1 does not require the rdf:about attribute for items. + * + * @param string $id any valid ID. + * @return XML_Feed_Parser_RSS1Element + */ + function getEntryById($id) + { + if (isset($this->idMappings[$id])) { + return $this->entries[$this->idMappings[$id]]; + } + + $entries = $this->xpath->query("//rss:item[@rdf:about='$id']"); + if ($entries->length > 0) { + $classname = $this->itemClass; + $entry = new $classname($entries->item(0), $this); + return $entry; + } + return false; + } + + /** + * Get details of the image associated with the feed. + * + * @return array|false an array simply containing the child elements + */ + protected function getImage() + { + $images = $this->model->getElementsByTagName('image'); + if ($images->length > 0) { + $image = $images->item(0); + $details = array(); + if ($image->hasChildNodes()) { + $details = array( + 'title' => $image->getElementsByTagName('title')->item(0)->value, + 'url' => $image->getElementsByTagName('url')->item(0)->value); + if ($image->getElementsByTagName('link')->length > 0) { + $details['link'] = + $image->getElementsByTagName('link')->item(0)->value; + } + } else { + $details = array('title' => false, + 'link' => false, + 'url' => $image->attributes->getNamedItem('resource')->nodeValue); + } + $details = array_merge($details, + array('description' => false, 'height' => false, 'width' => false)); + if (! empty($details)) { + return $details; + } + } + return false; + } + + /** + * The textinput element is little used, but in the interests of + * completeness we will support it. + * + * @return array|false + */ + protected function getTextInput() + { + $inputs = $this->model->getElementsByTagName('textinput'); + if ($inputs->length > 0) { + $input = $inputs->item(0); + $results = array(); + $results['title'] = isset( + $input->getElementsByTagName('title')->item(0)->value) ? + $input->getElementsByTagName('title')->item(0)->value : null; + $results['description'] = isset( + $input->getElementsByTagName('description')->item(0)->value) ? + $input->getElementsByTagName('description')->item(0)->value : null; + $results['name'] = isset( + $input->getElementsByTagName('name')->item(0)->value) ? + $input->getElementsByTagName('name')->item(0)->value : null; + $results['link'] = isset( + $input->getElementsByTagName('link')->item(0)->value) ? + $input->getElementsByTagName('link')->item(0)->value : null; + if (empty($results['link']) and + $input->attributes->getNamedItem('resource')) { + $results['link'] = $input->attributes->getNamedItem('resource')->nodeValue; + } + if (! empty($results)) { + return $results; + } + } + return false; + } + + /** + * Attempts to discern authorship + * + * Dublin Core provides the dc:creator, dc:contributor, and dc:publisher + * elements for defining authorship in RSS1. We will try each of those in + * turn in order to simulate the atom author element and will return it + * as text. + * + * @return array|false + */ + function getAuthor() + { + $options = array('creator', 'contributor', 'publisher'); + foreach ($options as $element) { + $test = $this->model->getElementsByTagName($element); + if ($test->length > 0) { + return $test->item(0)->value; + } + } + return false; + } + + /** + * Retrieve a link + * + * In RSS1 a link is a text element but in order to ensure that we resolve + * URLs properly we have a special function for them. + * + * @return string + */ + function getLink($offset = 0, $attribute = 'href', $params = false) + { + $links = $this->model->getElementsByTagName('link'); + if ($links->length <= $offset) { + return false; + } + $link = $links->item($offset); + return $this->addBase($link->nodeValue, $link); + } +} + +?> \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/RSS11Element.php b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS11Element.php new file mode 100755 index 000000000..75918beda --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS11Element.php @@ -0,0 +1,151 @@ + + * @copyright 2005 James Stewart + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: RSS11Element.php,v 1.4 2006/06/30 17:41:56 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/* + * This class provides support for RSS 1.1 entries. It will usually be called by + * XML_Feed_Parser_RSS11 with which it shares many methods. + * + * @author James Stewart + * @version Release: 1.0.3 + * @package XML_Feed_Parser + */ +class XML_Feed_Parser_RSS11Element extends XML_Feed_Parser_RSS11 +{ + /** + * This will be a reference to the parent object for when we want + * to use a 'fallback' rule + * @var XML_Feed_Parser_RSS1 + */ + protected $parent; + + /** + * Our specific element map + * @var array + */ + protected $map = array( + 'id' => array('Id'), + 'title' => array('Text'), + 'link' => array('Link'), + 'description' => array('Text'), # or dc:description + 'category' => array('Category'), + 'rights' => array('Text'), # dc:rights + 'creator' => array('Text'), # dc:creator + 'publisher' => array('Text'), # dc:publisher + 'contributor' => array('Text'), # dc:contributor + 'date' => array('Date'), # dc:date + 'content' => array('Content') + ); + + /** + * Here we map some elements to their atom equivalents. This is going to be + * quite tricky to pull off effectively (and some users' methods may vary) + * but is worth trying. The key is the atom version, the value is RSS1. + * @var array + */ + protected $compatMap = array( + 'content' => array('content'), + 'updated' => array('lastBuildDate'), + 'published' => array('pubdate'), + 'subtitle' => array('description'), + 'updated' => array('date'), + 'author' => array('creator'), + 'contributor' => array('contributor') + ); + + /** + * Store useful information for later. + * + * @param DOMElement $element - this item as a DOM element + * @param XML_Feed_Parser_RSS1 $parent - the feed of which this is a member + */ + function __construct(DOMElement $element, $parent, $xmlBase = '') + { + $this->model = $element; + $this->parent = $parent; + } + + /** + * If an rdf:about attribute is specified, return that as an ID + * + * There is no established way of showing an ID for an RSS1 entry. We will + * simulate it using the rdf:about attribute of the entry element. This cannot + * be relied upon for unique IDs but may prove useful. + * + * @return string|false + */ + function getId() + { + if ($this->model->attributes->getNamedItem('about')) { + return $this->model->attributes->getNamedItem('about')->nodeValue; + } + return false; + } + + /** + * Return the entry's content + * + * The official way to include full content in an RSS1 entry is to use + * the content module's element 'encoded'. Often, however, the 'description' + * element is used instead. We will offer that as a fallback. + * + * @return string|false + */ + function getContent() + { + $options = array('encoded', 'description'); + foreach ($options as $element) { + $test = $this->model->getElementsByTagName($element); + if ($test->length == 0) { + continue; + } + if ($test->item(0)->hasChildNodes()) { + $value = ''; + foreach ($test->item(0)->childNodes as $child) { + if ($child instanceof DOMText) { + $value .= $child->nodeValue; + } else { + $simple = simplexml_import_dom($child); + $value .= $simple->asXML(); + } + } + return $value; + } else if ($test->length > 0) { + return $test->item(0)->nodeValue; + } + } + return false; + } + + /** + * How RSS1.1 should support for enclosures is not clear. For now we will return + * false. + * + * @return false + */ + function getEnclosure() + { + return false; + } +} + +?> \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/RSS1Element.php b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS1Element.php new file mode 100755 index 000000000..8e36d5a9b --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS1Element.php @@ -0,0 +1,116 @@ + + * @copyright 2005 James Stewart + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: RSS1Element.php,v 1.6 2006/06/30 17:41:56 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/* + * This class provides support for RSS 1.0 entries. It will usually be called by + * XML_Feed_Parser_RSS1 with which it shares many methods. + * + * @author James Stewart + * @version Release: 1.0.3 + * @package XML_Feed_Parser + */ +class XML_Feed_Parser_RSS1Element extends XML_Feed_Parser_RSS1 +{ + /** + * This will be a reference to the parent object for when we want + * to use a 'fallback' rule + * @var XML_Feed_Parser_RSS1 + */ + protected $parent; + + /** + * Our specific element map + * @var array + */ + protected $map = array( + 'id' => array('Id'), + 'title' => array('Text'), + 'link' => array('Link'), + 'description' => array('Text'), # or dc:description + 'category' => array('Category'), + 'rights' => array('Text'), # dc:rights + 'creator' => array('Text'), # dc:creator + 'publisher' => array('Text'), # dc:publisher + 'contributor' => array('Text'), # dc:contributor + 'date' => array('Date'), # dc:date + 'content' => array('Content') + ); + + /** + * Here we map some elements to their atom equivalents. This is going to be + * quite tricky to pull off effectively (and some users' methods may vary) + * but is worth trying. The key is the atom version, the value is RSS1. + * @var array + */ + protected $compatMap = array( + 'content' => array('content'), + 'updated' => array('lastBuildDate'), + 'published' => array('pubdate'), + 'subtitle' => array('description'), + 'updated' => array('date'), + 'author' => array('creator'), + 'contributor' => array('contributor') + ); + + /** + * Store useful information for later. + * + * @param DOMElement $element - this item as a DOM element + * @param XML_Feed_Parser_RSS1 $parent - the feed of which this is a member + */ + function __construct(DOMElement $element, $parent, $xmlBase = '') + { + $this->model = $element; + $this->parent = $parent; + } + + /** + * If an rdf:about attribute is specified, return it as an ID + * + * There is no established way of showing an ID for an RSS1 entry. We will + * simulate it using the rdf:about attribute of the entry element. This cannot + * be relied upon for unique IDs but may prove useful. + * + * @return string|false + */ + function getId() + { + if ($this->model->attributes->getNamedItem('about')) { + return $this->model->attributes->getNamedItem('about')->nodeValue; + } + return false; + } + + /** + * How RSS1 should support for enclosures is not clear. For now we will return + * false. + * + * @return false + */ + function getEnclosure() + { + return false; + } +} + +?> \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/RSS2.php b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS2.php new file mode 100644 index 000000000..0936bd2f5 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS2.php @@ -0,0 +1,335 @@ + + * @copyright 2005 James Stewart + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: RSS2.php,v 1.12 2008/03/08 18:16:45 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/** + * This class handles RSS2 feeds. + * + * @author James Stewart + * @version Release: 1.0.3 + * @package XML_Feed_Parser + */ +class XML_Feed_Parser_RSS2 extends XML_Feed_Parser_Type +{ + /** + * The URI of the RelaxNG schema used to (optionally) validate the feed + * @var string + */ + private $relax = 'rss20.rnc'; + + /** + * We're likely to use XPath, so let's keep it global + * @var DOMXPath + */ + protected $xpath; + + /** + * The feed type we are parsing + * @var string + */ + public $version = 'RSS 2.0'; + + /** + * The class used to represent individual items + * @var string + */ + protected $itemClass = 'XML_Feed_Parser_RSS2Element'; + + /** + * The element containing entries + * @var string + */ + protected $itemElement = 'item'; + + /** + * Here we map those elements we're not going to handle individually + * to the constructs they are. The optional second parameter in the array + * tells the parser whether to 'fall back' (not apt. at the feed level) or + * fail if the element is missing. If the parameter is not set, the function + * will simply return false and leave it to the client to decide what to do. + * @var array + */ + protected $map = array( + 'ttl' => array('Text'), + 'pubDate' => array('Date'), + 'lastBuildDate' => array('Date'), + 'title' => array('Text'), + 'link' => array('Link'), + 'description' => array('Text'), + 'language' => array('Text'), + 'copyright' => array('Text'), + 'managingEditor' => array('Text'), + 'webMaster' => array('Text'), + 'category' => array('Text'), + 'generator' => array('Text'), + 'docs' => array('Text'), + 'ttl' => array('Text'), + 'image' => array('Image'), + 'skipDays' => array('skipDays'), + 'skipHours' => array('skipHours')); + + /** + * Here we map some elements to their atom equivalents. This is going to be + * quite tricky to pull off effectively (and some users' methods may vary) + * but is worth trying. The key is the atom version, the value is RSS2. + * @var array + */ + protected $compatMap = array( + 'title' => array('title'), + 'rights' => array('copyright'), + 'updated' => array('lastBuildDate'), + 'subtitle' => array('description'), + 'date' => array('pubDate'), + 'author' => array('managingEditor')); + + protected $namespaces = array( + 'dc' => 'http://purl.org/rss/1.0/modules/dc/', + 'content' => 'http://purl.org/rss/1.0/modules/content/'); + + /** + * Our constructor does nothing more than its parent. + * + * @param DOMDocument $xml A DOM object representing the feed + * @param bool (optional) $string Whether or not to validate this feed + */ + function __construct(DOMDocument $model, $strict = false) + { + $this->model = $model; + + if ($strict) { + if (! $this->model->relaxNGValidate($this->relax)) { + throw new XML_Feed_Parser_Exception('Failed required validation'); + } + } + + $this->xpath = new DOMXPath($this->model); + foreach ($this->namespaces as $key => $value) { + $this->xpath->registerNamespace($key, $value); + } + $this->numberEntries = $this->count('item'); + } + + /** + * Retrieves an entry by ID, if the ID is specified with the guid element + * + * This is not really something that will work with RSS2 as it does not have + * clear restrictions on the global uniqueness of IDs. But we can emulate + * it by allowing access based on the 'guid' element. If DOMXPath::evaluate + * is available, we also use that to store a reference to the entry in the array + * used by getEntryByOffset so that method does not have to seek out the entry + * if it's requested that way. + * + * @param string $id any valid ID. + * @return XML_Feed_Parser_RSS2Element + */ + function getEntryById($id) + { + if (isset($this->idMappings[$id])) { + return $this->entries[$this->idMappings[$id]]; + } + + $entries = $this->xpath->query("//item[guid='$id']"); + if ($entries->length > 0) { + $entry = new $this->itemElement($entries->item(0), $this); + if (in_array('evaluate', get_class_methods($this->xpath))) { + $offset = $this->xpath->evaluate("count(preceding-sibling::item)", $entries->item(0)); + $this->entries[$offset] = $entry; + } + $this->idMappings[$id] = $entry; + return $entry; + } + } + + /** + * Get a category from the element + * + * The category element is a simple text construct which can occur any number + * of times. We allow access by offset or access to an array of results. + * + * @param string $call for compatibility with our overloading + * @param array $arguments - arg 0 is the offset, arg 1 is whether to return as array + * @return string|array|false + */ + function getCategory($call, $arguments = array()) + { + $categories = $this->model->getElementsByTagName('category'); + $offset = empty($arguments[0]) ? 0 : $arguments[0]; + $array = empty($arguments[1]) ? false : true; + if ($categories->length <= $offset) { + return false; + } + if ($array) { + $list = array(); + foreach ($categories as $category) { + array_push($list, $category->nodeValue); + } + return $list; + } + return $categories->item($offset)->nodeValue; + } + + /** + * Get details of the image associated with the feed. + * + * @return array|false an array simply containing the child elements + */ + protected function getImage() + { + $images = $this->xpath->query("//image"); + if ($images->length > 0) { + $image = $images->item(0); + $desc = $image->getElementsByTagName('description'); + $description = $desc->length ? $desc->item(0)->nodeValue : false; + $heigh = $image->getElementsByTagName('height'); + $height = $heigh->length ? $heigh->item(0)->nodeValue : false; + $widt = $image->getElementsByTagName('width'); + $width = $widt->length ? $widt->item(0)->nodeValue : false; + return array( + 'title' => $image->getElementsByTagName('title')->item(0)->nodeValue, + 'link' => $image->getElementsByTagName('link')->item(0)->nodeValue, + 'url' => $image->getElementsByTagName('url')->item(0)->nodeValue, + 'description' => $description, + 'height' => $height, + 'width' => $width); + } + return false; + } + + /** + * The textinput element is little used, but in the interests of + * completeness... + * + * @return array|false + */ + function getTextInput() + { + $inputs = $this->model->getElementsByTagName('input'); + if ($inputs->length > 0) { + $input = $inputs->item(0); + return array( + 'title' => $input->getElementsByTagName('title')->item(0)->value, + 'description' => + $input->getElementsByTagName('description')->item(0)->value, + 'name' => $input->getElementsByTagName('name')->item(0)->value, + 'link' => $input->getElementsByTagName('link')->item(0)->value); + } + return false; + } + + /** + * Utility function for getSkipDays and getSkipHours + * + * This is a general function used by both getSkipDays and getSkipHours. It simply + * returns an array of the values of the children of the appropriate tag. + * + * @param string $tagName The tag name (getSkipDays or getSkipHours) + * @return array|false + */ + protected function getSkips($tagName) + { + $hours = $this->model->getElementsByTagName($tagName); + if ($hours->length == 0) { + return false; + } + $skipHours = array(); + foreach($hours->item(0)->childNodes as $hour) { + if ($hour instanceof DOMElement) { + array_push($skipHours, $hour->nodeValue); + } + } + return $skipHours; + } + + /** + * Retrieve skipHours data + * + * The skiphours element provides a list of hours on which this feed should + * not be checked. We return an array of those hours (integers, 24 hour clock) + * + * @return array + */ + function getSkipHours() + { + return $this->getSkips('skipHours'); + } + + /** + * Retrieve skipDays data + * + * The skipdays element provides a list of days on which this feed should + * not be checked. We return an array of those days. + * + * @return array + */ + function getSkipDays() + { + return $this->getSkips('skipDays'); + } + + /** + * Return content of the little-used 'cloud' element + * + * The cloud element is rarely used. It is designed to provide some details + * of a location to update the feed. + * + * @return array an array of the attributes of the element + */ + function getCloud() + { + $cloud = $this->model->getElementsByTagName('cloud'); + if ($cloud->length == 0) { + return false; + } + $cloudData = array(); + foreach ($cloud->item(0)->attributes as $attribute) { + $cloudData[$attribute->name] = $attribute->value; + } + return $cloudData; + } + + /** + * Get link URL + * + * In RSS2 a link is a text element but in order to ensure that we resolve + * URLs properly we have a special function for them. We maintain the + * parameter used by the atom getLink method, though we only use the offset + * parameter. + * + * @param int $offset The position of the link within the feed. Starts from 0 + * @param string $attribute The attribute of the link element required + * @param array $params An array of other parameters. Not used. + * @return string + */ + function getLink($offset, $attribute = 'href', $params = array()) + { + $xPath = new DOMXPath($this->model); + $links = $xPath->query('//link'); + + if ($links->length <= $offset) { + return false; + } + $link = $links->item($offset); + return $this->addBase($link->nodeValue, $link); + } +} + +?> \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/RSS2Element.php b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS2Element.php new file mode 100755 index 000000000..6edf910dc --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS2Element.php @@ -0,0 +1,171 @@ + + * @copyright 2005 James Stewart + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: RSS2Element.php,v 1.11 2006/07/26 21:18:47 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/** + * This class provides support for RSS 2.0 entries. It will usually be + * called by XML_Feed_Parser_RSS2 with which it shares many methods. + * + * @author James Stewart + * @version Release: 1.0.3 + * @package XML_Feed_Parser + */ +class XML_Feed_Parser_RSS2Element extends XML_Feed_Parser_RSS2 +{ + /** + * This will be a reference to the parent object for when we want + * to use a 'fallback' rule + * @var XML_Feed_Parser_RSS2 + */ + protected $parent; + + /** + * Our specific element map + * @var array + */ + protected $map = array( + 'title' => array('Text'), + 'guid' => array('Guid'), + 'description' => array('Text'), + 'author' => array('Text'), + 'comments' => array('Text'), + 'enclosure' => array('Enclosure'), + 'pubDate' => array('Date'), + 'source' => array('Source'), + 'link' => array('Text'), + 'content' => array('Content')); + + /** + * Here we map some elements to their atom equivalents. This is going to be + * quite tricky to pull off effectively (and some users' methods may vary) + * but is worth trying. The key is the atom version, the value is RSS2. + * @var array + */ + protected $compatMap = array( + 'id' => array('guid'), + 'updated' => array('lastBuildDate'), + 'published' => array('pubdate'), + 'guidislink' => array('guid', 'ispermalink'), + 'summary' => array('description')); + + /** + * Store useful information for later. + * + * @param DOMElement $element - this item as a DOM element + * @param XML_Feed_Parser_RSS2 $parent - the feed of which this is a member + */ + function __construct(DOMElement $element, $parent, $xmlBase = '') + { + $this->model = $element; + $this->parent = $parent; + } + + /** + * Get the value of the guid element, if specified + * + * guid is the closest RSS2 has to atom's ID. It is usually but not always a + * URI. The one attribute that RSS2 can posess is 'ispermalink' which specifies + * whether the guid is itself dereferencable. Use of guid is not obligatory, + * but is advisable. To get the guid you would call $item->id() (for atom + * compatibility) or $item->guid(). To check if this guid is a permalink call + * $item->guid("ispermalink"). + * + * @param string $method - the method name being called + * @param array $params - parameters required + * @return string the guid or value of ispermalink + */ + protected function getGuid($method, $params) + { + $attribute = (isset($params[0]) and $params[0] == 'ispermalink') ? + true : false; + $tag = $this->model->getElementsByTagName('guid'); + if ($tag->length > 0) { + if ($attribute) { + if ($tag->hasAttribute("ispermalink")) { + return $tag->getAttribute("ispermalink"); + } + } + return $tag->item(0)->nodeValue; + } + return false; + } + + /** + * Access details of file enclosures + * + * The RSS2 spec is ambiguous as to whether an enclosure element must be + * unique in a given entry. For now we will assume it needn't, and allow + * for an offset. + * + * @param string $method - the method being called + * @param array $parameters - we expect the first of these to be our offset + * @return array|false + */ + protected function getEnclosure($method, $parameters) + { + $encs = $this->model->getElementsByTagName('enclosure'); + $offset = isset($parameters[0]) ? $parameters[0] : 0; + if ($encs->length > $offset) { + try { + if (! $encs->item($offset)->hasAttribute('url')) { + return false; + } + $attrs = $encs->item($offset)->attributes; + return array( + 'url' => $attrs->getNamedItem('url')->value, + 'length' => $attrs->getNamedItem('length')->value, + 'type' => $attrs->getNamedItem('type')->value); + } catch (Exception $e) { + return false; + } + } + return false; + } + + /** + * Get the entry source if specified + * + * source is an optional sub-element of item. Like atom:source it tells + * us about where the entry came from (eg. if it's been copied from another + * feed). It is not a rich source of metadata in the same way as atom:source + * and while it would be good to maintain compatibility by returning an + * XML_Feed_Parser_RSS2 element, it makes a lot more sense to return an array. + * + * @return array|false + */ + protected function getSource() + { + $get = $this->model->getElementsByTagName('source'); + if ($get->length) { + $source = $get->item(0); + $array = array( + 'content' => $source->nodeValue); + foreach ($source->attributes as $attribute) { + $array[$attribute->name] = $attribute->value; + } + return $array; + } + return false; + } +} + +?> \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/Type.php b/plugins/FeedSub/extlib/XML/Feed/Parser/Type.php new file mode 100644 index 000000000..75052619b --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/Type.php @@ -0,0 +1,467 @@ + + * @copyright 2005 James Stewart + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: Type.php,v 1.25 2008/03/08 18:39:09 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/** + * This abstract class provides some general methods that are likely to be + * implemented exactly the same way for all feed types. + * + * @package XML_Feed_Parser + * @author James Stewart + * @version Release: 1.0.3 + */ +abstract class XML_Feed_Parser_Type +{ + /** + * Where we store our DOM object for this feed + * @var DOMDocument + */ + public $model; + + /** + * For iteration we'll want a count of the number of entries + * @var int + */ + public $numberEntries; + + /** + * Where we store our entry objects once instantiated + * @var array + */ + public $entries = array(); + + /** + * Store mappings between entry IDs and their position in the feed + */ + public $idMappings = array(); + + /** + * Proxy to allow use of element names as method names + * + * We are not going to provide methods for every entry type so this + * function will allow for a lot of mapping. We rely pretty heavily + * on this to handle our mappings between other feed types and atom. + * + * @param string $call - the method attempted + * @param array $arguments - arguments to that method + * @return mixed + */ + function __call($call, $arguments = array()) + { + if (! is_array($arguments)) { + $arguments = array(); + } + + if (isset($this->compatMap[$call])) { + $tempMap = $this->compatMap; + $tempcall = array_pop($tempMap[$call]); + if (! empty($tempMap)) { + $arguments = array_merge($arguments, $tempMap[$call]); + } + $call = $tempcall; + } + + /* To be helpful, we allow a case-insensitive search for this method */ + if (! isset($this->map[$call])) { + foreach (array_keys($this->map) as $key) { + if (strtoupper($key) == strtoupper($call)) { + $call = $key; + break; + } + } + } + + if (empty($this->map[$call])) { + return false; + } + + $method = 'get' . $this->map[$call][0]; + if ($method == 'getLink') { + $offset = empty($arguments[0]) ? 0 : $arguments[0]; + $attribute = empty($arguments[1]) ? 'href' : $arguments[1]; + $params = isset($arguments[2]) ? $arguments[2] : array(); + return $this->getLink($offset, $attribute, $params); + } + if (method_exists($this, $method)) { + return $this->$method($call, $arguments); + } + + return false; + } + + /** + * Proxy to allow use of element names as attribute names + * + * For many elements variable-style access will be desirable. This function + * provides for that. + * + * @param string $value - the variable required + * @return mixed + */ + function __get($value) + { + return $this->__call($value, array()); + } + + /** + * Utility function to help us resolve xml:base values + * + * We have other methods which will traverse the DOM and work out the different + * xml:base declarations we need to be aware of. We then need to combine them. + * If a declaration starts with a protocol then we restart the string. If it + * starts with a / then we add on to the domain name. Otherwise we simply tag + * it on to the end. + * + * @param string $base - the base to add the link to + * @param string $link + */ + function combineBases($base, $link) + { + if (preg_match('/^[A-Za-z]+:\/\//', $link)) { + return $link; + } else if (preg_match('/^\//', $link)) { + /* Extract domain and suffix link to that */ + preg_match('/^([A-Za-z]+:\/\/.*)?\/*/', $base, $results); + $firstLayer = $results[0]; + return $firstLayer . "/" . $link; + } else if (preg_match('/^\.\.\//', $base)) { + /* Step up link to find place to be */ + preg_match('/^((\.\.\/)+)(.*)$/', $link, $bases); + $suffix = $bases[3]; + $count = preg_match_all('/\.\.\//', $bases[1], $steps); + $url = explode("/", $base); + for ($i = 0; $i <= $count; $i++) { + array_pop($url); + } + return implode("/", $url) . "/" . $suffix; + } else if (preg_match('/^(?!\/$)/', $base)) { + $base = preg_replace('/(.*\/).*$/', '$1', $base) ; + return $base . $link; + } else { + /* Just stick it on the end */ + return $base . $link; + } + } + + /** + * Determine whether we need to apply our xml:base rules + * + * Gets us the xml:base data and then processes that with regard + * to our current link. + * + * @param string + * @param DOMElement + * @return string + */ + function addBase($link, $element) + { + if (preg_match('/^[A-Za-z]+:\/\//', $link)) { + return $link; + } + + return $this->combineBases($element->baseURI, $link); + } + + /** + * Get an entry by its position in the feed, starting from zero + * + * As well as allowing the items to be iterated over we want to allow + * users to be able to access a specific entry. This is one of two ways of + * doing that, the other being by ID. + * + * @param int $offset + * @return XML_Feed_Parser_RSS1Element + */ + function getEntryByOffset($offset) + { + if (! isset($this->entries[$offset])) { + $entries = $this->model->getElementsByTagName($this->itemElement); + if ($entries->length > $offset) { + $xmlBase = $entries->item($offset)->baseURI; + $this->entries[$offset] = new $this->itemClass( + $entries->item($offset), $this, $xmlBase); + if ($id = $this->entries[$offset]->id) { + $this->idMappings[$id] = $this->entries[$offset]; + } + } else { + throw new XML_Feed_Parser_Exception('No entries found'); + } + } + + return $this->entries[$offset]; + } + + /** + * Return a date in seconds since epoch. + * + * Get a date construct. We use PHP's strtotime to return it as a unix datetime, which + * is the number of seconds since 1970-01-01 00:00:00. + * + * @link http://php.net/strtotime + * @param string $method The name of the date construct we want + * @param array $arguments Included for compatibility with our __call usage + * @return int|false datetime + */ + protected function getDate($method, $arguments) + { + $time = $this->model->getElementsByTagName($method); + if ($time->length == 0 || empty($time->item(0)->nodeValue)) { + return false; + } + return strtotime($time->item(0)->nodeValue); + } + + /** + * Get a text construct. + * + * @param string $method The name of the text construct we want + * @param array $arguments Included for compatibility with our __call usage + * @return string + */ + protected function getText($method, $arguments = array()) + { + $tags = $this->model->getElementsByTagName($method); + if ($tags->length > 0) { + $value = $tags->item(0)->nodeValue; + return $value; + } + return false; + } + + /** + * Apply various rules to retrieve category data. + * + * There is no single way of declaring a category in RSS1/1.1 as there is in RSS2 + * and Atom. Instead the usual approach is to use the dublin core namespace to + * declare categories. For example delicious use both: + * PEAR and: + * + * to declare a categorisation of 'PEAR'. + * + * We need to be sensitive to this where possible. + * + * @param string $call for compatibility with our overloading + * @param array $arguments - arg 0 is the offset, arg 1 is whether to return as array + * @return string|array|false + */ + protected function getCategory($call, $arguments) + { + $categories = $this->model->getElementsByTagName('subject'); + $offset = empty($arguments[0]) ? 0 : $arguments[0]; + $array = empty($arguments[1]) ? false : true; + if ($categories->length <= $offset) { + return false; + } + if ($array) { + $list = array(); + foreach ($categories as $category) { + array_push($list, $category->nodeValue); + } + return $list; + } + return $categories->item($offset)->nodeValue; + } + + /** + * Count occurrences of an element + * + * This function will tell us how many times the element $type + * appears at this level of the feed. + * + * @param string $type the element we want to get a count of + * @return int + */ + protected function count($type) + { + if ($tags = $this->model->getElementsByTagName($type)) { + return $tags->length; + } + return 0; + } + + /** + * Part of our xml:base processing code + * + * We need a couple of methods to access XHTML content stored in feeds. + * This is because we dereference all xml:base references before returning + * the element. This method handles the attributes. + * + * @param DOMElement $node The DOM node we are iterating over + * @return string + */ + function processXHTMLAttributes($node) { + $return = ''; + foreach ($node->attributes as $attribute) { + if ($attribute->name == 'src' or $attribute->name == 'href') { + $attribute->value = $this->addBase(htmlentities($attribute->value, NULL, 'utf-8'), $attribute); + } + if ($attribute->name == 'base') { + continue; + } + $return .= $attribute->name . '="' . htmlentities($attribute->value, NULL, 'utf-8') .'" '; + } + if (! empty($return)) { + return ' ' . trim($return); + } + return ''; + } + + /** + * Convert HTML entities based on the current character set. + * + * @param String + * @return String + */ + function processEntitiesForNodeValue($node) + { + if (function_exists('iconv')) { + $current_encoding = $node->ownerDocument->encoding; + $value = iconv($current_encoding, 'UTF-8', $node->nodeValue); + } else if ($current_encoding == 'iso-8859-1') { + $value = utf8_encode($node->nodeValue); + } else { + $value = $node->nodeValue; + } + + $decoded = html_entity_decode($value, NULL, 'UTF-8'); + return htmlentities($decoded, NULL, 'UTF-8'); + } + + /** + * Part of our xml:base processing code + * + * We need a couple of methods to access XHTML content stored in feeds. + * This is because we dereference all xml:base references before returning + * the element. This method recurs through the tree descending from the node + * and builds our string. + * + * @param DOMElement $node The DOM node we are processing + * @return string + */ + function traverseNode($node) + { + $content = ''; + + /* Add the opening of this node to the content */ + if ($node instanceof DOMElement) { + $content .= '<' . $node->tagName . + $this->processXHTMLAttributes($node) . '>'; + } + + /* Process children */ + if ($node->hasChildNodes()) { + foreach ($node->childNodes as $child) { + $content .= $this->traverseNode($child); + } + } + + if ($node instanceof DOMText) { + $content .= $this->processEntitiesForNodeValue($node); + } + + /* Add the closing of this node to the content */ + if ($node instanceof DOMElement) { + $content .= 'tagName . '>'; + } + + return $content; + } + + /** + * Get content from RSS feeds (atom has its own implementation) + * + * The official way to include full content in an RSS1 entry is to use + * the content module's element 'encoded', and RSS2 feeds often duplicate that. + * Often, however, the 'description' element is used instead. We will offer that + * as a fallback. Atom uses its own approach and overrides this method. + * + * @return string|false + */ + protected function getContent() + { + $options = array('encoded', 'description'); + foreach ($options as $element) { + $test = $this->model->getElementsByTagName($element); + if ($test->length == 0) { + continue; + } + if ($test->item(0)->hasChildNodes()) { + $value = ''; + foreach ($test->item(0)->childNodes as $child) { + if ($child instanceof DOMText) { + $value .= $child->nodeValue; + } else { + $simple = simplexml_import_dom($child); + $value .= $simple->asXML(); + } + } + return $value; + } else if ($test->length > 0) { + return $test->item(0)->nodeValue; + } + } + return false; + } + + /** + * Checks if this element has a particular child element. + * + * @param String + * @param Integer + * @return bool + **/ + function hasKey($name, $offset = 0) + { + $search = $this->model->getElementsByTagName($name); + return $search->length > $offset; + } + + /** + * Return an XML serialization of the feed, should it be required. Most + * users however, will already have a serialization that they used when + * instantiating the object. + * + * @return string XML serialization of element + */ + function __toString() + { + $simple = simplexml_import_dom($this->model); + return $simple->asXML(); + } + + /** + * Get directory holding RNG schemas. Method is based on that + * found in Contact_AddressBook. + * + * @return string PEAR data directory. + * @access public + * @static + */ + static function getSchemaDir() + { + require_once 'PEAR/Config.php'; + $config = new PEAR_Config; + return $config->get('data_dir') . '/XML_Feed_Parser/schemas'; + } +} + +?> \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/samples/atom10-entryonly.xml b/plugins/FeedSub/extlib/XML/Feed/samples/atom10-entryonly.xml new file mode 100755 index 000000000..02e1c5800 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/samples/atom10-entryonly.xml @@ -0,0 +1,28 @@ + + + Atom draft-07 snapshot + + + tag:example.org,2003:3.2397 + 2005-07-10T12:29:29Z + 2003-12-13T08:29:29-04:00 + + Mark Pilgrim + http://example.org/ + f8dy@example.com + + + Sam Ruby + + + Joe Gregorio + + +
+

[Update: The Atom draft is finished.]

+
+
+
\ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/samples/atom10-example1.xml b/plugins/FeedSub/extlib/XML/Feed/samples/atom10-example1.xml new file mode 100755 index 000000000..d181d2b6f --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/samples/atom10-example1.xml @@ -0,0 +1,20 @@ + + + + Example Feed + + 2003-12-13T18:30:02Z + + John Doe + + urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 + + + Atom-Powered Robots Run Amok + + urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a + 2003-12-13T18:30:02Z + Some text. + + + \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/samples/atom10-example2.xml b/plugins/FeedSub/extlib/XML/Feed/samples/atom10-example2.xml new file mode 100755 index 000000000..98abf9d54 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/samples/atom10-example2.xml @@ -0,0 +1,45 @@ + + + dive into mark + + A <em>lot</em> of effort + went into making this effortless + + 2005-07-31T12:29:29Z + tag:example.org,2003:3 + + + Copyright (c) 2003, Mark Pilgrim + + Example Toolkit + + + Atom draft-07 snapshot + + + tag:example.org,2003:3.2397 + 2005-07-31T12:29:29Z + 2003-12-13T08:29:29-04:00 + + Mark Pilgrim + http://example.org/ + f8dy@example.com + + + Sam Ruby + + + Joe Gregorio + + +
+

[Update: The Atom draft is finished.]

+
+
+
+
\ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/samples/delicious.feed b/plugins/FeedSub/extlib/XML/Feed/samples/delicious.feed new file mode 100755 index 000000000..32f9fa493 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/samples/delicious.feed @@ -0,0 +1,177 @@ + + + + +del.icio.us/tag/greenbelt +http://del.icio.us/tag/greenbelt +Text + + + + + + + + + + + + + + + + +Greenbelt - Homepage Section +http://www.greenbelt.org.uk/ +jonnybaker +2005-05-16T16:30:38Z +greenbelt + + + + + + + + +Greenbelt festival (uk) +http://www.greenbelt.org.uk/ +sssshhhh +2005-05-14T18:19:40Z +audiology festival gigs greenbelt + + + + + + + + + + + +Natuerlichwien.at - Rundumadum +http://www.natuerlichwien.at/rundumadum/dergruenguertel/ +egmilman47 +2005-05-06T21:33:41Z +Austria Vienna Wien greenbelt nature walking + + + + + + + + + + + + + +Tank - GBMediaWiki +http://www.flickerweb.co.uk/wiki/index.php/Tank#Seminars +jystewart +2005-03-21T22:44:11Z +greenbelt + + + + + + + + +Greenbelt homepage +http://www.greenbelt.ca/home.htm +Gooberoo +2005-03-01T22:43:17Z +greenbelt ontario + + + + + + + + + +Pip Wilson bhp ...... blog +http://pipwilsonbhp.blogspot.com/ +sssshhhh +2004-12-27T11:20:51Z +Greenbelt friend ideas links thinking weblog + + + + + + + + + + + + + +maggi dawn +http://maggidawn.typepad.com/maggidawn/ +sssshhhh +2004-12-27T11:20:11Z +Greenbelt ideas links thinking weblog + + + + + + + + + + + + +John Davies +http://www.johndavies.org/ +sssshhhh +2004-12-27T11:18:37Z +Greenbelt ideas links thinking weblog + + + + + + + + + + + + +jonnybaker +http://jonnybaker.blogs.com/ +sssshhhh +2004-12-27T11:18:17Z +Greenbelt event ideas links resources thinking weblog youth + + + + + + + + + + + + + + + diff --git a/plugins/FeedSub/extlib/XML/Feed/samples/flickr.feed b/plugins/FeedSub/extlib/XML/Feed/samples/flickr.feed new file mode 100755 index 000000000..57e83af57 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/samples/flickr.feed @@ -0,0 +1,184 @@ + + + + jamesstewart - Everyone's Tagged Photos + + + A feed of jamesstewart - Everyone's Tagged Photos + 2005-08-01T18:50:26Z + Flickr + + + Oma and James + + + tag:flickr.com,2004:/photo/30367516 + 2005-08-01T18:50:26Z + 2005-08-01T18:50:26Z + <p><a href="http://www.flickr.com/people/30484029@N00/">kstewart</a> posted a photo:</p> + +<p><a href="http://www.flickr.com/photos/30484029@N00/30367516/" title="Oma and James"><img src="http://photos23.flickr.com/30367516_1f685a16e8_m.jpg" width="240" height="180" alt="Oma and James" style="border: 1px solid #000000;" /></a></p> + +<p>I have a beautiful Oma and a gorgeous husband.</p> + + kstewart + http://www.flickr.com/people/30484029@N00/ + + jamesstewart oma stoelfamily + + + + + tag:flickr.com,2004:/photo/21376174 + 2005-06-25T02:00:35Z + 2005-06-25T02:00:35Z + <p><a href="http://www.flickr.com/people/buddscreek/">Lan Rover</a> posted a photo:</p> + +<p><a href="http://www.flickr.com/photos/buddscreek/21376174/" title=""><img src="http://photos17.flickr.com/21376174_4314fd8d5c_m.jpg" width="240" height="160" alt="" style="border: 1px solid #000000;" /></a></p> + +<p>AMA Motocross Championship 2005, Budds Creek, Maryland</p> + + Lan Rover + http://www.flickr.com/people/buddscreek/ + + amamotocrosschampionship buddscreek maryland 2005 fathersday motocrossnational rickycarmichael 259 jamesstewart 4 + + + + + tag:flickr.com,2004:/photo/21375650 + 2005-06-25T01:56:24Z + 2005-06-25T01:56:24Z + <p><a href="http://www.flickr.com/people/buddscreek/">Lan Rover</a> posted a photo:</p> + +<p><a href="http://www.flickr.com/photos/buddscreek/21375650/" title=""><img src="http://photos16.flickr.com/21375650_5c60e0dab1_m.jpg" width="240" height="160" alt="" style="border: 1px solid #000000;" /></a></p> + + + + Lan Rover + http://www.flickr.com/people/buddscreek/ + + amamotocrosschampionship buddscreek maryland 2005 fathersday motocrossnational 259 jamesstewart + + + + + tag:flickr.com,2004:/photo/21375345 + 2005-06-25T01:54:11Z + 2005-06-25T01:54:11Z + <p><a href="http://www.flickr.com/people/buddscreek/">Lan Rover</a> posted a photo:</p> + +<p><a href="http://www.flickr.com/photos/buddscreek/21375345/" title=""><img src="http://photos15.flickr.com/21375345_4205fdd22b_m.jpg" width="160" height="240" alt="" style="border: 1px solid #000000;" /></a></p> + + + + Lan Rover + http://www.flickr.com/people/buddscreek/ + + amamotocrosschampionship buddscreek maryland 2005 fathersday motocrossnational 259 jamesstewart + + + Lunch with Kari & James, café in the crypt of St Martin in the fields + + tag:flickr.com,2004:/photo/16516618 + 2005-05-30T21:56:39Z + 2005-05-30T21:56:39Z + <p><a href="http://www.flickr.com/people/fidothe/">fidothe</a> posted a photo:</p> + +<p><a href="http://www.flickr.com/photos/fidothe/16516618/" title="Lunch with Kari &amp; James, café in the crypt of St Martin in the fields"><img src="http://photos14.flickr.com/16516618_afaa4a395e_m.jpg" width="240" height="180" alt="Lunch with Kari &amp; James, café in the crypt of St Martin in the fields" style="border: 1px solid #000000;" /></a></p> + + + + fidothe + http://www.flickr.com/people/fidothe/ + + nokia7610 london stmartininthefields clarepatterson jamesstewart parvinstewart jimstewart susanstewart + + + Stewart keeping it low over the obstacle. + + tag:flickr.com,2004:/photo/10224728 + 2005-04-21T07:30:29Z + 2005-04-21T07:30:29Z + <p><a href="http://www.flickr.com/people/pqbon/">pqbon</a> posted a photo:</p> + +<p><a href="http://www.flickr.com/photos/pqbon/10224728/" title="Stewart keeping it low over the obstacle."><img src="http://photos7.flickr.com/10224728_b756341957_m.jpg" width="240" height="180" alt="Stewart keeping it low over the obstacle." style="border: 1px solid #000000;" /></a></p> + + + + pqbon + http://www.flickr.com/people/pqbon/ + + ama hangtown motocross jamesstewart bubba + + + king james stewart + + tag:flickr.com,2004:/photo/7152910 + 2005-03-22T21:53:37Z + 2005-03-22T21:53:37Z + <p><a href="http://www.flickr.com/people/jjlook/">jj look</a> posted a photo:</p> + +<p><a href="http://www.flickr.com/photos/jjlook/7152910/" title="king james stewart"><img src="http://photos7.flickr.com/7152910_a02ab5a750_m.jpg" width="180" height="240" alt="king james stewart" style="border: 1px solid #000000;" /></a></p> + +<p>11th</p> + + jj look + http://www.flickr.com/people/jjlook/ + + dilomar05 eastside austin texas 78702 kingjames stewart jamesstewart borrowed + + + It's a Grind, downtown Grand Rapids (James, Susan, Jim, Harv, Lawson) + + tag:flickr.com,2004:/photo/1586562 + 2004-11-20T09:34:28Z + 2004-11-20T09:34:28Z + <p><a href="http://www.flickr.com/people/fidothe/">fidothe</a> posted a photo:</p> + +<p><a href="http://www.flickr.com/photos/fidothe/1586562/" title="It's a Grind, downtown Grand Rapids (James, Susan, Jim, Harv, Lawson)"><img src="http://photos2.flickr.com/1586562_0bc5313a3e_m.jpg" width="240" height="180" alt="It's a Grind, downtown Grand Rapids (James, Susan, Jim, Harv, Lawson)" style="border: 1px solid #000000;" /></a></p> + + + + fidothe + http://www.flickr.com/people/fidothe/ + + holiday grandrapids jamesstewart + + + It's a Grind, downtown Grand Rapids (James, Susan, Jim, Harv, Lawson) + + tag:flickr.com,2004:/photo/1586539 + 2004-11-20T09:28:16Z + 2004-11-20T09:28:16Z + <p><a href="http://www.flickr.com/people/fidothe/">fidothe</a> posted a photo:</p> + +<p><a href="http://www.flickr.com/photos/fidothe/1586539/" title="It's a Grind, downtown Grand Rapids (James, Susan, Jim, Harv, Lawson)"><img src="http://photos2.flickr.com/1586539_c51e5f2e7a_m.jpg" width="240" height="180" alt="It's a Grind, downtown Grand Rapids (James, Susan, Jim, Harv, Lawson)" style="border: 1px solid #000000;" /></a></p> + + + + fidothe + http://www.flickr.com/people/fidothe/ + + holiday grandrapids jamesstewart + + + It's a Grind, James and Jim can't decide) + + tag:flickr.com,2004:/photo/1586514 + 2004-11-20T09:25:05Z + 2004-11-20T09:25:05Z + <p><a href="http://www.flickr.com/people/fidothe/">fidothe</a> posted a photo:</p> + +<p><a href="http://www.flickr.com/photos/fidothe/1586514/" title="It's a Grind, James and Jim can't decide)"><img src="http://photos2.flickr.com/1586514_733c2dfa3e_m.jpg" width="240" height="180" alt="It's a Grind, James and Jim can't decide)" style="border: 1px solid #000000;" /></a></p> + + + + fidothe + http://www.flickr.com/people/fidothe/ + + holiday grandrapids jamesstewart johnkentish + + + \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/samples/grwifi-atom.xml b/plugins/FeedSub/extlib/XML/Feed/samples/grwifi-atom.xml new file mode 100755 index 000000000..c351d3c16 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/samples/grwifi-atom.xml @@ -0,0 +1,7 @@ + Updates to Grand Rapids WiFi hotspot details 2005-09-01T15:43:01-05:00 WiFi Hotspots in Grand Rapids, MI http://grwifi.net/atom/locations Creative Commons Attribution-NonCommercial-ShareAlike 2.0 http://creativecommons.org/licenses/by-nc-sa/2.0/ Hotspot Details Updated: Sweetwaters http://grwifi.net/location/sweetwaters 2005-09-01T15:43:01-05:00 The details of the WiFi hotspot at: Sweetwaters have been updated. Find out more at: +http://grwifi.net/location/sweetwaters James http://jystewart.net james@jystewart.net wifi hotspot Hotspot Details Updated: Common Ground Coffee Shop http://grwifi.net/location/common-ground 2005-09-01T15:42:39-05:00 The details of the WiFi hotspot at: Common Ground Coffee Shop have been updated. Find out more at: +http://grwifi.net/location/common-ground James http://jystewart.net james@jystewart.net wifi hotspot Hotspot Details Updated: Grand Rapids Public Library, Main Branch http://grwifi.net/location/grpl-main-branch 2005-09-01T15:42:20-05:00 The details of the WiFi hotspot at: Grand Rapids Public Library, Main Branch have been updated. Find out more at: +http://grwifi.net/location/grpl-main-branch James http://jystewart.net james@jystewart.net wifi hotspot Hotspot Details Updated: Four Friends Coffee House http://grwifi.net/location/four-friends 2005-09-01T15:41:35-05:00 The details of the WiFi hotspot at: Four Friends Coffee House have been updated. Find out more at: +http://grwifi.net/location/four-friends James http://jystewart.net james@jystewart.net wifi hotspot Hotspot Details Updated: Barnes and Noble, Rivertown Crossings http://grwifi.net/location/barnes-noble-rivertown 2005-09-01T15:40:41-05:00 The details of the WiFi hotspot at: Barnes and Noble, Rivertown Crossings have been updated. Find out more at: +http://grwifi.net/location/barnes-noble-rivertown James http://jystewart.net james@jystewart.net wifi hotspot Hotspot Details Updated: The Boss Sports Bar & Grille http://grwifi.net/location/boss-sports-bar 2005-09-01T15:40:19-05:00 The details of the WiFi hotspot at: The Boss Sports Bar & Grille have been updated. Find out more at: +http://grwifi.net/location/boss-sports-bar James http://jystewart.net james@jystewart.net wifi hotspot \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/samples/hoder.xml b/plugins/FeedSub/extlib/XML/Feed/samples/hoder.xml new file mode 100755 index 000000000..099463570 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/samples/hoder.xml @@ -0,0 +1,102 @@ + + + + +Editor: Myself (Persian) +http://editormyself.info +This is a Persian (Farsi) weblog, written by Hossein Derakhshan (aka, Hoder), an Iranian Multimedia designer and a journalist who lives in Toronto since Dec 2000. He also keeps an English weblog with the same name. +en-us +hoder@hotmail.com +2005-10-12T19:45:32-05:00 + +hourly +1 +2000-01-01T12:00+00:00 + + + +لينکدونی‌ | جلسه‌ی امریکن انترپرایز برای تقسیم قومی ایران +http://www.aei.org/events/type.upcoming,eventID.1166,filter.all/event_detail.asp +چطور بعضی‌ها فکر می‌کنند دست راستی‌های آمریکا از خامنه‌ای ملی‌گراترند +14645@http://i.hoder.com/ +iran +2005-10-12T19:45:32-05:00 + + + +لينکدونی‌ | به صبحانه آگهی بدهید +http://www.adbrite.com/mb/commerce/purchase_form.php?opid=24346&afsid=1 +خیلی ارزان و راحت است +14644@http://i.hoder.com/ +media/journalism +2005-10-12T17:23:15-05:00 + + + +لينکدونی‌ | نیروی انتظامی چگونه تابوهای هم‌جنس‌گرایانه را می‌شکند؛ فرنگوپولیس +http://farangeopolis.blogspot.com/2005/10/blog-post_08.html +از پس و پیش و حاشیه‌ی این ماجرا می‌توان یک مستند بی‌نظیر ساخت +14643@http://i.hoder.com/ +soc_popculture +2005-10-12T17:06:40-05:00 + + + +لينکدونی‌ | بازتاب توقیف شد +http://www.baztab.com/news/30201.php +اگر گفتید یک وب‌سایت را چطور توقیف می‌کنند؟ لابد ماوس‌شان را قایم می‌کنند. +14642@http://i.hoder.com/ +media/journalism +2005-10-12T14:41:57-05:00 + + + +لينکدونی‌ | رشد وب در سال 2005 از همیشه بیشتر بوده است" بی.بی.سی +http://news.bbc.co.uk/2/hi/technology/4325918.stm + +14640@http://i.hoder.com/ +tech +2005-10-12T13:04:46-05:00 + + + + + +==قرعه کشی گرین کارد به زودی شروع می‌شود== +http://nice.newsxphotos.biz/05/09/2007_dv_lottery_registration_to_begin_oct_5_14589.php + +14613@http://vagrantly.com +ads03 +2005-09-27T04:49:22-05:00 + + + + + + + + +پروژه‌ی هاروارد، قدم دوم +http://editormyself.info/archives/2005/10/051012_014641.shtml +اگر یادتان باشد چند وقت پیش نوشتم که دانشگاه هاروارد پروژه‌ای دارد با نام آواهای جهانی که در آن به وبلاگ‌های غیر انگلیسی‌زبان می‌پردازد. خواشتم که اگر کسی علاقه دارد ایمیل بزند. تعداد زیادی جواب دادند و ابراز علاقه کردند. حالا وقت قدم دوم است.

+ +

قدم دوم این است که برای اینکه مسوولین پروژه بتوانند تصمیم بگیرند که با چه کسی کار کنند، می‌خواهند نمونه‌ی کارهای علاقمندان مشارکت در این پرزو‌ه را ببینند.

+ +

برای همین از همه‌ی علاقماندان، حتی کسانی که قبلا اعلام آمادگی نکرده بودند، می‌‌خواهم که یک موضوع رایج این روزهای وبلاگستان فارسی را انتخاب کنند و در هفتصد کلمه، به انگلیسی، بنویسند که وبلاگ‌دارهای درباره‌اش چه می‌گویند. لینک به پنج، شش وبلاگ و بازنویسی آنچه آنها از جنبه‌های گوناگون درباره‌ی آن موضوع نوشته‌اند با نقل قول مستقیم از آنها (البته ترجمه شده از فارسی) کافی است. دو سه جمله هم اول کار توضیح دهید که چرا این موضوع مهم است.

+ +

متن نمونه را به آدرس ایمیل من hoder@hoder.com و نیز برای افراد زیر تا روز دوشنبه بفرستید:
+ربکا : rmackinnon@cyber.law.harvard.edu
+هیثم: haitham.sabbah@gmail.com

]]>
+14641@http://editormyself.info +weblog +2005-10-12T14:04:23-05:00 +
+ + + +
+
\ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/samples/illformed_atom10.xml b/plugins/FeedSub/extlib/XML/Feed/samples/illformed_atom10.xml new file mode 100755 index 000000000..612186897 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/samples/illformed_atom10.xml @@ -0,0 +1,13 @@ + + + + + Example author + me@example.com + http://example.com/ + + + + + + +Copyright 1997-1999 UserLand Software, Inc. +Thu, 08 Jul 1999 07:00:00 GMT +Thu, 08 Jul 1999 16:20:26 GMT +http://my.userland.com/stories/storyReader$11 +News and commentary from the cross-platform scripting community. +http://www.scripting.com/ +Scripting News + +http://www.scripting.com/ +Scripting News +http://www.scripting.com/gifs/tinyScriptingNews.gif +40 +78 +What is this used for? + +dave@userland.com (Dave Winer) +dave@userland.com (Dave Winer) +en-us + +6 +7 +8 +9 +10 +11 + + +Sunday + +(PICS-1.1 "http://www.rsac.org/ratingsv01.html" l gen true comment "RSACi North America Server" for "http://www.rsac.org" on "1996.04.16T08:15-0500" r (n 0 s 0 v 0 l 0)) + +stuff +http://bar +This is an article about some stuff + + +Search Now! +Enter your search <terms> +find +http://my.site.com/search.cgi + + + \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/samples/rss091-international.xml b/plugins/FeedSub/extlib/XML/Feed/samples/rss091-international.xml new file mode 100755 index 000000000..cfe91691f --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/samples/rss091-international.xml @@ -0,0 +1,30 @@ + + + + +膮ŸÛë´é´Ì´×´è´ŒÁ¹´Õ +http://www.mozilla.org +膮ŸÛë´é´Ì´×´è´ŒÁ¹´Õ +ja + +NYÒ™Á¢¸»ÌêÛì15285.25´ƒ´‘Á£´Û´—´ÀÁ¹´ê´Ì´éÒ™Ûì¡êçÒÕ‰ÌêÁ£ +http://www.mozilla.org/status/ +This is an item description... + + +‚§±Çç¡ËßÛÂҏéøÓ¸Á£Ë²®Ÿè†Ûè危ÇÌ’¡Íæ—éøë‡Á£ +http://www.mozilla.org/status/ +This is an item description... + + +ËÜË”ïÌëÈšÁ¢È†Ë§æàÀ豎ˉۂÁ¢Ë‚åܼšÛ˜íËüËÁ£ +http://www.mozilla.org/status/ +This is an item description... + + +2000‚øíŠåÁ¢«‘¦éÛ빏ېçéÛ§ÛÂè†ÒæÓ¸Á£Ì¾«…æ—ÕÝéøƒ¸Á£ +http://www.mozilla.org/status/ +This is an item description... + + + \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/samples/rss091-simple.xml b/plugins/FeedSub/extlib/XML/Feed/samples/rss091-simple.xml new file mode 100755 index 000000000..f0964a227 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/samples/rss091-simple.xml @@ -0,0 +1,15 @@ + + + + +en +News and commentary from the cross-platform scripting community. +http://www.scripting.com/ +Scripting News + +http://www.scripting.com/ +Scripting News +http://www.scripting.com/gifs/tinyScriptingNews.gif + + + \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/samples/rss092-sample.xml b/plugins/FeedSub/extlib/XML/Feed/samples/rss092-sample.xml new file mode 100755 index 000000000..5d75c352b --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/samples/rss092-sample.xml @@ -0,0 +1,103 @@ + + + + + Dave Winer: Grateful Dead + http://www.scripting.com/blog/categories/gratefulDead.html + A high-fidelity Grateful Dead song every day. This is where we're experimenting with enclosures on RSS news items that download when you're not using your computer. If it works (it will) it will be the end of the Click-And-Wait multimedia experience on the Internet. + Fri, 13 Apr 2001 19:23:02 GMT + http://backend.userland.com/rss092 + dave@userland.com (Dave Winer) + dave@userland.com (Dave Winer) + + + It's been a few days since I added a song to the Grateful Dead channel. Now that there are all these new Radio users, many of whom are tuned into this channel (it's #16 on the hotlist of upstreaming Radio users, there's no way of knowing how many non-upstreaming users are subscribing, have to do something about this..). Anyway, tonight's song is a live version of Weather Report Suite from Dick's Picks Volume 7. It's wistful music. Of course a beautiful song, oft-quoted here on Scripting News. <i>A little change, the wind and rain.</i> + + + + + Kevin Drennan started a <a href="http://deadend.editthispage.com/">Grateful Dead Weblog</a>. Hey it's cool, he even has a <a href="http://deadend.editthispage.com/directory/61">directory</a>. <i>A Frontier 7 feature.</i> + Scripting News + + + <a href="http://arts.ucsc.edu/GDead/AGDL/other1.html">The Other One</a>, live instrumental, One From The Vault. Very rhythmic very spacy, you can listen to it many times, and enjoy something new every time. + + + + This is a test of a change I just made. Still diggin.. + + + The HTML rendering almost <a href="http://validator.w3.org/check/referer">validates</a>. Close. Hey I wonder if anyone has ever published a style guide for ALT attributes on images? What are you supposed to say in the ALT attribute? I sure don't know. If you're blind send me an email if u cn rd ths. + + + <a href="http://www.cs.cmu.edu/~mleone/gdead/dead-lyrics/Franklin's_Tower.txt">Franklin's Tower</a>, a live version from One From The Vault. + + + + Moshe Weitzman says Shakedown Street is what I'm lookin for for tonight. I'm listening right now. It's one of my favorites. "Don't tell me this town ain't got no heart." Too bright. I like the jazziness of Weather Report Suite. Dreamy and soft. How about The Other One? "Spanish lady come to me.." + Scripting News + + + <a href="http://www.scripting.com/mp3s/youWinAgain.mp3">The news is out</a>, all over town..<p> +You've been seen, out runnin round. <p> +The lyrics are <a href="http://www.cs.cmu.edu/~mleone/gdead/dead-lyrics/You_Win_Again.txt">here</a>, short and sweet. <p> +<i>You win again!</i> + + + + + <a href="http://www.getlyrics.com/lyrics/grateful-dead/wake-of-the-flood/07.htm">Weather Report Suite</a>: "Winter rain, now tell me why, summers fade, and roses die? The answer came. The wind and rain. Golden hills, now veiled in grey, summer leaves have blown away. Now what remains? The wind and rain." + + + + <a href="http://arts.ucsc.edu/gdead/agdl/darkstar.html">Dark Star</a> crashes, pouring its light into ashes. + + + + DaveNet: <a href="http://davenet.userland.com/2001/01/21/theUsBlues">The U.S. Blues</a>. + + + Still listening to the US Blues. <i>"Wave that flag, wave it wide and high.."</i> Mistake made in the 60s. We gave our country to the assholes. Ah ah. Let's take it back. Hey I'm still a hippie. <i>"You could call this song The United States Blues."</i> + + + <a href="http://www.sixties.com/html/garcia_stack_0.html"><img src="http://www.scripting.com/images/captainTripsSmall.gif" height="51" width="42" border="0" hspace="10" vspace="10" align="right"></a>In celebration of today's inauguration, after hearing all those great patriotic songs, America the Beautiful, even The Star Spangled Banner made my eyes mist up. It made my choice of Grateful Dead song of the night realllly easy. Here are the <a href="http://searchlyrics2.homestead.com/gd_usblues.html">lyrics</a>. Click on the audio icon to the left to give it a listen. "Red and white, blue suede shoes, I'm Uncle Sam, how do you do?" It's a different kind of patriotic music, but man I love my country and I love Jerry and the band. <i>I truly do!</i> + + + + Grateful Dead: "Tennessee, Tennessee, ain't no place I'd rather be." + + + + Ed Cone: "Had a nice Deadhead experience with my wife, who never was one but gets the vibe and knows and likes a lot of the music. Somehow she made it to the age of 40 without ever hearing Wharf Rat. We drove to Jersey and back over Christmas with the live album commonly known as Skull and Roses in the CD player much of the way, and it was cool to see her discover one the band's finest moments. That song is unique and underappreciated. Fun to hear that disc again after a few years off -- you get Jerry as blues-guitar hero on Big Railroad Blues and a nice version of Bertha." + + + + <a href="http://arts.ucsc.edu/GDead/AGDL/fotd.html">Tonight's Song</a>: "If I get home before daylight I just might get some sleep tonight." + + + + <a href="http://arts.ucsc.edu/GDead/AGDL/uncle.html">Tonight's song</a>: "Come hear Uncle John's Band by the river side. Got some things to talk about here beside the rising tide." + + + + <a href="http://www.cs.cmu.edu/~mleone/gdead/dead-lyrics/Me_and_My_Uncle.txt">Me and My Uncle</a>: "I loved my uncle, God rest his soul, taught me good, Lord, taught me all I know. Taught me so well, I grabbed that gold and I left his dead ass there by the side of the road." + + + + + Truckin, like the doo-dah man, once told me gotta play your hand. Sometimes the cards ain't worth a dime, if you don't lay em down. + + + + Two-Way-Web: <a href="http://www.thetwowayweb.com/payloadsForRss">Payloads for RSS</a>. "When I started talking with Adam late last year, he wanted me to think about high quality video on the Internet, and I totally didn't want to hear about it." + + + A touch of gray, kinda suits you anyway.. + + + + <a href="http://www.sixties.com/html/garcia_stack_0.html"><img src="http://www.scripting.com/images/captainTripsSmall.gif" height="51" width="42" border="0" hspace="10" vspace="10" align="right"></a>In celebration of today's inauguration, after hearing all those great patriotic songs, America the Beautiful, even The Star Spangled Banner made my eyes mist up. It made my choice of Grateful Dead song of the night realllly easy. Here are the <a href="http://searchlyrics2.homestead.com/gd_usblues.html">lyrics</a>. Click on the audio icon to the left to give it a listen. "Red and white, blue suede shoes, I'm Uncle Sam, how do you do?" It's a different kind of patriotic music, but man I love my country and I love Jerry and the band. <i>I truly do!</i> + + + + \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/samples/rss10-example1.xml b/plugins/FeedSub/extlib/XML/Feed/samples/rss10-example1.xml new file mode 100755 index 000000000..0edecf58e --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/samples/rss10-example1.xml @@ -0,0 +1,62 @@ + + + + + + XML.com + http://xml.com/pub + + XML.com features a rich mix of information and services + for the XML community. + + + + + + + + + + + + + + + + + XML.com + http://www.xml.com + http://xml.com/universal/images/xml_tiny.gif + + + + Processing Inclusions with XSLT + http://xml.com/pub/2000/08/09/xslt/xslt.html + + Processing document inclusions with general XML tools can be + problematic. This article proposes a way of preserving inclusion + information through SAX-based processing. + + + + + Putting RDF to Work + http://xml.com/pub/2000/08/09/rdfdb/index.html + + Tool and API support for the Resource Description Framework + is slowly coming of age. Edd Dumbill takes a look at RDFDB, + one of the most exciting new RDF toolkits. + + + + + Search XML.com + Search XML.com's XML collection + s + http://search.xml.com + + + \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/samples/rss10-example2.xml b/plugins/FeedSub/extlib/XML/Feed/samples/rss10-example2.xml new file mode 100755 index 000000000..26235f78f --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/samples/rss10-example2.xml @@ -0,0 +1,67 @@ + + + + + + Meerkat + http://meerkat.oreillynet.com + Meerkat: An Open Wire Service + The O'Reilly Network + Rael Dornfest (mailto:rael@oreilly.com) + Copyright © 2000 O'Reilly & Associates, Inc. + 2000-01-01T12:00+00:00 + hourly + 2 + 2000-01-01T12:00+00:00 + + + + + + + + + + + + + + + Meerkat Powered! + http://meerkat.oreillynet.com/icons/meerkat-powered.jpg + http://meerkat.oreillynet.com + + + + XML: A Disruptive Technology + http://c.moreover.com/click/here.pl?r123 + + XML is placing increasingly heavy loads on the existing technical + infrastructure of the Internet. + + The O'Reilly Network + Simon St.Laurent (mailto:simonstl@simonstl.com) + Copyright © 2000 O'Reilly & Associates, Inc. + XML + XML.com + NASDAQ + XML + + + + Search Meerkat + Search Meerkat's RSS Database... + s + http://meerkat.oreillynet.com/ + search + regex + + + \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/samples/rss2sample.xml b/plugins/FeedSub/extlib/XML/Feed/samples/rss2sample.xml new file mode 100755 index 000000000..53483cc51 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/samples/rss2sample.xml @@ -0,0 +1,42 @@ + + + + Liftoff News + http://liftoff.msfc.nasa.gov/ + Liftoff to Space Exploration. + en-us + Tue, 10 Jun 2003 04:00:00 GMT + Tue, 10 Jun 2003 09:41:01 GMT + http://blogs.law.harvard.edu/tech/rss + Weblog Editor 2.0 + editor@example.com + webmaster@example.com + + Star City + http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp + How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia's <a href="http://howe.iki.rssi.ru/GCTC/gctc_e.htm">Star City</a>. + Tue, 03 Jun 2003 09:39:21 GMT + http://liftoff.msfc.nasa.gov/2003/06/03.html#item573 + + + Sky watchers in Europe, Asia, and parts of Alaska and Canada will experience a <a href="http://science.nasa.gov/headlines/y2003/30may_solareclipse.htm">partial eclipse of the Sun</a> on Saturday, May 31st. + Fri, 30 May 2003 11:06:42 GMT + http://liftoff.msfc.nasa.gov/2003/05/30.html#item572 + + + The Engine That Does More + http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp + Before man travels to Mars, NASA hopes to design new engines that will let us fly through the Solar System more quickly. The proposed VASIMR engine would do that. + Tue, 27 May 2003 08:37:32 GMT + http://liftoff.msfc.nasa.gov/2003/05/27.html#item571 + Test content

]]>
+
+ + Astronauts' Dirty Laundry + http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp + Compared to earlier spacecraft, the International Space Station has many luxuries, but laundry facilities are not one of them. Instead, astronauts have other options. + Tue, 20 May 2003 08:56:02 GMT + http://liftoff.msfc.nasa.gov/2003/05/20.html#item570 + +
+
\ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/samples/sixapart-jp.xml b/plugins/FeedSub/extlib/XML/Feed/samples/sixapart-jp.xml new file mode 100755 index 000000000..f8a04bba5 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/samples/sixapart-jp.xml @@ -0,0 +1,226 @@ + + + +Six Apart - News +http://www.sixapart.jp/ + +ja +Copyright 2005 +Fri, 07 Oct 2005 19:09:34 +0900 +http://www.movabletype.org/?v=3.2-ja +http://blogs.law.harvard.edu/tech/rss + + +ファイブ・ディーが、Movable Typeでブログプロモーションをスタート +MIYAZAWAblog_banner.jpg
+ファイブ・ディーは、Movable Typeで構築したプロモーション ブログ『宮沢和史 中南米ツアーblog Latin America 2005』を開設しました。

+ +

9月21日に開設されたこのブログは、ブラジル、ホンジュラス、ニカラグア、メキシコ、キューバの5か国を巡る「Latin America 2005」ツアーに合わせ、そのツアーの模様を同行マネージャーがレポートしていきます。
+さらに今月2日からは宮沢和史自身が日々録音した声をPodcastingするという点でも、ブログを使ったユニークなプロモーションとなっています。

+ +

「宮沢和史 中南米ツアーblog Latin America 2005」

+ +

※シックス・アパートではこうしたブログを使ったプロモーションに最適な製品をご用意しております。
+

]]>
+http://www.sixapart.jp/news/2005/10/07-1909.html +http://www.sixapart.jp/news/2005/10/07-1909.html +news +Fri, 07 Oct 2005 19:09:34 +0900 +
+ +Movable Type 3.2日本語版の提供を開始 +Movable Type Logo

+

シックス・アパートは、Movable Type 3.2日本語版の提供を開始いたしました。
+ベータテストにご協力いただいた多くの皆様に、スタッフ一同、心から感謝いたします。

+

製品概要など、詳しくはプレスリリースをご参照下さい。

+

ご購入のご検討は、Movable Typeのご購入からどうぞ。

]]>
+http://www.sixapart.jp/news/2005/09/29-1530.html +http://www.sixapart.jp/news/2005/09/29-1530.html +news +Thu, 29 Sep 2005 15:30:00 +0900 +
+ +シックス・アパートが、スパム対策強化の「Movable Type 3.2 日本語版」を提供開始 +<プレスリリース資料>

+ +

シックス・アパートが、スパム対策強化の「Movable Type 3.2 日本語版」を提供開始 ~ スパムの自動判別機能や新ユーザー・インターフェースで、運用管理の機能を強化 ~

+

2005年9月29日
+シックス・アパート株式会社

+

ブログ・ソフトウェア大手のシックス・アパート株式会社(本社:東京都港区、代表取締役:関 信浩)は、「Movable Type(ムーバブル・タイプ) 3.2 日本語版」(URL:http://www.sixapart.jp/movabletype/)を9月29日より提供開始いたします。

]]>
+http://www.sixapart.jp/press_releases/2005/09/29-1529.html +http://www.sixapart.jp/press_releases/2005/09/29-1529.html +Press Releases +Thu, 29 Sep 2005 15:29:00 +0900 +
+ +スタッフを募集しています +シックス・アパートはMovable TypeやTypePadの開発エンジニアなど、スタッフを広く募集しています。具体的な募集職種は次の通りです。

+ + + +

拡大を続ける、日本のブログ市場を積極的にリードする人材を、シックス・アパートは募集しています。上記以外の職種につきましても、お気軽にお問い合わせください。詳しい募集要項や応募方法については、求人情報のページをご覧ください。
+

]]>
+http://www.sixapart.jp/news/2005/09/27-0906.html +http://www.sixapart.jp/news/2005/09/27-0906.html +news +Tue, 27 Sep 2005 09:06:10 +0900 +
+ +サイト接続不具合に関するお詫びと復旧のお知らせ +9月24日(土)の14:45ごろから、同日18:30ごろまで、シックス・アパート社のウェブサイトが不安定になっており、断続的に接続できない不具合が発生しておりました。このため、この期間中にウェブサイトの閲覧や製品のダウンロードができませんでした。

+ +

なお現在は不具合は解消しております。みなさまにご迷惑をおかけしたことをお詫びいたします。

]]>
+http://www.sixapart.jp/news/2005/09/26-1000.html +http://www.sixapart.jp/news/2005/09/26-1000.html +news +Mon, 26 Sep 2005 10:00:56 +0900 +
+ +企業ブログ向けパッケージ「TypePad Promotion」を新発売 +シックス・アパートは、ウェブログ・サービスTypePadの企業ブログ向けパッケージ「TypePad Promotion」(タイプパッド・プロモーションの発売を10月下旬から開始いたします。

+ +

詳しくは、プレスリリースをご参照下さい。

]]>
+http://www.sixapart.jp/news/2005/09/20-1500.html +http://www.sixapart.jp/news/2005/09/20-1500.html +news +Tue, 20 Sep 2005 15:00:01 +0900 +
+ +シックス・アパートが、法人向けブログパッケージ「TypePad Promotion」を発売 +<プレスリリース資料>
+印刷用(PDF版)

+ +


+シックス・アパートが、法人向けブログパッケージ「TypePad Promotion」を発売
+~PR/IRサイトやキャンペーンサイトなど企業のプロモーションニーズに特化~
+

+2005年9月20日
+シックス・アパート株式会社

+ +

ブログ・サービス大手のシックス・アパート株式会社(本社:東京都港区、代表取締役:関 信浩)は、法人向けプロモーションブログ・パッケージ「TypePad Promotion(タイプパッド・プロモーション)」(URL:http://www.sixapart.jp/typepad/typepad_promotion.html)を10月下旬より販売開始いたします。

]]>
+http://www.sixapart.jp/press_releases/2005/09/20-1500.html +http://www.sixapart.jp/press_releases/2005/09/20-1500.html +Press Releases +Tue, 20 Sep 2005 15:00:00 +0900 +
+ +Six [days] Apart Week +本日、9月16日はSix Apartの創業者ミナ・トロットの誕生日です。
+私たちの会社は、創業者のトロット夫妻(ベンとミナ)の誕生日が、6日離れていることからSix [days] Apart →Six Apartという風に名付けられています。本日から22日までの6日間を社名の由来となる Six [days] Apart Weekとして、私たちのプロダクトをご紹介させていただきます。

+ +

今日は、ブログ・サービスのTypePad(タイプパッド)をご紹介します。
+tp-logo.gif

+ +

TypePadは、米国PC MAGAZINE誌の2003年EDITOR'S CHOICE とBEST OF THE YEARに選ばれております。
+pcmag-ad.gif
+

]]>
+http://www.sixapart.jp/news/2005/09/16-1941.html +http://www.sixapart.jp/news/2005/09/16-1941.html +news +Fri, 16 Sep 2005 19:41:47 +0900 +
+ +ハイパーワークスが商用フォントを利用できるMovable Typeホスティングサービスを開始 +ソフト開発会社の有限会社ハイパーワークスは、商用フォントなど多彩なフォントをブログ上で利用できるブログ・サービス「Glyph-On!(グリフォン) Movable Type ホスティング サービス」の提供を開始しました。
+

]]>
+http://www.sixapart.jp/news/2005/09/14-1700.html +http://www.sixapart.jp/news/2005/09/14-1700.html +news +Wed, 14 Sep 2005 17:00:00 +0900 +
+ +Movable Type開発エンジニアの募集 + +勤務形態: フルタイム
+勤務地: 東京 (赤坂)
+職種: ソフトウェア・エンジニア
+職務内容: Movable Typeの開発業務全般
+募集人数: 若干名 +

]]>
+http://www.sixapart.jp/jobs/2005/09/13-0007.html +http://www.sixapart.jp/jobs/2005/09/13-0007.html +Jobs +Tue, 13 Sep 2005 00:07:00 +0900 +
+ +TypePad開発エンジニアの募集 + +勤務形態: フルタイム
+勤務地: 東京 (赤坂)
+職種: アプリケーション・エンジニア
+職務内容: TypePadのカスタマイズ、周辺開発
+募集人数: 若干名 +

]]>
+http://www.sixapart.jp/jobs/2005/09/13-0004.html +http://www.sixapart.jp/jobs/2005/09/13-0004.html +Jobs +Tue, 13 Sep 2005 00:04:00 +0900 +
+ +カスタマーサポート・ディレクターの募集 +勤務形態: フルタイム
+勤務地: 東京(赤坂)
+職種: カスタマーサポート・ディレクター
+職務内容: TypePadやMovable Typeのカスタマーサポート業務の統括
+募集人数: 若干名 +

+]]>
+http://www.sixapart.jp/jobs/2005/09/13-0003.html +http://www.sixapart.jp/jobs/2005/09/13-0003.html +Jobs +Tue, 13 Sep 2005 00:03:30 +0900 +
+ +アルバイト(マーケティング・広報アシスタント)の募集 +勤務形態: アルバイト
+勤務地: 東京(港区)
+職種:マーケティング・PRのアシスタント業務
+募集人数: 若干名
+時給:1000円~(但し、試用期間終了後に応相談)。交通費支給
+時間:平日10時30分~18時30分まで。週3日以上(応相談)
+

]]>
+http://www.sixapart.jp/jobs/2005/09/13-0002.html +http://www.sixapart.jp/jobs/2005/09/13-0002.html +Jobs +Tue, 13 Sep 2005 00:02:00 +0900 +
+ +アルバイト(開発アシスタント)の募集 +勤務形態: アルバイト
+勤務地: 東京(港区)
+職種: アプリケーション開発のアシスタント業務
+募集人数: 若干名
+時給:1000円~(但し、試用期間終了後に応相談)。交通費支給
+時間:平日10時30分~18時30分まで。週3日以上(応相談) +

]]>
+http://www.sixapart.jp/jobs/2005/09/13-0001.html +http://www.sixapart.jp/jobs/2005/09/13-0001.html +Jobs +Tue, 13 Sep 2005 00:01:00 +0900 +
+ +TypePad Japan がバージョンアップしました。 +「TypePad Japan(タイプパッドジャパン)」において、本日、「TypePad 1.6 日本語版」へのバージョンアップを行いました。最新版となる「TypePad 1.6 日本語版」では、ブログデザインの機能強化、ポッドキャスティング対応、モブログ対応に加え、今回新たに大幅な容量アップが行われております。皆様、新しくなったTypePad Japanにどうぞご期待ください。

+ +

なお、TypePadの携帯対応強化に関しましては、本日よりTypePad Japanのお客様を対象にオープン・ベータを開始しております。

+ +

2005年9月5日発表のTypePad日本語版 1.6プレスリリースはこちらをご覧下さい。

]]>
+http://www.sixapart.jp/news/2005/09/12-1953.html +http://www.sixapart.jp/news/2005/09/12-1953.html +news +Mon, 12 Sep 2005 19:53:07 +0900 +
+ + +
+
\ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/samples/technorati.feed b/plugins/FeedSub/extlib/XML/Feed/samples/technorati.feed new file mode 100755 index 000000000..6274a32cd --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/samples/technorati.feed @@ -0,0 +1,54 @@ + + + + [Technorati] Tag results for greenbelt + http://www.technorati.com/tag/greenbelt + Posts tagged with "greenbelt" on Technorati. + Mon, 08 Aug 2005 15:15:08 GMT + greenbelt + 2 + 2 + + Technorati v1.0 + + http://static.technorati.com/pix/logos/logo_reverse_sm.gif + Technorati logo + http://www.technorati.com + + + 1 + 7 + 9 + + support@technorati.com (Technorati Support) + http://blogs.law.harvad.edu/tech/rss + 60 + + Greenbelt + http://maggidawn.typepad.com/maggidawn/2005/07/greenbelt.html + So if the plan goes according to plan (!)... I'll be speaking at Greenbelt at these times: Slot 1... + http://maggidawn.typepad.com/maggidawn/2005/07/greenbelt.html + Mon, 18 Jul 2005 02:11:42 GMT + James + 2005-07-11 02:08:12 + http://www.technorati.com/cosmos/search.html?url=http%3A%2F%2Fmaggidawn.typepad.com%2Fmaggidawn%2F2005%2F07%2Fgreenbelt.html + 190 + 237 + maggi dawn + + + + Walking along the Greenbelt + http://pictureshomeless.blogspot.com/2005/06/walking-along-greenbelt.html + [IMG] Photo of homeless man walking near the greenbelt in Boise, Idaho Tags: photo homeless greenbelt Boise Idaho picture + http://pictureshomeless.blogspot.com/2005/06/walking-along-greenbelt.html + Tue, 28 Jun 2005 01:41:24 GMT + 2005-06-26 17:24:03 + http://www.technorati.com/cosmos/search.html?url=http%3A%2F%2Fpictureshomeless.blogspot.com%2F2005%2F06%2Fwalking-along-greenbelt.html + 2 + 2 + + + + diff --git a/plugins/FeedSub/extlib/XML/Feed/schemas/atom.rnc b/plugins/FeedSub/extlib/XML/Feed/schemas/atom.rnc new file mode 100755 index 000000000..e662d2626 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/schemas/atom.rnc @@ -0,0 +1,338 @@ +# -*- rnc -*- +# RELAX NG Compact Syntax Grammar for the +# Atom Format Specification Version 11 + +namespace atom = "http://www.w3.org/2005/Atom" +namespace xhtml = "http://www.w3.org/1999/xhtml" +namespace s = "http://www.ascc.net/xml/schematron" +namespace local = "" + +start = atomFeed | atomEntry + +# Common attributes + +atomCommonAttributes = + attribute xml:base { atomUri }?, + attribute xml:lang { atomLanguageTag }?, + undefinedAttribute* + +# Text Constructs + +atomPlainTextConstruct = + atomCommonAttributes, + attribute type { "text" | "html" }?, + text + +atomXHTMLTextConstruct = + atomCommonAttributes, + attribute type { "xhtml" }, + xhtmlDiv + +atomTextConstruct = atomPlainTextConstruct | atomXHTMLTextConstruct + +# Person Construct + +atomPersonConstruct = + atomCommonAttributes, + (element atom:name { text } + & element atom:uri { atomUri }? + & element atom:email { atomEmailAddress }? + & extensionElement*) + +# Date Construct + +atomDateConstruct = + atomCommonAttributes, + xsd:dateTime + +# atom:feed + +atomFeed = + [ + s:rule [ + context = "atom:feed" + s:assert [ + test = "atom:author or not(atom:entry[not(atom:author)])" + "An atom:feed must have an atom:author unless all " + ~ "of its atom:entry children have an atom:author." + ] + ] + ] + element atom:feed { + atomCommonAttributes, + (atomAuthor* + & atomCategory* + & atomContributor* + & atomGenerator? + & atomIcon? + & atomId + & atomLink* + & atomLogo? + & atomRights? + & atomSubtitle? + & atomTitle + & atomUpdated + & extensionElement*), + atomEntry* + } + +# atom:entry + +atomEntry = + [ + s:rule [ + context = "atom:entry" + s:assert [ + test = "atom:link[@rel='alternate'] " + ~ "or atom:link[not(@rel)] " + ~ "or atom:content" + "An atom:entry must have at least one atom:link element " + ~ "with a rel attribute of 'alternate' " + ~ "or an atom:content." + ] + ] + s:rule [ + context = "atom:entry" + s:assert [ + test = "atom:author or " + ~ "../atom:author or atom:source/atom:author" + "An atom:entry must have an atom:author " + ~ "if its feed does not." + ] + ] + ] + element atom:entry { + atomCommonAttributes, + (atomAuthor* + & atomCategory* + & atomContent? + & atomContributor* + & atomId + & atomLink* + & atomPublished? + & atomRights? + & atomSource? + & atomSummary? + & atomTitle + & atomUpdated + & extensionElement*) + } + +# atom:content + +atomInlineTextContent = + element atom:content { + atomCommonAttributes, + attribute type { "text" | "html" }?, + (text)* + } + +atomInlineXHTMLContent = + element atom:content { + atomCommonAttributes, + attribute type { "xhtml" }, + xhtmlDiv + } + +atomInlineOtherContent = + element atom:content { + atomCommonAttributes, + attribute type { atomMediaType }?, + (text|anyElement)* + } + +atomOutOfLineContent = + element atom:content { + atomCommonAttributes, + attribute type { atomMediaType }?, + attribute src { atomUri }, + empty + } + +atomContent = atomInlineTextContent + | atomInlineXHTMLContent + | atomInlineOtherContent + | atomOutOfLineContent + +# atom:author + +atomAuthor = element atom:author { atomPersonConstruct } + +# atom:category + +atomCategory = + element atom:category { + atomCommonAttributes, + attribute term { text }, + attribute scheme { atomUri }?, + attribute label { text }?, + undefinedContent + } + +# atom:contributor + +atomContributor = element atom:contributor { atomPersonConstruct } + +# atom:generator + +atomGenerator = element atom:generator { + atomCommonAttributes, + attribute uri { atomUri }?, + attribute version { text }?, + text +} + +# atom:icon + +atomIcon = element atom:icon { + atomCommonAttributes, + (atomUri) +} + +# atom:id + +atomId = element atom:id { + atomCommonAttributes, + (atomUri) +} + +# atom:logo + +atomLogo = element atom:logo { + atomCommonAttributes, + (atomUri) +} + +# atom:link + +atomLink = + element atom:link { + atomCommonAttributes, + attribute href { atomUri }, + attribute rel { atomNCName | atomUri }?, + attribute type { atomMediaType }?, + attribute hreflang { atomLanguageTag }?, + attribute title { text }?, + attribute length { text }?, + undefinedContent + } + +# atom:published + +atomPublished = element atom:published { atomDateConstruct } + +# atom:rights + +atomRights = element atom:rights { atomTextConstruct } + +# atom:source + +atomSource = + element atom:source { + atomCommonAttributes, + (atomAuthor* + & atomCategory* + & atomContributor* + & atomGenerator? + & atomIcon? + & atomId? + & atomLink* + & atomLogo? + & atomRights? + & atomSubtitle? + & atomTitle? + & atomUpdated? + & extensionElement*) + } + +# atom:subtitle + +atomSubtitle = element atom:subtitle { atomTextConstruct } + +# atom:summary + +atomSummary = element atom:summary { atomTextConstruct } + +# atom:title + +atomTitle = element atom:title { atomTextConstruct } + +# atom:updated + +atomUpdated = element atom:updated { atomDateConstruct } + +# Low-level simple types + +atomNCName = xsd:string { minLength = "1" pattern = "[^:]*" } + +# Whatever a media type is, it contains at least one slash +atomMediaType = xsd:string { pattern = ".+/.+" } + +# As defined in RFC 3066 +atomLanguageTag = xsd:string { + pattern = "[A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})*" +} + +# Unconstrained; it's not entirely clear how IRI fit into +# xsd:anyURI so let's not try to constrain it here +atomUri = text + +# Whatever an email address is, it contains at least one @ +atomEmailAddress = xsd:string { pattern = ".+@.+" } + +# Simple Extension + +simpleExtensionElement = + element * - atom:* { + text + } + +# Structured Extension + +structuredExtensionElement = + element * - atom:* { + (attribute * { text }+, + (text|anyElement)*) + | (attribute * { text }*, + (text?, anyElement+, (text|anyElement)*)) + } + +# Other Extensibility + +extensionElement = + simpleExtensionElement | structuredExtensionElement + +undefinedAttribute = + attribute * - (xml:base | xml:lang | local:*) { text } + +undefinedContent = (text|anyForeignElement)* + +anyElement = + element * { + (attribute * { text } + | text + | anyElement)* + } + +anyForeignElement = + element * - atom:* { + (attribute * { text } + | text + | anyElement)* + } + +# XHTML + +anyXHTML = element xhtml:* { + (attribute * { text } + | text + | anyXHTML)* +} + +xhtmlDiv = element xhtml:div { + (attribute * { text } + | text + | anyXHTML)* +} + +# EOF \ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/schemas/rss10.rnc b/plugins/FeedSub/extlib/XML/Feed/schemas/rss10.rnc new file mode 100755 index 000000000..725094788 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/schemas/rss10.rnc @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/FeedSub/extlib/XML/Feed/schemas/rss11.rnc b/plugins/FeedSub/extlib/XML/Feed/schemas/rss11.rnc new file mode 100755 index 000000000..c8633766f --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/schemas/rss11.rnc @@ -0,0 +1,218 @@ + + + + + + + + http://purl.org/net/rss1.1#Channel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + http://purl.org/net/rss1.1#title + + + + + + + + + + + + + + http://purl.org/net/rss1.1#link + + + + + + + + + + http://purl.org/net/rss1.1#description + + + + + + + + + + + + + http://purl.org/net/rss1.1#image + + + + + + + + + + + + + + + + + + + + + + + + + http://purl.org/net/rss1.1#url + + + + + + + + + + http://purl.org/net/rss1.1#items + + + + + + + + + + + + + + + + + http://purl.org/net/rss1.1#item + + + + + + + + + + + + + + + + + + + + + + + + + + + + http://purl.org/net/rss1.1#Any + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Resource + + + + + Collection + + + + diff --git a/plugins/FeedSub/extlib/xml-feed-parser-bug-16416.patch b/plugins/FeedSub/extlib/xml-feed-parser-bug-16416.patch new file mode 100644 index 000000000..c53bd9737 --- /dev/null +++ b/plugins/FeedSub/extlib/xml-feed-parser-bug-16416.patch @@ -0,0 +1,14 @@ +diff --git a/htdocs/lib/pear/XML/Feed/Parser/RSS2.php b/htdocs/lib/pear/XML/Feed/Parser/RSS2.php +index c5d79d1..308a4ab 100644 +--- a/htdocs/lib/pear/XML/Feed/Parser/RSS2.php ++++ b/htdocs/lib/pear/XML/Feed/Parser/RSS2.php +@@ -321,7 +321,8 @@ class XML_Feed_Parser_RSS2 extends XML_Feed_Parser_Type + */ + function getLink($offset, $attribute = 'href', $params = array()) + { +- $links = $this->model->getElementsByTagName('link'); ++ $xPath = new DOMXPath($this->model); ++ $links = $xPath->query('//link'); + + if ($links->length <= $offset) { + return false; diff --git a/plugins/FeedSub/feeddiscovery.php b/plugins/FeedSub/feeddiscovery.php new file mode 100644 index 000000000..35edaca33 --- /dev/null +++ b/plugins/FeedSub/feeddiscovery.php @@ -0,0 +1,209 @@ +. + */ + +/** + * @package FeedSubPlugin + * @maintainer Brion Vibber + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +class FeedSubBadURLException extends FeedSubException +{ +} + +class FeedSubBadResponseException extends FeedSubException +{ +} + +class FeedSubEmptyException extends FeedSubException +{ +} + +class FeedSubBadHTMLException extends FeedSubException +{ +} + +class FeedSubUnrecognizedTypeException extends FeedSubException +{ +} + +class FeedSubNoFeedException extends FeedSubException +{ +} + +class FeedDiscovery +{ + public $uri; + public $type; + public $body; + + + public function feedMunger() + { + require_once 'XML/Feed/Parser.php'; + $feed = new XML_Feed_Parser($this->body, false, false, true); // @fixme + return new FeedMunger($feed, $this->uri); + } + + /** + * @param string $url + * @param bool $htmlOk + * @return string with validated URL + * @throws FeedSubBadURLException + * @throws FeedSubBadHtmlException + * @throws FeedSubNoFeedException + * @throws FeedSubEmptyException + * @throws FeedSubUnrecognizedTypeException + */ + function discoverFromURL($url, $htmlOk=true) + { + try { + $client = new HTTPClient(); + $response = $client->get($url); + } catch (HTTP_Request2_Exception $e) { + throw new FeedSubBadURLException($e); + } + + if ($htmlOk) { + $type = $response->getHeader('Content-Type'); + $isHtml = preg_match('!^(text/html|application/xhtml\+xml)!i', $type); + if ($isHtml) { + $target = $this->discoverFromHTML($response->getUrl(), $response->getBody()); + if (!$target) { + throw new FeedSubNoFeedException($url); + } + return $this->discoverFromURL($target, false); + } + } + + return $this->initFromResponse($response); + } + + function initFromResponse($response) + { + if (!$response->isOk()) { + throw new FeedSubBadResponseException($response->getCode()); + } + + $sourceurl = $response->getUrl(); + $body = $response->getBody(); + if (!$body) { + throw new FeedSubEmptyException($sourceurl); + } + + $type = $response->getHeader('Content-Type'); + if (preg_match('!^(text/xml|application/xml|application/(rss|atom)\+xml)!i', $type)) { + $this->uri = $sourceurl; + $this->type = $type; + $this->body = $body; + return true; + } else { + common_log(LOG_WARNING, "Unrecognized feed type $type for $sourceurl"); + throw new FeedSubUnrecognizedTypeException($type); + } + } + + /** + * @param string $url source URL, used to resolve relative links + * @param string $body HTML body text + * @return mixed string with URL or false if no target found + */ + function discoverFromHTML($url, $body) + { + // DOMDocument::loadHTML may throw warnings on unrecognized elements. + $old = error_reporting(error_reporting() & ~E_WARNING); + $dom = new DOMDocument(); + $ok = $dom->loadHTML($body); + error_reporting($old); + + if (!$ok) { + throw new FeedSubBadHtmlException(); + } + + // Autodiscovery links may be relative to the page's URL or + $base = false; + $nodes = $dom->getElementsByTagName('base'); + for ($i = 0; $i < $nodes->length; $i++) { + $node = $nodes->item($i); + if ($node->hasAttributes()) { + $href = $node->attributes->getNamedItem('href'); + if ($href) { + $base = trim($href->value); + } + } + } + if ($base) { + $base = $this->resolveURI($base, $url); + } else { + $base = $url; + } + + // Ok... now on to the links! + // @fixme merge with the munger link checks + $nodes = $dom->getElementsByTagName('link'); + for ($i = 0; $i < $nodes->length; $i++) { + $node = $nodes->item($i); + if ($node->hasAttributes()) { + $rel = $node->attributes->getNamedItem('rel'); + $type = $node->attributes->getNamedItem('type'); + $href = $node->attributes->getNamedItem('href'); + if ($rel && $type && $href) { + $rel = trim($rel->value); + $type = trim($type->value); + $href = trim($href->value); + + $feedTypes = array( + 'application/rss+xml', + 'application/atom+xml', + ); + if (trim($rel) == 'alternate' && in_array($type, $feedTypes)) { + return $this->resolveURI($href, $base); + } + } + } + } + + return false; + } + + /** + * Resolve a possibly relative URL against some absolute base URL + * @param string $rel relative or absolute URL + * @param string $base absolute URL + * @return string absolute URL, or original URL if could not be resolved. + */ + function resolveURI($rel, $base) + { + require_once "Net/URL2.php"; + try { + $relUrl = new Net_URL2($rel); + if ($relUrl->isAbsolute()) { + return $rel; + } + $baseUrl = new Net_URL2($base); + $absUrl = $baseUrl->resolve($relUrl); + return $absUrl->getURL(); + } catch (Exception $e) { + common_log(LOG_WARNING, 'Unable to resolve relative link "' . + $rel . '" against base "' . $base . '": ' . $e->getMessage()); + return $rel; + } + } +} diff --git a/plugins/FeedSub/feedinfo.php b/plugins/FeedSub/feedinfo.php new file mode 100644 index 000000000..fff66afe9 --- /dev/null +++ b/plugins/FeedSub/feedinfo.php @@ -0,0 +1,212 @@ +subscribe() + generate random verification token + save to verify_token + sends a sub request to the hub... + + feedsub/callback + hub sends confirmation back to us via GET + We verify the request, then echo back the challenge. + On our end, we save the time we subscribed and the lease expiration + + feedsub/callback + hub sends us updates via POST + ? + +*/ + +class FeedDBException extends FeedSubException +{ + public $obj; + + function __construct($obj) + { + parent::__construct('Database insert failure'); + $this->obj = $obj; + } +} + +class Feedinfo extends Plugin_DataObject +{ + public $__table = 'feedinfo'; + + public $id; + public $profile_id; + + public $feeduri; + public $homeuri; + public $huburi; + + // PuSH subscription data + public $verify_token; + public $sub_start; + public $sub_end; + + public $created; + public $lastupdate; + + + public /*static*/ function staticGet($k, $v=null) + { + return parent::staticGet(__CLASS__, $k, $v); + } + + function tableDef() + { + class_exists('Schema'); // autoload hack + // warning: the autoincrement doesn't seem to set. + // alter table feedinfo change column id id int(11) not null auto_increment; + return new TableDef($this->__table, + array(new ColumnDef('id', 'integer', + null, false, 'PRI', '0', null, true), + new ColumnDef('profile_id', 'integer', + null, false), + new ColumnDef('feeduri', 'varchar', + 255, false, 'UNI'), + new ColumnDef('homeuri', 'varchar', + 255, false), + new ColumnDef('huburi', 'varchar', + 255, false), + new ColumnDef('verify_token', 'varchar', + 32, true), + new ColumnDef('sub_start', 'datetime', + null, true), + new ColumnDef('sub_end', 'datetime', + null, true), + new ColumnDef('created', 'datetime', + null, false), + new ColumnDef('lastupdate', 'datetime', + null, false))); + } + + public function getProfile() + { + return Profile::staticGet('id', $this->profile_id); + } + + /** + * @param FeedMunger $munger + * @return Feedinfo + */ + public static function ensureProfile($munger) + { + $feedinfo = $munger->feedinfo(); + + $current = self::staticGet('feeduri', $feedinfo->feeduri); + if ($current) { + // @fixme we should probably update info as necessary + return $current; + } + + $feedinfo->query('BEGIN'); + + try { + $profile = $munger->profile(); + $result = $profile->insert(); + if (empty($result)) { + throw new FeedDBException($profile); + } + + $feedinfo->profile_id = $profile->id; + $result = $feedinfo->insert(); + if (empty($result)) { + throw new FeedDBException($feedinfo); + } + + $feedinfo->query('COMMIT'); + } catch (FeedDBException $e) { + common_log_db_error($e->obj, 'INSERT', __FILE__); + $feedinfo->query('ROLLBACK'); + return false; + } + return $feedinfo; + } + + /** + * Send a subscription request to the hub for this feed. + * The hub will later send us a confirmation POST to /feedsub/callback. + * + * @return bool true on success, false on failure + */ + public function subscribe() + { + // @fixme use the verification token + #$token = md5(mt_rand() . ':' . $this->feeduri); + #$this->verify_token = $token; + #$this->update(); // @fixme + + try { + $callback = common_local_url('feedsubcallback', array('feed' => $this->id)); + $headers = array('Content-Type: application/x-www-form-urlencoded'); + $post = array('hub.mode' => 'subscribe', + 'hub.callback' => $callback, + 'hub.verify' => 'async', + //'hub.verify_token' => $token, + //'hub.lease_seconds' => 0, + 'hub.topic' => $this->feeduri); + $client = new HTTPClient(); + $response = $client->post($this->huburi, $headers, $post); + if ($response->getStatus() >= 200 && $response->getStatus() < 300) { + common_log(LOG_INFO, __METHOD__ . ': sub req ok'); + return true; + } else { + common_log(LOG_INFO, __METHOD__ . ': sub req failed'); + return false; + } + } catch (Exception $e) { + // wtf! + common_log(LOG_ERR, __METHOD__ . ": error \"{$e->getMessage()}\" hitting hub $this->huburi subscribing to $this->feeduri"); + return false; + } + } + + /** + * Read and post notices for updates from the feed. + * Currently assumes that all items in the feed are new, + * coming from a PuSH hub. + * + * @param string $xml source of Atom or RSS feed + */ + public function postUpdates($xml) + { + common_log(LOG_INFO, __METHOD__ . ": packet for \"$this->feeduri\"! $xml"); + require_once "XML/Feed/Parser.php"; + $feed = new XML_Feed_Parser($xml, false, false, true); + $munger = new FeedMunger($feed); + + $hits = 0; + foreach ($feed as $index => $entry) { + // @fixme this might sort in wrong order if we get multiple updates + + $notice = $munger->notice($index); + $notice->profile_id = $this->profile_id; + + // Double-check for oldies + // @fixme this could explode horribly for multiple feeds on a blog. sigh + $dupe = new Notice(); + $dupe->uri = $notice->uri; + $dupe->find(); + if ($dupe->fetch()) { + common_log(LOG_WARNING, __METHOD__ . ": tried to save dupe notice for entry {$notice->uri} of feed {$this->feeduri}"); + continue; + } + + if (Event::handle('StartNoticeSave', array(&$notice))) { + $id = $notice->insert(); + Event::handle('EndNoticeSave', array($notice)); + } + $notice->addToInboxes(); + + common_log(LOG_INFO, __METHOD__ . ": saved notice {$notice->id} for entry $index of update to \"{$this->feeduri}\""); + $hits++; + } + if ($hits == 0) { + common_log(LOG_INFO, __METHOD__ . ": no updates in packet for \"$this->feeduri\"! $xml"); + } + } +} diff --git a/plugins/FeedSub/feedinfo.sql b/plugins/FeedSub/feedinfo.sql new file mode 100644 index 000000000..e9b53d26e --- /dev/null +++ b/plugins/FeedSub/feedinfo.sql @@ -0,0 +1,14 @@ +CREATE TABLE `feedinfo` ( + `id` int(11) NOT NULL auto_increment, + `profile_id` int(11) NOT NULL, + `feeduri` varchar(255) NOT NULL, + `homeuri` varchar(255) NOT NULL, + `huburi` varchar(255) NOT NULL, + `verify_token` varchar(32) default NULL, + `sub_start` datetime default NULL, + `sub_end` datetime default NULL, + `created` datetime NOT NULL, + `lastupdate` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `feedinfo_feeduri_idx` (`feeduri`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; diff --git a/plugins/FeedSub/feedmunger.php b/plugins/FeedSub/feedmunger.php new file mode 100644 index 000000000..bb8075da9 --- /dev/null +++ b/plugins/FeedSub/feedmunger.php @@ -0,0 +1,238 @@ +. + */ + +/** + * @package FeedSubPlugin + * @maintainer Brion Vibber + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +class FeedSubPreviewNotice extends Notice +{ + protected $fetched = true; + + function __construct($profile) + { + //parent::__construct(); // uhhh? + $this->profile = $profile; + } + + function getProfile() + { + return $this->profile; + } + + function find() + { + return true; + } + + function fetch() + { + $got = $this->fetched; + $this->fetched = false; + return $got; + } +} + +class FeedSubPreviewProfile extends Profile +{ + function getAvatar($width, $height=null) + { + return new FeedSubPreviewAvatar($width, $height); + } +} + +class FeedSubPreviewAvatar extends Avatar +{ + function displayUrl() { + return common_path('plugins/FeedSub/images/48px-Feed-icon.svg.png'); + } +} + +class FeedMunger +{ + /** + * @param XML_Feed_Parser $feed + */ + function __construct($feed, $url=null) + { + $this->feed = $feed; + $this->url = $url; + } + + function feedinfo() + { + $feedinfo = new Feedinfo(); + $feedinfo->feeduri = $this->url; + $feedinfo->homeuri = $this->feed->link; + $feedinfo->huburi = $this->getHubLink(); + return $feedinfo; + } + + function getAtomLink($item, $attribs=array()) + { + // XML_Feed_Parser gets confused by multiple elements. + $dom = $item->model; + + // Note that RSS feeds would embed an so this should work for both. + /// http://code.google.com/p/pubsubhubbub/wiki/RssFeeds + // + $links = $dom->getElementsByTagNameNS('http://www.w3.org/2005/Atom', 'link'); + for ($i = 0; $i < $links->length; $i++) { + $node = $links->item($i); + if ($node->hasAttributes()) { + $href = $node->attributes->getNamedItem('href'); + if ($href) { + $matches = 0; + foreach ($attribs as $name => $val) { + $attrib = $node->attributes->getNamedItem($name); + if ($attrib && $attrib->value == $val) { + $matches++; + } + } + if ($matches == count($attribs)) { + return $href->value; + } + } + } + } + return false; + } + + function getRssLink($item) + { + // XML_Feed_Parser gets confused by multiple elements. + $dom = $item->model; + + // Note that RSS feeds would embed an so this should work for both. + /// http://code.google.com/p/pubsubhubbub/wiki/RssFeeds + // + $links = $dom->getElementsByTagName('link'); + for ($i = 0; $i < $links->length; $i++) { + $node = $links->item($i); + if (!$node->hasAttributes()) { + return $node->textContent; + } + } + return false; + } + + function getAltLink($item) + { + // Check for an atom link... + $link = $this->getAtomLink($item, array('rel' => 'alternate', 'type' => 'text/html')); + if (!$link) { + $link = $this->getRssLink($item); + } + return $link; + } + + function getHubLink() + { + return $this->getAtomLink($this->feed, array('rel' => 'hub')); + } + + function profile($preview=false) + { + if ($preview) { + $profile = new FeedSubPreviewProfile(); + } else { + $profile = new Profile(); + } + + // @todo validate/normalize nick? + $profile->nickname = $this->feed->title; + $profile->fullname = $this->feed->title; + $profile->homepage = $this->getAltLink($this->feed); + $profile->bio = $this->feed->description; + $profile->profileurl = $this->getAltLink($this->feed); + + // @todo tags from categories + // @todo lat/lon/location? + + return $profile; + } + + function notice($index=1, $preview=false) + { + $entry = $this->feed->getEntryByOffset($index); + if (!$entry) { + return null; + } + + if ($preview) { + $notice = new FeedSubPreviewNotice($this->profile(true)); + $notice->id = -1; + } else { + $notice = new Notice(); + } + + $link = $this->getAltLink($entry); + $notice->uri = $link; + $notice->url = $link; + $notice->content = $this->noticeFromEntry($entry); + $notice->rendered = common_render_content($notice->content, $notice); + $notice->created = common_sql_date($entry->updated); // @fixme + $notice->is_local = Notice::GATEWAY; + $notice->source = 'feed'; + + return $notice; + } + + /** + * @param XML_Feed_Type $entry + * @return string notice text, within post size limit + */ + function noticeFromEntry($entry) + { + $title = $entry->title; + $link = $entry->link; + + // @todo We can get entries like this: + // $cats = $entry->getCategory('category', array(0, true)); + // but it feels like an awful hack. If it's accessible cleanly, + // try adding #hashtags from the categories/tags on a post. + + // @todo Should we force a language here? + $format = dgettext("FeedSubPlugin", 'New post: "%1$s" %2$s'); + $title = $entry->title; + $link = $this->getAltLink($entry); + $out = sprintf($format, $title, $link); + + // Trim link if needed... + $max = Notice::maxContent(); + if (mb_strlen($out) > $max) { + $link = common_shorten_url($link); + $out = sprintf($format, $title, $link); + } + + // Trim title if needed... + if (mb_strlen($out) > $max) { + $ellipsis = "\xe2\x80\xa6"; // U+2026 HORIZONTAL ELLIPSIS + $used = mb_strlen($out) - mb_strlen($title); + $available = $max - $used - mb_strlen($ellipsis); + $title = mb_substr($title, 0, $available) . $ellipsis; + $out = sprintf($format, $title, $link); + } + + return $out; + } +} diff --git a/plugins/FeedSub/images/24px-Feed-icon.svg.png b/plugins/FeedSub/images/24px-Feed-icon.svg.png new file mode 100644 index 000000000..317225814 Binary files /dev/null and b/plugins/FeedSub/images/24px-Feed-icon.svg.png differ diff --git a/plugins/FeedSub/images/48px-Feed-icon.svg.png b/plugins/FeedSub/images/48px-Feed-icon.svg.png new file mode 100644 index 000000000..bd1da4f91 Binary files /dev/null and b/plugins/FeedSub/images/48px-Feed-icon.svg.png differ diff --git a/plugins/FeedSub/images/96px-Feed-icon.svg.png b/plugins/FeedSub/images/96px-Feed-icon.svg.png new file mode 100644 index 000000000..bf16571ec Binary files /dev/null and b/plugins/FeedSub/images/96px-Feed-icon.svg.png differ diff --git a/plugins/FeedSub/images/README b/plugins/FeedSub/images/README new file mode 100644 index 000000000..d9379c23e --- /dev/null +++ b/plugins/FeedSub/images/README @@ -0,0 +1,5 @@ +Feed icon rendered from http://commons.wikimedia.org/wiki/File:Feed-icon.svg + +Originally distributed by the Mozilla Foundation under a MPL/GPL/LGPL tri-license: + +http://www.mozilla.org/MPL/boilerplate-1.1/mpl-tri-license-html diff --git a/plugins/FeedSub/tests/FeedDiscoveryTest.php b/plugins/FeedSub/tests/FeedDiscoveryTest.php new file mode 100644 index 000000000..1c5249701 --- /dev/null +++ b/plugins/FeedSub/tests/FeedDiscoveryTest.php @@ -0,0 +1,111 @@ +discoverFromHTML($url, $html); + $this->assertEquals($expected, $url); + } + + static public function provider() + { + $sampleHeader = << + + + + + +leŭksman + + + + + + + + + + + + + + + + + + + + + +END; + return array( + array('http://example.com/', + '', + 'http://example.com/feed/rss'), + array('http://example.com/atom', + '', + 'http://example.com/feed/atom'), + array('http://example.com/empty', + '', + false), + array('http://example.com/tagsoup', + '
',
+                           'http://example.com/feed/rss'),
+                     array('http://example.com/relative/link2',
+                           '',
+                           'http://example.com/feed/rss'),
+                     array('http://example.com/relative/link3',
+                           '',
+                           'http://example.com/feed/rss'),
+                     array('http://example.com/base/link1',
+                           '',
+                           'http://target.example.com/feed/rss'),
+                     array('http://example.com/base/link2',
+                           '',
+                           'http://target.example.com/feed/rss'),
+                     array('http://example.com/base/link3',
+                           '',
+                           'http://target.example.com/feed/rss'),
+                     // Trick question! There's a  but no href on it
+                     array('http://example.com/relative/fauxbase',
+                           '',
+                           'http://example.com/feed/rss'),
+                     // Actual WordPress blog header example
+                     array('http://leuksman.com/log/',
+                           $sampleHeader,
+                           'http://leuksman.com/log/feed/'));
+    }
+}
diff --git a/plugins/FeedSub/tests/FeedMungerTest.php b/plugins/FeedSub/tests/FeedMungerTest.php
new file mode 100644
index 000000000..0ce24c9fb
--- /dev/null
+++ b/plugins/FeedSub/tests/FeedMungerTest.php
@@ -0,0 +1,147 @@
+profile();
+
+        foreach ($expected as $field => $val) {
+            $this->assertEquals($expected[$field], $profile->$field, "profile->$field");
+        }
+    }
+
+    static public function profileProvider()
+    {
+        return array(
+                     array(self::samplefeed(),
+                           array('nickname' => 'leŭksman', // @todo does this need to be asciified?
+                                 'fullname' => 'leŭksman',
+                                 'bio' => 'reticula, electronica, & oddities',
+                                 'homepage' => 'http://leuksman.com/log')));
+    }
+
+    /**
+     * @dataProvider noticeProvider
+     *
+     */
+    public function testNotices($xml, $entryIndex, $expected)
+    {
+        $feed = new XML_Feed_Parser($xml, false, false, true);
+        $entry = $feed->getEntryByOffset($entryIndex);
+
+        $munger = new FeedMunger($feed);
+        $notice = $munger->noticeFromEntry($entry);
+
+        $this->assertTrue(mb_strlen($notice) <= Notice::maxContent());
+        $this->assertEquals($expected, $notice);
+    }
+
+    static public function noticeProvider()
+    {
+        return array(
+                     array('A fairly short titlehttp://example.com/short/link', 0,
+                           'New post: "A fairly short title" http://example.com/short/link'),
+                     // Requires URL shortening ...
+                     array('A fairly short titlehttp://example.com/but/a/very/long/link/indeed/this/is/far/too/long/for/mere/humans/to/comprehend/oh/my/gosh', 0,
+                           'New post: "A fairly short title" http://ur1.ca/g2o1'),
+                     array('A fairly long title in this case, which will have to get cut down at some point alongside its very long link. Really who even makes titles this long? It\'s just ridiculous imo...http://example.com/but/a/very/long/link/indeed/this/is/far/too/long/for/mere/humans/to/comprehend/oh/my/gosh', 0,
+                           'New post: "A fairly long title in this case, which will have to get cut down at some point alongside its very long li…" http://ur1.ca/g2o1'),
+                     // Some real sample feeds
+                     array(self::samplefeed(), 0,
+                           'New post: "Compiling PHP on Snow Leopard" http://leuksman.com/log/2009/11/12/compiling-php-on-snow-leopard/'),
+                     array(self::samplefeedBlogspot(), 0,
+                           'New post: "I love posting" http://briontest.blogspot.com/2009/11/i-love-posting.html'),
+                     array(self::samplefeedBlogspot(), 1,
+                           'New post: "Hey dude" http://briontest.blogspot.com/2009/11/hey-dude.html'),
+        );
+    }
+
+    static protected function samplefeed()
+    {
+        $xml = '<' . '?xml version="1.0" encoding="UTF-8"?' . ">\n";
+        $samplefeed = $xml . <<
+
+
+	leŭksman
+	
+	http://leuksman.com/log
+	reticula, electronica, & oddities
+
+	Thu, 12 Nov 2009 17:44:42 +0000
+	http://wordpress.org/?v=2.8.6
+	en
+	hourly
+	1
+			
+
+		Compiling PHP on Snow Leopard
+		http://leuksman.com/log/2009/11/12/compiling-php-on-snow-leopard/
+		http://leuksman.com/log/2009/11/12/compiling-php-on-snow-leopard/#comments
+		Thu, 12 Nov 2009 17:44:42 +0000
+		brion
+				
+
+		
+
+		http://leuksman.com/log/?p=649
+		
+			If you’ve been having trouble compiling your own PHP installations on Mac OS X 10.6, here’s the secret to making it not suck! After running the configure script, edit the generated Makefile and make these fixes:

+
    +
  • Find the EXTRA_LIBS definition and add -lresolv to the end
  • +
  • Find the EXE_EXT definition and remove .dSYM
  • +
+

Standard make and make install should work from here…

+

For reference, here’s the whole configure line I currently use; MySQL is installed from the downloadable installer; other deps from MacPorts:

+

‘./configure’ ‘–prefix=/opt/php52′ ‘–with-mysql=/usr/local/mysql’ ‘–with-zlib’ ‘–with-bz2′ ‘–enable-mbstring’ ‘–enable-exif’ ‘–enable-fastcgi’ ‘–with-xmlrpc’ ‘–with-xsl’ ‘–with-readline=/opt/local’ –without-iconv –with-gd –with-png-dir=/opt/local –with-jpeg-dir=/opt/local –with-curl –with-gettext=/opt/local –with-mysqli=/usr/local/mysql/bin/mysql_config –with-tidy=/opt/local –enable-pcntl –with-openssl

+]]>
+ http://leuksman.com/log/2009/11/12/compiling-php-on-snow-leopard/feed/ + 0 +
+
+ +END; + return $samplefeed; + } + + static protected function samplefeedBlogspot() + { + return <<tag:blogger.com,1999:blog-77800835085316971672009-11-19T12:56:11.233-08:00Brion's Cool Test Blogbrionhttp://www.blogger.com/profile/12932299467049762017noreply@blogger.comBlogger2125tag:blogger.com,1999:blog-7780083508531697167.post-84566718790002906772009-11-19T12:55:00.000-08:002009-11-19T12:56:11.241-08:00I love postingIt's pretty awesome, if you like that sort of thing.<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7780083508531697167-8456671879000290677?l=briontest.blogspot.com' alt='' /></div>brionhttp://www.blogger.com/profile/12932299467049762017noreply@blogger.com0tag:blogger.com,1999:blog-7780083508531697167.post-82022969178973466332009-11-18T13:52:00.001-08:002009-11-18T13:52:48.444-08:00Hey dudetestingggggggggg<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7780083508531697167-8202296917897346633?l=briontest.blogspot.com' alt='' /></div>brionhttp://www.blogger.com/profile/12932299467049762017noreply@blogger.com0 +END; + } +} -- cgit v1.2.3-54-g00ecf From 501170f3cf8497496110d8c993f69d3ba0792fb1 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 20 Nov 2009 05:48:05 +0000 Subject: Updated form markup --- plugins/OpenID/finishopenidlogin.php | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) (limited to 'plugins') diff --git a/plugins/OpenID/finishopenidlogin.php b/plugins/OpenID/finishopenidlogin.php index ff0b451d3..e5551b412 100644 --- a/plugins/OpenID/finishopenidlogin.php +++ b/plugins/OpenID/finishopenidlogin.php @@ -89,33 +89,53 @@ class FinishopenidloginAction extends Action $this->elementStart('form', array('method' => 'post', 'id' => 'account_connect', + 'class' => 'form_settings', 'action' => common_local_url('finishopenidlogin'))); $this->hidden('token', common_session_token()); - $this->element('h2', null, + $this->elementStart('fieldset', array('id' => 'form_openid_createaccount')); + $this->element('legend', null, _('Create new account')); $this->element('p', null, _('Create a new user with this nickname.')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); $this->input('newname', _('New nickname'), ($this->username) ? $this->username : '', _('1-64 lowercase letters or numbers, no punctuation or spaces')); - $this->elementStart('p'); + $this->elementEnd('li'); + $this->elementStart('li'); $this->element('input', array('type' => 'checkbox', 'id' => 'license', + 'class' => 'checkbox', 'name' => 'license', 'value' => 'true')); + $this->elementStart('label', array('for' => 'license', + 'class' => 'checkbox')); $this->text(_('My text and files are available under ')); $this->element('a', array('href' => common_config('license', 'url')), common_config('license', 'title')); $this->text(_(' except this private data: password, email address, IM address, phone number.')); - $this->elementEnd('p'); + $this->elementEnd('label'); + $this->elementEnd('li'); + $this->elementEnd('ul'); $this->submit('create', _('Create')); - $this->element('h2', null, + $this->elementEnd('fieldset'); + + $this->elementStart('fieldset', array('id' => 'form_openid_createaccount')); + $this->element('legend', null, _('Connect existing account')); $this->element('p', null, _('If you already have an account, login with your username and password to connect it to your OpenID.')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); $this->input('nickname', _('Existing nickname')); + $this->elementEnd('li'); + $this->elementStart('li'); $this->password('password', _('Password')); + $this->elementEnd('li'); + $this->elementEnd('ul'); $this->submit('connect', _('Connect')); + $this->elementEnd('fieldset'); $this->elementEnd('form'); } -- cgit v1.2.3-54-g00ecf From 4bace8f1a5df797aead91ce802d4538e1dfed9ec Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 20 Nov 2009 11:11:18 +0000 Subject: Add routing rules for FB app in front of other rules --- plugins/Facebook/FacebookPlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/Facebook/FacebookPlugin.php b/plugins/Facebook/FacebookPlugin.php index b68534b24..c5b9298f7 100644 --- a/plugins/Facebook/FacebookPlugin.php +++ b/plugins/Facebook/FacebookPlugin.php @@ -58,7 +58,7 @@ class FacebookPlugin extends Plugin * @return boolean hook return */ - function onRouterInitialized($m) + function onStartInitializeRouter($m) { // Facebook App stuff -- cgit v1.2.3-54-g00ecf From 09d67d6f80abdf577f6cd94d7be2bc19ce5541fa Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Fri, 20 Nov 2009 11:16:39 -0500 Subject: Fix js error on non-map containing pages --- plugins/Mapstraction/usermap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/Mapstraction/usermap.js b/plugins/Mapstraction/usermap.js index 7fb73fa88..19ec54c39 100644 --- a/plugins/Mapstraction/usermap.js +++ b/plugins/Mapstraction/usermap.js @@ -5,7 +5,7 @@ $(document).ready(function() { if(notice['geo']) notices.push(notice); }); - if($("#map_canvas") && notices.length>0) + if($("#map_canvas").length && notices.length>0) { showMapstraction($("#map_canvas"), notices); } -- cgit v1.2.3-54-g00ecf From e7663ce38f8eb4d918102351c45a5829a523141b Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 20 Nov 2009 10:08:13 -0800 Subject: fix notice bug --- plugins/Mapstraction/MapstractionPlugin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/Mapstraction/MapstractionPlugin.php b/plugins/Mapstraction/MapstractionPlugin.php index a0dbf5204..37306a23c 100644 --- a/plugins/Mapstraction/MapstractionPlugin.php +++ b/plugins/Mapstraction/MapstractionPlugin.php @@ -58,12 +58,12 @@ class MapstractionPlugin extends Plugin * * The way to register new actions from a plugin. * - * @param Router &$m reference to router + * @param Router $m reference to router * * @return boolean event handler return */ - function onRouterInitialized(&$m) + function onRouterInitialized($m) { $m->connect(':nickname/all/map', array('action' => 'allmap'), -- cgit v1.2.3-54-g00ecf From 028a44e080e645d2fded19234e5cf389ed8086b5 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Nov 2009 07:58:28 -0800 Subject: use caching in geonames plugin --- plugins/GeonamesPlugin.php | 87 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/GeonamesPlugin.php b/plugins/GeonamesPlugin.php index 59232c1c5..340a6f0bf 100644 --- a/plugins/GeonamesPlugin.php +++ b/plugins/GeonamesPlugin.php @@ -63,6 +63,14 @@ class GeonamesPlugin extends Plugin function onLocationFromName($name, $language, &$location) { + $loc = $this->getCache(array('name' => $name, + 'language' => $language)); + + if (!empty($loc)) { + $location = $loc; + return false; + } + $client = HTTPClient::start(); // XXX: break down a name by commas, narrow by each @@ -87,6 +95,10 @@ class GeonamesPlugin extends Plugin $location->location_id = $n->geonameId; $location->location_ns = self::LOCATION_NS; + $this->setCache(array('name' => $name, + 'language' => $language), + $location); + // handled, don't continue processing! return false; } @@ -114,6 +126,13 @@ class GeonamesPlugin extends Plugin return true; } + $loc = $this->getCache(array('id' => $id)); + + if (!empty($loc)) { + $location = $loc; + return false; + } + $client = HTTPClient::start(); $str = http_build_query(array('geonameId' => $id, @@ -148,6 +167,9 @@ class GeonamesPlugin extends Plugin $location->lat = $last->lat; $location->lon = $last->lng; $location->names[$language] = implode(', ', array_reverse($parts)); + + $this->setCache(array('id' => $last->geonameId), + $location); } } @@ -173,6 +195,14 @@ class GeonamesPlugin extends Plugin function onLocationFromLatLon($lat, $lon, $language, &$location) { + $loc = $this->getCache(array('lat' => $lat, + 'lon' => $lon)); + + if (!empty($loc)) { + $location = $loc; + return false; + } + $client = HTTPClient::start(); $str = http_build_query(array('lat' => $lat, @@ -211,6 +241,10 @@ class GeonamesPlugin extends Plugin $location->names[$language] = implode(', ', $parts); + $this->setCache(array('lat' => $lat, + 'lon' => $lon), + $location); + // Success! We handled it, so no further processing return false; @@ -242,9 +276,17 @@ class GeonamesPlugin extends Plugin return true; } + $n = $this->getCache(array('id' => $location->location_id, + 'language' => $language)); + + if (!empty($n)) { + $name = $n; + return false; + } + $client = HTTPClient::start(); - $str = http_build_query(array('geonameId' => $id, + $str = http_build_query(array('geonameId' => $location->location_id, 'lang' => $language)); $result = $client->get('http://ws.geonames.org/hierarchyJSON?'.$str); @@ -271,6 +313,9 @@ class GeonamesPlugin extends Plugin if (count($parts)) { $name = implode(', ', array_reverse($parts)); + $this->setCache(array('id' => $location->location_id, + 'language' => $language), + $name); return false; } } @@ -326,4 +371,44 @@ class GeonamesPlugin extends Plugin // it's been filled, so don't process further. return false; } + + function getCache($attrs) + { + $c = common_memcache(); + + if (!$c) { + return null; + } + + return $c->get($this->cacheKey($attrs)); + } + + function setCache($attrs, $loc) + { + $c = common_memcache(); + + if (!$c) { + return null; + } + + $c->set($this->cacheKey($attrs), $loc); + } + + function clearCache($attrs) + { + $c = common_memcache(); + + if (!$c) { + return null; + } + + $c->delete($this->cacheKey($attrs)); + } + + function cacheKey($attrs) + { + return common_cache_key('geonames:'. + implode(',', array_keys($attrs)) . ':'. + common_keyize(implode(',', array_values($attrs)))); + } } -- cgit v1.2.3-54-g00ecf From 953f3a6e4fbc2e14a8aed18012b36200e7903849 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Fri, 20 Nov 2009 15:34:48 -0500 Subject: Undo part of c6e4feb815a60a7baf613026c414a24c5c918650 so that blacklisted notices are not displayed in realtime --- plugins/Realtime/RealtimePlugin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index c5fb6de03..b737e442a 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -139,8 +139,8 @@ class RealtimePlugin extends Plugin // Add to the public timeline - if ($notice->is_local || - ($notice->is_local == 0 && !common_config('public', 'localonly'))) { + if ($notice->is_local == Notice::LOCAL_PUBLIC || + ($notice->is_local == Notice::REMOTE_OMB && !common_config('public', 'localonly'))) { $paths[] = array('public'); } -- cgit v1.2.3-54-g00ecf From 83ba93e945e5b933a73139f9e5db710444c99893 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Fri, 20 Nov 2009 17:40:38 -0500 Subject: Change the format of the lat/lon output on a notice in HTML --- lib/noticelist.php | 9 ++++----- plugins/Mapstraction/usermap.js | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) (limited to 'plugins') diff --git a/lib/noticelist.php b/lib/noticelist.php index 5877827ff..167ba994b 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -392,15 +392,14 @@ class NoticeListItem extends Widget $name = $location->getName(); - if (empty($name)) { - // XXX: Could be a translation issue. Fall back to... something? - return; - } - $lat = $this->notice->lat; $lon = $this->notice->lon; $latlon = (!empty($lat) && !empty($lon)) ? $lat.';'.$lon : ''; + if (empty($name)) { + $name = $lat . ' ' . $lon; //TODO tranform to N/S deg/min/sec format + } + $url = $location->getUrl(); $this->out->elementStart('span', array('class' => 'location')); diff --git a/plugins/Mapstraction/usermap.js b/plugins/Mapstraction/usermap.js index 19ec54c39..e667dd579 100644 --- a/plugins/Mapstraction/usermap.js +++ b/plugins/Mapstraction/usermap.js @@ -1,7 +1,7 @@ $(document).ready(function() { - notices = []; + var notices = []; $(".notice").each(function(){ - notice = getNoticeFromElement($(this)); + var notice = getNoticeFromElement($(this)); if(notice['geo']) notices.push(notice); }); @@ -10,8 +10,8 @@ $(document).ready(function() { showMapstraction($("#map_canvas"), notices); } - $('a.geo').click(function(){ - noticeElement = $(this).closest(".notice"); + $('.geo').click(function(){ + var noticeElement = $(this).closest(".notice"); notice = getNoticeFromElement(noticeElement); $.fn.jOverlay.options = { @@ -23,7 +23,7 @@ $(document).ready(function() { autoHide : true, css : {'max-width':'542px', 'top':'5%', 'left':'32.5%'} }; - html="
"; + var html="
"; html+=""; html+=$("
").append($(this).clone()).html(); $().jOverlay({ "html": html }); @@ -47,11 +47,12 @@ function getMicroformatValue(element) function getNoticeFromElement(noticeElement) { - notice = {}; - if(noticeElement.find(".latitude").length){ + var notice = {}; + if(noticeElement.find(".geo").length){ + var latlon = noticeElement.find(".geo").attr('title').split(";"); notice['geo']={'coordinates': [ - parseFloat(getMicroformatValue(noticeElement.find(".latitude"))), - parseFloat(getMicroformatValue(noticeElement.find(".longitude")))] }; + parseFloat(latlon[0]), + parseFloat(latlon[1])] }; } notice['user']={ 'profile_image_url': noticeElement.find("img.avatar").attr('src'), -- cgit v1.2.3-54-g00ecf From 5973192593fde410f338e6de895b2e3f596e8617 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 24 Nov 2009 12:55:08 +0000 Subject: Enable border-top when Realtime pop up window is initialized --- plugins/Realtime/realtimeupdate.js | 2 -- 1 file changed, 2 deletions(-) (limited to 'plugins') diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index a2c4da113..4dbc80231 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -287,8 +287,6 @@ RealtimeUpdate = { $('#form_notice label[for=notice_data-text], h1').css({'display': 'none'}); - $('.notices li:first-child').css({'border-top-color':'transparent'}); - $('#form_notice label[for="notice_data-attach"], #form_notice #notice_data-attach').css({'top':'0'}); $('#form_notice #notice_data-attach').css({ -- cgit v1.2.3-54-g00ecf From 9f6b3daaec3a04ec301d13a0536fcdb0c76f1101 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 24 Nov 2009 13:09:17 +0000 Subject: Added realtime-popup class to for popup output. --- plugins/Realtime/RealtimePlugin.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index b737e442a..cbfa6bae0 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -220,8 +220,9 @@ class RealtimePlugin extends Plugin $action->elementStart('body', (common_current_user()) ? array('id' => $action->trimmed('action'), - 'class' => 'user_in') - : array('id' => $action->trimmed('action'))); + 'class' => 'user_in realtime-popup') + : array('id' => $action->trimmed('action'), + 'class'=> 'realtime-popup')); // XXX hack to deal with JS that tries to get the // root url from page output -- cgit v1.2.3-54-g00ecf From 84ed094f40d4087704fc3354ea4c3743b1447331 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 24 Nov 2009 13:10:34 +0000 Subject: Moved initPopupWindow CSS out of JS in Realtime plugin --- plugins/Realtime/realtimeupdate.css | 29 +++++++++++++++++++++++++++++ plugins/Realtime/realtimeupdate.js | 18 ------------------ 2 files changed, 29 insertions(+), 18 deletions(-) (limited to 'plugins') diff --git a/plugins/Realtime/realtimeupdate.css b/plugins/Realtime/realtimeupdate.css index 0ab5dd32b..a5728b83d 100644 --- a/plugins/Realtime/realtimeupdate.css +++ b/plugins/Realtime/realtimeupdate.css @@ -1,3 +1,32 @@ +.realtime-popup address { +display:none; +} + +.realtime-popup #content { +width:93.5%; +} + +.realtime-popup #form_notice { +margin:18px 0 18px 1.795%; +width:93%; +max-width:451px; +} + +.realtime-popup #form_notice label[for=notice_data-text], +.realtime-popup h1 { +display:none; +} + +.realtime-popup #form_notice label[for=notice_data-attach], +.realtime-popup #form_notice #notice_data-attach { +top:0; +} + +.realtime-popup #form_notice #notice_data-attach { +left:auto; +right:0; +} + #notices_primary { position:relative; } diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index 4dbc80231..b3c41456e 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -276,24 +276,6 @@ RealtimeUpdate = { initPopupWindow: function() { - $('address').hide(); - $('#content').css({'width':'93.5%'}); - - $('#form_notice').css({ - 'margin':'18px 0 18px 1.795%', - 'width':'93%', - 'max-width':'451px' - }); - - $('#form_notice label[for=notice_data-text], h1').css({'display': 'none'}); - - $('#form_notice label[for="notice_data-attach"], #form_notice #notice_data-attach').css({'top':'0'}); - - $('#form_notice #notice_data-attach').css({ - 'left':'auto', - 'right':'0' - }); - $('.notices .entry-title a, .notices .entry-content a').bind('click', function() { window.open(this.href, ''); -- cgit v1.2.3-54-g00ecf From b20b59e9eaf8db1a5a2b26b697b03abfa98d0583 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 24 Nov 2009 19:11:34 +0000 Subject: Fix SSL options for Twitter bridge HTTP requests --- lib/oauthclient.php | 1 + plugins/TwitterBridge/twitterbasicauthclient.php | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/lib/oauthclient.php b/lib/oauthclient.php index 1a86e2460..b22fd7897 100644 --- a/lib/oauthclient.php +++ b/lib/oauthclient.php @@ -197,6 +197,7 @@ class OAuthClient 'timeout' => 120, 'follow_redirects' => true, 'ssl_verify_peer' => false, + 'ssl_verify_host' => false )); // Twitter is strict about accepting invalid "Expect" headers diff --git a/plugins/TwitterBridge/twitterbasicauthclient.php b/plugins/TwitterBridge/twitterbasicauthclient.php index d1cf45aec..7ee8d7d4c 100644 --- a/plugins/TwitterBridge/twitterbasicauthclient.php +++ b/plugins/TwitterBridge/twitterbasicauthclient.php @@ -183,7 +183,8 @@ class TwitterBasicAuthClient 'follow_redirects' => true, 'connect_timeout' => 120, 'timeout' => 120, - 'ssl_verifypeer' => false, + 'ssl_verify_peer' => false, + 'ssl_verify_host' => false )); if ($auth) { -- cgit v1.2.3-54-g00ecf From f12972a50fa218835f57861583e28ae42d944e6b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 25 Nov 2009 00:43:20 +0000 Subject: Facebook plugin no longer takes over Login and Connect settings nav menus --- plugins/Facebook/FBCLoginGroupNav.php | 114 --------------------------------- plugins/Facebook/FBCSettingsNav.php | 115 ---------------------------------- plugins/Facebook/FacebookPlugin.php | 48 +++++++------- 3 files changed, 27 insertions(+), 250 deletions(-) delete mode 100644 plugins/Facebook/FBCLoginGroupNav.php delete mode 100644 plugins/Facebook/FBCSettingsNav.php (limited to 'plugins') diff --git a/plugins/Facebook/FBCLoginGroupNav.php b/plugins/Facebook/FBCLoginGroupNav.php deleted file mode 100644 index 81b2520a4..000000000 --- a/plugins/Facebook/FBCLoginGroupNav.php +++ /dev/null @@ -1,114 +0,0 @@ -. - * - * @category Menu - * @package StatusNet - * @author Evan Prodromou - * @author Zach Copley - * @copyright 2009 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR . '/lib/widget.php'; - -/** - * Menu for login group of actions - * - * @category Output - * @package StatusNet - * @author Evan Prodromou - * @author Zach Copley - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - * - * @see Widget - */ - -class FBCLoginGroupNav extends Widget -{ - var $action = null; - - /** - * Construction - * - * @param Action $action current action, used for output - */ - - function __construct($action=null) - { - parent::__construct($action); - $this->action = $action; - } - - /** - * Show the menu - * - * @return void - */ - - function show() - { - $this->action->elementStart('dl', array('id' => 'site_nav_local_views')); - $this->action->element('dt', null, _('Local views')); - $this->action->elementStart('dd'); - - // action => array('prompt', 'title') - $menu = array(); - - if (!common_config('site','openidonly')) { - $menu['login'] = array(_('Login'), - _('Login with a username and password')); - - if (!(common_config('site','closed') || common_config('site','inviteonly'))) { - $menu['register'] = array(_('Register'), - _('Sign up for a new account')); - } - } - - if (common_config('openid', 'enabled')) { - $menu['openidlogin'] = array(_('OpenID'), - _('Login or register with OpenID')); - } - - $menu['FBConnectLogin'] = array(_('Facebook'), - _('Login or register using Facebook')); - - $action_name = $this->action->trimmed('action'); - $this->action->elementStart('ul', array('class' => 'nav')); - - foreach ($menu as $menuaction => $menudesc) { - $this->action->menuItem(common_local_url($menuaction), - $menudesc[0], - $menudesc[1], - $action_name === $menuaction); - } - - $this->action->elementEnd('ul'); - - $this->action->elementEnd('dd'); - $this->action->elementEnd('dl'); - } -} diff --git a/plugins/Facebook/FBCSettingsNav.php b/plugins/Facebook/FBCSettingsNav.php deleted file mode 100644 index ed02371e2..000000000 --- a/plugins/Facebook/FBCSettingsNav.php +++ /dev/null @@ -1,115 +0,0 @@ -. - * - * @category Menu - * @package StatusNet - * @author Evan Prodromou - * @author Zach Copley - * @copyright 2009 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR . '/lib/widget.php'; - -/** - * A widget for showing the connect group local nav menu - * - * @category Output - * @package StatusNet - * @author Evan Prodromou - * @author Zach Copley - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - * - * @see Widget - */ - -class FBCSettingsNav extends Widget -{ - var $action = null; - - /** - * Construction - * - * @param Action $action current action, used for output - */ - - function __construct($action=null) - { - parent::__construct($action); - $this->action = $action; - } - - /** - * Show the menu - * - * @return void - */ - - function show() - { - - $this->action->elementStart('dl', array('id' => 'site_nav_local_views')); - $this->action->element('dt', null, _('Local views')); - $this->action->elementStart('dd'); - - # action => array('prompt', 'title') - $menu = array(); - if (common_config('xmpp', 'enabled')) { - $menu['imsettings'] = - array(_('IM'), - _('Updates by instant messenger (IM)')); - } - if (common_config('sms', 'enabled')) { - $menu['smssettings'] = - array(_('SMS'), - _('Updates by SMS')); - } - if (common_config('twitter', 'enabled')) { - $menu['twittersettings'] = - array(_('Twitter'), - _('Twitter integration options')); - } - $menu['FBConnectSettings'] = - array(_('Facebook'), - _('Facebook Connect settings')); - - $action_name = $this->action->trimmed('action'); - $this->action->elementStart('ul', array('class' => 'nav')); - - foreach ($menu as $menuaction => $menudesc) { - $this->action->menuItem(common_local_url($menuaction), - $menudesc[0], - $menudesc[1], - $action_name === $menuaction); - } - - $this->action->elementEnd('ul'); - - $this->action->elementEnd('dd'); - $this->action->elementEnd('dl'); - } -} diff --git a/plugins/Facebook/FacebookPlugin.php b/plugins/Facebook/FacebookPlugin.php index c5b9298f7..047477d9c 100644 --- a/plugins/Facebook/FacebookPlugin.php +++ b/plugins/Facebook/FacebookPlugin.php @@ -394,37 +394,43 @@ class FacebookPlugin extends Plugin return true; } - /** - * Alter the local nav menu to have a Facebook Connect login and - * settings pages + /* + * Add a login tab for Facebook Connect * - * @param Action $action the current action + * @param Action &action the current action * * @return void - * */ - function onStartShowLocalNavBlock($action) + function onEndLoginGroupNav(&$action) { - $action_name = get_class($action); - $login_actions = array('LoginAction', 'RegisterAction', - 'OpenidloginAction', 'FBConnectLoginAction'); + $action_name = $action->trimmed('action'); - if (in_array($action_name, $login_actions)) { - $nav = new FBCLoginGroupNav($action); - $nav->show(); - return false; - } + $action->menuItem(common_local_url('FBConnectLogin'), + _('Facebook'), + _('Login or register using Facebook'), + 'FBConnectLogin' === $action_name); - $connect_actions = array('SmssettingsAction', 'ImsettingsAction', - 'TwittersettingsAction', 'FBConnectSettingsAction'); + return true; + } - if (in_array($action_name, $connect_actions)) { - $nav = new FBCSettingsNav($action); - $nav->show(); - return false; - } + /* + * Add a tab for managing Facebook Connect settings + * + * @param Action &action the current action + * + * @return void + */ + + function onEndConnectSettingsNav(&$action) + { + $action_name = $action->trimmed('action'); + + $action->menuItem(common_local_url('FBConnectSettings'), + _('Facebook'), + _('Facebook Connect Settings'), + $action_name === 'FBConnectSettings'); return true; } -- cgit v1.2.3-54-g00ecf From b586063ebe32fd15e0bf4dd74456830688e90d61 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 25 Nov 2009 01:11:29 +0000 Subject: Forgot to render the nav menu when on FB Connect login tab --- plugins/Facebook/FBConnectLogin.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/Facebook/FBConnectLogin.php b/plugins/Facebook/FBConnectLogin.php index f146bef7d..d2bb8054c 100644 --- a/plugins/Facebook/FBConnectLogin.php +++ b/plugins/Facebook/FBConnectLogin.php @@ -21,7 +21,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - require_once INSTALLDIR . '/plugins/Facebook/FacebookPlugin.php'; class FBConnectLoginAction extends Action @@ -65,4 +64,9 @@ class FBConnectLoginAction extends Action $this->elementEnd('fieldset'); } + function showLocalNav() + { + $nav = new LoginGroupNav($this); + $nav->show(); + } } -- cgit v1.2.3-54-g00ecf From e6842b718933c5431e8153a2ba59194e52489591 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 25 Nov 2009 01:36:17 +0000 Subject: Fixed bug where reply-sync bit wasn't getting saved --- plugins/Facebook/facebooksettings.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'plugins') diff --git a/plugins/Facebook/facebooksettings.php b/plugins/Facebook/facebooksettings.php index 2f182e368..d1269f101 100644 --- a/plugins/Facebook/facebooksettings.php +++ b/plugins/Facebook/facebooksettings.php @@ -52,21 +52,21 @@ class FacebooksettingsAction extends FacebookAction function saveSettings() { - $noticesync = $this->arg('noticesync'); - $replysync = $this->arg('replysync'); - $prefix = $this->trimmed('prefix'); + $noticesync = $this->boolean('noticesync'); + $replysync = $this->boolean('replysync'); + $prefix = $this->trimmed('prefix'); $original = clone($this->flink); - $this->flink->set_flags($noticesync, $replysync, false, false); + $this->flink->set_flags($noticesync, false, $replysync, false); $result = $this->flink->update($original); if ($prefix == '' || $prefix == '0') { - // Facebook bug: saving empty strings to prefs now fails - // http://bugs.developers.facebook.com/show_bug.cgi?id=7110 - $trimmed = $prefix . ' '; + // Facebook bug: saving empty strings to prefs now fails + // http://bugs.developers.facebook.com/show_bug.cgi?id=7110 + $trimmed = $prefix . ' '; } else { - $trimmed = substr($prefix, 0, 128); - } + $trimmed = substr($prefix, 0, 128); + } $this->facebook->api_client->data_setUserPreference(FACEBOOK_NOTICE_PREFIX, $trimmed); -- cgit v1.2.3-54-g00ecf From f7c1c4e6015ace99caace90e09bec24384e43d88 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 25 Nov 2009 18:04:08 +0000 Subject: Catch uncaught exception --- plugins/TwitterBridge/twitter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php index 3c6803e49..fd5150638 100644 --- a/plugins/TwitterBridge/twitter.php +++ b/plugins/TwitterBridge/twitter.php @@ -178,7 +178,7 @@ function broadcast_oauth($notice, $flink) { try { $status = $client->statusesUpdate($statustxt); - } catch (OAuthClientCurlException $e) { + } catch (OAuthClientException $e) { return process_error($e, $flink); } -- cgit v1.2.3-54-g00ecf From 36aa933bc5627ccdcd56d505a66df958fa0e14c0 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 25 Nov 2009 20:37:03 +0000 Subject: Better layout for entity_profile and entity_actions --- plugins/Realtime/realtimeupdate.css | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'plugins') diff --git a/plugins/Realtime/realtimeupdate.css b/plugins/Realtime/realtimeupdate.css index a5728b83d..56f869354 100644 --- a/plugins/Realtime/realtimeupdate.css +++ b/plugins/Realtime/realtimeupdate.css @@ -27,6 +27,14 @@ left:auto; right:0; } +.realtime-popup .entity_profile { +width:70%; +} +.realtime-popup .entity_actions { +margin-left:1%; +} + + #notices_primary { position:relative; } -- cgit v1.2.3-54-g00ecf From 1ea5ccc5c1bcf3113164c67f552508e4c24041e6 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 25 Nov 2009 21:32:51 +0000 Subject: I can't hide the location and status bars in Firefox, IE or Chromium. It only works in Safari and Opera. https://developer.mozilla.org/En/DOM/Window.open confirms that by default, Firefox and IE forces their presence. --- plugins/Realtime/realtimeupdate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index b3c41456e..4ed95ff26 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -268,7 +268,7 @@ RealtimeUpdate = { PP.bind('click', function() { window.open(url, '', - 'toolbar=no,resizable=yes,scrollbars=yes,status=yes,width=500,height=550'); + 'toolbar=no,resizable=yes,scrollbars=yes,status=no,menubar=no,personalbar=no,location=no,width=500,height=550'); return false; }); -- cgit v1.2.3-54-g00ecf From 8acc1587b1cd4f6fc78bbedb88f414cd18efa942 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 25 Nov 2009 13:38:59 -0800 Subject: Revert "Allow plugin DB_DataObject classes to not have to use the .ini file by overriding keys(), table(), and sequenceKey() for them" This reverts commit a373d07ae00b878f47970f2e4a7d86c6ec3a65cf. Conflicts: classes/statusnet.ini lib/schema.php plugins/Authentication/AuthenticationPlugin.php plugins/OpenID/OpenIDPlugin.php plugins/UserFlag/UserFlagPlugin.php --- classes/Plugin_DataObject.php | 195 ------------------------ classes/statusnet.ini | 3 +- lib/schema.php | 20 --- plugins/Authentication/AuthenticationPlugin.php | 13 +- plugins/Authentication/User_username.php | 22 +-- plugins/OpenID/OpenIDPlugin.php | 20 ++- plugins/OpenID/User_openid.php | 22 +-- plugins/OpenID/User_openid_trustroot.php | 20 +-- plugins/UserFlag/UserFlagPlugin.php | 11 +- plugins/UserFlag/User_flag_profile.php | 21 +-- 10 files changed, 48 insertions(+), 299 deletions(-) delete mode 100644 classes/Plugin_DataObject.php (limited to 'plugins') diff --git a/classes/Plugin_DataObject.php b/classes/Plugin_DataObject.php deleted file mode 100644 index d5cecf0f7..000000000 --- a/classes/Plugin_DataObject.php +++ /dev/null @@ -1,195 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; - -abstract class Plugin_DataObject extends Memcached_DataObject -{ - function table() { - static $table = null; - if($table == null) { - $table = array(); - $DB = $this->getDatabaseConnection(); - $dbtype = $DB->phptype; - $tableDef = $this->tableDef(); - foreach($tableDef->columns as $columnDef){ - switch(strtoupper($columnDef->type)) { - /*shamelessly copied from DB_DataObject_Generator*/ - case 'INT': - case 'INT2': // postgres - case 'INT4': // postgres - case 'INT8': // postgres - case 'SERIAL4': // postgres - case 'SERIAL8': // postgres - case 'INTEGER': - case 'TINYINT': - case 'SMALLINT': - case 'MEDIUMINT': - case 'BIGINT': - $type = DB_DATAOBJECT_INT; - if ($columnDef->size == 1) { - $type += DB_DATAOBJECT_BOOL; - } - break; - - case 'REAL': - case 'DOUBLE': - case 'DOUBLE PRECISION': // double precision (firebird) - case 'FLOAT': - case 'FLOAT4': // real (postgres) - case 'FLOAT8': // double precision (postgres) - case 'DECIMAL': - case 'MONEY': // mssql and maybe others - case 'NUMERIC': - case 'NUMBER': // oci8 - $type = DB_DATAOBJECT_INT; // should really by FLOAT!!! / MONEY... - break; - - case 'YEAR': - $type = DB_DATAOBJECT_INT; - break; - - case 'BIT': - case 'BOOL': - case 'BOOLEAN': - - $type = DB_DATAOBJECT_BOOL; - // postgres needs to quote '0' - if ($dbtype == 'pgsql') { - $type += DB_DATAOBJECT_STR; - } - break; - - case 'STRING': - case 'CHAR': - case 'VARCHAR': - case 'VARCHAR2': - case 'TINYTEXT': - - case 'ENUM': - case 'SET': // not really but oh well - - case 'POINT': // mysql geometry stuff - not really string - but will do.. - - case 'TIMESTAMPTZ': // postgres - case 'BPCHAR': // postgres - case 'INTERVAL': // postgres (eg. '12 days') - - case 'CIDR': // postgres IP net spec - case 'INET': // postgres IP - case 'MACADDR': // postgress network Mac address. - - case 'INTEGER[]': // postgres type - case 'BOOLEAN[]': // postgres type - - $type = DB_DATAOBJECT_STR; - break; - - case 'TEXT': - case 'MEDIUMTEXT': - case 'LONGTEXT': - - $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT; - break; - - - case 'DATE': - $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE; - break; - - case 'TIME': - $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TIME; - break; - - - case 'DATETIME': - - $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME; - break; - - case 'TIMESTAMP': // do other databases use this??? - - $type = ($dbtype == 'mysql') ? - DB_DATAOBJECT_MYSQLTIMESTAMP : - DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME; - break; - - - case 'BLOB': /// these should really be ignored!!!??? - case 'TINYBLOB': - case 'MEDIUMBLOB': - case 'LONGBLOB': - - case 'CLOB': // oracle character lob support - - case 'BYTEA': // postgres blob support.. - $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_BLOB; - break; - - default: - throw new Exception("Cannot handle datatype: $columnDef->type"); - } - if(! $columnDef->nullable) { - $type+=DB_DATAOBJECT_NOTNULL; - } - $table[$columnDef->name]=$type; - } - } - return $table; - } - - function keys() { - static $keys = null; - if($keys == null) { - $keys = array(); - $tableDef = $this->tableDef(); - foreach($tableDef->columns as $columnDef){ - if($columnDef->key != null){ - $keys[] = $columnDef->name; - } - } - } - return $keys; - } - - function sequenceKey() { - static $sequenceKey = null; - if($sequenceKey == null) { - $sequenceKey = array(false,false); - $tableDef = $this->tableDef(); - foreach($tableDef->columns as $columnDef){ - if($columnDef->key == 'PRI' && $columnDef->auto_increment){ - $sequenceKey=array($columnDef->name,true); - } - } - } - return $sequenceKey; - } - - /** - * Get the TableDef object that represents the table backing this class - * Ideally, this function would a static function, but PHP doesn't allow - * abstract static functions - * @return TableDef TableDef instance - */ - abstract function tableDef(); -} - diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 69b355870..b2509dac5 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -261,7 +261,6 @@ modified = 384 [login_token__keys] user_id = K -token = K [message] id = 129 @@ -543,4 +542,4 @@ created = 142 modified = 384 [user_group__keys] -id = N +id = N \ No newline at end of file diff --git a/lib/schema.php b/lib/schema.php index 11e2b6f60..df7cb65f5 100644 --- a/lib/schema.php +++ b/lib/schema.php @@ -372,26 +372,6 @@ class Schema return true; } - /** - * Ensures that the table that backs a given - * Plugin_DataObject class exists. - * - * If the table does not yet exist, it will - * create the table. If it does exist, it will - * alter the table to match the column definitions. - * - * @param Plugin_DataObject $dataObjectClass - * - * @return boolean success flag - */ - - public function ensureDataObject($dataObjectClass) - { - $obj = new $dataObjectClass(); - $tableDef = $obj->tableDef(); - return $this->ensureTable($tableDef->name,$tableDef->columns); - } - /** * Ensures that a table exists with the given * name and the given column definitions. diff --git a/plugins/Authentication/AuthenticationPlugin.php b/plugins/Authentication/AuthenticationPlugin.php index cd1de1149..a76848b04 100644 --- a/plugins/Authentication/AuthenticationPlugin.php +++ b/plugins/Authentication/AuthenticationPlugin.php @@ -2,7 +2,7 @@ /** * StatusNet, the distributed open-source microblogging tool * - * Superclass for plugins that do authentication + * Superclass for plugins that do authentication and/or authorization * * PHP version 5 * @@ -204,7 +204,16 @@ abstract class AuthenticationPlugin extends Plugin function onCheckSchema() { $schema = Schema::get(); - $schema->ensureDataObject('User_username'); + $schema->ensureTable('user_username', + array(new ColumnDef('provider_name', 'varchar', + '255', false, 'PRI'), + new ColumnDef('username', 'varchar', + '255', false, 'PRI'), + new ColumnDef('user_id', 'integer', + null, false), + new ColumnDef('created', 'datetime', + null, false), + new ColumnDef('modified', 'timestamp'))); return true; } diff --git a/plugins/Authentication/User_username.php b/plugins/Authentication/User_username.php index 6826f2681..f30f60d83 100644 --- a/plugins/Authentication/User_username.php +++ b/plugins/Authentication/User_username.php @@ -2,9 +2,9 @@ /** * Table Definition for user_username */ -require_once INSTALLDIR.'/classes/Plugin_DataObject.php'; +require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; -class User_username extends Plugin_DataObject +class User_username extends Memcached_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -43,22 +43,4 @@ class User_username extends Plugin_DataObject return false; } } - - /** - * Get the TableDef object that represents the table backing this class - * @return TableDef TableDef instance - */ - function tableDef() - { - return new TableDef($this->__table, - array(new ColumnDef('provider_name', 'varchar', - '255', false, 'PRI'), - new ColumnDef('username', 'varchar', - '255', false, 'PRI'), - new ColumnDef('user_id', 'integer', - null, false), - new ColumnDef('created', 'datetime', - null, false), - new ColumnDef('modified', 'timestamp'))); - } } diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php index 6dd8a3f5a..9b0a4cd45 100644 --- a/plugins/OpenID/OpenIDPlugin.php +++ b/plugins/OpenID/OpenIDPlugin.php @@ -281,8 +281,24 @@ class OpenIDPlugin extends Plugin function onCheckSchema() { $schema = Schema::get(); - $schema->ensureDataObject('User_openid'); - $schema->ensureDataObject('User_openid_trustroot'); + $schema->ensureTable('user_openid', + array(new ColumnDef('canonical', 'varchar', + '255', false, 'PRI'), + new ColumnDef('display', 'varchar', + '255', false), + new ColumnDef('user_id', 'integer', + null, false, 'MUL'), + new ColumnDef('created', 'datetime', + null, false), + new ColumnDef('modified', 'timestamp'))); + $schema->ensureTable('user_openid_trustroot', + array(new ColumnDef('trustroot', 'varchar', + '255', false, 'PRI'), + new ColumnDef('user_id', 'integer', + null, false, 'PRI'), + new ColumnDef('created', 'datetime', + null, false), + new ColumnDef('modified', 'timestamp'))); return true; } diff --git a/plugins/OpenID/User_openid.php b/plugins/OpenID/User_openid.php index c3624118e..338e0f6e9 100644 --- a/plugins/OpenID/User_openid.php +++ b/plugins/OpenID/User_openid.php @@ -2,9 +2,9 @@ /** * Table Definition for user_openid */ -require_once INSTALLDIR.'/classes/Plugin_DataObject.php'; +require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; -class User_openid extends Plugin_DataObject +class User_openid extends Memcached_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -33,22 +33,4 @@ class User_openid extends Plugin_DataObject return ($cnt > 0); } - - /** - * Get the TableDef object that represents the table backing this class - * @return TableDef TableDef instance - */ - function tableDef() - { - return new TableDef($this->__table, - array(new ColumnDef('canonical', 'varchar', - '255', false, 'PRI'), - new ColumnDef('display', 'varchar', - '255', false), - new ColumnDef('user_id', 'integer', - null, false, 'MUL'), - new ColumnDef('created', 'datetime', - null, false), - new ColumnDef('modified', 'timestamp'))); - } } diff --git a/plugins/OpenID/User_openid_trustroot.php b/plugins/OpenID/User_openid_trustroot.php index b208dddfd..4654b72df 100644 --- a/plugins/OpenID/User_openid_trustroot.php +++ b/plugins/OpenID/User_openid_trustroot.php @@ -2,9 +2,9 @@ /** * Table Definition for user_openid_trustroot */ -require_once INSTALLDIR.'/classes/Plugin_DataObject.php'; +require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; -class User_openid_trustroot extends Plugin_DataObject +class User_openid_trustroot extends Memcached_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -26,20 +26,4 @@ class User_openid_trustroot extends Plugin_DataObject { return Memcached_DataObject::pkeyGet('User_openid_trustroot', $kv); } - - /** - * Get the TableDef object that represents the table backing this class - * @return TableDef TableDef instance - */ - function tableDef() - { - return new TableDef($this->__table, - array(new ColumnDef('trustroot', 'varchar', - '255', false, 'PRI'), - new ColumnDef('user_id', 'integer', - null, false, 'PRI'), - new ColumnDef('created', 'datetime', - null, false), - new ColumnDef('modified', 'timestamp'))); - } } diff --git a/plugins/UserFlag/UserFlagPlugin.php b/plugins/UserFlag/UserFlagPlugin.php index c276c4b9f..1a3ef3777 100644 --- a/plugins/UserFlag/UserFlagPlugin.php +++ b/plugins/UserFlag/UserFlagPlugin.php @@ -48,7 +48,16 @@ class UserFlagPlugin extends Plugin $schema = Schema::get(); // For storing user-submitted flags on profiles - $schema->ensureDataObject('User_flag_profile'); + + $schema->ensureTable('user_flag_profile', + array(new ColumnDef('profile_id', 'integer', null, + false, 'PRI'), + new ColumnDef('user_id', 'integer', null, + false, 'PRI'), + new ColumnDef('created', 'datetime', null, + false, 'MUL'), + new ColumnDef('cleared', 'datetime', null, + true, 'MUL'))); return true; } diff --git a/plugins/UserFlag/User_flag_profile.php b/plugins/UserFlag/User_flag_profile.php index 2fb27912d..30bd4ae68 100644 --- a/plugins/UserFlag/User_flag_profile.php +++ b/plugins/UserFlag/User_flag_profile.php @@ -21,9 +21,9 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/classes/Plugin_DataObject.php'; +require_once INSTALLDIR . '/classes/Memcached_DataObject.php'; -class User_flag_profile extends Plugin_DataObject +class User_flag_profile extends Memcached_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -65,21 +65,4 @@ class User_flag_profile extends Plugin_DataObject return !empty($ufp); } - - /** - * Get the TableDef object that represents the table backing this class - * @return TableDef TableDef instance - */ - function tableDef() - { - return new TableDef($this->__table, - array(new ColumnDef('profile_id', 'integer', null, - false, 'PRI'), - new ColumnDef('user_id', 'integer', null, - false, 'PRI'), - new ColumnDef('created', 'datetime', null, - false, 'MUL'), - new ColumnDef('cleared', 'datetime', null, - true, 'MUL'))); - } } -- cgit v1.2.3-54-g00ecf From 441b62e87ee740e85494422c9cca8dfe8e10c48c Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 25 Nov 2009 23:12:24 +0000 Subject: Have OpenID plugin DataObjects emit their own .ini info --- plugins/OpenID/User_openid.php | 19 +++++++++++++++++++ plugins/OpenID/User_openid_trustroot.php | 21 ++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/OpenID/User_openid.php b/plugins/OpenID/User_openid.php index 338e0f6e9..c24a1ee9a 100644 --- a/plugins/OpenID/User_openid.php +++ b/plugins/OpenID/User_openid.php @@ -23,6 +23,25 @@ class User_openid extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE + function table() { + + global $_DB_DATAOBJECT; + $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype']; + + return array('canonical' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, + 'display' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, + 'user_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, + 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL, + 'modified' => ($dbtype == 'mysql') ? + DB_DATAOBJECT_MYSQLTIMESTAMP : + DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + ); + } + + function keys() { + return array('canonical' => 'K', 'display' => 'U'); + } + static function hasOpenID($user_id) { $oid = new User_openid(); diff --git a/plugins/OpenID/User_openid_trustroot.php b/plugins/OpenID/User_openid_trustroot.php index 4654b72df..ecf625ab4 100644 --- a/plugins/OpenID/User_openid_trustroot.php +++ b/plugins/OpenID/User_openid_trustroot.php @@ -21,9 +21,28 @@ class User_openid_trustroot extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - + function &pkeyGet($kv) { return Memcached_DataObject::pkeyGet('User_openid_trustroot', $kv); } + + function table() { + + global $_DB_DATAOBJECT; + $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype']; + + return array('trustroot' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, + 'user_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, + 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL, + 'modified' => ($dbtype == 'mysql') ? + DB_DATAOBJECT_MYSQLTIMESTAMP : + DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + ); + } + + function keys() { + return array('trustroot' => 'K', 'user_id' => 'K'); + } + } -- cgit v1.2.3-54-g00ecf From a1f87f415a2fcc393c07a633423e2ce299256a18 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 26 Nov 2009 01:25:20 +0000 Subject: OpenID plugin should set 'user_openid.display' as unique key --- plugins/OpenID/OpenIDPlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php index 9b0a4cd45..e86725d70 100644 --- a/plugins/OpenID/OpenIDPlugin.php +++ b/plugins/OpenID/OpenIDPlugin.php @@ -285,7 +285,7 @@ class OpenIDPlugin extends Plugin array(new ColumnDef('canonical', 'varchar', '255', false, 'PRI'), new ColumnDef('display', 'varchar', - '255', false), + '255', false, 'UNI'), new ColumnDef('user_id', 'integer', null, false, 'MUL'), new ColumnDef('created', 'datetime', -- cgit v1.2.3-54-g00ecf From 2d25f288a7bebedd6c2eaf08081bccdbc9107d36 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 26 Nov 2009 02:21:23 +0000 Subject: Some changes to the OpenID DataObjects to make them emit the exact same .ini info as what used to be in statusnet.ini before OpenID was pulled out into a plugin. --- plugins/OpenID/User_openid.php | 16 +++++++++------- plugins/OpenID/User_openid_trustroot.php | 15 ++++++++------- 2 files changed, 17 insertions(+), 14 deletions(-) (limited to 'plugins') diff --git a/plugins/OpenID/User_openid.php b/plugins/OpenID/User_openid.php index c24a1ee9a..801b49ecc 100644 --- a/plugins/OpenID/User_openid.php +++ b/plugins/OpenID/User_openid.php @@ -23,26 +23,28 @@ class User_openid extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - function table() { + function table() + { - global $_DB_DATAOBJECT; - $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype']; + $db = $this->getDatabaseConnection(); + $dbtype = $db->phptype; // Database type is stored here. Crazy but true. return array('canonical' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, 'display' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, 'user_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL, - 'modified' => ($dbtype == 'mysql') ? - DB_DATAOBJECT_MYSQLTIMESTAMP : + 'modified' => ($dbtype == 'mysql' || $dbtype == 'mysqli') ? + DB_DATAOBJECT_MYSQLTIMESTAMP + DB_DATAOBJECT_NOTNULL : DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME ); } - function keys() { + function keys() + { return array('canonical' => 'K', 'display' => 'U'); } - static function hasOpenID($user_id) + Static function hasOpenID($user_id) { $oid = new User_openid(); diff --git a/plugins/OpenID/User_openid_trustroot.php b/plugins/OpenID/User_openid_trustroot.php index ecf625ab4..44288945b 100644 --- a/plugins/OpenID/User_openid_trustroot.php +++ b/plugins/OpenID/User_openid_trustroot.php @@ -27,21 +27,22 @@ class User_openid_trustroot extends Memcached_DataObject return Memcached_DataObject::pkeyGet('User_openid_trustroot', $kv); } - function table() { - - global $_DB_DATAOBJECT; - $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype']; + function table() + { + $db = $this->getDatabaseConnection(); + $dbtype = $db->phptype; // Database type is stored here. Crazy but true. return array('trustroot' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, 'user_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL, - 'modified' => ($dbtype == 'mysql') ? - DB_DATAOBJECT_MYSQLTIMESTAMP : + 'modified' => ($dbtype == 'mysql' || $dbtype == 'mysqli') ? + DB_DATAOBJECT_MYSQLTIMESTAMP + DB_DATAOBJECT_NOTNULL : DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME ); } - function keys() { + function keys() + { return array('trustroot' => 'K', 'user_id' => 'K'); } -- cgit v1.2.3-54-g00ecf From b16cabc3bccc1c46178e86dda2a8ba55ff97f28c Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 26 Nov 2009 12:57:52 +0000 Subject: Renamed icon file --- plugins/UserFlag/UserFlagPlugin.php | 2 +- plugins/UserFlag/flag.gif | Bin 80 -> 0 bytes plugins/UserFlag/icon_flag.gif | Bin 0 -> 80 bytes 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 plugins/UserFlag/flag.gif create mode 100644 plugins/UserFlag/icon_flag.gif (limited to 'plugins') diff --git a/plugins/UserFlag/UserFlagPlugin.php b/plugins/UserFlag/UserFlagPlugin.php index 1a3ef3777..198202847 100644 --- a/plugins/UserFlag/UserFlagPlugin.php +++ b/plugins/UserFlag/UserFlagPlugin.php @@ -140,7 +140,7 @@ class UserFlagPlugin extends Plugin { $action->elementStart('style', array('type' => 'text/css')); $action->raw('.entity_flag input, .entity_flag p {'. - ' background:url('.common_path('plugins/UserFlag/flag.gif').') 5px 5px no-repeat;'. + ' background:url('.common_path('plugins/UserFlag/icon_flag.gif').') 5px 5px no-repeat;'. ' }'); $action->elementEnd('style'); diff --git a/plugins/UserFlag/flag.gif b/plugins/UserFlag/flag.gif deleted file mode 100644 index 68c8aee25..000000000 Binary files a/plugins/UserFlag/flag.gif and /dev/null differ diff --git a/plugins/UserFlag/icon_flag.gif b/plugins/UserFlag/icon_flag.gif new file mode 100644 index 000000000..68c8aee25 Binary files /dev/null and b/plugins/UserFlag/icon_flag.gif differ -- cgit v1.2.3-54-g00ecf From 385573f174a66c18c6879ee9228241b842416f0d Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 26 Nov 2009 13:03:01 +0000 Subject: Moved CSS out to its own file for UserFlag --- plugins/UserFlag/UserFlagPlugin.php | 8 ++------ plugins/UserFlag/userflag.css | 4 ++++ 2 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 plugins/UserFlag/userflag.css (limited to 'plugins') diff --git a/plugins/UserFlag/UserFlagPlugin.php b/plugins/UserFlag/UserFlagPlugin.php index 198202847..1c58a6916 100644 --- a/plugins/UserFlag/UserFlagPlugin.php +++ b/plugins/UserFlag/UserFlagPlugin.php @@ -138,12 +138,8 @@ class UserFlagPlugin extends Plugin function onEndShowStatusNetStyles($action) { - $action->elementStart('style', array('type' => 'text/css')); - $action->raw('.entity_flag input, .entity_flag p {'. - ' background:url('.common_path('plugins/UserFlag/icon_flag.gif').') 5px 5px no-repeat;'. - ' }'); - $action->elementEnd('style'); - + $action->cssLink(common_path('plugins/UserFlag/userflag.css'), + null, 'screen, projection, tv'); return true; } diff --git a/plugins/UserFlag/userflag.css b/plugins/UserFlag/userflag.css new file mode 100644 index 000000000..98da24cc9 --- /dev/null +++ b/plugins/UserFlag/userflag.css @@ -0,0 +1,4 @@ +.entity_flag input.submit, +.entity_flag p { +background:url(icon_flag.gif) 5px 5px no-repeat; +} -- cgit v1.2.3-54-g00ecf From e9d27dc96ea3fb64cd7c6a5d5078a764a38d8e7c Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 26 Nov 2009 13:07:59 +0000 Subject: Updated moderation (sandbox, silence, delete) icons for UserFlag --- plugins/UserFlag/icon_sandbox.gif | Bin 0 -> 104 bytes plugins/UserFlag/icon_silence.gif | Bin 0 -> 103 bytes plugins/UserFlag/icon_skull.gif | Bin 0 -> 85 bytes plugins/UserFlag/userflag.css | 13 +++++++++++++ 4 files changed, 13 insertions(+) create mode 100644 plugins/UserFlag/icon_sandbox.gif create mode 100644 plugins/UserFlag/icon_silence.gif create mode 100644 plugins/UserFlag/icon_skull.gif (limited to 'plugins') diff --git a/plugins/UserFlag/icon_sandbox.gif b/plugins/UserFlag/icon_sandbox.gif new file mode 100644 index 000000000..17031962a Binary files /dev/null and b/plugins/UserFlag/icon_sandbox.gif differ diff --git a/plugins/UserFlag/icon_silence.gif b/plugins/UserFlag/icon_silence.gif new file mode 100644 index 000000000..7a8d43592 Binary files /dev/null and b/plugins/UserFlag/icon_silence.gif differ diff --git a/plugins/UserFlag/icon_skull.gif b/plugins/UserFlag/icon_skull.gif new file mode 100644 index 000000000..033506732 Binary files /dev/null and b/plugins/UserFlag/icon_skull.gif differ diff --git a/plugins/UserFlag/userflag.css b/plugins/UserFlag/userflag.css index 98da24cc9..e8ba66da8 100644 --- a/plugins/UserFlag/userflag.css +++ b/plugins/UserFlag/userflag.css @@ -2,3 +2,16 @@ .entity_flag p { background:url(icon_flag.gif) 5px 5px no-repeat; } +.entity_sandbox input.submit, +.entity_sandbox p { +background:url(icon_sandbox.gif) 5px 5px no-repeat; +} +.entity_silence input.submit, +.entity_silence p { +background:url(icon_silence.gif) 5px 5px no-repeat; +} +.entity_delete input.submit, +.entity_delete p { +background:url(icon_skull.gif) 5px 5px no-repeat; +} + -- cgit v1.2.3-54-g00ecf From cde4d866ebab1b5e305064788a29cf8c0cf5fe66 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 26 Nov 2009 13:20:08 +0000 Subject: Updated the cross line on silence icon --- plugins/UserFlag/icon_silence.gif | Bin 103 -> 103 bytes 1 file changed, 0 insertions(+), 0 deletions(-) (limited to 'plugins') diff --git a/plugins/UserFlag/icon_silence.gif b/plugins/UserFlag/icon_silence.gif index 7a8d43592..8c624acdd 100644 Binary files a/plugins/UserFlag/icon_silence.gif and b/plugins/UserFlag/icon_silence.gif differ -- cgit v1.2.3-54-g00ecf From 5f5a107991478824133c6e0d67aeb1a7efb8e213 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 26 Nov 2009 18:46:11 +0000 Subject: Init UI to show/hide a group of moderation actions. --- lib/userprofile.php | 57 ++++++++++++++++++++-------------- plugins/UserFlag/adminprofileflag.php | 5 +++ plugins/UserFlag/icon_badge.gif | Bin 0 -> 105 bytes plugins/UserFlag/userflag.css | 38 +++++++++++++++++++++++ 4 files changed, 76 insertions(+), 24 deletions(-) create mode 100644 plugins/UserFlag/icon_badge.gif (limited to 'plugins') diff --git a/lib/userprofile.php b/lib/userprofile.php index ee205af85..07e575085 100644 --- a/lib/userprofile.php +++ b/lib/userprofile.php @@ -304,34 +304,43 @@ class UserProfile extends Widget } $this->out->elementEnd('li'); - if ($cur->hasRight(Right::SANDBOXUSER)) { - $this->out->elementStart('li', 'entity_sandbox'); - if ($this->user->isSandboxed()) { - $usf = new UnSandboxForm($this->out, $this->profile, $r2args); - $usf->show(); - } else { - $sf = new SandboxForm($this->out, $this->profile, $r2args); - $sf->show(); + if ($cur->hasRight(Right::SANDBOXUSER) || + $cur->hasRight(Right::SILENCEUSER) || + $cur->hasRight(Right::DELETEUSER)) { + $this->out->elementStart('li', 'entity_moderation'); + $this->out->element('p', null, _('Moderate')); + $this->out->elementStart('ul'); + if ($cur->hasRight(Right::SANDBOXUSER)) { + $this->out->elementStart('li', 'entity_sandbox'); + if ($this->user->isSandboxed()) { + $usf = new UnSandboxForm($this->out, $this->profile, $r2args); + $usf->show(); + } else { + $sf = new SandboxForm($this->out, $this->profile, $r2args); + $sf->show(); + } + $this->out->elementEnd('li'); } - $this->out->elementEnd('li'); - } - if ($cur->hasRight(Right::SILENCEUSER)) { - $this->out->elementStart('li', 'entity_silence'); - if ($this->user->isSilenced()) { - $usf = new UnSilenceForm($this->out, $this->profile, $r2args); - $usf->show(); - } else { - $sf = new SilenceForm($this->out, $this->profile, $r2args); - $sf->show(); + if ($cur->hasRight(Right::SILENCEUSER)) { + $this->out->elementStart('li', 'entity_silence'); + if ($this->user->isSilenced()) { + $usf = new UnSilenceForm($this->out, $this->profile, $r2args); + $usf->show(); + } else { + $sf = new SilenceForm($this->out, $this->profile, $r2args); + $sf->show(); + } + $this->out->elementEnd('li'); } - $this->out->elementEnd('li'); - } - if ($cur->hasRight(Right::DELETEUSER)) { - $this->out->elementStart('li', 'entity_delete'); - $df = new DeleteUserForm($this->out, $this->profile, $r2args); - $df->show(); + if ($cur->hasRight(Right::DELETEUSER)) { + $this->out->elementStart('li', 'entity_delete'); + $df = new DeleteUserForm($this->out, $this->profile, $r2args); + $df->show(); + $this->out->elementEnd('li'); + } + $this->out->elementEnd('ul'); $this->out->elementEnd('li'); } } diff --git a/plugins/UserFlag/adminprofileflag.php b/plugins/UserFlag/adminprofileflag.php index 1ac76b506..20b808637 100644 --- a/plugins/UserFlag/adminprofileflag.php +++ b/plugins/UserFlag/adminprofileflag.php @@ -145,10 +145,15 @@ class FlaggedProfileListItem extends ProfileListItem $this->startActions(); if (Event::handle('StartProfileListItemActionElements', array($this))) { + $this->out->elementStart('li', 'entity_moderation'); + $this->out->element('p', null, _('Moderate')); + $this->out->elementStart('ul'); $this->showSandboxButton(); $this->showSilenceButton(); $this->showDeleteButton(); $this->showClearButton(); + $this->out->elementEnd('ul'); + $this->out->elementEnd('li'); Event::handle('EndProfileListItemActionElements', array($this)); } $this->endActions(); diff --git a/plugins/UserFlag/icon_badge.gif b/plugins/UserFlag/icon_badge.gif new file mode 100644 index 000000000..2dca5b99f Binary files /dev/null and b/plugins/UserFlag/icon_badge.gif differ diff --git a/plugins/UserFlag/userflag.css b/plugins/UserFlag/userflag.css index e8ba66da8..d69c2467f 100644 --- a/plugins/UserFlag/userflag.css +++ b/plugins/UserFlag/userflag.css @@ -15,3 +15,41 @@ background:url(icon_silence.gif) 5px 5px no-repeat; background:url(icon_skull.gif) 5px 5px no-repeat; } +.entity_moderation { +position:relative; +} +.entity_moderation p { +background:transparent url(icon_badge.gif) 5px 5px no-repeat; +box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); +-moz-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); +-webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.25); +border-radius:4px; +-moz-border-radius:4px; +-webkit-border-radius:4px; +font-weight:bold; +padding-bottom:2px; +margin-bottom:7px; + +color:#002FA7; +} +.entity_moderation ul { +display:none; +} +.entity_moderation:hover ul { +display:block; +background-color:#FFFFFF; +min-width:21%; +width:100%; +border:1px solid #DDDDDD; +padding:11px; +position:absolute; +top:-1px; +right:-1px; +z-index:1; +box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); +-moz-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); +-webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.25); +border-radius:7px; +-moz-border-radius:7px; +-webkit-border-radius:7px; +} -- cgit v1.2.3-54-g00ecf From 2755a6d3cfdd654657b00cf701af57c45ef05471 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 26 Nov 2009 20:38:06 +0000 Subject: Moved moderation styles out of UserFlag and put it into core. Updated icons. --- plugins/UserFlag/icon_badge.gif | Bin 105 -> 0 bytes plugins/UserFlag/icon_sandbox.gif | Bin 104 -> 0 bytes plugins/UserFlag/icon_silence.gif | Bin 103 -> 0 bytes plugins/UserFlag/icon_skull.gif | Bin 85 -> 0 bytes plugins/UserFlag/userflag.css | 51 ---------------------- theme/base/css/display.css | 29 ++++++++++++ theme/base/images/icons/icons-01.gif | Bin 2913 -> 3097 bytes theme/base/images/icons/twotone/green/badge.gif | Bin 0 -> 105 bytes theme/base/images/icons/twotone/green/sandbox.gif | Bin 0 -> 104 bytes theme/base/images/icons/twotone/green/silence.gif | Bin 0 -> 103 bytes theme/base/images/icons/twotone/green/skull.gif | Bin 0 -> 85 bytes theme/default/css/display.css | 31 ++++++++++--- theme/identica/css/display.css | 31 ++++++++++--- 13 files changed, 81 insertions(+), 61 deletions(-) delete mode 100644 plugins/UserFlag/icon_badge.gif delete mode 100644 plugins/UserFlag/icon_sandbox.gif delete mode 100644 plugins/UserFlag/icon_silence.gif delete mode 100644 plugins/UserFlag/icon_skull.gif create mode 100644 theme/base/images/icons/twotone/green/badge.gif create mode 100644 theme/base/images/icons/twotone/green/sandbox.gif create mode 100644 theme/base/images/icons/twotone/green/silence.gif create mode 100644 theme/base/images/icons/twotone/green/skull.gif (limited to 'plugins') diff --git a/plugins/UserFlag/icon_badge.gif b/plugins/UserFlag/icon_badge.gif deleted file mode 100644 index 2dca5b99f..000000000 Binary files a/plugins/UserFlag/icon_badge.gif and /dev/null differ diff --git a/plugins/UserFlag/icon_sandbox.gif b/plugins/UserFlag/icon_sandbox.gif deleted file mode 100644 index 17031962a..000000000 Binary files a/plugins/UserFlag/icon_sandbox.gif and /dev/null differ diff --git a/plugins/UserFlag/icon_silence.gif b/plugins/UserFlag/icon_silence.gif deleted file mode 100644 index 8c624acdd..000000000 Binary files a/plugins/UserFlag/icon_silence.gif and /dev/null differ diff --git a/plugins/UserFlag/icon_skull.gif b/plugins/UserFlag/icon_skull.gif deleted file mode 100644 index 033506732..000000000 Binary files a/plugins/UserFlag/icon_skull.gif and /dev/null differ diff --git a/plugins/UserFlag/userflag.css b/plugins/UserFlag/userflag.css index d69c2467f..98da24cc9 100644 --- a/plugins/UserFlag/userflag.css +++ b/plugins/UserFlag/userflag.css @@ -2,54 +2,3 @@ .entity_flag p { background:url(icon_flag.gif) 5px 5px no-repeat; } -.entity_sandbox input.submit, -.entity_sandbox p { -background:url(icon_sandbox.gif) 5px 5px no-repeat; -} -.entity_silence input.submit, -.entity_silence p { -background:url(icon_silence.gif) 5px 5px no-repeat; -} -.entity_delete input.submit, -.entity_delete p { -background:url(icon_skull.gif) 5px 5px no-repeat; -} - -.entity_moderation { -position:relative; -} -.entity_moderation p { -background:transparent url(icon_badge.gif) 5px 5px no-repeat; -box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); --moz-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); --webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.25); -border-radius:4px; --moz-border-radius:4px; --webkit-border-radius:4px; -font-weight:bold; -padding-bottom:2px; -margin-bottom:7px; - -color:#002FA7; -} -.entity_moderation ul { -display:none; -} -.entity_moderation:hover ul { -display:block; -background-color:#FFFFFF; -min-width:21%; -width:100%; -border:1px solid #DDDDDD; -padding:11px; -position:absolute; -top:-1px; -right:-1px; -z-index:1; -box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); --moz-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); --webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.25); -border-radius:7px; --moz-border-radius:7px; --webkit-border-radius:7px; -} diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 33d5ba07e..c9aa81cfb 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -750,6 +750,35 @@ display:none; text-align:center; } +.entity_moderation { +position:relative; +} +.entity_moderation p { +border-radius:4px; +-moz-border-radius:4px; +-webkit-border-radius:4px; +font-weight:bold; +padding-bottom:2px; +margin-bottom:7px; +} +.entity_moderation ul { +display:none; +} +.entity_moderation:hover ul { +display:block; +min-width:21%; +width:100%; +padding:11px; +position:absolute; +top:-1px; +right:-1px; +z-index:1; +border-width:1px; +border-style:solid; +border-radius:7px; +-moz-border-radius:7px; +-webkit-border-radius:7px; +} .entity_tags ul { list-style-type:none; diff --git a/theme/base/images/icons/icons-01.gif b/theme/base/images/icons/icons-01.gif index f19d6a3f6..e910c86c0 100644 Binary files a/theme/base/images/icons/icons-01.gif and b/theme/base/images/icons/icons-01.gif differ diff --git a/theme/base/images/icons/twotone/green/badge.gif b/theme/base/images/icons/twotone/green/badge.gif new file mode 100644 index 000000000..2dca5b99f Binary files /dev/null and b/theme/base/images/icons/twotone/green/badge.gif differ diff --git a/theme/base/images/icons/twotone/green/sandbox.gif b/theme/base/images/icons/twotone/green/sandbox.gif new file mode 100644 index 000000000..17031962a Binary files /dev/null and b/theme/base/images/icons/twotone/green/sandbox.gif differ diff --git a/theme/base/images/icons/twotone/green/silence.gif b/theme/base/images/icons/twotone/green/silence.gif new file mode 100644 index 000000000..8c624acdd Binary files /dev/null and b/theme/base/images/icons/twotone/green/silence.gif differ diff --git a/theme/base/images/icons/twotone/green/skull.gif b/theme/base/images/icons/twotone/green/skull.gif new file mode 100644 index 000000000..033506732 Binary files /dev/null and b/theme/base/images/icons/twotone/green/skull.gif differ diff --git a/theme/default/css/display.css b/theme/default/css/display.css index 00cac0ea3..4b1c26967 100644 --- a/theme/default/css/display.css +++ b/theme/default/css/display.css @@ -40,7 +40,8 @@ box-shadow:3px 3px 7px rgba(194, 194, 194, 0.3); .entity_send-a-message .form_notice, .pagination .nav_prev a, .pagination .nav_next a, -.form_settings fieldset fieldset { +.form_settings fieldset fieldset, +.entity_moderation:hover ul { border-color:#DDDDDD; } @@ -61,6 +62,7 @@ input.submit, .entity_remote_subscribe, .entity_actions a, .entity_actions input, +.entity_moderation p, button { box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); -moz-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); @@ -89,7 +91,8 @@ a, .form_settings input.form_action-primary, .notice-options input, .entity_actions a, -.entity_actions input { +.entity_actions input, +.entity_moderation p { color:#002FA7; } @@ -144,7 +147,11 @@ button.close, .form_user_unsubscribe input.submit, .form_group_join input.submit, .form_user_subscribe input.submit, -.entity_subscribe a { +.entity_subscribe a, +.entity_moderation p, +.entity_sandbox input.submit, +.entity_silence input.submit, +.entity_delete input.submit { background-image:url(../../base/images/icons/icons-01.gif); background-repeat:no-repeat; background-color:transparent; @@ -169,7 +176,8 @@ border-color:transparent; } #content, #site_nav_local_views .current a, -.entity_send-a-message .form_notice { +.entity_send-a-message .form_notice, +.entity_moderation:hover ul { background-color:#FFFFFF; } @@ -252,7 +260,8 @@ background-position: 5px -718px; .entity_send-a-message a { background-position: 5px -852px; } -.entity_send-a-message .form_notice { +.entity_send-a-message .form_notice, +.entity_moderation:hover ul { box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7); -moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7); -webkit-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7); @@ -271,6 +280,18 @@ background-position: 5px -918px; .form_make_admin input.submit { background-position: 5px -983px; } +.entity_moderation p { +background-position: 5px -1313px; +} +.form_user_sandbox input.submit { +background-position: 5px -1380px; +} +.form_user_silence input.submit { +background-position: 5px -1445px; +} +.form_user_deleteuser input.submit { +background-position: 5px -1511px; +} /* NOTICES */ .notice .attachment { diff --git a/theme/identica/css/display.css b/theme/identica/css/display.css index 12541341d..8bacd7673 100644 --- a/theme/identica/css/display.css +++ b/theme/identica/css/display.css @@ -40,7 +40,8 @@ box-shadow:3px 3px 7px rgba(194, 194, 194, 0.3); .entity_send-a-message .form_notice, .pagination .nav_prev a, .pagination .nav_next a, -.form_settings fieldset fieldset { +.form_settings fieldset fieldset, +.entity_moderation:hover ul { border-color:#DDDDDD; } @@ -61,6 +62,7 @@ input.submit, .entity_remote_subscribe, .entity_actions a, .entity_actions input, +.entity_moderation p, button { box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); -moz-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); @@ -89,7 +91,8 @@ a, .form_settings input.form_action-primary, .notice-options input, .entity_actions a, -.entity_actions input { +.entity_actions input, +.entity_moderation p { color:#002FA7; } @@ -144,7 +147,11 @@ button.close, .form_user_unsubscribe input.submit, .form_group_join input.submit, .form_user_subscribe input.submit, -.entity_subscribe a { +.entity_subscribe a, +.entity_moderation p, +.entity_sandbox input.submit, +.entity_silence input.submit, +.entity_delete input.submit { background-image:url(../../base/images/icons/icons-01.gif); background-repeat:no-repeat; background-color:transparent; @@ -169,7 +176,8 @@ border-color:transparent; } #content, #site_nav_local_views .current a, -.entity_send-a-message .form_notice { +.entity_send-a-message .form_notice, +.entity_moderation:hover ul { background-color:#FFFFFF; } @@ -251,7 +259,8 @@ background-position: 5px -718px; .entity_send-a-message a { background-position: 5px -852px; } -.entity_send-a-message .form_notice { +.entity_send-a-message .form_notice, +.entity_moderation:hover ul { box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7); -moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7); -webkit-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7); @@ -270,6 +279,18 @@ background-position: 5px -918px; .form_make_admin input.submit { background-position: 5px -983px; } +.entity_moderation p { +background-position: 5px -1313px; +} +.form_user_sandbox input.submit { +background-position: 5px -1380px; +} +.form_user_silence input.submit { +background-position: 5px -1445px; +} +.form_user_deleteuser input.submit { +background-position: 5px -1511px; +} /* NOTICES */ .notice .attachment { -- cgit v1.2.3-54-g00ecf From 36e38b4b6037ee36c0c1aaef445c1914a865be94 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 28 Nov 2009 14:29:25 +0000 Subject: Only bind submit to the notice created by Realtime --- plugins/Realtime/realtimeupdate.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index 4ed95ff26..3043f43a3 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -87,11 +87,13 @@ RealtimeUpdate = { } var noticeItem = RealtimeUpdate.makeNoticeItem(data); + var noticeItemID = $(noticeItem).attr('id'); + $("#notices_primary .notices").prepend(noticeItem); $("#notices_primary .notice:first").css({display:"none"}); $("#notices_primary .notice:first").fadeIn(1000); - SN.U.NoticeReply(); + SN.U.FormXHR($('#'+noticeItemID+' .form_favor')); SN.U.NoticeFavor(); }, -- cgit v1.2.3-54-g00ecf From 2ac1179a05f50a9825a2d1955f49aee7a6254f04 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 28 Nov 2009 14:51:22 +0000 Subject: SN.U.NoticeFavor should be SN.U.NoticeReply --- plugins/Realtime/realtimeupdate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index 3043f43a3..8f99b6e03 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -94,7 +94,7 @@ RealtimeUpdate = { $("#notices_primary .notice:first").fadeIn(1000); SN.U.FormXHR($('#'+noticeItemID+' .form_favor')); - SN.U.NoticeFavor(); + SN.U.NoticeReply(); }, purgeLastNoticeItem: function() { -- cgit v1.2.3-54-g00ecf From c413a4d80b9a5bfc339f0c7e2fff8b2b4bbccc32 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 28 Nov 2009 14:58:57 +0000 Subject: Explictly unbinding is unnecessary as jQuery's remove() takes care of it --- plugins/Realtime/realtimeupdate.js | 3 --- 1 file changed, 3 deletions(-) (limited to 'plugins') diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index 8f99b6e03..6d47c4487 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -99,9 +99,6 @@ RealtimeUpdate = { purgeLastNoticeItem: function() { if ($('#notices_primary .notice').length > RealtimeUpdate._maxnotices) { - $("#notices_primary .notice:last .form_disfavor").unbind('submit'); - $("#notices_primary .notice:last .form_favor").unbind('submit'); - $("#notices_primary .notice:last .notice_reply").unbind('click'); $("#notices_primary .notice:last").remove(); } }, -- cgit v1.2.3-54-g00ecf From 237f2c2d786f0b9fdd2b120d37e21e7ff120b252 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 28 Nov 2009 15:46:24 +0000 Subject: Calling NoticeReplyTo instead of NoticeReply in Realtime --- plugins/Realtime/realtimeupdate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index 6d47c4487..267c99f78 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -94,7 +94,7 @@ RealtimeUpdate = { $("#notices_primary .notice:first").fadeIn(1000); SN.U.FormXHR($('#'+noticeItemID+' .form_favor')); - SN.U.NoticeReply(); + SN.U.NoticeReplyTo($('#'+noticeItemID)); }, purgeLastNoticeItem: function() { -- cgit v1.2.3-54-g00ecf From 6d29f3f2de58e4552942718ddef7ce67dcb99c5b Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 28 Nov 2009 20:54:32 +0100 Subject: Bind for UserFlag only if there is a form for it --- plugins/UserFlag/UserFlagPlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/UserFlag/UserFlagPlugin.php b/plugins/UserFlag/UserFlagPlugin.php index 1c58a6916..4717a63dd 100644 --- a/plugins/UserFlag/UserFlagPlugin.php +++ b/plugins/UserFlag/UserFlagPlugin.php @@ -146,7 +146,7 @@ class UserFlagPlugin extends Plugin function onEndShowScripts($action) { $action->elementStart('script', array('type' => 'text/javascript')); - $action->raw('/**/'); + $action->raw('/* 0) { SN.U.FormXHR($(".form_entity_flag")); } /*]]>*/'); $action->elementEnd('script'); return true; } -- cgit v1.2.3-54-g00ecf From a1bea8bb38c20e1174ba733fdf1e8af69c2ef3d7 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 29 Nov 2009 19:27:33 +0000 Subject: Allow Realtime notices to use the attachments interface --- plugins/Realtime/realtimeupdate.js | 1 + 1 file changed, 1 insertion(+) (limited to 'plugins') diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index 267c99f78..9ea5c3bce 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -95,6 +95,7 @@ RealtimeUpdate = { SN.U.FormXHR($('#'+noticeItemID+' .form_favor')); SN.U.NoticeReplyTo($('#'+noticeItemID)); + SN.U.NoticeWithAttachment($('#'+noticeItemID)); }, purgeLastNoticeItem: function() { -- cgit v1.2.3-54-g00ecf From 1d1778137b61ec3013fd7d1a9ac72a40c0fc4c49 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 30 Nov 2009 11:12:45 +0100 Subject: Minor. Removed whitespace. --- plugins/MobileProfile/MobileProfilePlugin.php | 3 --- 1 file changed, 3 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index 9f8923396..fd6057550 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -63,9 +63,6 @@ class MobileProfilePlugin extends WAP20Plugin function onStartShowHTML($action) { - - - // XXX: This should probably graduate to WAP20Plugin // If they are on the mobile site, serve them MP -- cgit v1.2.3-54-g00ecf From 68a42f49175245cd79e085685f883f9a92ead921 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 30 Nov 2009 11:45:40 +0000 Subject: Updated notice-options items alignment in MobileProfile --- plugins/MobileProfile/mp-screen.css | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 1bb0248ec..fa94275f4 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -1,3 +1,12 @@ +/** theme: mobile profile screen + * + * @package StatusNet + * @author Sarven Capadisli + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + @import url(../../theme/base/css/display.css); @import url(../../theme/identica/css/display.css); @@ -167,9 +176,6 @@ margin-bottom:0; .profile { padding-top:4px; padding-bottom:4px; -} -.notice .entry-title { - } .notice div.entry-content { margin-left:0; @@ -180,14 +186,21 @@ width:30%; margin-right:2%; } - +.notice-options form { +width:16px; +height:16px; +} +.notice-options a, +.notice-options input { +box-shadow:none; +-moz-box-shadow:none; +-webkit-box-shadow:none; +} .entity_profile { width:auto; } - - .entity_actions { margin-right:0; margin-left:0; @@ -239,8 +252,6 @@ height:auto; margin-right:5%; } - - #footer { width:96%; padding:2%; -- cgit v1.2.3-54-g00ecf From 5f63c0279fcc09a54473a683e3b006b6f0efcc3d Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 30 Nov 2009 12:27:48 +0000 Subject: Better notice_data-attach alignment for mobile devices that support input type file --- plugins/MobileProfile/mp-screen.css | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index fa94275f4..88826a199 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -73,8 +73,6 @@ width:auto; margin-left:0; } - - #site_nav_global_primary { margin:0; width:100%; @@ -90,7 +88,6 @@ float:left; font-size:0.9em; } - #form_notice { width:100%; } @@ -115,13 +112,16 @@ iPhone/iPod Touch, Android, Opera Mini Simulator display:none; } #form_notice #notice_data-attach { -top:auto; -bottom:0; -left:0; -right:auto; -opacity:1; +position:static; +clear:both; +width:50%; +height:auto; +display:block; z-index:9; -width:65%; +padding:0; +margin:0; +background:none; +opacity:1; } #form_notice #notice_action-submit { -- cgit v1.2.3-54-g00ecf From 5bb07ba6bedb4a30a3c5be2f0f66e8d997ad6201 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 30 Nov 2009 12:29:02 +0000 Subject: Increased notice_data-attach width --- plugins/MobileProfile/mp-screen.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 88826a199..e05adeb83 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -114,7 +114,7 @@ display:none; #form_notice #notice_data-attach { position:static; clear:both; -width:50%; +width:65%; height:auto; display:block; z-index:9; -- cgit v1.2.3-54-g00ecf From 10f40661a2f517393331b554c2fec295c8c160e8 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 30 Nov 2009 08:09:30 -0800 Subject: Ticket 1870: drop unnecessary Tidy module installation requirement. Tidy was only being used by a couple of non-default URL shortener plugins, PtitUrl and TightUrl. Both were easily changed to load the tag-soup HTML via DOMDocument (using the default DOM module which is already used by other dependencies). Added xml, dom, and simplexml modules to the requirements check in install.php, as they were being used but not checked for. Also cleaned up LilUrl, PtitUrl, and TightUrl to return URL as a string instead of as a SimpleXML node object. --- README | 2 -- install.php | 2 +- plugins/LilUrl/LilUrlPlugin.php | 2 +- plugins/PtitUrl/PtitUrlPlugin.php | 9 ++++++--- plugins/TightUrl/TightUrlPlugin.php | 9 ++++++--- plugins/UrlShortener/UrlShortenerPlugin.php | 8 -------- 6 files changed, 14 insertions(+), 18 deletions(-) (limited to 'plugins') diff --git a/README b/README index eb1fb8cd7..51a8e91cc 100644 --- a/README +++ b/README @@ -98,7 +98,6 @@ released Aug 26 2009. Notable changes this version: - Better error handling in Twitter posting. - Show oEmbed data for XHTML files as well as plain HTML. - Updated bug database link in README. -- require HTML tidy extension. - add support for HTTP Basic Auth in PHP CGI or FastCGI (e.g. GoDaddy). - autofocus input to selected entry elements depending on page. - updated layout for filter-by-tag form. @@ -179,7 +178,6 @@ Your PHP installation must include the following PHP extensions: - GD. For scaling down avatar images. - mbstring. For handling Unicode (UTF-8) encoded strings. - gettext. For multiple languages. Default on many PHP installs. -- tidy. Used to clean up HTML/URLs for the URL shortener to consume. For some functionality, you will also need the following extensions: diff --git a/install.php b/install.php index e7f7cf318..1c62bb2b2 100644 --- a/install.php +++ b/install.php @@ -301,7 +301,7 @@ function checkPrereqs() } $reqs = array('gd', 'curl', - 'xmlwriter', 'mbstring','tidy'); + 'xmlwriter', 'mbstring', 'xml', 'dom', 'simplexml'); foreach ($reqs as $req) { if (!checkExtension($req)) { diff --git a/plugins/LilUrl/LilUrlPlugin.php b/plugins/LilUrl/LilUrlPlugin.php index e906751e8..4a6f1cdc7 100644 --- a/plugins/LilUrl/LilUrlPlugin.php +++ b/plugins/LilUrl/LilUrlPlugin.php @@ -54,7 +54,7 @@ class LilUrlPlugin extends UrlShortenerPlugin if (!isset($y->body)) return; $x = $y->body->p[0]->a->attributes(); if (isset($x['href'])) { - return $x['href']; + return strval($x['href']); } } } diff --git a/plugins/PtitUrl/PtitUrlPlugin.php b/plugins/PtitUrl/PtitUrlPlugin.php index ef453e96d..76a438dd5 100644 --- a/plugins/PtitUrl/PtitUrlPlugin.php +++ b/plugins/PtitUrl/PtitUrlPlugin.php @@ -47,11 +47,14 @@ class PtitUrlPlugin extends UrlShortenerPlugin { $response = $this->http_get(sprintf($this->serviceUrl,urlencode($url))); if (!$response) return; - $response = $this->tidy($response); - $y = @simplexml_load_string($response); + $dom = new DOMDocument(); + @$dom->loadHTML($response); + $y = @simplexml_import_dom($dom); if (!isset($y->body)) return; $xml = $y->body->center->table->tr->td->pre->a->attributes(); - if (isset($xml['href'])) return $xml['href']; + if (isset($xml['href'])) { + return strval($xml['href']); + } } } diff --git a/plugins/TightUrl/TightUrlPlugin.php b/plugins/TightUrl/TightUrlPlugin.php index 56414c8c8..6ced9afdc 100644 --- a/plugins/TightUrl/TightUrlPlugin.php +++ b/plugins/TightUrl/TightUrlPlugin.php @@ -48,10 +48,13 @@ class TightUrlPlugin extends UrlShortenerPlugin { $response = $this->http_get(sprintf($this->serviceUrl,urlencode($url))); if (!$response) return; - $response = $this->tidy($response); - $y = @simplexml_load_string($response); + $dom = new DOMDocument(); + @$dom->loadHTML($response); + $y = @simplexml_import_dom($dom); if (!isset($y->body)) return; $xml = $y->body->p[0]->code[0]->a->attributes(); - if (isset($xml['href'])) return $xml['href']; + if (isset($xml['href'])) { + return strval($xml['href']); + } } } diff --git a/plugins/UrlShortener/UrlShortenerPlugin.php b/plugins/UrlShortener/UrlShortenerPlugin.php index 37206aa89..027624b7a 100644 --- a/plugins/UrlShortener/UrlShortenerPlugin.php +++ b/plugins/UrlShortener/UrlShortenerPlugin.php @@ -68,14 +68,6 @@ abstract class UrlShortenerPlugin extends Plugin return $response->getBody(); } - protected function tidy($response) { - $response = str_replace(' ', ' ', $response); - $config = array('output-xhtml' => true); - $tidy = new tidy; - $tidy->parseString($response, $config, 'utf8'); - $tidy->cleanRepair(); - return (string)$tidy; - } //------------Below are the methods that connect StatusNet to the implementing Url Shortener plugin------------\\ function onInitializePlugin(){ -- cgit v1.2.3-54-g00ecf From fcf1c4e53fb765a90cb01a3430826e64c4e83e40 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 2 Dec 2009 19:33:08 +0000 Subject: Remove useless debugging statement --- plugins/TwitterBridge/daemons/twitterstatusfetcher.php | 1 - 1 file changed, 1 deletion(-) (limited to 'plugins') diff --git a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php index b5428316b..eba1d563b 100755 --- a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php +++ b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php @@ -109,7 +109,6 @@ class TwitterStatusFetcher extends ParallelizingDaemon $flink->find(); $flinks = array(); - common_log(LOG_INFO, "hello"); while ($flink->fetch()) { -- cgit v1.2.3-54-g00ecf