summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--EVENTS.txt8
-rw-r--r--README6
-rw-r--r--actions/allrss.php2
-rw-r--r--actions/apiatomservice.php100
-rw-r--r--actions/apistatusesshow.php62
-rw-r--r--actions/apitimelineuser.php301
-rw-r--r--actions/oembed.php19
-rw-r--r--actions/rsd.php14
-rw-r--r--actions/shownotice.php14
-rw-r--r--classes/File.php44
-rw-r--r--classes/File_oembed.php28
-rw-r--r--classes/File_redirection.php21
-rw-r--r--classes/File_thumbnail.php41
-rw-r--r--classes/Notice.php29
-rw-r--r--classes/Profile.php23
-rw-r--r--classes/User.php7
-rw-r--r--extlib/Services/oEmbed.php357
-rw-r--r--extlib/Services/oEmbed/Exception.php65
-rw-r--r--extlib/Services/oEmbed/Exception/NoSupport.php63
-rw-r--r--extlib/Services/oEmbed/Object.php126
-rw-r--r--extlib/Services/oEmbed/Object/Common.php139
-rw-r--r--extlib/Services/oEmbed/Object/Exception.php65
-rw-r--r--extlib/Services/oEmbed/Object/Link.php73
-rw-r--r--extlib/Services/oEmbed/Object/Photo.php89
-rw-r--r--extlib/Services/oEmbed/Object/Rich.php82
-rw-r--r--extlib/Services/oEmbed/Object/Video.php82
-rw-r--r--js/util.js57
-rw-r--r--lib/action.php11
-rw-r--r--lib/apiaction.php6
-rw-r--r--lib/attachmentlist.php69
-rw-r--r--lib/default.php3
-rw-r--r--lib/imagefile.php103
-rw-r--r--lib/inlineattachmentlist.php108
-rw-r--r--lib/mediafile.php49
-rw-r--r--lib/noticelist.php8
-rw-r--r--lib/oembedhelper.php318
-rw-r--r--lib/profileaction.php10
-rw-r--r--lib/router.php11
-rw-r--r--lib/statusnet.php6
-rw-r--r--lib/userprofile.php25
-rw-r--r--lib/util.php2
-rw-r--r--lib/xmppmanager.php2
-rw-r--r--plugins/EmailSummary/EmailSummaryPlugin.php202
-rw-r--r--plugins/EmailSummary/Email_summary_status.php167
-rw-r--r--plugins/EmailSummary/sendemailsummary.php47
-rw-r--r--plugins/EmailSummary/siteemailsummaryhandler.php96
-rw-r--r--plugins/EmailSummary/useremailsummaryhandler.php226
-rw-r--r--plugins/Mapstraction/MapstractionPlugin.php3
-rw-r--r--plugins/ModPlus/ModPlusPlugin.php116
-rw-r--r--plugins/ModPlus/modplus.css23
-rw-r--r--plugins/ModPlus/modplus.js23
-rw-r--r--plugins/ModPlus/remoteprofileaction.php106
-rw-r--r--plugins/TwitterBridge/Notice_to_status.php7
-rw-r--r--plugins/TwitterBridge/twitter.php32
-rw-r--r--plugins/TwitterBridge/twitterimport.php17
-rwxr-xr-xscripts/clear_jabber.php92
-rw-r--r--tests/oEmbedTest.php140
-rw-r--r--theme/base/css/display.css15
58 files changed, 2600 insertions, 1360 deletions
diff --git a/EVENTS.txt b/EVENTS.txt
index d31f2a227..8bdc93db8 100644
--- a/EVENTS.txt
+++ b/EVENTS.txt
@@ -1180,3 +1180,11 @@ StartRevokeRole: when a role is being revoked
EndRevokeRole: when a role has been revoked
- $profile: profile that lost the role
- $role: string name of the role
+
+StartAtomPubNewActivity: When a new activity comes in through Atom Pub API
+- &$activity: received activity
+
+EndAtomPubNewActivity: When a new activity comes in through Atom Pub API
+- $activity: received activity
+- $notice: notice that was created
+
diff --git a/README b/README
index b36d8b745..6343e3e02 100644
--- a/README
+++ b/README
@@ -220,14 +220,12 @@ and the URLs are listed here for your convenience.
version may render your StatusNet site unable to send or receive XMPP
messages.
- Facebook library. Used for the Facebook application.
-- PEAR Services_oEmbed. Used for some multimedia integration.
-- PEAR HTTP_Request is an oEmbed dependency.
-- PEAR Validate is an oEmbed dependency.
-- PEAR Net_URL2 is an oEmbed dependency.
+- PEAR Validate is used for URL and email validation.
- Console_GetOpt for parsing command-line options.
- libomb. a library for implementing OpenMicroBlogging 0.1, the
predecessor to OStatus.
- HTTP_Request2, a library for making HTTP requests.
+- PEAR Net_URL2 is an HTTP_Request2 dependency.
A design goal of StatusNet is that the basic Web functionality should
work on even the most restrictive commercial hosting services.
diff --git a/actions/allrss.php b/actions/allrss.php
index d398c8a6a..573bb4eb2 100644
--- a/actions/allrss.php
+++ b/actions/allrss.php
@@ -56,6 +56,8 @@ class AllrssAction extends Rss10Action
* @param array $args Web and URL arguments
*
* @return boolean false if user doesn't exist
+ *
+ */
function prepare($args)
{
parent::prepare($args);
diff --git a/actions/apiatomservice.php b/actions/apiatomservice.php
new file mode 100644
index 000000000..fb9d6aee8
--- /dev/null
+++ b/actions/apiatomservice.php
@@ -0,0 +1,100 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * An AtomPub service document for a user
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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 <http://www.gnu.org/licenses/>.
+ *
+ * @category API
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
+ * @link http://status.net/
+ */
+
+require_once INSTALLDIR.'/lib/apibareauth.php';
+
+/**
+ * Shows an AtomPub service document for a user
+ *
+ * @category API
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
+ * @link http://status.net/
+ */
+
+class ApiAtomServiceAction extends ApiBareAuthAction
+{
+ /**
+ * Take arguments for running
+ *
+ * @param array $args $_REQUEST args
+ *
+ * @return boolean success flag
+ *
+ */
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+ $this->user = $this->getTargetUser($this->arg('id'));
+
+ if (empty($this->user)) {
+ $this->clientError(_('No such user.'), 404, $this->format);
+ return;
+ }
+
+ return true;
+ }
+
+ /**
+ * Handle the arguments. In our case, show a service document.
+ *
+ * @param Array $args unused.
+ *
+ * @return void
+ */
+
+ function handle($args)
+ {
+ parent::handle($args);
+
+ header('Content-Type: application/atomsvc+xml');
+
+ $this->startXML();
+ $this->elementStart('service', array('xmlns' => 'http://www.w3.org/2007/app',
+ 'xmlns:atom' => 'http://www.w3.org/2005/Atom'));
+ $this->elementStart('workspace');
+ $this->element('atom:title', null, _('Main'));
+ $this->elementStart('collection',
+ array('href' => common_local_url('ApiTimelineUser',
+ array('id' => $this->user->id,
+ 'format' => 'atom'))));
+ $this->element('atom:title',
+ null,
+ sprintf(_("%s timeline"),
+ $this->user->nickname));
+ $this->element('accept', null, 'application/atom+xml;type=entry');
+ $this->elementEnd('collection');
+ $this->elementEnd('workspace');
+ $this->elementEnd('service');
+ $this->endXML();
+ }
+}
diff --git a/actions/apistatusesshow.php b/actions/apistatusesshow.php
index a98e45f79..e684a07ee 100644
--- a/actions/apistatusesshow.php
+++ b/actions/apistatusesshow.php
@@ -100,13 +100,23 @@ class ApiStatusesShowAction extends ApiPrivateAuthAction
{
parent::handle($args);
- if (!in_array($this->format, array('xml', 'json'))) {
+ if (!in_array($this->format, array('xml', 'json', 'atom'))) {
// TRANS: Client error displayed when trying to handle an unknown API method.
- $this->clientError(_('API method not found.'), $code = 404);
+ $this->clientError(_('API method not found.'), 404);
return;
}
- $this->showNotice();
+ switch ($_SERVER['REQUEST_METHOD']) {
+ case 'GET':
+ $this->showNotice();
+ break;
+ case 'DELETE':
+ $this->deleteNotice();
+ break;
+ default:
+ $this->clientError(_('HTTP method not supported.'), 405);
+ return;
+ }
}
/**
@@ -117,10 +127,18 @@ class ApiStatusesShowAction extends ApiPrivateAuthAction
function showNotice()
{
if (!empty($this->notice)) {
- if ($this->format == 'xml') {
+ switch ($this->format) {
+ case 'xml':
$this->showSingleXmlStatus($this->notice);
- } elseif ($this->format == 'json') {
+ break;
+ case 'json':
$this->show_single_json_status($this->notice);
+ break;
+ case 'atom':
+ $this->showSingleAtomStatus($this->notice);
+ break;
+ default:
+ throw new Exception(sprintf(_("Unsupported format: %s"), $this->format));
}
} else {
// XXX: Twitter just sets a 404 header and doens't bother
@@ -153,9 +171,14 @@ class ApiStatusesShowAction extends ApiPrivateAuthAction
*
* @return boolean true
*/
+
function isReadOnly($args)
{
- return true;
+ if ($_SERVER['REQUEST_METHOD'] == 'GET') {
+ return true;
+ } else {
+ return false;
+ }
}
/**
@@ -197,4 +220,31 @@ class ApiStatusesShowAction extends ApiPrivateAuthAction
return null;
}
+
+ function deleteNotice()
+ {
+ if ($this->format != 'atom') {
+ $this->clientError(_("Can only delete using the Atom format."));
+ return;
+ }
+
+ if (empty($this->auth_user) ||
+ ($this->notice->profile_id != $this->auth_user->id &&
+ !$this->auth_user->hasRight(Right::DELETEOTHERSNOTICE))) {
+ $this->clientError(_('Can\'t delete this notice.'), 403);
+ return;
+ }
+
+ if (Event::handle('StartDeleteOwnNotice', array($this->auth_user, $this->notice))) {
+ $this->notice->delete();
+ Event::handle('EndDeleteOwnNotice', array($this->auth_user, $this->notice));
+ }
+
+ // @fixme is there better output we could do here?
+
+ header('HTTP/1.1 200 OK');
+ header('Content-Type: text/plain');
+ print(sprintf(_('Deleted notice %d'), $this->notice->id));
+ print("\n");
+ }
}
diff --git a/actions/apitimelineuser.php b/actions/apitimelineuser.php
index 0046c462d..f716232e4 100644
--- a/actions/apitimelineuser.php
+++ b/actions/apitimelineuser.php
@@ -97,7 +97,12 @@ class ApiTimelineUserAction extends ApiBareAuthAction
function handle($args)
{
parent::handle($args);
- $this->showTimeline();
+
+ if ($this->isPost()) {
+ $this->handlePost();
+ } else {
+ $this->showTimeline();
+ }
}
/**
@@ -114,9 +119,9 @@ class ApiTimelineUserAction extends ApiBareAuthAction
$atom = new AtomUserNoticeFeed($this->user, $this->auth_user);
$link = common_local_url(
- 'showstream',
- array('nickname' => $this->user->nickname)
- );
+ 'showstream',
+ array('nickname' => $this->user->nickname)
+ );
$self = $this->getSelfUri();
@@ -132,20 +137,63 @@ class ApiTimelineUserAction extends ApiBareAuthAction
break;
case 'rss':
$this->showRssTimeline(
- $this->notices,
- $atom->title,
- $link,
- $atom->subtitle,
- $suplink,
- $atom->logo,
- $self
- );
+ $this->notices,
+ $atom->title,
+ $link,
+ $atom->subtitle,
+ $suplink,
+ $atom->logo,
+ $self
+ );
break;
case 'atom':
header('Content-Type: application/atom+xml; charset=utf-8');
$atom->setId($self);
$atom->setSelfLink($self);
+
+ // Add navigation links: next, prev, first
+ // Note: we use IDs rather than pages for navigation; page boundaries
+ // change too quickly!
+
+ if (!empty($this->next_id)) {
+ $nextUrl = common_local_url('ApiTimelineUser',
+ array('format' => 'atom',
+ 'id' => $this->user->id),
+ array('max_id' => $this->next_id));
+
+ $atom->addLink($nextUrl,
+ array('rel' => 'next',
+ 'type' => 'application/atom+xml'));
+ }
+
+ if (($this->page > 1 || !empty($this->max_id)) && !empty($this->notices)) {
+
+ $lastNotice = $this->notices[0];
+ $lastId = $lastNotice->id;
+
+ $prevUrl = common_local_url('ApiTimelineUser',
+ array('format' => 'atom',
+ 'id' => $this->user->id),
+ array('since_id' => $lastId));
+
+ $atom->addLink($prevUrl,
+ array('rel' => 'prev',
+ 'type' => 'application/atom+xml'));
+ }
+
+ if ($this->page > 1 || !empty($this->since_id) || !empty($this->max_id)) {
+
+ $firstUrl = common_local_url('ApiTimelineUser',
+ array('format' => 'atom',
+ 'id' => $this->user->id));
+
+ $atom->addLink($firstUrl,
+ array('rel' => 'first',
+ 'type' => 'application/atom+xml'));
+
+ }
+
$atom->addEntryFromNotices($this->notices);
$this->raw($atom->getString());
@@ -169,13 +217,18 @@ class ApiTimelineUserAction extends ApiBareAuthAction
{
$notices = array();
- $notice = $this->user->getNotices(
- ($this->page-1) * $this->count, $this->count,
- $this->since_id, $this->max_id
- );
+ $notice = $this->user->getNotices(($this->page-1) * $this->count,
+ $this->count + 1,
+ $this->since_id,
+ $this->max_id);
while ($notice->fetch()) {
- $notices[] = clone($notice);
+ if (count($notices) < $this->count) {
+ $notices[] = clone($notice);
+ } else {
+ $this->next_id = $notice->id;
+ break;
+ }
}
return $notices;
@@ -188,9 +241,14 @@ class ApiTimelineUserAction extends ApiBareAuthAction
*
* @return boolean true
*/
+
function isReadOnly($args)
{
- return true;
+ if ($_SERVER['REQUEST_METHOD'] == 'GET') {
+ return true;
+ } else {
+ return false;
+ }
}
/**
@@ -221,17 +279,206 @@ class ApiTimelineUserAction extends ApiBareAuthAction
$last = count($this->notices) - 1;
return '"' . implode(
- ':',
- array($this->arg('action'),
- common_user_cache_hash($this->auth_user),
- common_language(),
- $this->user->id,
- strtotime($this->notices[0]->created),
- strtotime($this->notices[$last]->created))
- )
- . '"';
+ ':',
+ array($this->arg('action'),
+ common_user_cache_hash($this->auth_user),
+ common_language(),
+ $this->user->id,
+ strtotime($this->notices[0]->created),
+ strtotime($this->notices[$last]->created))
+ )
+ . '"';
}
return null;
}
+
+ function handlePost()
+ {
+ if (empty($this->auth_user) ||
+ $this->auth_user->id != $this->user->id) {
+ $this->clientError(_("Only the user can add to their own timeline."));
+ return;
+ }
+
+ if ($this->format != 'atom') {
+ // Only handle posts for Atom
+ $this->clientError(_("Only accept AtomPub for atom feeds."));
+ return;
+ }
+
+ $xml = file_get_contents('php://input');
+
+ $dom = DOMDocument::loadXML($xml);
+
+ if ($dom->documentElement->namespaceURI != Activity::ATOM ||
+ $dom->documentElement->localName != 'entry') {
+ $this->clientError(_('Atom post must be an Atom entry.'));
+ return;
+ }
+
+ $activity = new Activity($dom->documentElement);
+
+ if (Event::handle('StartAtomPubNewActivity', array(&$activity))) {
+
+ if ($activity->verb != ActivityVerb::POST) {
+ $this->clientError(_('Can only handle post activities.'));
+ return;
+ }
+
+ $note = $activity->objects[0];
+
+ if (!in_array($note->type, array(ActivityObject::NOTE,
+ ActivityObject::BLOGENTRY,
+ ActivityObject::STATUS))) {
+ $this->clientError(sprintf(_('Cannot handle activity object type "%s"',
+ $note->type)));
+ return;
+ }
+
+ $saved = $this->postNote($activity);
+
+ Event::handle('EndAtomPubNewActivity', array($activity, $saved));
+ }
+
+ if (!empty($saved)) {
+ header("Location: " . common_local_url('ApiStatusesShow', array('notice_id' => $saved->id,
+ 'format' => 'atom')));
+ $this->showSingleAtomStatus($saved);
+ }
+ }
+
+ function postNote($activity)
+ {
+ $note = $activity->objects[0];
+
+ // Use summary as fallback for content
+
+ if (!empty($note->content)) {
+ $sourceContent = $note->content;
+ } else if (!empty($note->summary)) {
+ $sourceContent = $note->summary;
+ } else if (!empty($note->title)) {
+ $sourceContent = $note->title;
+ } else {
+ // @fixme fetch from $sourceUrl?
+ // @todo i18n FIXME: use sprintf and add i18n.
+ $this->clientError("No content for notice {$note->id}.");
+ return;
+ }
+
+ // Get (safe!) HTML and text versions of the content
+
+ $rendered = $this->purify($sourceContent);
+ $content = html_entity_decode(strip_tags($rendered), ENT_QUOTES, 'UTF-8');
+
+ $shortened = common_shorten_links($content);
+
+ $options = array('is_local' => Notice::LOCAL_PUBLIC,
+ 'rendered' => $rendered,
+ 'replies' => array(),
+ 'groups' => array(),
+ 'tags' => array(),
+ 'urls' => array());
+
+ // accept remote URI (not necessarily a good idea)
+
+ common_debug("Note ID is {$note->id}");
+
+ if (!empty($note->id)) {
+ $notice = Notice::staticGet('uri', trim($note->id));
+
+ if (!empty($notice)) {
+ $this->clientError(sprintf(_('Notice with URI "%s" already exists.'),
+ $note->id));
+ return;
+ }
+ common_log(LOG_NOTICE, "Saving client-supplied notice URI '$note->id'");
+ $options['uri'] = $note->id;
+ }
+
+ // accept remote create time (also maybe not such a good idea)
+
+ if (!empty($activity->time)) {
+ common_log(LOG_NOTICE, "Saving client-supplied create time {$activity->time}");
+ $options['created'] = common_sql_date($activity->time);
+ }
+
+ // Check for optional attributes...
+
+ if (!empty($activity->context)) {
+
+ foreach ($activity->context->attention as $uri) {
+
+ $profile = Profile::fromURI($uri);
+
+ if (!empty($profile)) {
+ $options['replies'] = $uri;
+ } else {
+ $group = User_group::staticGet('uri', $uri);
+ if (!empty($group)) {
+ $options['groups'] = $uri;
+ } else {
+ // @fixme: hook for discovery here
+ common_log(LOG_WARNING, sprintf(_('AtomPub post with unknown attention URI %s'), $uri));
+ }
+ }
+ }
+
+ // Maintain direct reply associations
+ // @fixme what about conversation ID?
+
+ if (!empty($activity->context->replyToID)) {
+ $orig = Notice::staticGet('uri',
+ $activity->context->replyToID);
+ if (!empty($orig)) {
+ $options['reply_to'] = $orig->id;
+ }
+ }
+
+ $location = $activity->context->location;
+
+ if ($location) {
+ $options['lat'] = $location->lat;
+ $options['lon'] = $location->lon;
+ if ($location->location_id) {
+ $options['location_ns'] = $location->location_ns;
+ $options['location_id'] = $location->location_id;
+ }
+ }
+ }
+
+ // Atom categories <-> hashtags
+
+ foreach ($activity->categories as $cat) {
+ if ($cat->term) {
+ $term = common_canonical_tag($cat->term);
+ if ($term) {
+ $options['tags'][] = $term;
+ }
+ }
+ }
+
+ // Atom enclosures -> attachment URLs
+ foreach ($activity->enclosures as $href) {
+ // @fixme save these locally or....?
+ $options['urls'][] = $href;
+ }
+
+ $saved = Notice::saveNew($this->user->id,
+ $content,
+ 'atompub', // TODO: deal with this
+ $options);
+
+ return $saved;
+ }
+
+ function purify($content)
+ {
+ require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php';
+
+ $config = array('safe' => 1,
+ 'deny_attribute' => 'id,style,on*');
+ return htmLawed($content, $config);
+ }
}
diff --git a/actions/oembed.php b/actions/oembed.php
index da3aa0c71..09d68a446 100644
--- a/actions/oembed.php
+++ b/actions/oembed.php
@@ -108,10 +108,23 @@ class OembedAction extends Action
$oembed['url']=$file_oembed->url;
}else if(substr($attachment->mimetype,0,strlen('image/'))=='image/'){
$oembed['type']='photo';
- //TODO set width and height
- //$oembed['width']=
- //$oembed['height']=
+ if ($attachment->filename) {
+ $filepath = File::path($attachment->filename);
+ $gis = @getimagesize($filepath);
+ if ($gis) {
+ $oembed['width'] = $gis[0];
+ $oembed['height'] = $gis[1];
+ } else {
+ // TODO Either throw an error or find a fallback?
+ }
+ }
$oembed['url']=$attachment->url;
+ $thumb = $attachment->getThumbnail();
+ if ($thumb) {
+ $oembed['thumbnail_url'] = $thumb->url;
+ $oembed['thumbnail_width'] = $thumb->width;
+ $oembed['thumbnail_height'] = $thumb->height;
+ }
}else{
$oembed['type']='link';
$oembed['url']=common_local_url('attachment',
diff --git a/actions/rsd.php b/actions/rsd.php
index f88bf2e9a..e02c85c41 100644
--- a/actions/rsd.php
+++ b/actions/rsd.php
@@ -162,6 +162,20 @@ class RsdAction extends Action
'true');
$this->elementEnd('settings');
$this->elementEnd('api');
+
+ // Atom API
+
+ if (empty($this->user)) {
+ $service = common_local_url('ApiAtomService');
+ } else {
+ $service = common_local_url('ApiAtomService', array('id' => $this->user->nickname));
+ }
+
+ $this->element('api', array('name' => 'Atom',
+ 'preferred' => 'false',
+ 'apiLink' => $service,
+ 'blogID' => $blogID));
+
Event::handle('EndRsdListApis', array($this, $this->user));
}
$this->elementEnd('apis');
diff --git a/actions/shownotice.php b/actions/shownotice.php
index b7e61a137..b4af7dbaa 100644
--- a/actions/shownotice.php
+++ b/actions/shownotice.php
@@ -332,6 +332,15 @@ class SingleNoticeItem extends DoFollowListItem
}
/**
+ * For our zoomed-in special case we'll use a fuller list
+ * for the attachment info.
+ */
+ function showNoticeAttachments() {
+ $al = new AttachmentList($this->notice, $this->out);
+ $al->show();
+ }
+
+ /**
* show the avatar of the notice's author
*
* We use the larger size for single notice page.
@@ -356,9 +365,4 @@ class SingleNoticeItem extends DoFollowListItem
$this->profile->fullname :
$this->profile->nickname));
}
-
- function showNoticeAttachments() {
- $al = new AttachmentList($this->notice, $this->out);
- $al->show();
- }
}
diff --git a/classes/File.php b/classes/File.php
index 16e00024a..ef9dbf14a 100644
--- a/classes/File.php
+++ b/classes/File.php
@@ -116,10 +116,24 @@ class File extends Memcached_DataObject
}
/**
+ * Go look at a URL and possibly save data about it if it's new:
+ * - follow redirect chains and store them in file_redirection
+ * - look up oEmbed data and save it in file_oembed
+ * - if a thumbnail is available, save it in file_thumbnail
+ * - save file record with basic info
+ * - optionally save a file_to_post record
+ * - return the File object with the full reference
+ *
* @fixme refactor this mess, it's gotten pretty scary.
- * @param bool $followRedirects
+ * @param string $given_url the URL we're looking at
+ * @param int $notice_id (optional)
+ * @param bool $followRedirects defaults to true
+ *
+ * @return mixed File on success, -1 on some errors
+ *
+ * @throws ServerException on some errors
*/
- function processNew($given_url, $notice_id=null, $followRedirects=true) {
+ public function processNew($given_url, $notice_id=null, $followRedirects=true) {
if (empty($given_url)) return -1; // error, no url to process
$given_url = File_redirection::_canonUrl($given_url);
if (empty($given_url)) return -1; // error, no url to process
@@ -352,22 +366,28 @@ class File extends Memcached_DataObject
$mimetype = substr($mimetype,0,$semicolon);
}
if(in_array($mimetype,$notEnclosureMimeTypes)){
+ // Never treat generic HTML links as an enclosure type!
+ // But if we have oEmbed info, we'll consider it golden.
$oembed = File_oembed::staticGet('file_id',$this->id);
- if($oembed){
+ if($oembed && in_array($oembed->type, array('photo', 'video'))){
$mimetype = strtolower($oembed->mimetype);
$semicolon = strpos($mimetype,';');
if($semicolon){
$mimetype = substr($mimetype,0,$semicolon);
}
- if(in_array($mimetype,$notEnclosureMimeTypes)){
- return false;
- }else{
+ // @fixme uncertain if this is right.
+ // we want to expose things like YouTube videos as
+ // viewable attachments, but don't expose them as
+ // downloadable enclosures.....?
+ //if (in_array($mimetype, $notEnclosureMimeTypes)) {
+ // return false;
+ //} else {
if($oembed->mimetype) $enclosure->mimetype=$oembed->mimetype;
if($oembed->url) $enclosure->url=$oembed->url;
if($oembed->title) $enclosure->title=$oembed->title;
if($oembed->modified) $enclosure->modified=$oembed->modified;
unset($oembed->size);
- }
+ //}
} else {
return false;
}
@@ -382,4 +402,14 @@ class File extends Memcached_DataObject
$enclosure = $this->getEnclosure();
return !empty($enclosure);
}
+
+ /**
+ * Get the attachment's thumbnail record, if any.
+ *
+ * @return File_thumbnail
+ */
+ function getThumbnail()
+ {
+ return File_thumbnail::staticGet('file_id', $this->id);
+ }
}
diff --git a/classes/File_oembed.php b/classes/File_oembed.php
index 4813d5dda..b7bf3a5da 100644
--- a/classes/File_oembed.php
+++ b/classes/File_oembed.php
@@ -58,26 +58,16 @@ class File_oembed extends Memcached_DataObject
return array(false, false, false);
}
- function _getOembed($url, $maxwidth = 500, $maxheight = 400) {
- require_once INSTALLDIR.'/extlib/Services/oEmbed.php';
+ function _getOembed($url) {
$parameters = array(
- 'maxwidth'=>$maxwidth,
- 'maxheight'=>$maxheight,
+ 'maxwidth' => common_config('attachments', 'thumb_width'),
+ 'maxheight' => common_config('attachments', 'thumb_height'),
);
- try{
- $oEmbed = new Services_oEmbed($url);
- $object = $oEmbed->getObject($parameters);
- return $object;
- }catch(Exception $e){
- try{
- $oEmbed = new Services_oEmbed($url, array(
- Services_oEmbed::OPTION_API => common_config('oohembed', 'endpoint')
- ));
- $object = $oEmbed->getObject($parameters);
- return $object;
- }catch(Exception $ex){
- return false;
- }
+ try {
+ return oEmbedHelper::getObject($url, $parameters);
+ } catch (Exception $e) {
+ common_log(LOG_ERR, "Error during oembed lookup for $url - " . $e->getMessage());
+ return false;
}
}
@@ -120,7 +110,7 @@ class File_oembed extends Memcached_DataObject
}
}
$file_oembed->insert();
- if (!empty($data->thumbnail_url)) {
+ if (!empty($data->thumbnail_url) || ($data->type == 'photo')) {
$ft = File_thumbnail::staticGet('file_id', $file_id);
if (!empty($ft)) {
common_log(LOG_WARNING, "Strangely, a File_thumbnail object exists for new file $file_id",
diff --git a/classes/File_redirection.php b/classes/File_redirection.php
index 68fed77e8..1976e3439 100644
--- a/classes/File_redirection.php
+++ b/classes/File_redirection.php
@@ -91,9 +91,16 @@ class File_redirection extends Memcached_DataObject
$request->setMethod(HTTP_Request2::METHOD_HEAD);
$response = $request->send();
- if (405 == $response->getStatus()) {
+ if (405 == $response->getStatus() || 204 == $response->getStatus()) {
+ // HTTP 405 Unsupported Method
// Server doesn't support HEAD method? Can this really happen?
// We'll try again as a GET and ignore the response data.
+ //
+ // HTTP 204 No Content
+ // YFrog sends 204 responses back for our HEAD checks, which
+ // seems like it may be a logic error in their servers. If
+ // we get a 204 back, re-run it as a GET... if there's really
+ // no content it'll be cheap. :)
$request = self::_commonHttp($short_url, $redirs);
$response = $request->send();
}
@@ -235,6 +242,18 @@ class File_redirection extends Memcached_DataObject
return null;
}
+ /**
+ * Basic attempt to canonicalize a URL, cleaning up some standard variants
+ * such as funny syntax or a missing path. Used internally when cleaning
+ * up URLs for storage and following redirect chains.
+ *
+ * Note that despite being on File_redirect, this function DOES NOT perform
+ * any dereferencing of redirects.
+ *
+ * @param string $in_url input URL
+ * @param string $default_scheme if given a bare link; defaults to 'http://'
+ * @return string
+ */
function _canonUrl($in_url, $default_scheme = 'http://') {
if (empty($in_url)) return false;
$out_url = $in_url;
diff --git a/classes/File_thumbnail.php b/classes/File_thumbnail.php
index edae8ac21..17bac7f08 100644
--- a/classes/File_thumbnail.php
+++ b/classes/File_thumbnail.php
@@ -48,12 +48,45 @@ class File_thumbnail extends Memcached_DataObject
return array(false, false, false);
}
- function saveNew($data, $file_id) {
+ /**
+ * Save oEmbed-provided thumbnail data
+ *
+ * @param object $data
+ * @param int $file_id
+ */
+ public static function saveNew($data, $file_id) {
+ if (!empty($data->thumbnail_url)) {
+ // Non-photo types such as video will usually
+ // show us a thumbnail, though it's not required.
+ self::saveThumbnail($file_id,
+ $data->thumbnail_url,
+ $data->thumbnail_width,
+ $data->thumbnail_height);
+ } else if ($data->type == 'photo') {
+ // The inline photo URL given should also fit within
+ // our requested thumbnail size, per oEmbed spec.
+ self::saveThumbnail($file_id,
+ $data->url,
+ $data->width,
+ $data->height);
+ }
+ }
+
+ /**
+ * Save a thumbnail record for the referenced file record.
+ *
+ * @param int $file_id
+ * @param string $url
+ * @param int $width
+ * @param int $height
+ */
+ static function saveThumbnail($file_id, $url, $width, $height)
+ {
$tn = new File_thumbnail;
$tn->file_id = $file_id;
- $tn->url = $data->thumbnail_url;
- $tn->width = intval($data->thumbnail_width);
- $tn->height = intval($data->thumbnail_height);
+ $tn->url = $url;
+ $tn->width = intval($width);
+ $tn->height = intval($height);
$tn->insert();
}
}
diff --git a/classes/Notice.php b/classes/Notice.php
index eff0d3251..85c7dabea 100644
--- a/classes/Notice.php
+++ b/classes/Notice.php
@@ -1611,6 +1611,35 @@ class Notice extends Memcached_DataObject
Event::handle('EndActivityGeo', array(&$this, &$xs, $lat, $lon));
}
+ // @fixme check this logic
+
+ if ($this->isLocal()) {
+
+ $selfUrl = common_local_url('ApiStatusesShow', array('id' => $this->id,
+ 'format' => 'atom'));
+
+ if (Event::handle('StartActivityRelSelf', array(&$this, &$xs, &$selfUrl))) {
+ $xs->element('link', array('rel' => 'self',
+ 'type' => 'application/atom+xml',
+ 'href' => $selfUrl));
+ Event::handle('EndActivityRelSelf', array(&$this, &$xs, $selfUrl));
+ }
+
+ if (!empty($cur) && $cur->id == $this->profile_id) {
+
+ // note: $selfUrl may have been changed by a plugin
+ $relEditUrl = common_local_url('ApiStatusesShow', array('id' => $this->id,
+ 'format' => 'atom'));
+
+ if (Event::handle('StartActivityRelEdit', array(&$this, &$xs, &$relEditUrl))) {
+ $xs->element('link', array('rel' => 'edit',
+ 'type' => 'application/atom+xml',
+ 'href' => $relEditUrl));
+ Event::handle('EndActivityRelEdit', array(&$this, &$xs, $relEditUrl));
+ }
+ }
+ }
+
if (Event::handle('StartActivityEnd', array(&$this, &$xs))) {
$xs->elementEnd('entry');
Event::handle('EndActivityEnd', array(&$this, &$xs));
diff --git a/classes/Profile.php b/classes/Profile.php
index d580e1235..3ea95ab01 100644
--- a/classes/Profile.php
+++ b/classes/Profile.php
@@ -494,6 +494,29 @@ class Profile extends Memcached_DataObject
return $cnt;
}
+ /**
+ * Is this profile subscribed to another profile?
+ *
+ * @param Profile $other
+ * @return boolean
+ */
+ function isSubscribed($other)
+ {
+ return Subscription::exists($this, $other);
+ }
+
+ /**
+ * Are these two profiles subscribed to each other?
+ *
+ * @param Profile $other
+ * @return boolean
+ */
+ function mutuallySubscribed($other)
+ {
+ return $this->isSubscribed($other) &&
+ $other->isSubscribed($this);
+ }
+
function hasFave($notice)
{
$cache = common_memcache();
diff --git a/classes/User.php b/classes/User.php
index 7345dc7f9..964bc3e7f 100644
--- a/classes/User.php
+++ b/classes/User.php
@@ -84,7 +84,8 @@ class User extends Memcached_DataObject
function isSubscribed($other)
{
- return Subscription::exists($this->getProfile(), $other);
+ $profile = $this->getProfile();
+ return $profile->isSubscribed($other);
}
// 'update' won't write key columns, so we have to do it ourselves.
@@ -418,8 +419,8 @@ class User extends Memcached_DataObject
function mutuallySubscribed($other)
{
- return $this->isSubscribed($other) &&
- $other->isSubscribed($this);
+ $profile = $this->getProfile();
+ return $profile->mutuallySubscribed($other);
}
function mutuallySubscribedUsers()
diff --git a/extlib/Services/oEmbed.php b/extlib/Services/oEmbed.php
deleted file mode 100644
index 0dc8f01b2..000000000
--- a/extlib/Services/oEmbed.php
+++ /dev/null
@@ -1,357 +0,0 @@
-<?php
-
-/**
- * An interface for oEmbed consumption
- *
- * PHP version 5.1.0+
- *
- * Copyright (c) 2008, Digg.com, Inc.
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * - Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- * - Neither the name of Digg.com, Inc. nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version SVN: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-
-require_once 'Validate.php';
-require_once 'Net/URL2.php';
-require_once 'HTTP/Request.php';
-require_once 'Services/oEmbed/Exception.php';
-require_once 'Services/oEmbed/Exception/NoSupport.php';
-require_once 'Services/oEmbed/Object.php';
-
-/**
- * Base class for consuming oEmbed objects
- *
- * <code>
- * <?php
- *
- * require_once 'Services/oEmbed.php';
- *
- * // The URL that we'd like to find out more information about.
- * $url = 'http://flickr.com/photos/joestump/2848795611/';
- *
- * // The oEmbed API URI. Not all providers support discovery yet so we're
- * // explicitly providing one here. If one is not provided Services_oEmbed
- * // attempts to discover it. If none is found an exception is thrown.
- * $oEmbed = new Services_oEmbed($url, array(
- * Services_oEmbed::OPTION_API => 'http://www.flickr.com/services/oembed/'
- * ));
- * $object = $oEmbed->getObject();
- *
- * // All of the objects have somewhat sane __toString() methods that allow
- * // you to output them directly.
- * echo (string)$object;
- *
- * ?>
- * </code>
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version Release: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-class Services_oEmbed
-{
- /**
- * HTTP timeout in seconds
- *
- * All HTTP requests made by Services_oEmbed will respect this timeout.
- * This can be passed to {@link Services_oEmbed::setOption()} or to the
- * options parameter in {@link Services_oEmbed::__construct()}.
- *
- * @var string OPTION_TIMEOUT Timeout in seconds
- */
- const OPTION_TIMEOUT = 'http_timeout';
-
- /**
- * HTTP User-Agent
- *
- * All HTTP requests made by Services_oEmbed will be sent with the
- * string set by this option.
- *
- * @var string OPTION_USER_AGENT The HTTP User-Agent string
- */
- const OPTION_USER_AGENT = 'http_user_agent';
-
- /**
- * The API's URI
- *
- * If the API is known ahead of time this option can be used to explicitly
- * set it. If not present then the API is attempted to be discovered
- * through the auto-discovery mechanism.
- *
- * @var string OPTION_API
- */
- const OPTION_API = 'oembed_api';
-
- /**
- * Options for oEmbed requests
- *
- * @var array $options The options for making requests
- */
- protected $options = array(
- self::OPTION_TIMEOUT => 3,
- self::OPTION_API => null,
- self::OPTION_USER_AGENT => 'Services_oEmbed 0.1.0'
- );
-
- /**
- * URL of object to get embed information for
- *
- * @var object $url {@link Net_URL2} instance of URL of object
- */
- protected $url = null;
-
- /**
- * Constructor
- *
- * @param string $url The URL to fetch an oEmbed for
- * @param array $options A list of options for the oEmbed lookup
- *
- * @throws {@link Services_oEmbed_Exception} if the $url is invalid
- * @throws {@link Services_oEmbed_Exception} when no valid API is found
- * @return void
- */
- public function __construct($url, array $options = array())
- {
- if (Validate::uri($url)) {
- $this->url = new Net_URL2($url);
- } else {
- throw new Services_oEmbed_Exception('URL is invalid');
- }
-
- if (count($options)) {
- foreach ($options as $key => $val) {
- $this->setOption($key, $val);
- }
- }
-
- if ($this->options[self::OPTION_API] === null) {
- $this->options[self::OPTION_API] = $this->discover($url);
- }
- }
-
- /**
- * Set an option for the oEmbed request
- *
- * @param mixed $option The option name
- * @param mixed $value The option value
- *
- * @see Services_oEmbed::OPTION_API, Services_oEmbed::OPTION_TIMEOUT
- * @throws {@link Services_oEmbed_Exception} on invalid option
- * @access public
- * @return void
- */
- public function setOption($option, $value)
- {
- switch ($option) {
- case self::OPTION_API:
- case self::OPTION_TIMEOUT:
- break;
- default:
- throw new Services_oEmbed_Exception(
- 'Invalid option "' . $option . '"'
- );
- }
-
- $func = '_set_' . $option;
- if (method_exists($this, $func)) {
- $this->options[$option] = $this->$func($value);
- } else {
- $this->options[$option] = $value;
- }
- }
-
- /**
- * Set the API option
- *
- * @param string $value The API's URI
- *
- * @throws {@link Services_oEmbed_Exception} on invalid API URI
- * @see Validate::uri()
- * @return string
- */
- protected function _set_oembed_api($value)
- {
- if (!Validate::uri($value)) {
- throw new Services_oEmbed_Exception(
- 'API URI provided is invalid'
- );
- }
-
- return $value;
- }
-
- /**
- * Get the oEmbed response
- *
- * @param array $params Optional parameters for
- *
- * @throws {@link Services_oEmbed_Exception} on cURL errors
- * @throws {@link Services_oEmbed_Exception} on HTTP errors
- * @throws {@link Services_oEmbed_Exception} when result is not parsable
- * @return object The oEmbed response as an object
- */
- public function getObject(array $params = array())
- {
- $params['url'] = $this->url->getURL();
- if (!isset($params['format'])) {
- $params['format'] = 'json';
- }
-
- $sets = array();
- foreach ($params as $var => $val) {
- $sets[] = $var . '=' . urlencode($val);
- }
-
- $url = $this->options[self::OPTION_API] . '?' . implode('&', $sets);
-
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_HEADER, false);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->options[self::OPTION_TIMEOUT]);
- $result = curl_exec($ch);
-
- if (curl_errno($ch)) {
- throw new Services_oEmbed_Exception(
- curl_error($ch), curl_errno($ch)
- );
- }
-
- $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- if (substr($code, 0, 1) != '2') {
- throw new Services_oEmbed_Exception('Non-200 code returned. Got code ' . $code);
- }
-
- curl_close($ch);
-
- switch ($params['format']) {
- case 'json':
- $res = json_decode($result);
- if (!is_object($res)) {
- throw new Services_oEmbed_Exception(
- 'Could not parse JSON response'
- );
- }
- break;
- case 'xml':
- libxml_use_internal_errors(true);
- $res = simplexml_load_string($result);
- if (!$res instanceof SimpleXMLElement) {
- $errors = libxml_get_errors();
- $err = array_shift($errors);
- libxml_clear_errors();
- libxml_use_internal_errors(false);
- throw new Services_oEmbed_Exception(
- $err->message, $error->code
- );
- }
- break;
- }
-
- return Services_oEmbed_Object::factory($res);
- }
-
- /**
- * Discover an oEmbed API
- *
- * @param string $url The URL to attempt to discover oEmbed for
- *
- * @throws {@link Services_oEmbed_Exception} if the $url is invalid
- * @return string The oEmbed API endpoint discovered
- */
- protected function discover($url)
- {
- $body = $this->sendRequest($url);
-
- // Find all <link /> tags that have a valid oembed type set. We then
- // extract the href attribute for each type.
- $regexp = '#<link([^>]*)type[\s\n]*=[\s\n]*"' .
- '(application/json|text/xml)\+oembed"([^>]*)>#im';
-
- $m = $ret = array();
- if (!preg_match_all($regexp, $body, $m)) {
- throw new Services_oEmbed_Exception_NoSupport(
- 'No valid oEmbed links found on page'
- );
- }
-
- foreach ($m[0] as $i => $link) {
- $h = array();
- if (preg_match('/[\s\n]+href[\s\n]*=[\s\n]*"([^"]+)"/im', $link, $h)) {
- $ret[$m[2][$i]] = $h[1];
- }
- }
-
- return (isset($ret['application/json']) ? $ret['application/json'] : array_pop($ret));
- }
-
- /**
- * Send a GET request to the provider
- *
- * @param mixed $url The URL to send the request to
- *
- * @throws {@link Services_oEmbed_Exception} on HTTP errors
- * @return string The contents of the response
- */
- private function sendRequest($url)
- {
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_HEADER, false);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->options[self::OPTION_TIMEOUT]);
- curl_setopt($ch, CURLOPT_USERAGENT, $this->options[self::OPTION_USER_AGENT]);
- $result = curl_exec($ch);
- if (curl_errno($ch)) {
- throw new Services_oEmbed_Exception(
- curl_error($ch), curl_errno($ch)
- );
- }
-
- $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- if (substr($code, 0, 1) != '2') {
- throw new Services_oEmbed_Exception('Non-200 code returned. Got code ' . $code);
- }
-
- return $result;
- }
-}
-
-?>
diff --git a/extlib/Services/oEmbed/Exception.php b/extlib/Services/oEmbed/Exception.php
deleted file mode 100644
index 446ac2a70..000000000
--- a/extlib/Services/oEmbed/Exception.php
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php
-
-/**
- * Base exception class for {@link Services_oEmbed}
- *
- * PHP version 5.1.0+
- *
- * Copyright (c) 2008, Digg.com, Inc.
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * - Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- * - Neither the name of Digg.com, Inc. nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version SVN: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-
-require_once 'PEAR/Exception.php';
-
-/**
- * Base exception class for {@link Services_oEmbed}
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version Release: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-class Services_oEmbed_Exception extends PEAR_Exception
-{
-
-}
-
-?>
diff --git a/extlib/Services/oEmbed/Exception/NoSupport.php b/extlib/Services/oEmbed/Exception/NoSupport.php
deleted file mode 100644
index 384c7191f..000000000
--- a/extlib/Services/oEmbed/Exception/NoSupport.php
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-
-/**
- * Exception class when no oEmbed support is discovered
- *
- * PHP version 5.2.0+
- *
- * Copyright (c) 2008, Digg.com, Inc.
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * - Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- * - Neither the name of Digg.com, Inc. nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version SVN: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-
-/**
- * Exception class when no oEmbed support is discovered
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version Release: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-class Services_oEmbed_Exception_NoSupport extends Services_oEmbed_Exception
-{
-
-}
-
-?>
diff --git a/extlib/Services/oEmbed/Object.php b/extlib/Services/oEmbed/Object.php
deleted file mode 100644
index 9eedd7efb..000000000
--- a/extlib/Services/oEmbed/Object.php
+++ /dev/null
@@ -1,126 +0,0 @@
-<?php
-
-/**
- * An interface for oEmbed consumption
- *
- * PHP version 5.1.0+
- *
- * Copyright (c) 2008, Digg.com, Inc.
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * - Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- * - Neither the name of Digg.com, Inc. nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version SVN: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-
-require_once 'Services/oEmbed/Object/Exception.php';
-
-/**
- * Base class for consuming oEmbed objects
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version Release: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-abstract class Services_oEmbed_Object
-{
-
- /**
- * Valid oEmbed object types
- *
- * @var array $types Array of valid object types
- * @see Services_oEmbed_Object::factory()
- */
- static protected $types = array(
- 'photo' => 'Photo',
- 'video' => 'Video',
- 'link' => 'Link',
- 'rich' => 'Rich'
- );
-
- /**
- * Create an oEmbed object from result
- *
- * @param object $object Raw object returned from API
- *
- * @throws {@link Services_oEmbed_Object_Exception} on object error
- * @return object Instance of object driver
- * @see Services_oEmbed_Object_Link, Services_oEmbed_Object_Photo
- * @see Services_oEmbed_Object_Rich, Services_oEmbed_Object_Video
- */
- static public function factory($object)
- {
- if (!isset($object->type)) {
- throw new Services_oEmbed_Object_Exception(
- 'Object has no type'
- );
- }
-
- $type = (string)$object->type;
- if (!isset(self::$types[$type])) {
- throw new Services_oEmbed_Object_Exception(
- 'Object type is unknown or invalid: ' . $type
- );
- }
-
- $file = 'Services/oEmbed/Object/' . self::$types[$type] . '.php';
- include_once $file;
-
- $class = 'Services_oEmbed_Object_' . self::$types[$type];
- if (!class_exists($class)) {
- throw new Services_oEmbed_Object_Exception(
- 'Object class is invalid or not present'
- );
- }
-
- $instance = new $class($object);
- return $instance;
- }
-
- /**
- * Instantiation is not allowed
- *
- * @return void
- */
- private function __construct()
- {
-
- }
-}
-
-?>
diff --git a/extlib/Services/oEmbed/Object/Common.php b/extlib/Services/oEmbed/Object/Common.php
deleted file mode 100644
index f568ec89f..000000000
--- a/extlib/Services/oEmbed/Object/Common.php
+++ /dev/null
@@ -1,139 +0,0 @@
-<?php
-
-/**
- * Base class for oEmbed objects
- *
- * PHP version 5.1.0+
- *
- * Copyright (c) 2008, Digg.com, Inc.
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * - Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- * - Neither the name of Digg.com, Inc. nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version SVN: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-
-/**
- * Base class for oEmbed objects
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version Release: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-abstract class Services_oEmbed_Object_Common
-{
- /**
- * Raw object returned from API
- *
- * @var object $object The raw object from the API
- */
- protected $object = null;
-
- /**
- * Required fields per the specification
- *
- * @var array $required Array of required fields
- * @link http://oembed.com
- */
- protected $required = array();
-
- /**
- * Constructor
- *
- * @param object $object Raw object returned from the API
- *
- * @throws {@link Services_oEmbed_Object_Exception} on missing fields
- * @return void
- */
- public function __construct($object)
- {
- $this->object = $object;
-
- $this->required[] = 'version';
- foreach ($this->required as $field) {
- if (!isset($this->$field)) {
- throw new Services_oEmbed_Object_Exception(
- 'Object is missing required ' . $field . ' attribute'
- );
- }
- }
- }
-
- /**
- * Get object variable
- *
- * @param string $var Variable to get
- *
- * @see Services_oEmbed_Object_Common::$object
- * @return mixed Attribute's value or null if it's not set/exists
- */
- public function __get($var)
- {
- if (property_exists($this->object, $var)) {
- return $this->object->$var;
- }
-
- return null;
- }
-
- /**
- * Is variable set?
- *
- * @param string $var Variable name to check
- *
- * @return boolean True if set, false if not
- * @see Services_oEmbed_Object_Common::$object
- */
- public function __isset($var)
- {
- if (property_exists($this->object, $var)) {
- return (isset($this->object->$var));
- }
-
- return false;
- }
-
- /**
- * Require a sane __toString for all objects
- *
- * @return string
- */
- abstract public function __toString();
-}
-
-?>
diff --git a/extlib/Services/oEmbed/Object/Exception.php b/extlib/Services/oEmbed/Object/Exception.php
deleted file mode 100644
index 6025ffd49..000000000
--- a/extlib/Services/oEmbed/Object/Exception.php
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php
-
-/**
- * Exception for {@link Services_oEmbed_Object}
- *
- * PHP version 5.1.0+
- *
- * Copyright (c) 2008, Digg.com, Inc.
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * - Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- * - Neither the name of Digg.com, Inc. nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version SVN: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-
-require_once 'Services/oEmbed/Exception.php';
-
-/**
- * Exception for {@link Services_oEmbed_Object}
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version Release: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-class Services_oEmbed_Object_Exception extends Services_oEmbed_Exception
-{
-
-}
-
-?>
diff --git a/extlib/Services/oEmbed/Object/Link.php b/extlib/Services/oEmbed/Object/Link.php
deleted file mode 100644
index 9b627a89a..000000000
--- a/extlib/Services/oEmbed/Object/Link.php
+++ /dev/null
@@ -1,73 +0,0 @@
-<?php
-
-/**
- * Link object for {@link Services_oEmbed}
- *
- * PHP version 5.2.0+
- *
- * Copyright (c) 2008, Digg.com, Inc.
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * - Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- * - Neither the name of Digg.com, Inc. nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version SVN: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-
-require_once 'Services/oEmbed/Object/Common.php';
-
-/**
- * Link object for {@link Services_oEmbed}
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version Release: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-class Services_oEmbed_Object_Link extends Services_oEmbed_Object_Common
-{
- /**
- * Output a sane link
- *
- * @return string An HTML link of the object
- */
- public function __toString()
- {
- return '<a href="' . $this->url . '">' . $this->title . '</a>';
- }
-}
-
-?>
diff --git a/extlib/Services/oEmbed/Object/Photo.php b/extlib/Services/oEmbed/Object/Photo.php
deleted file mode 100644
index 5fbf4292f..000000000
--- a/extlib/Services/oEmbed/Object/Photo.php
+++ /dev/null
@@ -1,89 +0,0 @@
-<?php
-
-/**
- * Photo object for {@link Services_oEmbed}
- *
- * PHP version 5.2.0+
- *
- * Copyright (c) 2008, Digg.com, Inc.
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * - Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- * - Neither the name of Digg.com, Inc. nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version SVN: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-
-require_once 'Services/oEmbed/Object/Common.php';
-
-/**
- * Photo object for {@link Services_oEmbed}
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version Release: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-class Services_oEmbed_Object_Photo extends Services_oEmbed_Object_Common
-{
- /**
- * Required fields for photo objects
- *
- * @var array $required Required fields
- */
- protected $required = array(
- 'url', 'width', 'height'
- );
-
- /**
- * Output a valid HTML tag for image
- *
- * @return string HTML <img /> tag for Photo
- */
- public function __toString()
- {
- $img = '<img src="' . $this->url . '" width="' . $this->width . '" ' .
- 'height="' . $this->height . '"';
-
- if (isset($this->title)) {
- $img .= ' alt="' . $this->title . '"';
- }
-
- return $img . ' />';
- }
-}
-
-?>
diff --git a/extlib/Services/oEmbed/Object/Rich.php b/extlib/Services/oEmbed/Object/Rich.php
deleted file mode 100644
index dbf6933ac..000000000
--- a/extlib/Services/oEmbed/Object/Rich.php
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-
-/**
- * Photo object for {@link Services_oEmbed}
- *
- * PHP version 5.2.0+
- *
- * Copyright (c) 2008, Digg.com, Inc.
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * - Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- * - Neither the name of Digg.com, Inc. nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version SVN: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-
-require_once 'Services/oEmbed/Object/Common.php';
-
-/**
- * Photo object for {@link Services_oEmbed}
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version Release: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-class Services_oEmbed_Object_Rich extends Services_oEmbed_Object_Common
-{
- /**
- * Required fields for rich objects
- *
- * @var array $required Required fields
- */
- protected $required = array(
- 'html', 'width', 'height'
- );
-
- /**
- * Output a the HTML tag for rich object
- *
- * @return string HTML for rich object
- */
- public function __toString()
- {
- return $this->html;
- }
-}
-
-?>
diff --git a/extlib/Services/oEmbed/Object/Video.php b/extlib/Services/oEmbed/Object/Video.php
deleted file mode 100644
index 746108115..000000000
--- a/extlib/Services/oEmbed/Object/Video.php
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-
-/**
- * Photo object for {@link Services_oEmbed}
- *
- * PHP version 5.2.0+
- *
- * Copyright (c) 2008, Digg.com, Inc.
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * - Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- * - Neither the name of Digg.com, Inc. nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version SVN: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-
-require_once 'Services/oEmbed/Object/Common.php';
-
-/**
- * Photo object for {@link Services_oEmbed}
- *
- * @category Services
- * @package Services_oEmbed
- * @author Joe Stump <joe@joestump.net>
- * @copyright 2008 Digg.com, Inc.
- * @license http://tinyurl.com/42zef New BSD License
- * @version Release: @version@
- * @link http://code.google.com/p/digg
- * @link http://oembed.com
- */
-class Services_oEmbed_Object_Video extends Services_oEmbed_Object_Common
-{
- /**
- * Required fields for video objects
- *
- * @var array $required Required fields
- */
- protected $required = array(
- 'html', 'width', 'height'
- );
-
- /**
- * Output a valid embed tag for video
- *
- * @return string HTML for video
- */
- public function __toString()
- {
- return $this->html;
- }
-}
-
-?>
diff --git a/js/util.js b/js/util.js
index 1be3f3053..74eef4df1 100644
--- a/js/util.js
+++ b/js/util.js
@@ -56,7 +56,7 @@ var SN = { // StatusNet
NoticeDataGeoCookie: 'NoticeDataGeo',
NoticeDataGeoSelected: 'notice_data-geo_selected',
StatusNetInstance:'StatusNetInstance'
- },
+ }
},
messages: {},
@@ -427,61 +427,6 @@ var SN = { // StatusNet
return false;
}).attr('title', SN.msg('showmore_tooltip'));
}
- else {
- $.fn.jOverlay.options = {
- method : 'GET',
- data : '',
- url : '',
- color : '#000',
- opacity : '0.6',
- zIndex : 9999,
- center : false,
- imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif',
- bgClickToClose : true,
- success : function() {
- $('#jOverlayContent').append('<button class="close">&#215;</button>');
- $('#jOverlayContent button').click($.closeOverlay);
- },
- timeout : 0,
- autoHide : true,
- css : {'max-width':'542px', 'top':'5%', 'left':'32.5%'}
- };
-
- notice.find('a.attachment').click(function() {
- var attachId = ($(this).attr('id').substring('attachment'.length + 1));
- if (attachId) {
- $().jOverlay({url: $('address .url')[0].href+'attachment/' + attachId + '/ajax'});
- return false;
- }
- });
-
- if ($('#shownotice').length == 0) {
- var t;
- notice.find('a.thumbnail').hover(
- function() {
- var anchor = $(this);
- $('a.thumbnail').children('img').hide();
- anchor.closest(".entry-title").addClass('ov');
-
- if (anchor.children('img').length === 0) {
- t = setTimeout(function() {
- $.get($('address .url')[0].href+'attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) {
- anchor.append(data);
- });
- }, 500);
- }
- else {
- anchor.children('img').show();
- }
- },
- function() {
- clearTimeout(t);
- $('a.thumbnail').children('img').hide();
- $(this).closest('.entry-title').removeClass('ov');
- }
- );
- }
- }
},
NoticeDataAttach: function() {
diff --git a/lib/action.php b/lib/action.php
index 427b85427..17d3e2311 100644
--- a/lib/action.php
+++ b/lib/action.php
@@ -1404,4 +1404,15 @@ class Action extends HTMLOutputter // lawsuit
$this->clientError(_('There was a problem with your session token.'));
}
}
+
+ /**
+ * Check if the current request is a POST
+ *
+ * @return boolean true if POST; otherwise false.
+ */
+
+ function isPost()
+ {
+ return ($_SERVER['REQUEST_METHOD'] == 'POST');
+ }
}
diff --git a/lib/apiaction.php b/lib/apiaction.php
index 4e9dbb310..8a7be3150 100644
--- a/lib/apiaction.php
+++ b/lib/apiaction.php
@@ -726,6 +726,12 @@ class ApiAction extends Action
$this->endDocument('xml');
}
+ function showSingleAtomStatus($notice)
+ {
+ header('Content-Type: application/atom+xml; charset=utf-8');
+ print $notice->asAtomEntry(true, true, true, $this->auth_user);
+ }
+
function show_single_json_status($notice)
{
$this->initDocument('json');
diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php
index f6b09fb49..7e536925b 100644
--- a/lib/attachmentlist.php
+++ b/lib/attachmentlist.php
@@ -79,23 +79,33 @@ class AttachmentList extends Widget
$atts = new File;
$att = $atts->getAttachments($this->notice->id);
if (empty($att)) return 0;
+ $this->showListStart();
+
+ foreach ($att as $n=>$attachment) {
+ $item = $this->newListItem($attachment);
+ $item->show();
+ }
+
+ $this->showListEnd();
+
+ return count($att);
+ }
+
+ function showListStart()
+ {
$this->out->elementStart('dl', array('id' =>'attachments',
'class' => 'entry-content'));
// TRANS: DT element label in attachment list.
$this->out->element('dt', null, _('Attachments'));
$this->out->elementStart('dd');
$this->out->elementStart('ol', array('class' => 'attachments'));
+ }
- foreach ($att as $n=>$attachment) {
- $item = $this->newListItem($attachment);
- $item->show();
- }
-
+ function showListEnd()
+ {
$this->out->elementEnd('dd');
$this->out->elementEnd('ol');
$this->out->elementEnd('dl');
-
- return count($att);
}
/**
@@ -187,7 +197,10 @@ class AttachmentListItem extends Widget
}
function linkAttr() {
- return array('class' => 'attachment', 'href' => $this->attachment->url, 'id' => 'attachment-' . $this->attachment->id);
+ return array('class' => 'attachment',
+ 'href' => $this->attachment->url,
+ 'id' => 'attachment-' . $this->attachment->id,
+ 'title' => $this->title());
}
function showLink() {
@@ -203,10 +216,32 @@ class AttachmentListItem extends Widget
}
function showRepresentation() {
+ $thumb = $this->getThumbInfo();
+ if ($thumb) {
+ $this->out->element('img', array('alt' => '', 'src' => $thumb->url, 'width' => $thumb->width, 'height' => $thumb->height));
+ }
+ }
+
+ /**
+ * Pull a thumbnail image reference for the given file, and if necessary
+ * resize it to match currently thumbnail size settings.
+ *
+ * @return File_Thumbnail or false/null
+ */
+ function getThumbInfo()
+ {
$thumbnail = File_thumbnail::staticGet('file_id', $this->attachment->id);
- if (!empty($thumbnail)) {
- $this->out->element('img', array('alt' => '', 'src' => $thumbnail->url, 'width' => $thumbnail->width, 'height' => $thumbnail->height));
+ if ($thumbnail) {
+ $maxWidth = common_config('attachments', 'thumb_width');
+ $maxHeight = common_config('attachments', 'thumb_height');
+ if ($thumbnail->width > $maxWidth) {
+ $thumb = clone($thumbnail);
+ $thumb->width = $maxWidth;
+ $thumb->height = intval($thumbnail->height * $maxWidth / $thumbnail->width);
+ return $thumb;
+ }
}
+ return $thumbnail;
}
/**
@@ -234,6 +269,9 @@ class AttachmentListItem extends Widget
}
}
+/**
+ * used for one-off attachment action
+ */
class Attachment extends AttachmentListItem
{
function showLink() {
@@ -417,15 +455,6 @@ class Attachment extends AttachmentListItem
function showFallback()
{
- // If we don't know how to display an attachment inline, we probably
- // shouldn't have gotten to this point.
- //
- // But, here we are... displaying details on a file or remote URL
- // either on the main view or in an ajax-loaded lightbox. As a lesser
- // of several evils, we'll try redirecting to the actual target via
- // client-side JS.
-
- common_log(LOG_ERR, "Empty or unknown type for file id {$this->attachment->id}; falling back to client-side redirect.");
- $this->out->raw('<script>window.location = ' . json_encode($this->attachment->url) . ';</script>');
+ // still needed: should show a link?
}
}
diff --git a/lib/default.php b/lib/default.php
index a19453fce..ece01f2a8 100644
--- a/lib/default.php
+++ b/lib/default.php
@@ -250,6 +250,9 @@ $default =
'monthly_quota' => 15000000,
'uploads' => true,
'filecommand' => '/usr/bin/file',
+ 'show_thumbs' => true, // show thumbnails in notice lists for uploaded images, and photos and videos linked remotely that provide oEmbed info
+ 'thumb_width' => 100,
+ 'thumb_height' => 75,
),
'application' =>
array('desclimit' => null),
diff --git a/lib/imagefile.php b/lib/imagefile.php
index b70fd248e..159deead6 100644
--- a/lib/imagefile.php
+++ b/lib/imagefile.php
@@ -115,10 +115,46 @@ class ImageFile
return new ImageFile(null, $_FILES[$param]['tmp_name']);
}
+ /**
+ * Compat interface for old code generating avatar thumbnails...
+ * Saves the scaled file directly into the avatar area.
+ *
+ * @param int $size target width & height -- must be square
+ * @param int $x (default 0) upper-left corner to crop from
+ * @param int $y (default 0) upper-left corner to crop from
+ * @param int $w (default full) width of image area to crop
+ * @param int $h (default full) height of image area to crop
+ * @return string filename
+ */
function resize($size, $x = 0, $y = 0, $w = null, $h = null)
{
+ $targetType = $this->preferredType($this->type);
+ $outname = Avatar::filename($this->id,
+ image_type_to_extension($targetType),
+ $size,
+ common_timestamp());
+ $outpath = Avatar::path($outname);
+ $this->resizeTo($outpath, $size, $size, $x, $y, $w, $h);
+ return $outname;
+ }
+
+ /**
+ * Create and save a thumbnail image.
+ *
+ * @param string $outpath
+ * @param int $width target width
+ * @param int $height target height
+ * @param int $x (default 0) upper-left corner to crop from
+ * @param int $y (default 0) upper-left corner to crop from
+ * @param int $w (default full) width of image area to crop
+ * @param int $h (default full) height of image area to crop
+ * @return string full local filesystem filename
+ */
+ function resizeTo($outpath, $width, $height, $x=0, $y=0, $w=null, $h=null)
+ {
$w = ($w === null) ? $this->width:$w;
$h = ($h === null) ? $this->height:$h;
+ $targetType = $this->preferredType($this->type);
if (!file_exists($this->filepath)) {
throw new Exception(_('Lost our file.'));
@@ -126,20 +162,16 @@ class ImageFile
}
// Don't crop/scale if it isn't necessary
- if ($size === $this->width
- && $size === $this->height
+ if ($width === $this->width
+ && $height === $this->height
&& $x === 0
&& $y === 0
&& $w === $this->width
- && $h === $this->height) {
+ && $h === $this->height
+ && $this->type == $targetType) {
- $outname = Avatar::filename($this->id,
- image_type_to_extension($this->type),
- $size,
- common_timestamp());
- $outpath = Avatar::path($outname);
@copy($this->filepath, $outpath);
- return $outname;
+ return $outpath;
}
switch ($this->type) {
@@ -166,7 +198,7 @@ class ImageFile
return;
}
- $image_dest = imagecreatetruecolor($size, $size);
+ $image_dest = imagecreatetruecolor($width, $height);
if ($this->type == IMAGETYPE_GIF || $this->type == IMAGETYPE_PNG || $this->type == IMAGETYPE_BMP) {
@@ -189,30 +221,9 @@ class ImageFile
}
}
- imagecopyresampled($image_dest, $image_src, 0, 0, $x, $y, $size, $size, $w, $h);
-
- if($this->type == IMAGETYPE_BMP) {
- //we don't want to save BMP... it's an inefficient, rare, antiquated format
- //save png instead
- $this->type = IMAGETYPE_PNG;
- } else if($this->type == IMAGETYPE_WBMP) {
- //we don't want to save WBMP... it's a rare format that we can't guarantee clients will support
- //save png instead
- $this->type = IMAGETYPE_PNG;
- } else if($this->type == IMAGETYPE_XBM) {
- //we don't want to save XBM... it's a rare format that we can't guarantee clients will support
- //save png instead
- $this->type = IMAGETYPE_PNG;
- }
-
- $outname = Avatar::filename($this->id,
- image_type_to_extension($this->type),
- $size,
- common_timestamp());
-
- $outpath = Avatar::path($outname);
+ imagecopyresampled($image_dest, $image_src, 0, 0, $x, $y, $width, $height, $w, $h);
- switch ($this->type) {
+ switch ($targetType) {
case IMAGETYPE_GIF:
imagegif($image_dest, $outpath);
break;
@@ -230,7 +241,31 @@ class ImageFile
imagedestroy($image_src);
imagedestroy($image_dest);
- return $outname;
+ return $outpath;
+ }
+
+ /**
+ * Several obscure file types should be normalized to PNG on resize.
+ *
+ * @param int $type
+ * @return int
+ */
+ function preferredType($type)
+ {
+ if($type == IMAGETYPE_BMP) {
+ //we don't want to save BMP... it's an inefficient, rare, antiquated format
+ //save png instead
+ return IMAGETYPE_PNG;
+ } else if($type == IMAGETYPE_WBMP) {
+ //we don't want to save WBMP... it's a rare format that we can't guarantee clients will support
+ //save png instead
+ return IMAGETYPE_PNG;
+ } else if($type == IMAGETYPE_XBM) {
+ //we don't want to save XBM... it's a rare format that we can't guarantee clients will support
+ //save png instead
+ return IMAGETYPE_PNG;
+ }
+ return $type;
}
function unlink()
diff --git a/lib/inlineattachmentlist.php b/lib/inlineattachmentlist.php
new file mode 100644
index 000000000..de5008e87
--- /dev/null
+++ b/lib/inlineattachmentlist.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * widget for displaying notice attachments thumbnails
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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 <http://www.gnu.org/licenses/>.
+ *
+ * @category UI
+ * @package StatusNet
+ * @author Brion Vibber <brion@status.net>
+ * @copyright 2010 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')) {
+ exit(1);
+}
+
+class InlineAttachmentList extends AttachmentList
+{
+ function showListStart()
+ {
+ $this->out->elementStart('div', array('class' => 'entry-content thumbnails'));
+ }
+
+ function showListEnd()
+ {
+ $this->out->elementEnd('div');
+ }
+
+ /**
+ * returns a new list item for the current attachment
+ *
+ * @param File $notice the current attachment
+ *
+ * @return ListItem a list item for displaying the attachment
+ */
+ function newListItem($attachment)
+ {
+ return new InlineAttachmentListItem($attachment, $this->out);
+ }
+}
+
+class InlineAttachmentListItem extends AttachmentListItem
+{
+ function show()
+ {
+ if ($this->attachment->isEnclosure()) {
+ parent::show();
+ }
+ }
+
+ function showLink() {
+ $this->out->elementStart('a', $this->linkAttr());
+ $this->showRepresentation();
+ $this->out->elementEnd('a');
+ }
+
+ /**
+ * Build HTML attributes for the link
+ * @return array
+ */
+ function linkAttr()
+ {
+ $attr = parent::linkAttr();
+ $attr['class'] = 'attachment-thumbnail';
+ return $attr;
+ }
+
+ /**
+ * start a single notice.
+ *
+ * @return void
+ */
+ function showStart()
+ {
+ // XXX: RDFa
+ // TODO: add notice_type class e.g., notice_video, notice_image
+ $this->out->elementStart('span', array('class' => 'inline-attachment'));
+ }
+
+ /**
+ * finish the notice
+ *
+ * Close the last elements in the notice list item
+ *
+ * @return void
+ */
+ function showEnd()
+ {
+ $this->out->elementEnd('span');
+ }
+}
diff --git a/lib/mediafile.php b/lib/mediafile.php
index aad3575d7..a41d7c76b 100644
--- a/lib/mediafile.php
+++ b/lib/mediafile.php
@@ -48,11 +48,14 @@ class MediaFile
{
if ($user == null) {
$this->user = common_current_user();
+ } else {
+ $this->user = $user;
}
$this->filename = $filename;
$this->mimetype = $mimetype;
$this->fileRecord = $this->storeFile();
+ $this->thumbnailRecord = $this->storeThumbnail();
$this->fileurl = common_local_url('attachment',
array('attachment' => $this->fileRecord->id));
@@ -102,6 +105,52 @@ class MediaFile
return $file;
}
+ /**
+ * Generate and store a thumbnail image for the uploaded file, if applicable.
+ *
+ * @return File_thumbnail or null
+ */
+ function storeThumbnail()
+ {
+ if (substr($this->mimetype, 0, strlen('image/')) != 'image/') {
+ // @fixme video thumbs would be nice!
+ return null;
+ }
+ try {
+ $image = new ImageFile($this->fileRecord->id,
+ File::path($this->filename));
+ } catch (Exception $e) {
+ // Unsupported image type.
+ return null;
+ }
+
+ $outname = File::filename($this->user->getProfile(), 'thumb-' . $this->filename, $this->mimetype);
+ $outpath = File::path($outname);
+
+ $maxWidth = common_config('attachments', 'thumb_width');
+ $maxHeight = common_config('attachments', 'thumb_height');
+ list($width, $height) = $this->scaleToFit($image->width, $image->height, $maxWidth, $maxHeight);
+
+ $image->resizeTo($outpath, $width, $height);
+ File_thumbnail::saveThumbnail($this->fileRecord->id,
+ File::url($outname),
+ $width,
+ $height);
+ }
+
+ function scaleToFit($width, $height, $maxWidth, $maxHeight)
+ {
+ $aspect = $maxWidth / $maxHeight;
+ $w1 = $maxWidth;
+ $h1 = intval($height * $maxWidth / $width);
+ if ($h1 > $maxHeight) {
+ $w2 = intval($width * $maxHeight / $height);
+ $h2 = $maxHeight;
+ return array($w2, $h2);
+ }
+ return array($w1, $h1);
+ }
+
function rememberFile($file, $short)
{
$this->maybeAddRedir($file->id, $short);
diff --git a/lib/noticelist.php b/lib/noticelist.php
index 6f82c9269..c6f964662 100644
--- a/lib/noticelist.php
+++ b/lib/noticelist.php
@@ -208,6 +208,7 @@ class NoticeListItem extends Widget
$this->showStart();
if (Event::handle('StartShowNoticeItem', array($this))) {
$this->showNotice();
+ $this->showNoticeAttachments();
$this->showNoticeInfo();
$this->showNoticeOptions();
Event::handle('EndShowNoticeItem', array($this));
@@ -383,6 +384,13 @@ class NoticeListItem extends Widget
$this->out->elementEnd('p');
}
+ function showNoticeAttachments() {
+ if (common_config('attachments', 'show_thumbs')) {
+ $al = new InlineAttachmentList($this->notice, $this->out);
+ $al->show();
+ }
+ }
+
/**
* show the link to the main page for the notice
*
diff --git a/lib/oembedhelper.php b/lib/oembedhelper.php
new file mode 100644
index 000000000..84cf10586
--- /dev/null
+++ b/lib/oembedhelper.php
@@ -0,0 +1,318 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008-2010, 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 <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+
+/**
+ * Utility class to wrap basic oEmbed lookups.
+ *
+ * Blacklisted hosts will use an alternate lookup method:
+ * - Twitpic
+ *
+ * Whitelisted hosts will use known oEmbed API endpoints:
+ * - Flickr, YFrog
+ *
+ * Sites that provide discovery links will use them directly; a bug
+ * in use of discovery links with query strings is worked around.
+ *
+ * Others will fall back to oohembed (unless disabled).
+ * The API endpoint can be configured or disabled through config
+ * as 'oohembed'/'endpoint'.
+ */
+class oEmbedHelper
+{
+ protected static $apiMap = array(
+ 'flickr.com' => 'http://www.flickr.com/services/oembed/',
+ 'yfrog.com' => 'http://www.yfrog.com/api/oembed',
+ );
+ protected static $functionMap = array(
+ 'twitpic.com' => 'oEmbedHelper::twitPic',
+ );
+
+ /**
+ * Perform or fake an oEmbed lookup for the given resource.
+ *
+ * Some known hosts are whitelisted with API endpoints where we
+ * know they exist but autodiscovery data isn't available.
+ * If autodiscovery links are missing and we don't recognize the
+ * host, we'll pass it to oohembed.com's public service which
+ * will either proxy or fake info on a lot of sites.
+ *
+ * A few hosts are blacklisted due to known problems with oohembed,
+ * in which case we'll look up the info another way and return
+ * equivalent data.
+ *
+ * Throws exceptions on failure.
+ *
+ * @param string $url
+ * @param array $params
+ * @return object
+ */
+ public static function getObject($url, $params=array())
+ {
+ $host = parse_url($url, PHP_URL_HOST);
+ if (substr($host, 0, 4) == 'www.') {
+ $host = substr($host, 4);
+ }
+
+ // Blacklist: systems with no oEmbed API of their own, which are
+ // either missing from or broken on oohembed.com's proxy.
+ // we know how to look data up in another way...
+ if (array_key_exists($host, self::$functionMap)) {
+ $func = self::$functionMap[$host];
+ return call_user_func($func, $url, $params);
+ }
+
+ // Whitelist: known API endpoints for sites that don't provide discovery...
+ if (array_key_exists($host, self::$apiMap)) {
+ $api = self::$apiMap[$host];
+ } else {
+ try {
+ $api = self::discover($url);
+ } catch (Exception $e) {
+ // Discovery failed... fall back to oohembed if enabled.
+ $oohembed = common_config('oohembed', 'endpoint');
+ if ($oohembed) {
+ $api = $oohembed;
+ } else {
+ throw $e;
+ }
+ }
+ }
+ return self::getObjectFrom($api, $url, $params);
+ }
+
+ /**
+ * Perform basic discovery.
+ * @return string
+ */
+ static function discover($url)
+ {
+ // @fixme ideally skip this for non-HTML stuff!
+ $body = self::http($url);
+ return self::discoverFromHTML($url, $body);
+ }
+
+ /**
+ * Partially ripped from OStatus' FeedDiscovery class.
+ *
+ * @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
+ */
+ static function discoverFromHTML($url, $body)
+ {
+ // DOMDocument::loadHTML may throw warnings on unrecognized elements,
+ // and notices on unrecognized namespaces.
+ $old = error_reporting(error_reporting() & ~(E_WARNING | E_NOTICE));
+ $dom = new DOMDocument();
+ $ok = $dom->loadHTML($body);
+ error_reporting($old);
+
+ if (!$ok) {
+ throw new oEmbedHelper_BadHtmlException();
+ }
+
+ // Ok... now on to the links!
+ $feeds = array(
+ 'application/json+oembed' => false,
+ );
+
+ $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 = array_filter(explode(" ", $rel->value));
+ $type = trim($type->value);
+ $href = trim($href->value);
+
+ if (in_array('alternate', $rel) && array_key_exists($type, $feeds) && empty($feeds[$type])) {
+ // Save the first feed found of each type...
+ $feeds[$type] = $href;
+ }
+ }
+ }
+ }
+
+ // Return the highest-priority feed found
+ foreach ($feeds as $type => $url) {
+ if ($url) {
+ return $url;
+ }
+ }
+
+ throw new oEmbedHelper_DiscoveryException();
+ }
+
+ /**
+ * Actually do an oEmbed lookup to a particular API endpoint.
+ *
+ * @param string $api oEmbed API endpoint URL
+ * @param string $url target URL to look up info about
+ * @param array $params
+ * @return object
+ */
+ static function getObjectFrom($api, $url, $params=array())
+ {
+ $params['url'] = $url;
+ $params['format'] = 'json';
+ $data = self::json($api, $params);
+ return self::normalize($data);
+ }
+
+ /**
+ * Normalize oEmbed format.
+ *
+ * @param object $orig
+ * @return object
+ */
+ static function normalize($orig)
+ {
+ $data = clone($orig);
+
+ if (empty($data->type)) {
+ throw new Exception('Invalid oEmbed data: no type field.');
+ }
+
+ if ($data->type == 'image') {
+ // YFrog does this.
+ $data->type = 'photo';
+ }
+
+ if (isset($data->thumbnail_url)) {
+ if (!isset($data->thumbnail_width)) {
+ // !?!?!
+ $data->thumbnail_width = common_config('attachments', 'thumb_width');
+ $data->thumbnail_height = common_config('attachments', 'thumb_height');
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Using a local function for twitpic lookups, as oohembed's adapter
+ * doesn't return a valid result:
+ * http://code.google.com/p/oohembed/issues/detail?id=19
+ *
+ * This code fetches metadata from Twitpic's own API, and attempts
+ * to guess proper thumbnail size from the original's size.
+ *
+ * @todo respect maxwidth and maxheight params
+ *
+ * @param string $url
+ * @param array $params
+ * @return object
+ */
+ static function twitPic($url, $params=array())
+ {
+ $matches = array();
+ if (preg_match('!twitpic\.com/(\w+)!', $url, $matches)) {
+ $id = $matches[1];
+ } else {
+ throw new Exception("Invalid twitpic URL");
+ }
+
+ // Grab metadata from twitpic's API...
+ // http://dev.twitpic.com/docs/2/media_show
+ $data = self::json('http://api.twitpic.com/2/media/show.json',
+ array('id' => $id));
+ $oembed = (object)array('type' => 'photo',
+ 'url' => 'http://twitpic.com/show/full/' . $data->short_id,
+ 'width' => $data->width,
+ 'height' => $data->height);
+ if (!empty($data->message)) {
+ $oembed->title = $data->message;
+ }
+
+ // Thumbnail is cropped and scaled to 150x150 box:
+ // http://dev.twitpic.com/docs/thumbnails/
+ $thumbSize = 150;
+ $oembed->thumbnail_url = 'http://twitpic.com/show/thumb/' . $data->short_id;
+ $oembed->thumbnail_width = $thumbSize;
+ $oembed->thumbnail_height = $thumbSize;
+
+ return $oembed;
+ }
+
+ /**
+ * Fetch some URL and return JSON data.
+ *
+ * @param string $url
+ * @param array $params query-string params
+ * @return object
+ */
+ static protected function json($url, $params=array())
+ {
+ $data = self::http($url, $params);
+ return json_decode($data);
+ }
+
+ /**
+ * Hit some web API and return data on success.
+ * @param string $url
+ * @param array $params
+ * @return string
+ */
+ static protected function http($url, $params=array())
+ {
+ $client = HTTPClient::start();
+ if ($params) {
+ $query = http_build_query($params, null, '&');
+ if (strpos($url, '?') === false) {
+ $url .= '?' . $query;
+ } else {
+ $url .= '&' . $query;
+ }
+ }
+ $response = $client->get($url);
+ if ($response->isOk()) {
+ return $response->getBody();
+ } else {
+ throw new Exception('Bad HTTP response code: ' . $response->getStatus());
+ }
+ }
+}
+
+class oEmbedHelper_Exception extends Exception
+{
+}
+
+class oEmbedHelper_BadHtmlException extends oEmbedHelper_Exception
+{
+ function __construct($previous=null)
+ {
+ return parent::__construct('Bad HTML in discovery data.', 0, $previous);
+ }
+}
+
+class oEmbedHelper_DiscoveryException extends oEmbedHelper_Exception
+{
+ function __construct($previous=null)
+ {
+ return parent::__construct('No oEmbed discovery data.', 0, $previous);
+ }
+}
diff --git a/lib/profileaction.php b/lib/profileaction.php
index 504b77566..4bfc4d48d 100644
--- a/lib/profileaction.php
+++ b/lib/profileaction.php
@@ -101,7 +101,7 @@ class ProfileAction extends OwnerDesignAction
function showSubscriptions()
{
- $profile = $this->user->getSubscriptions(0, PROFILES_PER_MINILIST + 1);
+ $profile = $this->profile->getSubscriptions(0, PROFILES_PER_MINILIST + 1);
$this->elementStart('div', array('id' => 'entity_subscriptions',
'class' => 'section'));
@@ -134,7 +134,7 @@ class ProfileAction extends OwnerDesignAction
function showSubscribers()
{
- $profile = $this->user->getSubscribers(0, PROFILES_PER_MINILIST + 1);
+ $profile = $this->profile->getSubscribers(0, PROFILES_PER_MINILIST + 1);
$this->elementStart('div', array('id' => 'entity_subscribers',
'class' => 'section'));
@@ -173,7 +173,7 @@ class ProfileAction extends OwnerDesignAction
$subs_count = $this->profile->subscriptionCount();
$subbed_count = $this->profile->subscriberCount();
$notice_count = $this->profile->noticeCount();
- $group_count = $this->user->getGroups()->N;
+ $group_count = $this->profile->getGroups()->N;
$age_days = (time() - strtotime($this->profile->created)) / 86400;
if ($age_days < 1) {
// Rather than extrapolating out to a bajillion...
@@ -241,7 +241,7 @@ class ProfileAction extends OwnerDesignAction
function showGroups()
{
- $groups = $this->user->getGroups(0, GROUPS_PER_MINILIST + 1);
+ $groups = $this->profile->getGroups(0, GROUPS_PER_MINILIST + 1);
$this->elementStart('div', array('id' => 'entity_groups',
'class' => 'section'));
@@ -249,7 +249,7 @@ class ProfileAction extends OwnerDesignAction
$this->element('h2', null, _('Groups'));
if ($groups) {
- $gml = new GroupMiniList($groups, $this->user, $this);
+ $gml = new GroupMiniList($groups, $this->profile, $this);
$cnt = $gml->show();
if ($cnt == 0) {
$this->element('p', null, _('(None)'));
diff --git a/lib/router.php b/lib/router.php
index 9aaac7dfe..c0f3bf31d 100644
--- a/lib/router.php
+++ b/lib/router.php
@@ -399,12 +399,12 @@ class Router
$m->connect('api/statuses/show.:format',
array('action' => 'ApiStatusesShow',
- 'format' => '(xml|json)'));
+ 'format' => '(xml|json|atom)'));
$m->connect('api/statuses/show/:id.:format',
array('action' => 'ApiStatusesShow',
'id' => '[0-9]+',
- 'format' => '(xml|json)'));
+ 'format' => '(xml|json|atom)'));
$m->connect('api/statuses/update.:format',
array('action' => 'ApiStatusesUpdate',
@@ -686,6 +686,13 @@ class Router
$m->connect('api/oauth/authorize',
array('action' => 'ApiOauthAuthorize'));
+ $m->connect('api/statusnet/app/service/:id.xml',
+ array('action' => 'ApiAtomService',
+ 'id' => '[a-zA-Z0-9]+'));
+
+ $m->connect('api/statusnet/app/service.xml',
+ array('action' => 'ApiAtomService'));
+
// Admin
$m->connect('admin/site', array('action' => 'siteadminpanel'));
diff --git a/lib/statusnet.php b/lib/statusnet.php
index 33bf32b10..85b46bbb3 100644
--- a/lib/statusnet.php
+++ b/lib/statusnet.php
@@ -377,7 +377,11 @@ class StatusNet
static function isHTTPS()
{
// There are some exceptions to this; add them here!
- return !empty($_SERVER['HTTPS']);
+ if(empty($_SERVER['HTTPS'])) {
+ return false;
+ } else {
+ return $_SERVER['HTTPS'] !== 'off';
+ }
}
}
diff --git a/lib/userprofile.php b/lib/userprofile.php
index ca060842b..2813f735e 100644
--- a/lib/userprofile.php
+++ b/lib/userprofile.php
@@ -98,6 +98,10 @@ class UserProfile extends Widget
if (Event::handle('StartProfilePageAvatar', array($this->out, $this->profile))) {
$avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
+ if (!$avatar) {
+ // hack for remote Twitter users: no 96px, but large Twitter size is 73px
+ $avatar = $this->profile->getAvatar(73);
+ }
$this->out->elementStart('dl', 'entity_depiction');
$this->out->element('dt', null, _('Photo'));
@@ -109,10 +113,8 @@ class UserProfile extends Widget
'alt' => $this->profile->nickname));
$this->out->elementEnd('dd');
- $user = User::staticGet('id', $this->profile->id);
-
$cur = common_current_user();
- if ($cur && $cur->id == $user->id) {
+ if ($cur && $cur->id == $this->profile->id) {
$this->out->elementStart('dd');
$this->out->element('a', array('href' => common_local_url('avatarsettings')), _('Edit Avatar'));
$this->out->elementEnd('dd');
@@ -278,7 +280,7 @@ class UserProfile extends Widget
}
$this->out->elementEnd('li');
- if ($cur->mutuallySubscribed($this->user)) {
+ if ($cur->mutuallySubscribed($this->profile)) {
// message
@@ -290,7 +292,7 @@ class UserProfile extends Widget
// nudge
- if ($this->user->email && $this->user->emailnotifynudge) {
+ if ($this->user && $this->user->email && $this->user->emailnotifynudge) {
$this->out->elementStart('li', 'entity_nudge');
$nf = new NudgeForm($this->out, $this->user);
$nf->show();
@@ -319,6 +321,9 @@ class UserProfile extends Widget
}
$this->out->elementEnd('li');
+ // Some actions won't be applicable to non-local users.
+ $isLocal = !empty($this->user);
+
if ($cur->hasRight(Right::SANDBOXUSER) ||
$cur->hasRight(Right::SILENCEUSER) ||
$cur->hasRight(Right::DELETEUSER)) {
@@ -327,7 +332,7 @@ class UserProfile extends Widget
$this->out->elementStart('ul');
if ($cur->hasRight(Right::SANDBOXUSER)) {
$this->out->elementStart('li', 'entity_sandbox');
- if ($this->user->isSandboxed()) {
+ if ($this->profile->isSandboxed()) {
$usf = new UnSandboxForm($this->out, $this->profile, $r2args);
$usf->show();
} else {
@@ -339,7 +344,7 @@ class UserProfile extends Widget
if ($cur->hasRight(Right::SILENCEUSER)) {
$this->out->elementStart('li', 'entity_silence');
- if ($this->user->isSilenced()) {
+ if ($this->profile->isSilenced()) {
$usf = new UnSilenceForm($this->out, $this->profile, $r2args);
$usf->show();
} else {
@@ -349,7 +354,7 @@ class UserProfile extends Widget
$this->out->elementEnd('li');
}
- if ($cur->hasRight(Right::DELETEUSER)) {
+ if ($isLocal && $cur->hasRight(Right::DELETEUSER)) {
$this->out->elementStart('li', 'entity_delete');
$df = new DeleteUserForm($this->out, $this->profile, $r2args);
$df->show();
@@ -359,7 +364,7 @@ class UserProfile extends Widget
$this->out->elementEnd('li');
}
- if ($cur->hasRight(Right::GRANTROLE)) {
+ if ($isLocal && $cur->hasRight(Right::GRANTROLE)) {
$this->out->elementStart('li', 'entity_role');
$this->out->element('p', null, _('User role'));
$this->out->elementStart('ul');
@@ -387,7 +392,7 @@ class UserProfile extends Widget
$r2args['action'] = $action;
$this->out->elementStart('li', "entity_role_$role");
- if ($this->user->hasRole($role)) {
+ if ($this->profile->hasRole($role)) {
$rf = new RevokeRoleForm($role, $label, $this->out, $this->profile, $r2args);
$rf->show();
} else {
diff --git a/lib/util.php b/lib/util.php
index 14814b7e9..68592bf74 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -877,7 +877,7 @@ function common_linkify($url) {
}
if (!empty($f)) {
- if ($f->getEnclosure() || File_oembed::staticGet('file_id',$f->id)) {
+ if ($f->getEnclosure()) {
$is_attachment = true;
$attachment_id = $f->id;
diff --git a/lib/xmppmanager.php b/lib/xmppmanager.php
index 7acd7663a..238696664 100644
--- a/lib/xmppmanager.php
+++ b/lib/xmppmanager.php
@@ -198,10 +198,12 @@ class XmppManager extends IoManager
$this->conn->processTime(0);
return true;
} else {
+ common_log(LOG_ERR, __METHOD__ . ' failed: 0 bytes sent');
return false;
}
} else {
// Can't send right now...
+ common_log(LOG_ERR, __METHOD__ . ' failed: XMPP server connection currently down');
return false;
}
}
diff --git a/plugins/EmailSummary/EmailSummaryPlugin.php b/plugins/EmailSummary/EmailSummaryPlugin.php
new file mode 100644
index 000000000..58c40e43c
--- /dev/null
+++ b/plugins/EmailSummary/EmailSummaryPlugin.php
@@ -0,0 +1,202 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * Sends an email summary of the inbox to users in the network
+ *
+ * PHP version 5
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * @category Sample
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Plugin for sending email summaries to users
+ *
+ * @category Email
+ * @package StatusNet
+ * @author Brion Vibber <brionv@status.net>
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+class EmailSummaryPlugin extends Plugin
+{
+ /**
+ * Database schema setup
+ *
+ * @return boolean hook value
+ */
+
+ function onCheckSchema()
+ {
+ $schema = Schema::get();
+
+ // For storing user-submitted flags on profiles
+
+ $schema->ensureTable('email_summary_status',
+ array(new ColumnDef('user_id', 'integer', null,
+ false, 'PRI'),
+ new ColumnDef('send_summary', 'tinyint', null,
+ false, null, 1),
+ new ColumnDef('last_summary_id', 'integer', null,
+ true),
+ new ColumnDef('created', 'datetime', null,
+ false),
+ new ColumnDef('modified', 'datetime', null,
+ false),
+ )
+ );
+ return true;
+ }
+
+ /**
+ * Load related modules when needed
+ *
+ * @param string $cls Name of the class to be loaded
+ *
+ * @return boolean hook value; true means continue processing, false means stop.
+ *
+ */
+
+ function onAutoload($cls)
+ {
+ $dir = dirname(__FILE__);
+
+ switch ($cls)
+ {
+ case 'SiteEmailSummaryHandler':
+ case 'UserEmailSummaryHandler':
+ include_once $dir . '/'.strtolower($cls).'.php';
+ return false;
+ case 'Email_summary_status':
+ include_once $dir . '/'.$cls.'.php';
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Version info for this plugin
+ *
+ * @param array &$versions array of version data
+ *
+ * @return boolean hook value; true means continue processing, false means stop.
+ *
+ */
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'EmailSummary',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Evan Prodromou',
+ 'homepage' => 'http://status.net/wiki/Plugin:EmailSummary',
+ 'rawdescription' =>
+ _m('Send an email summary of the inbox to users.'));
+ return true;
+ }
+
+ /**
+ * Register our queue handlers
+ *
+ * @param QueueManager $qm Current queue manager
+ *
+ * @return boolean hook value
+ */
+
+ function onEndInitializeQueueManager($qm)
+ {
+ $qm->connect('sitesum', 'SiteEmailSummaryHandler');
+ $qm->connect('usersum', 'UserEmailSummaryHandler');
+ return true;
+ }
+
+ /**
+ * Add a checkbox to turn off email summaries
+ *
+ * @param Action $action Action being executed (emailsettings)
+ *
+ * @return boolean hook value
+ */
+
+ function onEndEmailFormData($action)
+ {
+ $user = common_current_user();
+
+ $action->elementStart('li');
+ $action->checkbox('emailsummary',
+ // TRANS: Checkbox label in e-mail preferences form.
+ _('Send me a periodic summary of updates from my network.'),
+ Email_summary_status::getSendSummary($user->id));
+ $action->elementEnd('li');
+ return true;
+ }
+
+ /**
+ * Add a checkbox to turn off email summaries
+ *
+ * @param Action $action Action being executed (emailsettings)
+ *
+ * @return boolean hook value
+ */
+
+ function onEndEmailSaveForm($action)
+ {
+ $sendSummary = $action->boolean('emailsummary');
+
+ $user = common_current_user();
+
+ if (!empty($user)) {
+
+ $ess = Email_summary_status::staticGet('user_id', $user->id);
+
+ if (empty($ess)) {
+
+ $ess = new Email_summary_status();
+
+ $ess->user_id = $user->id;
+ $ess->send_summary = $sendSummary;
+ $ess->created = common_sql_now();
+ $ess->modified = common_sql_now();
+
+ $ess->insert();
+
+ } else {
+
+ $orig = clone($ess);
+
+ $ess->send_summary = $sendSummary;
+ $ess->modified = common_sql_now();
+
+ $ess->update($orig);
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/plugins/EmailSummary/Email_summary_status.php b/plugins/EmailSummary/Email_summary_status.php
new file mode 100644
index 000000000..5b5b231e3
--- /dev/null
+++ b/plugins/EmailSummary/Email_summary_status.php
@@ -0,0 +1,167 @@
+<?php
+/**
+ * Data class for email summary status
+ *
+ * PHP version 5
+ *
+ * @category Data
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, 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 <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
+
+/**
+ * Data class for email summaries
+ *
+ * Email summary information for users
+ *
+ * @category Action
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
+ * @see DB_DataObject
+ */
+
+class Email_summary_status extends Memcached_DataObject
+{
+ public $__table = 'email_summary_status'; // table name
+ public $user_id; // int(4) primary_key not_null
+ public $send_summary; // tinyint not_null
+ public $last_summary_id; // int(4) null
+ public $created; // datetime not_null
+ public $modified; // datetime not_null
+
+ /**
+ * Get an instance by key
+ *
+ * @param string $k Key to use to lookup (usually 'user_id' for this class)
+ * @param mixed $v Value to lookup
+ *
+ * @return Email_summary_status object found, or null for no hits
+ *
+ */
+ function staticGet($k, $v=null)
+ {
+ return Memcached_DataObject::staticGet('email_summary_status', $k, $v);
+ }
+
+ /**
+ * return table definition for DB_DataObject
+ *
+ * DB_DataObject needs to know something about the table to manipulate
+ * instances. This method provides all the DB_DataObject needs to know.
+ *
+ * @return array array of column definitions
+ */
+
+ function table()
+ {
+ return array('user_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ 'send_summary' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ 'last_summary_id' => DB_DATAOBJECT_INT,
+ 'created' => DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
+ 'modified' => DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
+ }
+
+ /**
+ * return key definitions for DB_DataObject
+ *
+ * @return array list of key field names
+ */
+
+ function keys()
+ {
+ return array_keys($this->keyTypes());
+ }
+
+ /**
+ * return key definitions for Memcached_DataObject
+ *
+ * Our caching system uses the same key definitions, but uses a different
+ * method to get them. This key information is used to store and clear
+ * cached data, so be sure to list any key that will be used for static
+ * lookups.
+ *
+ * @return array associative array of key definitions, field name to type:
+ * 'K' for primary key: for compound keys, add an entry for each component;
+ * 'U' for unique keys: compound keys are not well supported here.
+ */
+ function keyTypes()
+ {
+ return array('user_id' => 'K');
+ }
+
+ /**
+ * Magic formula for non-autoincrementing integer primary keys
+ *
+ * @return array magic three-false array that stops auto-incrementing.
+ */
+
+ function sequenceKey()
+ {
+ return array(false, false, false);
+ }
+
+ /**
+ * Helper function
+ *
+ * @param integer $user_id ID of the user to get a count for
+ *
+ * @return int flag for whether to send this user a summary email
+ */
+
+ static function getSendSummary($user_id)
+ {
+ $ess = Email_summary_status::staticGet('user_id', $user_id);
+
+ if (!empty($ess)) {
+ return $ess->send_summary;
+ } else {
+ return 1;
+ }
+ }
+
+ /**
+ * Get email summary status for a user
+ *
+ * @param integer $user_id ID of the user to get a count for
+ *
+ * @return Email_summary_status instance for this user, with count already incremented.
+ */
+
+ static function getLastSummaryID($user_id)
+ {
+ $ess = Email_summary_status::staticGet('user_id', $user_id);
+
+ if (!empty($ess)) {
+ return $ess->last_summary_id;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/plugins/EmailSummary/sendemailsummary.php b/plugins/EmailSummary/sendemailsummary.php
new file mode 100644
index 000000000..37bfdcfbd
--- /dev/null
+++ b/plugins/EmailSummary/sendemailsummary.php
@@ -0,0 +1,47 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - a distributed open-source microblogging tool
+ * Copyright (C) 2010, 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 <http://www.gnu.org/licenses/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..'));
+
+$shortoptions = 'i:n:a';
+$longoptions = array('id=', 'nickname=', 'all');
+
+$helptext = <<<END_OF_SENDEMAILSUMMARY_HELP
+sendemailsummary.php [options]
+Send an email summary of the inbox to users
+
+ -i --id ID of user to send summary to
+ -n --nickname nickname of the user to send summary to
+ -a --all send summary to all users
+
+END_OF_SENDEMAILSUMMARY_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+$qm = QueueManager::get();
+
+// enqueue summary for user or all users
+
+try {
+ $user = getUser();
+ $qm->enqueue($user->id, 'usersum');
+} catch (NoUserArgumentException $nuae) {
+ $qm->enqueue(null, 'sitesum');
+}
diff --git a/plugins/EmailSummary/siteemailsummaryhandler.php b/plugins/EmailSummary/siteemailsummaryhandler.php
new file mode 100644
index 000000000..595c3267a
--- /dev/null
+++ b/plugins/EmailSummary/siteemailsummaryhandler.php
@@ -0,0 +1,96 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ *
+ * Handler for queue items of type 'sitesum', sends email summaries
+ * to all users on the site.
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * @category Sample
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ *
+ * Handler for queue items of type 'sitesum', sends email summaries
+ * to all users on the site.
+ *
+ * @category Email
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+class SiteEmailSummaryHandler extends QueueHandler
+{
+
+ /**
+ * Return transport keyword which identifies items this queue handler
+ * services; must be defined for all subclasses.
+ *
+ * Must be 8 characters or less to fit in the queue_item database.
+ * ex "email", "jabber", "sms", "irc", ...
+ *
+ * @return string
+ */
+
+ function transport()
+ {
+ return 'sitesum';
+ }
+
+ /**
+ * Handle the site
+ *
+ * @param mixed $object
+ * @return boolean true on success, false on failure
+ */
+
+ function handle($object)
+ {
+ $qm = QueueManager::get();
+
+ try {
+ // Enqueue a summary for all users
+
+ $user = new User();
+ $user->find();
+
+ while ($user->fetch()) {
+ try {
+ $qm->enqueue($user->id, 'usersum');
+ } catch (Exception $e) {
+ common_log(LOG_WARNING, $e->getMessage());
+ continue;
+ }
+ }
+ } catch (Exception $e) {
+ common_log(LOG_WARNING, $e->getMessage());
+ }
+
+ return true;
+ }
+}
+
diff --git a/plugins/EmailSummary/useremailsummaryhandler.php b/plugins/EmailSummary/useremailsummaryhandler.php
new file mode 100644
index 000000000..b1ebd0c42
--- /dev/null
+++ b/plugins/EmailSummary/useremailsummaryhandler.php
@@ -0,0 +1,226 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ *
+ * Handler for queue items of type 'usersum', sends an email summaries
+ * to a particular user.
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * @category Sample
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Handler for queue items of type 'usersum', sends an email summaries
+ * to a particular user.
+ *
+ * @category Email
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+class UserEmailSummaryHandler extends QueueHandler
+{
+ // Maximum number of notices to include by default. This is probably too much.
+
+ const MAX_NOTICES = 200;
+
+ /**
+ * Return transport keyword which identifies items this queue handler
+ * services; must be defined for all subclasses.
+ *
+ * Must be 8 characters or less to fit in the queue_item database.
+ * ex "email", "jabber", "sms", "irc", ...
+ *
+ * @return string
+ */
+
+ function transport()
+ {
+ return 'sitesum';
+ }
+
+ /**
+ * Send a summary email to the user
+ *
+ * @param mixed $object
+ * @return boolean true on success, false on failure
+ */
+
+ function handle($user_id)
+ {
+ // Skip if they've asked not to get summaries
+
+ $ess = Email_summary_status::staticGet('user_id', $user_id);
+
+ if (!empty($ess) && !$ess->send_summary) {
+ common_log(LOG_INFO, sprintf('Not sending email summary for user %s by request.', $user_id));
+ return true;
+ }
+
+ $since_id = null;
+
+ if (!empty($ess)) {
+ $since_id = $ess->last_summary_id;
+ }
+
+ $user = User::staticGet('id', $user_id);
+
+ if (empty($user)) {
+ common_log(LOG_INFO, sprintf('Not sending email summary for user %s; no such user.', $user_id));
+ return true;
+ }
+
+ if (empty($user->email)) {
+ common_log(LOG_INFO, sprintf('Not sending email summary for user %s; no email address.', $user_id));
+ return true;
+ }
+
+ $profile = $user->getProfile();
+
+ if (empty($profile)) {
+ common_log(LOG_WARNING, sprintf('Not sending email summary for user %s; no profile.', $user_id));
+ return true;
+ }
+
+ $notice = $user->ownFriendsTimeline(0, self::MAX_NOTICES, $since_id);
+
+ if (empty($notice) || $notice->N == 0) {
+ common_log(LOG_WARNING, sprintf('Not sending email summary for user %s; no notices.', $user_id));
+ return true;
+ }
+
+ // XXX: This is risky fingerpoken in der objektvars, but I didn't feel like
+ // figuring out a better way. -ESP
+
+ $new_top = null;
+
+ if ($notice instanceof ArrayWrapper) {
+ $new_top = $notice->_items[0]->id;
+ }
+
+ $out = new XMLStringer();
+
+ $out->raw(sprintf(_('<p>Recent updates from %1s for %2s:</p>'),
+ common_config('site', 'name'),
+ $profile->getBestName()));
+
+
+ $out->elementStart('table', array('width' => '541px', 'style' => 'border: none'));
+
+ while ($notice->fetch()) {
+
+ $profile = Profile::staticGet('id', $notice->profile_id);
+
+ if (empty($profile)) {
+ continue;
+ }
+
+ $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
+
+ $out->elementStart('tr');
+ $out->elementStart('td', array('width' => AVATAR_STREAM_SIZE,
+ 'height' => AVATAR_STREAM_SIZE,
+ 'align' => 'left',
+ 'valign' => 'top'));
+ $out->element('img', array('src' => ($avatar) ?
+ $avatar->displayUrl() :
+ Avatar::defaultImage($avatar_size),
+ 'class' => 'avatar photo',
+ 'width' => AVATAR_STREAM_SIZE,
+ 'height' => AVATAR_STREAM_SIZE,
+ 'alt' => $profile->getBestName()));
+ $out->elementEnd('td');
+ $out->elementStart('td', array('align' => 'left',
+ 'valign' => 'top'));
+ $out->element('a', array('href' => $profile->profileurl),
+ $profile->nickname);
+ $out->text(' ');
+ $out->raw($notice->rendered);
+ $out->element('br'); // yeah, you know it. I just wrote a <br> in the middle of my table layout.
+ $noticeurl = $notice->bestUrl();
+ // above should always return an URL
+ assert(!empty($noticeurl));
+ $out->elementStart('a', array('rel' => 'bookmark',
+ 'class' => 'timestamp',
+ 'href' => $noticeurl));
+ $dt = common_date_iso8601($notice->created);
+ $out->element('abbr', array('class' => 'published',
+ 'title' => $dt),
+ common_date_string($notice->created));
+ $out->elementEnd('a');
+ if ($notice->hasConversation()) {
+ $conv = Conversation::staticGet('id', $notice->conversation);
+ $convurl = $conv->uri;
+ if (!empty($convurl)) {
+ $out->text(' ');
+ $out->element('a',
+ array('href' => $convurl.'#notice-'.$notice->id,
+ 'class' => 'response'),
+ _('in context'));
+ }
+ }
+ $out->elementEnd('td');
+ $out->elementEnd('tr');
+ }
+
+ $out->elementEnd('table');
+
+ $out->raw(sprintf(_('<p><a href="%1s">change your email settings for %2s</a></p>'),
+ common_local_url('emailsettings'),
+ common_config('site', 'name')));
+
+ $body = $out->getString();
+
+ // FIXME: do something for people who don't like HTML email
+
+ mail_to_user($user, _('Updates from your network'), $body,
+ array('Content-Type' => 'text/html; charset=UTF-8'));
+
+ if (empty($ess)) {
+
+ $ess = new Email_summary_status();
+
+ $ess->user_id = $user_id;
+ $ess->created = common_sql_now();
+ $ess->last_summary_id = $new_top;
+ $ess->modified = common_sql_now();
+
+ $ess->insert();
+
+ } else {
+
+ $orig = clone($ess);
+
+ $ess->last_summary_id = $new_top;
+ $ess->modified = common_sql_now();
+
+ $ess->update($orig);
+ }
+
+ return true;
+ }
+}
diff --git a/plugins/Mapstraction/MapstractionPlugin.php b/plugins/Mapstraction/MapstractionPlugin.php
index c4ba6464e..d5261d8bc 100644
--- a/plugins/Mapstraction/MapstractionPlugin.php
+++ b/plugins/Mapstraction/MapstractionPlugin.php
@@ -156,7 +156,8 @@ class MapstractionPlugin extends Plugin
' var user = null; '.
(($actionName == 'showstream') ? ' user = scrapeUser(); ' : '') .
' var notices = scrapeNotices(user); ' .
- ' showMapstraction($("#map_canvas"), notices); '.
+ ' var canvas = $("#map_canvas")[0]; ' .
+ ' if (typeof(canvas) != "undefined") { showMapstraction(canvas, notices); } '.
'});');
}
diff --git a/plugins/ModPlus/ModPlusPlugin.php b/plugins/ModPlus/ModPlusPlugin.php
new file mode 100644
index 000000000..3e7a8c745
--- /dev/null
+++ b/plugins/ModPlus/ModPlusPlugin.php
@@ -0,0 +1,116 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, 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 <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Some UI extras for now...
+ *
+ * @package ModPlusPlugin
+ * @maintainer Brion Vibber <brion@status.net>
+ */
+class ModPlusPlugin extends Plugin
+{
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'ModPlus',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Brion Vibber',
+ 'homepage' => 'http://status.net/wiki/Plugin:ModPlus',
+ 'rawdescription' =>
+ _m('UI extensions for profile moderation actions.'));
+
+ return true;
+ }
+
+ /**
+ * Load JS at runtime if we're logged in.
+ *
+ * @param Action $action
+ * @return boolean hook result
+ */
+ function onEndShowScripts($action)
+ {
+ $user = common_current_user();
+ if ($user) {
+ $action->script('plugins/ModPlus/modplus.js');
+ }
+ return true;
+ }
+
+ function onEndShowStatusNetStyles($action) {
+ $action->cssLink('plugins/ModPlus/modplus.css');
+ return true;
+ }
+
+ /**
+ * Autoloader
+ *
+ * Loads our classes if they're requested.
+ *
+ * @param string $cls Class requested
+ *
+ * @return boolean hook return
+ */
+ function onAutoload($cls)
+ {
+ switch ($cls)
+ {
+ case 'RemoteprofileAction':
+ case 'RemoteProfileAction':
+ require_once dirname(__FILE__) . '/remoteprofileaction.php';
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Add OpenID-related paths to the router table
+ *
+ * Hook for RouterInitialized event.
+ *
+ * @param Net_URL_Mapper $m URL mapper
+ *
+ * @return boolean hook return
+ */
+ function onStartInitializeRouter($m)
+ {
+ $m->connect('user/remote/:id',
+ array('action' => 'remoteprofile'),
+ array('id' => '[\d]+'));
+
+ return true;
+ }
+
+ function onStartShowNoticeItem($item)
+ {
+ $profile = $item->profile;
+ $isRemote = !(User::staticGet('id', $profile->id));
+ if ($isRemote) {
+ $target = common_local_url('remoteprofile', array('id' => $profile->id));
+ $label = _m('Remote profile options...');
+ $item->out->elementStart('div', 'remote-profile-options');
+ $item->out->element('a', array('href' => $target), $label);
+ $item->out->elementEnd('div');
+ }
+ }
+}
diff --git a/plugins/ModPlus/modplus.css b/plugins/ModPlus/modplus.css
new file mode 100644
index 000000000..8d2fc8fba
--- /dev/null
+++ b/plugins/ModPlus/modplus.css
@@ -0,0 +1,23 @@
+.remote-profile-options {
+ position: absolute;
+ z-index: 999;
+
+ background: url(../../theme/base/images/icons/twotone/green/admin.gif) no-repeat 8px 8px white;
+ border: solid 1px #c0c0c0;
+
+ margin-top: 56px;
+
+ padding: 6px 16px;
+ padding-left: 32px;
+
+ -moz-border-radius: 8px;
+ -webkit-border-radius: 8px;
+ -msie-border-radius: 8px;
+ border-radius: 8px;
+
+ box-shadow:3px 3px 7px rgba(194, 194, 194, 0.3);
+ -moz-box-shadow:3px 3px 7px rgba(194, 194, 194, 0.3);
+ -webkit-box-shadow:3px 3px 7px rgba(194, 194, 194, 0.3);
+
+ display: none;
+}
diff --git a/plugins/ModPlus/modplus.js b/plugins/ModPlus/modplus.js
new file mode 100644
index 000000000..2e90de4f1
--- /dev/null
+++ b/plugins/ModPlus/modplus.js
@@ -0,0 +1,23 @@
+/**
+ * modplus.js
+ * (c) 2010 StatusNet, Inc
+ */
+
+$(function() {
+ function ModPlus_setup(notice) {
+ if ($(notice).find('.remote-profile-options').size()) {
+ var $options = $(notice).find('.remote-profile-options');
+ $options.prepend($())
+ $(notice).find('.author').mouseenter(function(event) {
+ $(notice).find('.remote-profile-options').fadeIn();
+ });
+ $(notice).mouseleave(function(event) {
+ $(notice).find('.remote-profile-options').fadeOut();
+ });
+ }
+ }
+
+ $('.notice').each(function() {
+ ModPlus_setup(this);
+ });
+});
diff --git a/plugins/ModPlus/remoteprofileaction.php b/plugins/ModPlus/remoteprofileaction.php
new file mode 100644
index 000000000..caa5e6fbf
--- /dev/null
+++ b/plugins/ModPlus/remoteprofileaction.php
@@ -0,0 +1,106 @@
+<?php
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+class RemoteProfileAction extends ShowstreamAction
+{
+ function prepare($args)
+ {
+ OwnerDesignAction::prepare($args); // skip the ProfileAction code and replace it...
+
+ $id = $this->arg('id');
+ $this->user = false;
+ $this->profile = Profile::staticGet('id', $id);
+
+ if (!$this->profile) {
+ $this->serverError(_('User has no profile.'));
+ return false;
+ }
+
+ $user = User::staticGet('id', $this->profile->id);
+ if ($user) {
+ // This is a local user -- send to their regular profile.
+ $url = common_local_url('showstream', array('nickname' => $user->nickname));
+ common_redirect($url);
+ return false;
+ }
+
+ $this->tag = $this->trimmed('tag');
+ $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
+ common_set_returnto($this->selfUrl());
+ return true;
+ }
+
+ function handle($args)
+ {
+ // skip yadis thingy
+ $this->showPage();
+ }
+
+ function title()
+ {
+ // maybe fixed in 0.9.x
+ if (!empty($this->profile->fullname)) {
+ $base = $this->profile->fullname . ' (' . $this->profile->nickname . ') ';
+ } else {
+ $base = $this->profile->nickname;
+ }
+ $host = parse_url($this->profile->profileurl, PHP_URL_HOST);
+ return sprintf(_m('%s on %s'), $base, $host);
+ }
+
+ /**
+ * Instead of showing notices, link to the original offsite profile.
+ */
+ function showNotices()
+ {
+ $url = $this->profile->profileurl;
+ $host = parse_url($url, PHP_URL_HOST);
+ $markdown = sprintf(
+ _m('This remote profile is registered on another site; see [%s\'s original profile page on %s](%s).'),
+ $this->profile->nickname,
+ $host,
+ $url);
+ $html = common_markup_to_html($markdown);
+ $this->raw($html);
+
+ if ($this->profile->hasRole(Profile_role::SILENCED)) {
+ $markdown = _m('Site moderators have silenced this profile, which prevents delivery of new messages to any users on this site.');
+ $this->raw(common_markup_to_html($markdown));
+ }
+ }
+
+ function getFeeds()
+ {
+ // none
+ }
+
+ /**
+ * Don't do various extra stuff, and also trim some things to avoid crawlers.
+ */
+ function extraHead()
+ {
+ $this->element('meta', array('name' => 'robots',
+ 'content' => 'noindex,nofollow'));
+ }
+
+ function showLocalNav()
+ {
+ $nav = new PublicGroupNav($this);
+ $nav->show();
+ }
+
+ function showSections()
+ {
+ ProfileAction::showSections();
+ // skip tag cloud
+ }
+
+ function showStatistics()
+ {
+ // skip
+ }
+
+} \ No newline at end of file
diff --git a/plugins/TwitterBridge/Notice_to_status.php b/plugins/TwitterBridge/Notice_to_status.php
index 2e32ba963..3b8f816cf 100644
--- a/plugins/TwitterBridge/Notice_to_status.php
+++ b/plugins/TwitterBridge/Notice_to_status.php
@@ -144,6 +144,7 @@ class Notice_to_status extends Memcached_DataObject
/**
* Save a mapping between a notice and a status
+ * Warning: status_id values may not fit in 32-bit integers.
*
* @param integer $notice_id ID of the notice in StatusNet
* @param integer $status_id ID of the status in Twitter
@@ -153,12 +154,18 @@ class Notice_to_status extends Memcached_DataObject
static function saveNew($notice_id, $status_id)
{
+ if (empty($notice_id)) {
+ throw new Exception("Invalid notice_id $notice_id");
+ }
$n2s = Notice_to_status::staticGet('notice_id', $notice_id);
if (!empty($n2s)) {
return $n2s;
}
+ if (empty($status_id)) {
+ throw new Exception("Invalid status_id $status_id");
+ }
$n2s = Notice_to_status::staticGet('status_id', $status_id);
if (!empty($n2s)) {
diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php
index cd1ad70b9..b34488069 100644
--- a/plugins/TwitterBridge/twitter.php
+++ b/plugins/TwitterBridge/twitter.php
@@ -128,6 +128,16 @@ function is_twitter_notice($id)
return (!empty($n2s));
}
+/**
+ * Check if we need to broadcast a notice over the Twitter bridge, and
+ * do so if necessary. Will determine whether to do a straight post or
+ * a repeat/retweet
+ *
+ * This function is meant to be called directly from TwitterQueueHandler.
+ *
+ * @param Notice $notice
+ * @return boolean true if complete or successful, false if we should retry
+ */
function broadcast_twitter($notice)
{
$flink = Foreign_link::getByUserID($notice->profile_id,
@@ -137,8 +147,13 @@ function broadcast_twitter($notice)
if (!empty($flink) && TwitterOAuthClient::isPackedToken($flink->credentials)) {
if (!empty($notice->repeat_of) && is_twitter_notice($notice->repeat_of)) {
$retweet = retweet_notice($flink, Notice::staticGet('id', $notice->repeat_of));
- if (!empty($retweet)) {
+ if (is_object($retweet)) {
Notice_to_status::saveNew($notice->id, $retweet->id);
+ return true;
+ } else {
+ // Our error processing will have decided if we need to requeue
+ // this or can discard safely.
+ return $retweet;
}
} else if (is_twitter_bound($notice, $flink)) {
return broadcast_oauth($notice, $flink);
@@ -148,6 +163,21 @@ function broadcast_twitter($notice)
return true;
}
+/**
+ * Send a retweet to Twitter for a notice that has been previously bridged
+ * in or out.
+ *
+ * Warning: the return value is not guaranteed to be an object; some error
+ * conditions will return a 'true' which should be passed on to a calling
+ * queue handler.
+ *
+ * No local information about the resulting retweet is saved: it's up to
+ * caller to save new mappings etc if appropriate.
+ *
+ * @param Foreign_link $flink
+ * @param Notice $notice
+ * @return mixed object with resulting Twitter status data on success, or true/false/null on error conditions.
+ */
function retweet_notice($flink, $notice)
{
$token = TwitterOAuthClient::unpackToken($flink->credentials);
diff --git a/plugins/TwitterBridge/twitterimport.php b/plugins/TwitterBridge/twitterimport.php
index 07a9cf95f..498e9b1fc 100644
--- a/plugins/TwitterBridge/twitterimport.php
+++ b/plugins/TwitterBridge/twitterimport.php
@@ -189,6 +189,7 @@ class TwitterImport
Notice_to_status::saveNew($notice->id, $status->id);
$this->saveStatusMentions($notice, $status);
+ $this->saveStatusAttachments($notice, $status);
$notice->blowOnInsert();
@@ -648,4 +649,20 @@ class TwitterImport
}
}
}
+
+ /**
+ * Record URL links from the notice. Needed to get thumbnail records
+ * for referenced photo and video posts, etc.
+ *
+ * @param Notice $notice
+ * @param object $status
+ */
+ function saveStatusAttachments($notice, $status)
+ {
+ if (!empty($status->entities) && !empty($status->entities->urls)) {
+ foreach ($status->entities->urls as $url) {
+ File::processNew($url->url, $notice->id);
+ }
+ }
+ }
} \ No newline at end of file
diff --git a/scripts/clear_jabber.php b/scripts/clear_jabber.php
new file mode 100755
index 000000000..5ec53caf0
--- /dev/null
+++ b/scripts/clear_jabber.php
@@ -0,0 +1,92 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, 2010, 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 <http://www.gnu.org/licenses/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+
+$shortoptions = 'i::n::y';
+$longoptions = array('id=', 'nickname=', 'yes', 'all', 'dry-run');
+
+$helptext = <<<END_OF_DELETEUSER_HELP
+clear_jabber.php [options]
+Deletes a user's confirmed Jabber/XMPP address from the database.
+
+ -i --id ID of the user
+ -n --nickname nickname of the user
+ --all all users with confirmed Jabber addresses
+ --dry-run Don't actually delete info.
+
+END_OF_DELETEUSER_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+if (have_option('i', 'id')) {
+ $id = get_option_value('i', 'id');
+ $user = User::staticGet('id', $id);
+ if (empty($user)) {
+ print "Can't find user with ID $id\n";
+ exit(1);
+ }
+} else if (have_option('n', 'nickname')) {
+ $nickname = get_option_value('n', 'nickname');
+ $user = User::staticGet('nickname', $nickname);
+ if (empty($user)) {
+ print "Can't find user with nickname '$nickname'\n";
+ exit(1);
+ }
+} else if (have_option('all')) {
+ $user = new User();
+ $user->whereAdd("jabber != ''");
+ $user->find(true);
+ if ($user->N == 0) {
+ print "No users with registered Jabber addresses in database.\n";
+ exit(1);
+ }
+} else {
+ print "You must provide either an ID or a nickname.\n";
+ print "\n";
+ print $helptext;
+ exit(1);
+}
+
+function clear_jabber($id)
+{
+ $user = User::staticGet('id', $id);
+ if ($user && $user->jabber) {
+ echo "clearing user $id's user.jabber, was: $user->jabber";
+ if (have_option('dry-run')) {
+ echo " (SKIPPING)";
+ } else {
+ $original = clone($user);
+ $user->jabber = null;
+ $result = $user->updateKeys($original);
+ }
+ echo "\n";
+ } else if (!$user) {
+ echo "Missing user for $id\n";
+ } else {
+ echo "Cleared jabber already for $id\n";
+ }
+}
+
+do {
+ clear_jabber($user->id);
+} while ($user->fetch());
+
+print "DONE.\n";
diff --git a/tests/oEmbedTest.php b/tests/oEmbedTest.php
new file mode 100644
index 000000000..b5e441c42
--- /dev/null
+++ b/tests/oEmbedTest.php
@@ -0,0 +1,140 @@
+<?php
+
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('STATUSNET', true);
+
+require_once INSTALLDIR . '/lib/common.php';
+
+class oEmbedTest extends PHPUnit_Framework_TestCase
+{
+
+ public function setup()
+ {
+ $this->old_oohembed = common_config('oohembed', 'endpoint');
+ }
+
+ public function tearDown()
+ {
+ $GLOBALS['config']['oohembed']['endpoint'] = $this->old_oohembed;
+ }
+
+ /**
+ * Test with oohembed DISABLED.
+ *
+ * @dataProvider discoverableSources
+ */
+ public function testoEmbed($url, $expectedType)
+ {
+ $GLOBALS['config']['oohembed']['endpoint'] = false;
+ $this->_doTest($url, $expectedType);
+ }
+
+ /**
+ * Test with oohembed ENABLED.
+ *
+ * @dataProvider fallbackSources
+ */
+ public function testoohEmbed($url, $expectedType)
+ {
+ $GLOBALS['config']['oohembed']['endpoint'] = $this->_endpoint();
+ $this->_doTest($url, $expectedType);
+ }
+
+ /**
+ * Get default oohembed endpoint.
+ *
+ * @return string
+ */
+ function _endpoint()
+ {
+ $default = array();
+ $_server = 'localhost'; $_path = '';
+ require INSTALLDIR . '/lib/default.php';
+ return $default['oohembed']['endpoint'];
+ }
+
+ /**
+ * Actually run an individual test.
+ *
+ * @param string $url
+ * @param string $expectedType
+ */
+ function _doTest($url, $expectedType)
+ {
+ try {
+ $data = oEmbedHelper::getObject($url);
+ $this->assertEquals($expectedType, $data->type);
+ if ($data->type == 'photo') {
+ $this->assertTrue(!empty($data->url), 'Photo must have a URL.');
+ $this->assertTrue(!empty($data->width), 'Photo must have a width.');
+ $this->assertTrue(!empty($data->height), 'Photo must have a height.');
+ } else if ($data->type == 'video') {
+ $this->assertTrue(!empty($data->html), 'Video must have embedding HTML.');
+ $this->assertTrue(!empty($data->thumbnail_url), 'Video should have a thumbnail.');
+ }
+ if (!empty($data->thumbnail_url)) {
+ $this->assertTrue(!empty($data->thumbnail_width), 'Thumbnail must list a width.');
+ $this->assertTrue(!empty($data->thumbnail_height), 'Thumbnail must list a height.');
+ }
+ } catch (Exception $e) {
+ if ($expectedType == 'none') {
+ $this->assertEquals($expectedType, 'none', 'Should not have data for this URL.');
+ } else {
+ throw $e;
+ }
+ }
+ }
+
+ /**
+ * Sample oEmbed targets for sites we know ourselves...
+ * @return array
+ */
+ static public function knownSources()
+ {
+ $sources = array(
+ array('http://www.flickr.com/photos/brionv/5172500179/', 'photo'),
+ array('http://yfrog.com/fy42747177j', 'photo'),
+ array('http://twitpic.com/36adw6', 'photo'),
+ );
+ return $sources;
+ }
+
+ /**
+ * Sample oEmbed targets that can be found via discovery.
+ * Includes also knownSources() output.
+ *
+ * @return array
+ */
+ static public function discoverableSources()
+ {
+ $sources = array(
+ array('http://identi.ca/attachment/34437400', 'photo'),
+
+ array('http://www.youtube.com/watch?v=eUgLR232Cnw', 'video'),
+ array('http://vimeo.com/9283184', 'video'),
+
+ // Will fail discovery:
+ array('http://leuksman.com/log/2010/10/29/statusnet-0-9-6-release/', 'none'),
+ );
+ return array_merge(self::knownSources(), $sources);
+ }
+
+ /**
+ * Sample oEmbed targets that can be found via oohembed.com.
+ * Includes also discoverableSources() output.
+ *
+ * @return array
+ */
+ static public function fallbackSources()
+ {
+ $sources = array(
+ array('http://en.wikipedia.org/wiki/File:Wiki.png', 'link'), // @fixme in future there may be a native provider -- will change to 'photo'
+ );
+ return array_merge(self::discoverableSources(), $sources);
+ }
+}
diff --git a/theme/base/css/display.css b/theme/base/css/display.css
index 7ac66095a..8c364febc 100644
--- a/theme/base/css/display.css
+++ b/theme/base/css/display.css
@@ -1325,15 +1325,6 @@ margin-left:4px;
.notice .attachment.more {
padding-left:0;
}
-.notice .attachment img {
-position:absolute;
-top:18px;
-left:0;
-z-index:99;
-}
-#shownotice .notice .attachment img {
-position:static;
-}
#attachments {
clear:both;
@@ -1716,6 +1707,12 @@ width:auto;
min-width:0;
}
+.inline-attachment img {
+ /* Why on earth is this changed to block at the top? */
+ display: inline;
+ border: solid 1px #aaa;
+ padding: 1px;
+}
}/*end of @media screen, projection, tv*/