Merge remote branch 'gitorious/0.9.x' into 1.0.x
Conflicts: lib/common.php
This commit is contained in:
commit
32eb4c5e2d
@ -1058,3 +1058,8 @@ EndImportActivity: when we finish importing an activity
|
||||
- $activity: The current activity
|
||||
- $trusted: How "trusted" the process is
|
||||
|
||||
StartProfileSettingsActions: when we're showing account-management action list
|
||||
- $action: Action being shown (use for output)
|
||||
|
||||
EndProfileSettingsActions: when we're showing account-management action list
|
||||
- $action: Action being shown (use for output)
|
||||
|
@ -125,10 +125,6 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
|
||||
header('Content-Type: application/atom+xml; charset=utf-8');
|
||||
|
||||
try {
|
||||
$atom->addAuthorRaw($this->group->asAtomAuthor());
|
||||
$atom->setActivitySubject($this->group->asActivitySubject());
|
||||
$atom->setId($self);
|
||||
$atom->setSelfLink($self);
|
||||
$atom->addEntryFromNotices($this->notices);
|
||||
$this->raw($atom->getString());
|
||||
} catch (Atom10FeedException $e) {
|
||||
|
@ -458,6 +458,8 @@ class ProfilesettingsAction extends AccountSettingsAction
|
||||
|
||||
$this->elementStart('div', array('id' => 'aside_primary',
|
||||
'class' => 'aside'));
|
||||
$this->elementStart('ul');
|
||||
if (Event::handle('StartProfileSettingsActions', array($this))) {
|
||||
if ($user->hasRight(Right::BACKUPACCOUNT)) {
|
||||
$this->elementStart('li');
|
||||
$this->element('a',
|
||||
@ -479,6 +481,9 @@ class ProfilesettingsAction extends AccountSettingsAction
|
||||
_('Restore account'));
|
||||
$this->elementEnd('li');
|
||||
}
|
||||
Event::handle('EndProfileSettingsActions', array($this));
|
||||
}
|
||||
$this->elementEnd('ul');
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
}
|
||||
|
@ -412,4 +412,102 @@ class File extends Memcached_DataObject
|
||||
{
|
||||
return File_thumbnail::staticGet('file_id', $this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blow the cache of notices that link to this URL
|
||||
*
|
||||
* @param boolean $last Whether to blow the "last" cache too
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function blowCache($last=false)
|
||||
{
|
||||
self::blow('file:notice-ids:%s', $this->url);
|
||||
if ($last) {
|
||||
self::blow('file:notice-ids:%s;last', $this->url);
|
||||
}
|
||||
self::blow('file:notice-count:%d', $this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream of notices linking to this URL
|
||||
*
|
||||
* @param integer $offset Offset to show; default is 0
|
||||
* @param integer $limit Limit of notices to show
|
||||
* @param integer $since_id Since this notice
|
||||
* @param integer $max_id Before this notice
|
||||
*
|
||||
* @return array ids of notices that link to this file
|
||||
*/
|
||||
|
||||
function stream($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
|
||||
{
|
||||
$ids = Notice::stream(array($this, '_streamDirect'),
|
||||
array(),
|
||||
'file:notice-ids:'.$this->url,
|
||||
$offset, $limit, $since_id, $max_id);
|
||||
|
||||
return Notice::getStreamByIds($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream of notices linking to this URL
|
||||
*
|
||||
* @param integer $offset Offset to show; default is 0
|
||||
* @param integer $limit Limit of notices to show
|
||||
* @param integer $since_id Since this notice
|
||||
* @param integer $max_id Before this notice
|
||||
*
|
||||
* @return array ids of notices that link to this file
|
||||
*/
|
||||
|
||||
function _streamDirect($offset, $limit, $since_id, $max_id)
|
||||
{
|
||||
$f2p = new File_to_post();
|
||||
|
||||
$f2p->selectAdd();
|
||||
$f2p->selectAdd('post_id');
|
||||
|
||||
$f2p->file_id = $this->id;
|
||||
|
||||
Notice::addWhereSinceId($f2p, $since_id, 'post_id', 'modified');
|
||||
Notice::addWhereMaxId($f2p, $max_id, 'post_id', 'modified');
|
||||
|
||||
$f2p->orderBy('modified DESC, post_id DESC');
|
||||
|
||||
if (!is_null($offset)) {
|
||||
$f2p->limit($offset, $limit);
|
||||
}
|
||||
|
||||
$ids = array();
|
||||
|
||||
if ($f2p->find()) {
|
||||
while ($f2p->fetch()) {
|
||||
$ids[] = $f2p->post_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
function noticeCount()
|
||||
{
|
||||
$cacheKey = sprintf('file:notice-count:%d', $this->id);
|
||||
|
||||
$count = self::cacheGet($cacheKey);
|
||||
|
||||
if ($count === false) {
|
||||
|
||||
$f2p = new File_to_post();
|
||||
|
||||
$f2p->file_id = $this->id;
|
||||
|
||||
$count = $f2p->count();
|
||||
|
||||
self::cacheSet($cacheKey, $count);
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,12 @@ class File_to_post extends Memcached_DataObject
|
||||
$f2p->file_id = $file_id;
|
||||
$f2p->post_id = $notice_id;
|
||||
$f2p->insert();
|
||||
|
||||
$f = File::staticGet($file_id);
|
||||
|
||||
if (!empty($f)) {
|
||||
$f->blowCache();
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($seen[$notice_id])) {
|
||||
@ -66,4 +72,13 @@ class File_to_post extends Memcached_DataObject
|
||||
{
|
||||
return Memcached_DataObject::pkeyGet('File_to_post', $kv);
|
||||
}
|
||||
|
||||
function delete()
|
||||
{
|
||||
$f = File::staticGet('id', $this->file_id);
|
||||
if (!empty($f)) {
|
||||
$f->blowCache();
|
||||
}
|
||||
return parent::delete();
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ class Memcached_DataObject extends Safe_DataObject
|
||||
return $i;
|
||||
} else {
|
||||
$i = DB_DataObject::factory($cls);
|
||||
if (empty($i)) {
|
||||
if (empty($i) || PEAR::isError($i)) {
|
||||
return false;
|
||||
}
|
||||
foreach ($kv as $k => $v) {
|
||||
|
@ -135,6 +135,7 @@ class Notice extends Memcached_DataObject
|
||||
$this->clearFaves();
|
||||
$this->clearTags();
|
||||
$this->clearGroupInboxes();
|
||||
$this->clearFiles();
|
||||
|
||||
// NOTE: we don't clear inboxes
|
||||
// NOTE: we don't clear queue items
|
||||
@ -1785,6 +1786,21 @@ class Notice extends Memcached_DataObject
|
||||
$reply->free();
|
||||
}
|
||||
|
||||
function clearFiles()
|
||||
{
|
||||
$f2p = new File_to_post();
|
||||
|
||||
$f2p->post_id = $this->id;
|
||||
|
||||
if ($f2p->find()) {
|
||||
while ($f2p->fetch()) {
|
||||
$f2p->delete();
|
||||
}
|
||||
}
|
||||
// FIXME: decide whether to delete File objects
|
||||
// ...and related (actual) files
|
||||
}
|
||||
|
||||
function clearRepeats()
|
||||
{
|
||||
$repeatNotice = new Notice();
|
||||
|
@ -87,4 +87,19 @@ class Notice_tag extends Memcached_DataObject
|
||||
{
|
||||
return Memcached_DataObject::pkeyGet('Notice_tag', $kv);
|
||||
}
|
||||
|
||||
static function url($tag)
|
||||
{
|
||||
if (common_config('singleuser', 'enabled')) {
|
||||
// regular TagAction isn't set up in 1user mode
|
||||
$nickname = User::singleUserNickname();
|
||||
$url = common_local_url('showstream',
|
||||
array('nickname' => $nickname,
|
||||
'tag' => $tag));
|
||||
} else {
|
||||
$url = common_local_url('tag', array('tag' => $tag));
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
|
@ -377,13 +377,7 @@ class Activity
|
||||
$xs->element('updated', null, $published);
|
||||
|
||||
if ($author) {
|
||||
$xs->elementStart('author');
|
||||
$xs->element('uri', array(), $this->actor->id);
|
||||
if ($this->actor->title) {
|
||||
$xs->element('name', array(), $this->actor->title);
|
||||
}
|
||||
$xs->elementEnd('author');
|
||||
$this->actor->outputTo($xs, 'activity:actor');
|
||||
$this->actor->outputTo($xs, 'author');
|
||||
}
|
||||
|
||||
if ($this->verb != ActivityVerb::POST || count($this->objects) != 1) {
|
||||
@ -446,7 +440,7 @@ class Activity
|
||||
}
|
||||
|
||||
foreach ($this->categories as $cat) {
|
||||
$xs->raw($cat->asString());
|
||||
$cat->outputTo($xs);
|
||||
}
|
||||
|
||||
// can be either URLs or enclosure objects
|
||||
|
@ -172,10 +172,39 @@ class ActivityObject
|
||||
|
||||
private function _fromAuthor($element)
|
||||
{
|
||||
$this->type = self::PERSON; // XXX: is this fair?
|
||||
$this->title = $this->_childContent($element, self::NAME);
|
||||
$this->type = $this->_childContent($element,
|
||||
Activity::OBJECTTYPE,
|
||||
Activity::SPEC);
|
||||
|
||||
if (empty($this->type)) {
|
||||
$this->type = self::PERSON; // XXX: is this fair?
|
||||
}
|
||||
|
||||
// start with <atom:title>
|
||||
|
||||
$title = ActivityUtils::childHtmlContent($element, self::TITLE);
|
||||
|
||||
if (!empty($title)) {
|
||||
$this->title = html_entity_decode(strip_tags($title), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
// fall back to <atom:name>
|
||||
|
||||
if (empty($this->title)) {
|
||||
$this->title = $this->_childContent($element, self::NAME);
|
||||
}
|
||||
|
||||
// start with <atom:id>
|
||||
|
||||
$this->id = $this->_childContent($element, self::ID);
|
||||
|
||||
// fall back to <atom:uri>
|
||||
|
||||
if (empty($this->id)) {
|
||||
$this->id = $this->_childContent($element, self::URI);
|
||||
}
|
||||
|
||||
// fall further back to <atom:email>
|
||||
|
||||
if (empty($this->id)) {
|
||||
$email = $this->_childContent($element, self::EMAIL);
|
||||
@ -184,6 +213,14 @@ class ActivityObject
|
||||
$this->id = 'mailto:'.$email;
|
||||
}
|
||||
}
|
||||
|
||||
$this->link = ActivityUtils::getPermalink($element);
|
||||
|
||||
// fall finally back to <link rel=alternate>
|
||||
|
||||
if (empty($this->id) && !empty($this->link)) { // fallback if there's no ID
|
||||
$this->id = $this->link;
|
||||
}
|
||||
}
|
||||
|
||||
private function _fromAtomEntry($element)
|
||||
@ -498,14 +535,21 @@ class ActivityObject
|
||||
|
||||
$xo->element('activity:object-type', null, $this->type);
|
||||
|
||||
// <author> uses URI
|
||||
|
||||
if ($tag == 'author') {
|
||||
$xo->element(self::URI, null, $this->id);
|
||||
} else {
|
||||
$xo->element(self::ID, null, $this->id);
|
||||
}
|
||||
|
||||
if (!empty($this->title)) {
|
||||
$xo->element(
|
||||
self::TITLE,
|
||||
null,
|
||||
common_xml_safe_str($this->title)
|
||||
);
|
||||
$name = common_xml_safe_str($this->title);
|
||||
if ($tag == 'author') {
|
||||
$xo->element(self::NAME, null, $name);
|
||||
} else {
|
||||
$xo->element(self::TITLE, null, $name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($this->summary)) {
|
||||
@ -563,7 +607,7 @@ class ActivityObject
|
||||
}
|
||||
|
||||
if (!empty($this->poco)) {
|
||||
$xo->raw($this->poco->asString());
|
||||
$this->poco->outputTo($xo);
|
||||
}
|
||||
|
||||
foreach ($this->extra as $el) {
|
||||
|
@ -146,15 +146,16 @@ class Atom10Feed extends XMLStringer
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a activity feed subject via raw XML string
|
||||
* Deprecated <activity:subject>; ignored
|
||||
*
|
||||
* @param string $xmlSubject An XML string representation of the subject
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function setActivitySubject($xmlSubject)
|
||||
{
|
||||
$this->subject = $xmlSubject;
|
||||
throw new ServerException(_('Don\'t use this method!'));
|
||||
}
|
||||
|
||||
function getNamespaces()
|
||||
|
@ -59,6 +59,13 @@ class AtomCategory
|
||||
}
|
||||
|
||||
function asString()
|
||||
{
|
||||
$xs = new XMLStringer();
|
||||
$this->outputTo($xs);
|
||||
return $xs->getString();
|
||||
}
|
||||
|
||||
function outputTo($xo)
|
||||
{
|
||||
$attribs = array();
|
||||
if ($this->term !== null) {
|
||||
@ -70,8 +77,6 @@ class AtomCategory
|
||||
if ($this->label !== null) {
|
||||
$attribs['label'] = $this->label;
|
||||
}
|
||||
$xs = new XMLStringer();
|
||||
$xs->element('category', $attribs);
|
||||
return $xs->getString();
|
||||
$xo->element('category', $attribs);
|
||||
}
|
||||
}
|
||||
|
@ -85,8 +85,14 @@ class AtomGroupNoticeFeed extends AtomNoticeFeed
|
||||
$this->setId($self);
|
||||
$this->setSelfLink($self);
|
||||
|
||||
$this->addAuthorRaw($group->asAtomAuthor());
|
||||
$this->setActivitySubject($group->asActivitySubject());
|
||||
// For groups, we generate an author _AND_ an <activity:subject>
|
||||
// Versions of StatusNet under 0.9.7 treat <author> as a person
|
||||
// XXX: remove this workaround in future versions
|
||||
|
||||
$ao = ActivityObject::fromGroup($group);
|
||||
|
||||
$this->addAuthorRaw($ao->asString('author').
|
||||
$ao->asString('activity:subject'));
|
||||
|
||||
$this->addLink($group->homeUrl());
|
||||
}
|
||||
|
@ -60,8 +60,8 @@ class AtomUserNoticeFeed extends AtomNoticeFeed
|
||||
$this->user = $user;
|
||||
if (!empty($user)) {
|
||||
$profile = $user->getProfile();
|
||||
$this->addAuthor($profile->nickname, $user->uri);
|
||||
$this->setActivitySubject($profile->asActivityNoun('subject'));
|
||||
$ao = ActivityObject::fromProfile($profile);
|
||||
$this->addAuthorRaw($ao->asString('author'));
|
||||
}
|
||||
|
||||
// TRANS: Title in atom user notice feed. %s is a user name.
|
||||
|
@ -299,6 +299,10 @@ class oEmbedHelper
|
||||
|
||||
class oEmbedHelper_Exception extends Exception
|
||||
{
|
||||
public function __construct($message = "", $code = 0, $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code);
|
||||
}
|
||||
}
|
||||
|
||||
class oEmbedHelper_BadHtmlException extends oEmbedHelper_Exception
|
||||
|
18
lib/poco.php
18
lib/poco.php
@ -211,30 +211,34 @@ class PoCo
|
||||
function asString()
|
||||
{
|
||||
$xs = new XMLStringer(true);
|
||||
$xs->element(
|
||||
$this->outputTo($xs);
|
||||
return $xs->getString();
|
||||
}
|
||||
|
||||
function outputTo($xo)
|
||||
{
|
||||
$xo->element(
|
||||
'poco:preferredUsername',
|
||||
null,
|
||||
$this->preferredUsername
|
||||
);
|
||||
|
||||
$xs->element(
|
||||
$xo->element(
|
||||
'poco:displayName',
|
||||
null,
|
||||
$this->displayName
|
||||
);
|
||||
|
||||
if (!empty($this->note)) {
|
||||
$xs->element('poco:note', null, common_xml_safe_str($this->note));
|
||||
$xo->element('poco:note', null, common_xml_safe_str($this->note));
|
||||
}
|
||||
|
||||
if (!empty($this->address)) {
|
||||
$xs->raw($this->address->asString());
|
||||
$this->address->outputTo($xo);
|
||||
}
|
||||
|
||||
foreach ($this->urls as $url) {
|
||||
$xs->raw($url->asString());
|
||||
$url->outputTo($xo);
|
||||
}
|
||||
|
||||
return $xs->getString();
|
||||
}
|
||||
}
|
||||
|
@ -43,14 +43,17 @@ class PoCoAddress
|
||||
|
||||
function asString()
|
||||
{
|
||||
if (!empty($this->formatted)) {
|
||||
$xs = new XMLStringer(true);
|
||||
$xs->elementStart('poco:address');
|
||||
$xs->element('poco:formatted', null, common_xml_safe_str($this->formatted));
|
||||
$xs->elementEnd('poco:address');
|
||||
$this->outputTo($xs);
|
||||
return $xs->getString();
|
||||
}
|
||||
|
||||
return null;
|
||||
function outputTo($xo)
|
||||
{
|
||||
if (!empty($this->formatted)) {
|
||||
$xo->elementStart('poco:address');
|
||||
$xo->element('poco:formatted', null, common_xml_safe_str($this->formatted));
|
||||
$xo->elementEnd('poco:address');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,13 +53,18 @@ class PoCoURL
|
||||
function asString()
|
||||
{
|
||||
$xs = new XMLStringer(true);
|
||||
$xs->elementStart('poco:urls');
|
||||
$xs->element('poco:type', null, $this->type);
|
||||
$xs->element('poco:value', null, $this->value);
|
||||
if (!empty($this->primary)) {
|
||||
$xs->element('poco:primary', null, 'true');
|
||||
}
|
||||
$xs->elementEnd('poco:urls');
|
||||
$this->outputTo($xs);
|
||||
return $xs->getString();
|
||||
}
|
||||
|
||||
function outputTo($xo)
|
||||
{
|
||||
$xo->elementStart('poco:urls');
|
||||
$xo->element('poco:type', null, $this->type);
|
||||
$xo->element('poco:value', null, $this->value);
|
||||
if (!empty($this->primary)) {
|
||||
$xo->element('poco:primary', null, 'true');
|
||||
}
|
||||
$xo->elementEnd('poco:urls');
|
||||
}
|
||||
}
|
||||
|
351
plugins/Bookmark/Bookmark.php
Normal file
351
plugins/Bookmark/Bookmark.php
Normal file
@ -0,0 +1,351 @@
|
||||
<?php
|
||||
/**
|
||||
* Data class to mark notices as bookmarks
|
||||
*
|
||||
* 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) 2009, StatusNet, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* For storing the fact that a notice is a bookmark
|
||||
*
|
||||
* @category Bookmark
|
||||
* @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 Bookmark extends Memcached_DataObject
|
||||
{
|
||||
public $__table = 'bookmark'; // table name
|
||||
public $profile_id; // int(4) primary_key not_null
|
||||
public $url; // varchar(255) primary_key not_null
|
||||
public $title; // varchar(255)
|
||||
public $description; // text
|
||||
public $uri; // varchar(255)
|
||||
public $url_crc32; // int(4) not_null
|
||||
public $created; // datetime
|
||||
|
||||
/**
|
||||
* Get an instance by key
|
||||
*
|
||||
* This is a utility method to get a single instance with a given key value.
|
||||
*
|
||||
* @param string $k Key to use to lookup (usually 'user_id' for this class)
|
||||
* @param mixed $v Value to lookup
|
||||
*
|
||||
* @return User_greeting_count object found, or null for no hits
|
||||
*
|
||||
*/
|
||||
|
||||
function staticGet($k, $v=null)
|
||||
{
|
||||
return Memcached_DataObject::staticGet('Bookmark', $k, $v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance by compound key
|
||||
*
|
||||
* This is a utility method to get a single instance with a given set of
|
||||
* key-value pairs. Usually used for the primary key for a compound key; thus
|
||||
* the name.
|
||||
*
|
||||
* @param array $kv array of key-value mappings
|
||||
*
|
||||
* @return Bookmark object found, or null for no hits
|
||||
*
|
||||
*/
|
||||
|
||||
function pkeyGet($kv)
|
||||
{
|
||||
return Memcached_DataObject::pkeyGet('Bookmark', $kv);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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('profile_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
|
||||
'url' => DB_DATAOBJECT_STR,
|
||||
'title' => DB_DATAOBJECT_STR,
|
||||
'description' => DB_DATAOBJECT_STR,
|
||||
'uri' => DB_DATAOBJECT_STR,
|
||||
'url_crc32' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
|
||||
'created' => DB_DATAOBJECT_STR + 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
|
||||
*
|
||||
* @return array associative array of key definitions
|
||||
*/
|
||||
|
||||
function keyTypes()
|
||||
{
|
||||
return array('profile_id' => 'K',
|
||||
'url' => 'K',
|
||||
'uri' => 'U');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a bookmark based on a notice
|
||||
*
|
||||
* @param Notice $notice Notice to check for
|
||||
*
|
||||
* @return Bookmark found bookmark or null
|
||||
*/
|
||||
|
||||
function getByNotice($notice)
|
||||
{
|
||||
return self::staticGet('uri', $notice->uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bookmark that a user made for an URL
|
||||
*
|
||||
* @param Profile $profile Profile to check for
|
||||
* @param string $url URL to check for
|
||||
*
|
||||
* @return Bookmark bookmark found or null
|
||||
*/
|
||||
|
||||
static function getByURL($profile, $url)
|
||||
{
|
||||
return self::pkeyGet(array('profile_id' => $profile->id,
|
||||
'url' => $url));
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bookmark that a user made for an URL
|
||||
*
|
||||
* @param Profile $profile Profile to check for
|
||||
* @param integer $crc32 CRC-32 of URL to check for
|
||||
*
|
||||
* @return array Bookmark objects found (usually 1 or 0)
|
||||
*/
|
||||
|
||||
static function getByCRC32($profile, $crc32)
|
||||
{
|
||||
$bookmarks = array();
|
||||
|
||||
$nb = new Bookmark();
|
||||
|
||||
$nb->profile_id = $profile->id;
|
||||
$nb->url_crc32 = $crc32;
|
||||
|
||||
if ($nb->find()) {
|
||||
while ($nb->fetch()) {
|
||||
$bookmarks[] = clone($nb);
|
||||
}
|
||||
}
|
||||
|
||||
return $bookmarks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a new notice bookmark
|
||||
*
|
||||
* @param Profile $profile To save the bookmark for
|
||||
* @param string $title Title of the bookmark
|
||||
* @param string $url URL of the bookmark
|
||||
* @param mixed $rawtags array of tags or string
|
||||
* @param string $description Description of the bookmark
|
||||
* @param array $options Options for the Notice::saveNew()
|
||||
*
|
||||
* @return Notice saved notice
|
||||
*/
|
||||
|
||||
static function saveNew($profile, $title, $url, $rawtags, $description,
|
||||
$options=null)
|
||||
{
|
||||
$nb = self::getByURL($profile, $url);
|
||||
|
||||
if (!empty($nb)) {
|
||||
throw new ClientException(_('Bookmark already exists.'));
|
||||
}
|
||||
|
||||
if (empty($options)) {
|
||||
$options = array();
|
||||
}
|
||||
|
||||
if (array_key_exists('uri', $options)) {
|
||||
$other = Bookmark::staticGet('uri', $options['uri']);
|
||||
if (!empty($other)) {
|
||||
throw new ClientException(_('Bookmark already exists.'));
|
||||
}
|
||||
}
|
||||
|
||||
if (is_string($rawtags)) {
|
||||
$rawtags = preg_split('/[\s,]+/', $rawtags);
|
||||
}
|
||||
|
||||
$nb = new Bookmark();
|
||||
|
||||
$nb->profile_id = $profile->id;
|
||||
$nb->url = $url;
|
||||
$nb->title = $title;
|
||||
$nb->description = $description;
|
||||
$nb->url_crc32 = crc32($nb->url);
|
||||
|
||||
if (array_key_exists('created', $options)) {
|
||||
$nb->created = $options['created'];
|
||||
} else {
|
||||
$nb->created = common_sql_now();
|
||||
}
|
||||
|
||||
if (array_key_exists('uri', $options)) {
|
||||
$nb->uri = $options['uri'];
|
||||
} else {
|
||||
$dt = new DateTime($nb->created, new DateTimeZone('UTC'));
|
||||
|
||||
// I posit that it's sufficiently impossible
|
||||
// for the same user to generate two CRC-32-clashing
|
||||
// URLs in the same second that this is a safe unique identifier.
|
||||
// If you find a real counterexample, contact me at acct:evan@status.net
|
||||
// and I will publicly apologize for my hubris.
|
||||
|
||||
$created = $dt->format('YmdHis');
|
||||
|
||||
$crc32 = sprintf('%08x', $nb->url_crc32);
|
||||
|
||||
$nb->uri = common_local_url('showbookmark',
|
||||
array('user' => $profile->id,
|
||||
'created' => $created,
|
||||
'crc32' => $crc32));
|
||||
}
|
||||
|
||||
$nb->insert();
|
||||
|
||||
$tags = array();
|
||||
$replies = array();
|
||||
|
||||
// filter "for:nickname" tags
|
||||
|
||||
foreach ($rawtags as $tag) {
|
||||
if (strtolower(mb_substr($tag, 0, 4)) == 'for:') {
|
||||
// skip if done by caller
|
||||
if (!array_key_exists('replies', $options)) {
|
||||
$nickname = mb_substr($tag, 4);
|
||||
$other = common_relative_profile($profile,
|
||||
$nickname);
|
||||
if (!empty($other)) {
|
||||
$replies[] = $other->getUri();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$tags[] = common_canonical_tag($tag);
|
||||
}
|
||||
}
|
||||
|
||||
$hashtags = array();
|
||||
$taglinks = array();
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
$hashtags[] = '#'.$tag;
|
||||
$attrs = array('href' => Notice_tag::url($tag),
|
||||
'rel' => $tag,
|
||||
'class' => 'tag');
|
||||
$taglinks[] = XMLStringer::estring('a', $attrs, $tag);
|
||||
}
|
||||
|
||||
// Use user's preferences for short URLs, if possible
|
||||
|
||||
$user = User::staticGet('id', $profile->id);
|
||||
|
||||
$shortUrl = File_redirection::makeShort($url,
|
||||
empty($user) ? null : $user);
|
||||
|
||||
$content = sprintf(_('"%s" %s %s %s'),
|
||||
$title,
|
||||
$shortUrl,
|
||||
$description,
|
||||
implode(' ', $hashtags));
|
||||
|
||||
$rendered = sprintf(_('<span class="xfolkentry">'.
|
||||
'<a class="taggedlink" href="%s">%s</a> '.
|
||||
'<span class="description">%s</span> '.
|
||||
'<span class="meta">%s</span>'.
|
||||
'</span>'),
|
||||
htmlspecialchars($url),
|
||||
htmlspecialchars($title),
|
||||
htmlspecialchars($description),
|
||||
implode(' ', $taglinks));
|
||||
|
||||
$options = array_merge(array('urls' => array($url),
|
||||
'rendered' => $rendered,
|
||||
'tags' => $tags,
|
||||
'replies' => $replies),
|
||||
$options);
|
||||
|
||||
if (!array_key_exists('uri', $options)) {
|
||||
$options['uri'] = $nb->uri;
|
||||
}
|
||||
|
||||
$saved = Notice::saveNew($profile->id,
|
||||
$content,
|
||||
array_key_exists('source', $options) ?
|
||||
$options['source'] : 'web',
|
||||
$options);
|
||||
|
||||
return $saved;
|
||||
}
|
||||
}
|
772
plugins/Bookmark/BookmarkPlugin.php
Normal file
772
plugins/Bookmark/BookmarkPlugin.php
Normal file
@ -0,0 +1,772 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2010, StatusNet, Inc.
|
||||
*
|
||||
* A plugin to enable social-bookmarking functionality
|
||||
*
|
||||
* 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 SocialBookmark
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bookmark plugin main class
|
||||
*
|
||||
* @category Bookmark
|
||||
* @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 BookmarkPlugin extends Plugin
|
||||
{
|
||||
const VERSION = '0.1';
|
||||
const IMPORTDELICIOUS = 'BookmarkPlugin:IMPORTDELICIOUS';
|
||||
|
||||
/**
|
||||
* Authorization for importing delicious bookmarks
|
||||
*
|
||||
* By default, everyone can import bookmarks except silenced people.
|
||||
*
|
||||
* @param Profile $profile Person whose rights to check
|
||||
* @param string $right Right to check; const value
|
||||
* @param boolean &$result Result of the check, writeable
|
||||
*
|
||||
* @return boolean hook value
|
||||
*/
|
||||
|
||||
function onUserRightsCheck($profile, $right, &$result)
|
||||
{
|
||||
if ($right == self::IMPORTDELICIOUS) {
|
||||
$result = !$profile->isSilenced();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Database schema setup
|
||||
*
|
||||
* @see Schema
|
||||
* @see ColumnDef
|
||||
*
|
||||
* @return boolean hook value; true means continue processing, false means stop.
|
||||
*/
|
||||
|
||||
function onCheckSchema()
|
||||
{
|
||||
$schema = Schema::get();
|
||||
|
||||
// For storing user-submitted flags on profiles
|
||||
|
||||
$schema->ensureTable('bookmark',
|
||||
array(new ColumnDef('profile_id',
|
||||
'integer',
|
||||
null,
|
||||
false,
|
||||
'PRI'),
|
||||
new ColumnDef('url',
|
||||
'varchar',
|
||||
255,
|
||||
false,
|
||||
'PRI'),
|
||||
new ColumnDef('title',
|
||||
'varchar',
|
||||
255),
|
||||
new ColumnDef('description',
|
||||
'text'),
|
||||
new ColumnDef('uri',
|
||||
'varchar',
|
||||
255,
|
||||
false,
|
||||
'UNI'),
|
||||
new ColumnDef('url_crc32',
|
||||
'integer unsigned',
|
||||
null,
|
||||
false,
|
||||
'MUL'),
|
||||
new ColumnDef('created',
|
||||
'datetime',
|
||||
null,
|
||||
false,
|
||||
'MUL')));
|
||||
|
||||
try {
|
||||
$schema->createIndex('bookmark',
|
||||
array('profile_id',
|
||||
'url_crc32'),
|
||||
'bookmark_profile_url_idx');
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_ERR, $e->getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* When a notice is deleted, delete the related Bookmark
|
||||
*
|
||||
* @param Notice $notice Notice being deleted
|
||||
*
|
||||
* @return boolean hook value
|
||||
*/
|
||||
|
||||
function onNoticeDeleteRelated($notice)
|
||||
{
|
||||
$nb = Bookmark::getByNotice($notice);
|
||||
|
||||
if (!empty($nb)) {
|
||||
$nb->delete();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the CSS necessary for this plugin
|
||||
*
|
||||
* @param Action $action the action being run
|
||||
*
|
||||
* @return boolean hook value
|
||||
*/
|
||||
|
||||
function onEndShowStyles($action)
|
||||
{
|
||||
$action->cssLink('plugins/Bookmark/bookmark.css');
|
||||
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 'ShowbookmarkAction':
|
||||
case 'NewbookmarkAction':
|
||||
case 'BookmarkpopupAction':
|
||||
case 'NoticebyurlAction':
|
||||
case 'ImportdeliciousAction':
|
||||
include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
|
||||
return false;
|
||||
case 'Bookmark':
|
||||
include_once $dir.'/'.$cls.'.php';
|
||||
return false;
|
||||
case 'BookmarkForm':
|
||||
case 'DeliciousBackupImporter':
|
||||
case 'DeliciousBookmarkImporter':
|
||||
include_once $dir.'/'.strtolower($cls).'.php';
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map URLs to actions
|
||||
*
|
||||
* @param Net_URL_Mapper $m path-to-action mapper
|
||||
*
|
||||
* @return boolean hook value; true means continue processing, false means stop.
|
||||
*/
|
||||
|
||||
function onRouterInitialized($m)
|
||||
{
|
||||
$m->connect('main/bookmark/new',
|
||||
array('action' => 'newbookmark'),
|
||||
array('id' => '[0-9]+'));
|
||||
|
||||
$m->connect('main/bookmark/popup',
|
||||
array('action' => 'bookmarkpopup'));
|
||||
|
||||
$m->connect('main/bookmark/import',
|
||||
array('action' => 'importdelicious'));
|
||||
|
||||
$m->connect('bookmark/:user/:created/:crc32',
|
||||
array('action' => 'showbookmark'),
|
||||
array('user' => '[0-9]+',
|
||||
'created' => '[0-9]{14}',
|
||||
'crc32' => '[0-9a-f]{8}'));
|
||||
|
||||
$m->connect('notice/by-url/:id',
|
||||
array('action' => 'noticebyurl'),
|
||||
array('id' => '[0-9]+'));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the HTML for a bookmark in a list
|
||||
*
|
||||
* @param NoticeListItem $nli The list item being shown.
|
||||
*
|
||||
* @return boolean hook value
|
||||
*/
|
||||
|
||||
function onStartShowNoticeItem($nli)
|
||||
{
|
||||
$nb = Bookmark::getByNotice($nli->notice);
|
||||
|
||||
if (!empty($nb)) {
|
||||
|
||||
$out = $nli->out;
|
||||
$notice = $nli->notice;
|
||||
$profile = $nli->profile;
|
||||
|
||||
$atts = $notice->attachments();
|
||||
|
||||
if (count($atts) < 1) {
|
||||
// Something wrong; let default code deal with it.
|
||||
return true;
|
||||
}
|
||||
|
||||
$att = $atts[0];
|
||||
|
||||
// XXX: only show the bookmark URL for non-single-page stuff
|
||||
|
||||
if ($out instanceof ShowbookmarkAction) {
|
||||
} else {
|
||||
$out->elementStart('h3');
|
||||
$out->element('a',
|
||||
array('href' => $att->url),
|
||||
$nb->title);
|
||||
$out->elementEnd('h3');
|
||||
|
||||
$countUrl = common_local_url('noticebyurl',
|
||||
array('id' => $att->id));
|
||||
|
||||
$out->element('a', array('class' => 'bookmark_notice_count',
|
||||
'href' => $countUrl),
|
||||
$att->noticeCount());
|
||||
}
|
||||
|
||||
$out->elementStart('ul', array('class' => 'bookmark_tags'));
|
||||
|
||||
// Replies look like "for:" tags
|
||||
|
||||
$replies = $nli->notice->getReplies();
|
||||
|
||||
if (!empty($replies)) {
|
||||
foreach ($replies as $reply) {
|
||||
$other = Profile::staticGet('id', $reply);
|
||||
$out->elementStart('li');
|
||||
$out->element('a', array('rel' => 'tag',
|
||||
'href' => $other->profileurl,
|
||||
'title' => $other->getBestName()),
|
||||
sprintf('for:%s', $other->nickname));
|
||||
$out->elementEnd('li');
|
||||
$out->text(' ');
|
||||
}
|
||||
}
|
||||
|
||||
$tags = $nli->notice->getTags();
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
$out->elementStart('li');
|
||||
$out->element('a',
|
||||
array('rel' => 'tag',
|
||||
'href' => Notice_tag::url($tag)),
|
||||
$tag);
|
||||
$out->elementEnd('li');
|
||||
$out->text(' ');
|
||||
}
|
||||
|
||||
$out->elementEnd('ul');
|
||||
|
||||
$out->element('p',
|
||||
array('class' => 'bookmark_description'),
|
||||
$nb->description);
|
||||
|
||||
if (common_config('attachments', 'show_thumbs')) {
|
||||
$al = new InlineAttachmentList($notice, $out);
|
||||
$al->show();
|
||||
}
|
||||
|
||||
$out->elementStart('p', array('style' => 'float: left'));
|
||||
|
||||
$avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
|
||||
|
||||
$out->element('img', array('src' => ($avatar) ?
|
||||
$avatar->displayUrl() :
|
||||
Avatar::defaultImage(AVATAR_MINI_SIZE),
|
||||
'class' => 'avatar photo bookmark_avatar',
|
||||
'width' => AVATAR_MINI_SIZE,
|
||||
'height' => AVATAR_MINI_SIZE,
|
||||
'alt' => $profile->getBestName()));
|
||||
$out->raw(' ');
|
||||
$out->element('a', array('href' => $profile->profileurl,
|
||||
'title' => $profile->getBestName()),
|
||||
$profile->nickname);
|
||||
|
||||
$nli->showNoticeLink();
|
||||
$nli->showNoticeSource();
|
||||
$nli->showNoticeLocation();
|
||||
$nli->showContext();
|
||||
$nli->showRepeat();
|
||||
|
||||
$out->elementEnd('p');
|
||||
|
||||
$nli->showNoticeOptions();
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a notice as a Bookmark object
|
||||
*
|
||||
* @param Notice $notice Notice to render
|
||||
* @param ActivityObject &$object Empty object to fill
|
||||
*
|
||||
* @return boolean hook value
|
||||
*/
|
||||
|
||||
function onStartActivityObjectFromNotice($notice, &$object)
|
||||
{
|
||||
common_log(LOG_INFO,
|
||||
"Checking {$notice->uri} to see if it's a bookmark.");
|
||||
|
||||
$nb = Bookmark::getByNotice($notice);
|
||||
|
||||
if (!empty($nb)) {
|
||||
|
||||
common_log(LOG_INFO,
|
||||
"Formatting notice {$notice->uri} as a bookmark.");
|
||||
|
||||
$object->id = $notice->uri;
|
||||
$object->type = ActivityObject::BOOKMARK;
|
||||
$object->title = $nb->title;
|
||||
$object->summary = $nb->description;
|
||||
$object->link = $notice->bestUrl();
|
||||
|
||||
// Attributes of the URL
|
||||
|
||||
$attachments = $notice->attachments();
|
||||
|
||||
if (count($attachments) != 1) {
|
||||
throw new ServerException(_('Bookmark notice with the '.
|
||||
'wrong number of attachments.'));
|
||||
}
|
||||
|
||||
$target = $attachments[0];
|
||||
|
||||
$attrs = array('rel' => 'related',
|
||||
'href' => $target->url);
|
||||
|
||||
if (!empty($target->title)) {
|
||||
$attrs['title'] = $target->title;
|
||||
}
|
||||
|
||||
$object->extra[] = array('link', $attrs, null);
|
||||
|
||||
// Attributes of the thumbnail, if any
|
||||
|
||||
$thumbnail = $target->getThumbnail();
|
||||
|
||||
if (!empty($thumbnail)) {
|
||||
$tattrs = array('rel' => 'preview',
|
||||
'href' => $thumbnail->url);
|
||||
|
||||
if (!empty($thumbnail->width)) {
|
||||
$tattrs['media:width'] = $thumbnail->width;
|
||||
}
|
||||
|
||||
if (!empty($thumbnail->height)) {
|
||||
$tattrs['media:height'] = $thumbnail->height;
|
||||
}
|
||||
|
||||
$object->extra[] = array('link', $attrs, null);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add our two queue handlers to the queue manager
|
||||
*
|
||||
* @param QueueManager $qm current queue manager
|
||||
*
|
||||
* @return boolean hook value
|
||||
*/
|
||||
|
||||
function onEndInitializeQueueManager($qm)
|
||||
{
|
||||
$qm->connect('dlcsback', 'DeliciousBackupImporter');
|
||||
$qm->connect('dlcsbkmk', 'DeliciousBookmarkImporter');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin version data
|
||||
*
|
||||
* @param array &$versions array of version data
|
||||
*
|
||||
* @return value
|
||||
*/
|
||||
|
||||
function onPluginVersion(&$versions)
|
||||
{
|
||||
$versions[] = array('name' => 'Sample',
|
||||
'version' => self::VERSION,
|
||||
'author' => 'Evan Prodromou',
|
||||
'homepage' => 'http://status.net/wiki/Plugin:Bookmark',
|
||||
'rawdescription' =>
|
||||
_m('Simple extension for supporting bookmarks.'));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load our document if requested
|
||||
*
|
||||
* @param string &$title Title to fetch
|
||||
* @param string &$output HTML to output
|
||||
*
|
||||
* @return boolean hook value
|
||||
*/
|
||||
|
||||
function onStartLoadDoc(&$title, &$output)
|
||||
{
|
||||
if ($title == 'bookmarklet') {
|
||||
$filename = INSTALLDIR.'/plugins/Bookmark/bookmarklet';
|
||||
|
||||
$c = file_get_contents($filename);
|
||||
$output = common_markup_to_html($c);
|
||||
return false; // success!
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a posted bookmark from PuSH
|
||||
*
|
||||
* @param Activity $activity activity to handle
|
||||
* @param Ostatus_profile $oprofile Profile for the feed
|
||||
*
|
||||
* @return boolean hook value
|
||||
*/
|
||||
|
||||
function onStartHandleFeedEntryWithProfile($activity, $oprofile)
|
||||
{
|
||||
common_log(LOG_INFO, "BookmarkPlugin called for new feed entry.");
|
||||
|
||||
if (self::_isPostBookmark($activity)) {
|
||||
|
||||
common_log(LOG_INFO,
|
||||
"Importing activity {$activity->id} as a bookmark.");
|
||||
|
||||
$author = $oprofile->checkAuthorship($activity);
|
||||
|
||||
if (empty($author)) {
|
||||
throw new ClientException(_('Can\'t get author for activity.'));
|
||||
}
|
||||
|
||||
self::_postRemoteBookmark($author,
|
||||
$activity);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a posted bookmark from Salmon
|
||||
*
|
||||
* @param Activity $activity activity to handle
|
||||
* @param mixed $target user or group targeted
|
||||
*
|
||||
* @return boolean hook value
|
||||
*/
|
||||
|
||||
function onStartHandleSalmonTarget($activity, $target)
|
||||
{
|
||||
if (self::_isPostBookmark($activity)) {
|
||||
|
||||
$this->log(LOG_INFO, "Checking {$activity->id} as a valid Salmon slap.");
|
||||
|
||||
if ($target instanceof User_group) {
|
||||
$uri = $target->getUri();
|
||||
if (!in_array($uri, $activity->context->attention)) {
|
||||
throw new ClientException(_("Bookmark not posted ".
|
||||
"to this group."));
|
||||
}
|
||||
} else if ($target instanceof User) {
|
||||
$uri = $target->uri;
|
||||
$original = null;
|
||||
if (!empty($activity->context->replyToID)) {
|
||||
$original = Notice::staticGet('uri',
|
||||
$activity->context->replyToID);
|
||||
}
|
||||
if (!in_array($uri, $activity->context->attention) &&
|
||||
(empty($original) ||
|
||||
$original->profile_id != $target->id)) {
|
||||
throw new ClientException(_("Bookmark not posted ".
|
||||
"to this user."));
|
||||
}
|
||||
} else {
|
||||
throw new ServerException(_("Don't know how to handle ".
|
||||
"this kind of target."));
|
||||
}
|
||||
|
||||
$author = Ostatus_profile::ensureActivityObjectProfile($activity->actor);
|
||||
|
||||
self::_postRemoteBookmark($author,
|
||||
$activity);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle bookmark posted via AtomPub
|
||||
*
|
||||
* @param Activity &$activity Activity that was posted
|
||||
* @param User $user User that posted it
|
||||
* @param Notice &$notice Resulting notice
|
||||
*
|
||||
* @return boolean hook value
|
||||
*/
|
||||
|
||||
function onStartAtomPubNewActivity(&$activity, $user, &$notice)
|
||||
{
|
||||
if (self::_isPostBookmark($activity)) {
|
||||
$options = array('source' => 'atompub');
|
||||
$notice = self::_postBookmark($user->getProfile(),
|
||||
$activity,
|
||||
$options);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle bookmark imported from a backup file
|
||||
*
|
||||
* @param User $user User to import for
|
||||
* @param ActivityObject $author Original author per import file
|
||||
* @param Activity $activity Activity to import
|
||||
* @param boolean $trusted Is this a trusted user?
|
||||
* @param boolean &$done Is this done (success or unrecoverable error)
|
||||
*
|
||||
* @return boolean hook value
|
||||
*/
|
||||
|
||||
function onStartImportActivity($user, $author, $activity, $trusted, &$done)
|
||||
{
|
||||
if (self::_isPostBookmark($activity)) {
|
||||
|
||||
$bookmark = $activity->objects[0];
|
||||
|
||||
$this->log(LOG_INFO,
|
||||
'Importing Bookmark ' . $bookmark->id .
|
||||
' for user ' . $user->nickname);
|
||||
|
||||
$options = array('uri' => $bookmark->id,
|
||||
'url' => $bookmark->link,
|
||||
'source' => 'restore');
|
||||
|
||||
$saved = self::_postBookmark($user->getProfile(), $activity, $options);
|
||||
|
||||
if (!empty($saved)) {
|
||||
$done = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a link to our delicious import page on profile settings form
|
||||
*
|
||||
* @param Action $action Profile settings action being shown
|
||||
*
|
||||
* @return boolean hook value
|
||||
*/
|
||||
|
||||
function onEndProfileSettingsActions($action)
|
||||
{
|
||||
$user = common_current_user();
|
||||
|
||||
if (!empty($user) && $user->hasRight(self::IMPORTDELICIOUS)) {
|
||||
$action->elementStart('li');
|
||||
$action->element('a',
|
||||
array('href' => common_local_url('importdelicious')),
|
||||
_('Import del.icio.us bookmarks'));
|
||||
$action->elementEnd('li');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a remote bookmark (from Salmon or PuSH)
|
||||
*
|
||||
* @param Ostatus_profile $author Author of the bookmark
|
||||
* @param Activity $activity Activity to save
|
||||
*
|
||||
* @return Notice resulting notice.
|
||||
*/
|
||||
|
||||
static private function _postRemoteBookmark(Ostatus_profile $author,
|
||||
Activity $activity)
|
||||
{
|
||||
$bookmark = $activity->objects[0];
|
||||
|
||||
$options = array('uri' => $bookmark->id,
|
||||
'url' => $bookmark->link,
|
||||
'is_local' => Notice::REMOTE_OMB,
|
||||
'source' => 'ostatus');
|
||||
|
||||
return self::_postBookmark($author->localProfile(), $activity, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a bookmark from an activity
|
||||
*
|
||||
* @param Profile $profile Profile to use as author
|
||||
* @param Activity $activity Activity to save
|
||||
* @param array $options Options to pass to bookmark-saving code
|
||||
*
|
||||
* @return Notice resulting notice
|
||||
*/
|
||||
|
||||
static private function _postBookmark(Profile $profile,
|
||||
Activity $activity,
|
||||
$options=array())
|
||||
{
|
||||
$bookmark = $activity->objects[0];
|
||||
|
||||
$relLinkEls = ActivityUtils::getLinks($bookmark->element, 'related');
|
||||
|
||||
if (count($relLinkEls) < 1) {
|
||||
throw new ClientException(_('Expected exactly 1 link '.
|
||||
'rel=related in a Bookmark.'));
|
||||
}
|
||||
|
||||
if (count($relLinkEls) > 1) {
|
||||
common_log(LOG_WARNING,
|
||||
"Got too many link rel=related in a Bookmark.");
|
||||
}
|
||||
|
||||
$linkEl = $relLinkEls[0];
|
||||
|
||||
$url = $linkEl->getAttribute('href');
|
||||
|
||||
$tags = array();
|
||||
|
||||
foreach ($activity->categories as $category) {
|
||||
$tags[] = common_canonical_tag($category->term);
|
||||
}
|
||||
|
||||
if (!empty($activity->time)) {
|
||||
$options['created'] = common_sql_date($activity->time);
|
||||
}
|
||||
|
||||
// Fill in location if available
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
$replies = $activity->context->attention;
|
||||
|
||||
$options['groups'] = array();
|
||||
$options['replies'] = array();
|
||||
|
||||
foreach ($replies as $replyURI) {
|
||||
$other = Profile::fromURI($replyURI);
|
||||
if (!empty($other)) {
|
||||
$options['replies'][] = $replyURI;
|
||||
} else {
|
||||
$group = User_group::staticGet('uri', $replyURI);
|
||||
if (!empty($group)) {
|
||||
$options['groups'][] = $replyURI;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
return Bookmark::saveNew($profile,
|
||||
$bookmark->title,
|
||||
$url,
|
||||
$tags,
|
||||
$bookmark->summary,
|
||||
$options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if an activity represents posting a bookmark
|
||||
*
|
||||
* @param Activity $activity Activity to test
|
||||
*
|
||||
* @return true if it's a Post of a Bookmark, else false
|
||||
*/
|
||||
|
||||
static private function _isPostBookmark($activity)
|
||||
{
|
||||
return ($activity->verb == ActivityVerb::POST &&
|
||||
$activity->objects[0]->type == ActivityObject::BOOKMARK);
|
||||
}
|
||||
}
|
||||
|
4
plugins/Bookmark/bookmark.css
Normal file
4
plugins/Bookmark/bookmark.css
Normal file
@ -0,0 +1,4 @@
|
||||
.bookmark_tags li { display: inline; }
|
||||
.bookmark_mentions li { display: inline; }
|
||||
.bookmark_avatar { float: left }
|
||||
.bookmark_notice_count { float: right }
|
164
plugins/Bookmark/bookmarkform.php
Normal file
164
plugins/Bookmark/bookmarkform.php
Normal file
@ -0,0 +1,164 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2010, StatusNet, Inc.
|
||||
*
|
||||
* Form for adding a new bookmark
|
||||
*
|
||||
* 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 Bookmark
|
||||
* @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')) {
|
||||
// This check helps protect against security problems;
|
||||
// your code file can't be executed directly from the web.
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form to add a new bookmark
|
||||
*
|
||||
* @category Bookmark
|
||||
* @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 BookmarkForm extends Form
|
||||
{
|
||||
private $_title = null;
|
||||
private $_url = null;
|
||||
private $_tags = null;
|
||||
private $_description = null;
|
||||
|
||||
/**
|
||||
* Construct a bookmark form
|
||||
*
|
||||
* @param HTMLOutputter $out output channel
|
||||
* @param string $title Title of the bookmark
|
||||
* @param string $url URL of the bookmark
|
||||
* @param string $tags Tags to show
|
||||
* @param string $description Description of the bookmark
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function __construct($out=null, $title=null, $url=null, $tags=null,
|
||||
$description=null)
|
||||
{
|
||||
parent::__construct($out);
|
||||
|
||||
$this->_title = $title;
|
||||
$this->_url = $url;
|
||||
$this->_tags = $tags;
|
||||
$this->_description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the form
|
||||
*
|
||||
* @return int ID of the form
|
||||
*/
|
||||
|
||||
function id()
|
||||
{
|
||||
return 'form_new_bookmark';
|
||||
}
|
||||
|
||||
/**
|
||||
* class of the form
|
||||
*
|
||||
* @return string class of the form
|
||||
*/
|
||||
|
||||
function formClass()
|
||||
{
|
||||
return 'form_settings';
|
||||
}
|
||||
|
||||
/**
|
||||
* Action of the form
|
||||
*
|
||||
* @return string URL of the action
|
||||
*/
|
||||
|
||||
function action()
|
||||
{
|
||||
return common_local_url('newbookmark');
|
||||
}
|
||||
|
||||
/**
|
||||
* Data elements of the form
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function formData()
|
||||
{
|
||||
$this->out->elementStart('fieldset', array('id' => 'new_bookmark_data'));
|
||||
$this->out->elementStart('ul', 'form_data');
|
||||
|
||||
$this->li();
|
||||
$this->out->input('title',
|
||||
_('Title'),
|
||||
$this->_title,
|
||||
_('Title of the bookmark'));
|
||||
$this->unli();
|
||||
|
||||
$this->li();
|
||||
$this->out->input('url',
|
||||
_('URL'),
|
||||
$this->_url,
|
||||
_('URL to bookmark'));
|
||||
$this->unli();
|
||||
|
||||
$this->li();
|
||||
$this->out->input('tags',
|
||||
_('Tags'),
|
||||
$this->_tags,
|
||||
_('Comma- or space-separated list of tags'));
|
||||
$this->unli();
|
||||
|
||||
$this->li();
|
||||
$this->out->input('description',
|
||||
_('Description'),
|
||||
$this->_description,
|
||||
_('Description of the URL'));
|
||||
$this->unli();
|
||||
|
||||
$this->out->elementEnd('ul');
|
||||
$this->out->elementEnd('fieldset');
|
||||
}
|
||||
|
||||
/**
|
||||
* Action elements
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function formActions()
|
||||
{
|
||||
$this->out->submit('submit', _m('BUTTON', 'Save'));
|
||||
}
|
||||
}
|
9
plugins/Bookmark/bookmarklet
Normal file
9
plugins/Bookmark/bookmarklet
Normal file
@ -0,0 +1,9 @@
|
||||
<!-- Copyright 2008-2010 StatusNet Inc. and contributors. -->
|
||||
<!-- Document licensed under Creative Commons Attribution 3.0 Unported. See -->
|
||||
<!-- http://creativecommons.org/licenses/by/3.0/ for details. -->
|
||||
|
||||
A bookmarklet is a small piece of javascript code used as a bookmark. This one will let you post to %%site.name%% simply by selecting some text on a page and pressing the bookmarklet.
|
||||
|
||||
Drag-and-drop the following link to your bookmarks bar or right-click it and add it to your browser favorites to keep it handy.
|
||||
|
||||
<a href="javascript:(function(){var%20d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,s=(e?e():(k)?k():(x?x.createRange().text:0)),f='http://%%site.server%%/%%site.path%%/index.php?action=bookmarkpopup',l=d.location,e=encodeURIComponent,g=f+'&title='+((e(s))?e(s):e(document.title))+'&url='+e(l.href);function%20a(){if(!w.open(g,'t','toolbar=0,resizable=0,scrollbars=1,status=1,width=650,height=470')){l.href=g;}}a();})()">Bookmark on %%site.name%%</a>
|
23
plugins/Bookmark/bookmarkpopup.js
Normal file
23
plugins/Bookmark/bookmarkpopup.js
Normal file
@ -0,0 +1,23 @@
|
||||
$(document).ready(
|
||||
function() {
|
||||
var form = $('#form_new_bookmark');
|
||||
form.append('<input type="hidden" name="ajax" value="1"/>');
|
||||
form.ajaxForm({dataType: 'xml',
|
||||
timeout: '60000',
|
||||
beforeSend: function(formData) {
|
||||
form.addClass('processing');
|
||||
form.find('#submit').addClass('disabled');
|
||||
},
|
||||
error: function (xhr, textStatus, errorThrown) {
|
||||
form.removeClass('processing');
|
||||
form.find('#submit').removeClass('disabled');
|
||||
self.close();
|
||||
},
|
||||
success: function(data, textStatus) {
|
||||
form.removeClass('processing');
|
||||
form.find('#submit').removeClass('disabled');
|
||||
self.close();
|
||||
}});
|
||||
|
||||
}
|
||||
);
|
112
plugins/Bookmark/bookmarkpopup.php
Normal file
112
plugins/Bookmark/bookmarkpopup.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* Post a new bookmark in a popup window
|
||||
*
|
||||
* 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 Bookmark
|
||||
* @package StatusNet
|
||||
* @author Sarven Capadisli <csarven@status.net>
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @copyright 2008-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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action for posting a new bookmark
|
||||
*
|
||||
* @category Bookmark
|
||||
* @package StatusNet
|
||||
* @author Sarven Capadisli <csarven@status.net>
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
|
||||
* @link http://status.net/
|
||||
*/
|
||||
class BookmarkpopupAction extends NewbookmarkAction
|
||||
{
|
||||
/**
|
||||
* Show the title section of the window
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function showTitle()
|
||||
{
|
||||
// TRANS: Title for mini-posting window loaded from bookmarklet.
|
||||
// TRANS: %s is the StatusNet site name.
|
||||
$this->element('title',
|
||||
null, sprintf(_('Bookmark on %s'),
|
||||
common_config('site', 'name')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the header section of the page
|
||||
*
|
||||
* Shows a stub page and the bookmark form.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function showHeader()
|
||||
{
|
||||
$this->elementStart('div', array('id' => 'header'));
|
||||
$this->elementStart('address');
|
||||
$this->element('a', array('class' => 'url',
|
||||
'href' => common_local_url('public')),
|
||||
'');
|
||||
$this->elementEnd('address');
|
||||
if (common_logged_in()) {
|
||||
$form = new BookmarkForm($this,
|
||||
$this->title,
|
||||
$this->url);
|
||||
$form->show();
|
||||
}
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the core section of the page
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function showCore()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the footer section of the page
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function showFooter()
|
||||
{
|
||||
}
|
||||
|
||||
function showScripts()
|
||||
{
|
||||
parent::showScripts();
|
||||
$this->script(common_path('plugins/Bookmark/bookmarkpopup.js'));
|
||||
}
|
||||
}
|
196
plugins/Bookmark/deliciousbackupimporter.php
Normal file
196
plugins/Bookmark/deliciousbackupimporter.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2010, StatusNet, Inc.
|
||||
*
|
||||
* Importer class for Delicious.com backups
|
||||
*
|
||||
* 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 Bookmark
|
||||
* @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')) {
|
||||
// This check helps protect against security problems;
|
||||
// your code file can't be executed directly from the web.
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Importer class for Delicious bookmarks
|
||||
*
|
||||
* @category Bookmark
|
||||
* @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 DeliciousBackupImporter extends QueueHandler
|
||||
{
|
||||
/**
|
||||
* Transport of the importer
|
||||
*
|
||||
* @return string transport string
|
||||
*/
|
||||
|
||||
function transport()
|
||||
{
|
||||
return 'dlcsback';
|
||||
}
|
||||
|
||||
/**
|
||||
* Import an in-memory bookmark list to a user's account
|
||||
*
|
||||
* Take a delicious.com backup file (same as Netscape bookmarks.html)
|
||||
* and import to StatusNet as Bookmark activities.
|
||||
*
|
||||
* The document format is terrible. It consists of a <dl> with
|
||||
* a bunch of <dt>'s, occasionally with <dd>'s.
|
||||
* There are sometimes <p>'s lost inside.
|
||||
*
|
||||
* @param array $data pair of user, text
|
||||
*
|
||||
* @return boolean success value
|
||||
*/
|
||||
|
||||
function handle($data)
|
||||
{
|
||||
list($user, $body) = $data;
|
||||
|
||||
$doc = $this->importHTML($body);
|
||||
|
||||
$dls = $doc->getElementsByTagName('dl');
|
||||
|
||||
if ($dls->length != 1) {
|
||||
throw new ClientException(_("Bad import file."));
|
||||
}
|
||||
|
||||
$dl = $dls->item(0);
|
||||
|
||||
$children = $dl->childNodes;
|
||||
|
||||
$dt = null;
|
||||
|
||||
for ($i = 0; $i < $children->length; $i++) {
|
||||
try {
|
||||
$child = $children->item($i);
|
||||
if ($child->nodeType != XML_ELEMENT_NODE) {
|
||||
continue;
|
||||
}
|
||||
switch (strtolower($child->tagName)) {
|
||||
case 'dt':
|
||||
if (!empty($dt)) {
|
||||
// No DD provided
|
||||
$this->importBookmark($user, $dt);
|
||||
$dt = null;
|
||||
}
|
||||
$dt = $child;
|
||||
break;
|
||||
case 'dd':
|
||||
$dd = $child;
|
||||
|
||||
$saved = $this->importBookmark($user, $dt, $dd);
|
||||
|
||||
$dt = null;
|
||||
$dd = null;
|
||||
case 'p':
|
||||
common_log(LOG_INFO, 'Skipping the <p> in the <dl>.');
|
||||
break;
|
||||
default:
|
||||
common_log(LOG_WARNING,
|
||||
"Unexpected element $child->tagName ".
|
||||
" found in import.");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_ERR, $e->getMessage());
|
||||
$dt = $dd = null;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a single bookmark
|
||||
*
|
||||
* Takes a <dt>/<dd> pair. The <dt> has a single
|
||||
* <a> in it with some non-standard attributes.
|
||||
*
|
||||
* A <dt><dt><dd> sequence will appear as a <dt> with
|
||||
* anothe <dt> as a child. We handle this case recursively.
|
||||
*
|
||||
* @param User $user User to import data as
|
||||
* @param DOMElement $dt <dt> element
|
||||
* @param DOMElement $dd <dd> element
|
||||
*
|
||||
* @return Notice imported notice
|
||||
*/
|
||||
|
||||
function importBookmark($user, $dt, $dd = null)
|
||||
{
|
||||
// We have to go squirrelling around in the child nodes
|
||||
// on the off chance that we've received another <dt>
|
||||
// as a child.
|
||||
|
||||
for ($i = 0; $i < $dt->childNodes->length; $i++) {
|
||||
$child = $dt->childNodes->item($i);
|
||||
if ($child->nodeType == XML_ELEMENT_NODE) {
|
||||
if ($child->tagName == 'dt' && !is_null($dd)) {
|
||||
$this->importBookmark($user, $dt);
|
||||
$this->importBookmark($user, $child, $dd);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$qm = QueueManager::get();
|
||||
|
||||
$qm->enqueue(array($user, $dt, $dd), 'dlcsbkmk');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse some HTML
|
||||
*
|
||||
* Hides the errors that the dom parser returns
|
||||
*
|
||||
* @param string $body Data to import
|
||||
*
|
||||
* @return DOMDocument parsed document
|
||||
*/
|
||||
|
||||
function importHTML($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) {
|
||||
return $dom;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
109
plugins/Bookmark/deliciousbookmarkimporter.php
Normal file
109
plugins/Bookmark/deliciousbookmarkimporter.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2010, StatusNet, Inc.
|
||||
*
|
||||
* Importer class for Delicious.com bookmarks
|
||||
*
|
||||
* 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 Bookmark
|
||||
* @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')) {
|
||||
// This check helps protect against security problems;
|
||||
// your code file can't be executed directly from the web.
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Importer class for Delicious bookmarks
|
||||
*
|
||||
* @category Bookmark
|
||||
* @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 DeliciousBookmarkImporter extends QueueHandler
|
||||
{
|
||||
/**
|
||||
* Return the transport for this queue handler
|
||||
*
|
||||
* @return string 'dlcsbkmk'
|
||||
*/
|
||||
|
||||
function transport()
|
||||
{
|
||||
return 'dlcsbkmk';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the data
|
||||
*
|
||||
* @param array $data array of user, dt, dd
|
||||
*
|
||||
* @return boolean success value
|
||||
*/
|
||||
|
||||
function handle($data)
|
||||
{
|
||||
list($user, $dt, $dd) = $data;
|
||||
|
||||
$as = $dt->getElementsByTagName('a');
|
||||
|
||||
if ($as->length == 0) {
|
||||
throw new ClientException(_("No <A> tag in a <DT>."));
|
||||
}
|
||||
|
||||
$a = $as->item(0);
|
||||
|
||||
$private = $a->getAttribute('private');
|
||||
|
||||
if ($private != 0) {
|
||||
throw new ClientException(_('Skipping private bookmark.'));
|
||||
}
|
||||
|
||||
if (!empty($dd)) {
|
||||
$description = $dd->nodeValue;
|
||||
} else {
|
||||
$description = null;
|
||||
}
|
||||
|
||||
$title = $a->nodeValue;
|
||||
$url = $a->getAttribute('href');
|
||||
$tags = $a->getAttribute('tags');
|
||||
$addDate = $a->getAttribute('add_date');
|
||||
$created = common_sql_date(intval($addDate));
|
||||
|
||||
$saved = Bookmark::saveNew($user->getProfile(),
|
||||
$title,
|
||||
$url,
|
||||
$tags,
|
||||
$description,
|
||||
array('created' => $created,
|
||||
'distribute' => false));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
96
plugins/Bookmark/importbookmarks.php
Normal file
96
plugins/Bookmark/importbookmarks.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2010 StatusNet, Inc.
|
||||
*
|
||||
* Import a bookmarks file as notices
|
||||
*
|
||||
* 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 Bookmark
|
||||
* @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/
|
||||
*/
|
||||
|
||||
define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..'));
|
||||
|
||||
$shortoptions = 'i:n:f:';
|
||||
$longoptions = array('id=', 'nickname=', 'file=');
|
||||
|
||||
$helptext = <<<END_OF_IMPORTBOOKMARKS_HELP
|
||||
importbookmarks.php [options]
|
||||
Restore a backed-up Delicious.com bookmark file
|
||||
|
||||
-i --id ID of user to import bookmarks for
|
||||
-n --nickname nickname of the user to import for
|
||||
-f --file file to read from (STDIN by default)
|
||||
END_OF_IMPORTBOOKMARKS_HELP;
|
||||
|
||||
require_once INSTALLDIR.'/scripts/commandline.inc';
|
||||
|
||||
/**
|
||||
* Get the bookmarks file as a string
|
||||
*
|
||||
* Uses the -f or --file parameter to open and read a
|
||||
* a bookmarks file
|
||||
*
|
||||
* @return string Contents of the file
|
||||
*/
|
||||
|
||||
function getBookmarksFile()
|
||||
{
|
||||
$filename = get_option_value('f', 'file');
|
||||
|
||||
if (empty($filename)) {
|
||||
show_help();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!file_exists($filename)) {
|
||||
throw new Exception("No such file '$filename'.");
|
||||
}
|
||||
|
||||
if (!is_file($filename)) {
|
||||
throw new Exception("Not a regular file: '$filename'.");
|
||||
}
|
||||
|
||||
if (!is_readable($filename)) {
|
||||
throw new Exception("File '$filename' not readable.");
|
||||
}
|
||||
|
||||
// TRANS: %s is the filename that contains a backup for a user.
|
||||
printfv(_("Getting backup from file '%s'.")."\n", $filename);
|
||||
|
||||
$html = file_get_contents($filename);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
try {
|
||||
$user = getUser();
|
||||
$html = getBookmarksFile();
|
||||
|
||||
$qm = QueueManager::get();
|
||||
|
||||
$qm->enqueue(array($user, $html), 'dlcsback');
|
||||
|
||||
} catch (Exception $e) {
|
||||
print $e->getMessage()."\n";
|
||||
exit(1);
|
||||
}
|
336
plugins/Bookmark/importdelicious.php
Normal file
336
plugins/Bookmark/importdelicious.php
Normal file
@ -0,0 +1,336 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2010, StatusNet, Inc.
|
||||
*
|
||||
* Import del.icio.us bookmarks backups
|
||||
*
|
||||
* 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 Bookmark
|
||||
* @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')) {
|
||||
// This check helps protect against security problems;
|
||||
// your code file can't be executed directly from the web.
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* UI for importing del.icio.us bookmark backups
|
||||
*
|
||||
* @category Bookmark
|
||||
* @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 ImportdeliciousAction extends Action
|
||||
{
|
||||
protected $success = false;
|
||||
|
||||
/**
|
||||
* Return the title of the page
|
||||
*
|
||||
* @return string page title
|
||||
*/
|
||||
|
||||
function title()
|
||||
{
|
||||
return _("Import del.icio.us bookmarks");
|
||||
}
|
||||
|
||||
/**
|
||||
* For initializing members of the class.
|
||||
*
|
||||
* @param array $argarray misc. arguments
|
||||
*
|
||||
* @return boolean true
|
||||
*/
|
||||
|
||||
function prepare($argarray)
|
||||
{
|
||||
parent::prepare($argarray);
|
||||
|
||||
$cur = common_current_user();
|
||||
|
||||
if (empty($cur)) {
|
||||
throw new ClientException(_('Only logged-in users can '.
|
||||
'import del.icio.us backups.'),
|
||||
403);
|
||||
}
|
||||
|
||||
if (!$cur->hasRight(BookmarkPlugin::IMPORTDELICIOUS)) {
|
||||
throw new ClientException(_('You may not restore your account.'), 403);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler method
|
||||
*
|
||||
* @param array $argarray is ignored since it's now passed in in prepare()
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function handle($argarray=null)
|
||||
{
|
||||
parent::handle($argarray);
|
||||
|
||||
if ($this->isPost()) {
|
||||
$this->importDelicious();
|
||||
} else {
|
||||
$this->showPage();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a file for importation
|
||||
*
|
||||
* Uses the DeliciousBackupImporter class; may take a long time!
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function importDelicious()
|
||||
{
|
||||
$this->checkSessionToken();
|
||||
|
||||
if (!isset($_FILES[ImportDeliciousForm::FILEINPUT]['error'])) {
|
||||
throw new ClientException(_('No uploaded file.'));
|
||||
}
|
||||
|
||||
switch ($_FILES[ImportDeliciousForm::FILEINPUT]['error']) {
|
||||
case UPLOAD_ERR_OK: // success, jump out
|
||||
break;
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
// TRANS: Client exception thrown when an uploaded file is too large.
|
||||
throw new ClientException(_('The uploaded file exceeds the ' .
|
||||
'upload_max_filesize directive in php.ini.'));
|
||||
return;
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
throw new ClientException(
|
||||
// TRANS: Client exception.
|
||||
_('The uploaded file exceeds the MAX_FILE_SIZE directive' .
|
||||
' that was specified in the HTML form.'));
|
||||
return;
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
@unlink($_FILES[ImportDeliciousForm::FILEINPUT]['tmp_name']);
|
||||
// TRANS: Client exception.
|
||||
throw new ClientException(_('The uploaded file was only' .
|
||||
' partially uploaded.'));
|
||||
return;
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
// No file; probably just a non-AJAX submission.
|
||||
throw new ClientException(_('No uploaded file.'));
|
||||
return;
|
||||
case UPLOAD_ERR_NO_TMP_DIR:
|
||||
// TRANS: Client exception thrown when a temporary folder is not present
|
||||
throw new ClientException(_('Missing a temporary folder.'));
|
||||
return;
|
||||
case UPLOAD_ERR_CANT_WRITE:
|
||||
// TRANS: Client exception thrown when writing to disk is not possible
|
||||
throw new ClientException(_('Failed to write file to disk.'));
|
||||
return;
|
||||
case UPLOAD_ERR_EXTENSION:
|
||||
// TRANS: Client exception thrown when a file upload has been stopped
|
||||
throw new ClientException(_('File upload stopped by extension.'));
|
||||
return;
|
||||
default:
|
||||
common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " .
|
||||
$_FILES[ImportDeliciousForm::FILEINPUT]['error']);
|
||||
// TRANS: Client exception thrown when a file upload operation has failed
|
||||
throw new ClientException(_('System error uploading file.'));
|
||||
return;
|
||||
}
|
||||
|
||||
$filename = $_FILES[ImportDeliciousForm::FILEINPUT]['tmp_name'];
|
||||
|
||||
try {
|
||||
if (!file_exists($filename)) {
|
||||
throw new ServerException("No such file '$filename'.");
|
||||
}
|
||||
|
||||
if (!is_file($filename)) {
|
||||
throw new ServerException("Not a regular file: '$filename'.");
|
||||
}
|
||||
|
||||
if (!is_readable($filename)) {
|
||||
throw new ServerException("File '$filename' not readable.");
|
||||
}
|
||||
|
||||
common_debug(sprintf(_("Getting backup from file '%s'."), $filename));
|
||||
|
||||
$html = file_get_contents($filename);
|
||||
|
||||
// Enqueue for processing.
|
||||
|
||||
$qm = QueueManager::get();
|
||||
$qm->enqueue(array(common_current_user(), $html), 'dlcsback');
|
||||
|
||||
$this->success = true;
|
||||
|
||||
$this->showPage();
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Delete the file and re-throw
|
||||
@unlink($_FILES[ImportDeliciousForm::FILEINPUT]['tmp_name']);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the content of the page
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function showContent()
|
||||
{
|
||||
if ($this->success) {
|
||||
$this->element('p', null,
|
||||
_('Feed will be restored. '.
|
||||
'Please wait a few minutes for results.'));
|
||||
} else {
|
||||
$form = new ImportDeliciousForm($this);
|
||||
$form->show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if read only.
|
||||
*
|
||||
* MAY override
|
||||
*
|
||||
* @param array $args other arguments
|
||||
*
|
||||
* @return boolean is read only action?
|
||||
*/
|
||||
|
||||
function isReadOnly($args)
|
||||
{
|
||||
return !$this->isPost();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A form for backing up the account.
|
||||
*
|
||||
* @category Account
|
||||
* @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 ImportDeliciousForm extends Form
|
||||
{
|
||||
const FILEINPUT = 'deliciousbackupfile';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Set the encoding type, since this is a file upload.
|
||||
*
|
||||
* @param HTMLOutputter $out output channel
|
||||
*
|
||||
* @return ImportDeliciousForm this
|
||||
*/
|
||||
|
||||
function __construct($out=null)
|
||||
{
|
||||
parent::__construct($out);
|
||||
$this->enctype = 'multipart/form-data';
|
||||
}
|
||||
|
||||
/**
|
||||
* Class of the form.
|
||||
*
|
||||
* @return string the form's class
|
||||
*/
|
||||
|
||||
function formClass()
|
||||
{
|
||||
return 'form_import_delicious';
|
||||
}
|
||||
|
||||
/**
|
||||
* URL the form posts to
|
||||
*
|
||||
* @return string the form's action URL
|
||||
*/
|
||||
|
||||
function action()
|
||||
{
|
||||
return common_local_url('importdelicious');
|
||||
}
|
||||
|
||||
/**
|
||||
* Output form data
|
||||
*
|
||||
* Really, just instructions for doing a backup.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function formData()
|
||||
{
|
||||
$this->out->elementStart('p', 'instructions');
|
||||
|
||||
$this->out->raw(_('You can upload a backed-up '.
|
||||
'delicious.com bookmarks file.'));
|
||||
|
||||
$this->out->elementEnd('p');
|
||||
|
||||
$this->out->elementStart('ul', 'form_data');
|
||||
|
||||
$this->out->elementStart('li', array ('id' => 'settings_attach'));
|
||||
$this->out->element('input', array('name' => self::FILEINPUT,
|
||||
'type' => 'file',
|
||||
'id' => self::FILEINPUT));
|
||||
$this->out->elementEnd('li');
|
||||
|
||||
$this->out->elementEnd('ul');
|
||||
}
|
||||
|
||||
/**
|
||||
* Buttons for the form
|
||||
*
|
||||
* In this case, a single submit button
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function formActions()
|
||||
{
|
||||
$this->out->submit('submit',
|
||||
_m('BUTTON', 'Upload'),
|
||||
'submit',
|
||||
null,
|
||||
_('Upload the file'));
|
||||
}
|
||||
}
|
196
plugins/Bookmark/newbookmark.php
Normal file
196
plugins/Bookmark/newbookmark.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2010, StatusNet, Inc.
|
||||
*
|
||||
* Add a new bookmark
|
||||
*
|
||||
* 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 Bookmark
|
||||
* @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')) {
|
||||
// This check helps protect against security problems;
|
||||
// your code file can't be executed directly from the web.
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new bookmark
|
||||
*
|
||||
* @category Bookmark
|
||||
* @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 NewbookmarkAction extends Action
|
||||
{
|
||||
protected $user = null;
|
||||
protected $error = null;
|
||||
protected $complete = null;
|
||||
protected $title = null;
|
||||
protected $url = null;
|
||||
protected $tags = null;
|
||||
protected $description = null;
|
||||
|
||||
/**
|
||||
* Returns the title of the action
|
||||
*
|
||||
* @return string Action title
|
||||
*/
|
||||
|
||||
function title()
|
||||
{
|
||||
return _('New bookmark');
|
||||
}
|
||||
|
||||
/**
|
||||
* For initializing members of the class.
|
||||
*
|
||||
* @param array $argarray misc. arguments
|
||||
*
|
||||
* @return boolean true
|
||||
*/
|
||||
|
||||
function prepare($argarray)
|
||||
{
|
||||
parent::prepare($argarray);
|
||||
|
||||
$this->user = common_current_user();
|
||||
|
||||
if (empty($this->user)) {
|
||||
throw new ClientException(_("Must be logged in to post a bookmark."),
|
||||
403);
|
||||
}
|
||||
|
||||
if ($this->isPost()) {
|
||||
$this->checkSessionToken();
|
||||
}
|
||||
|
||||
$this->title = $this->trimmed('title');
|
||||
$this->url = $this->trimmed('url');
|
||||
$this->tags = $this->trimmed('tags');
|
||||
$this->description = $this->trimmed('description');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler method
|
||||
*
|
||||
* @param array $argarray is ignored since it's now passed in in prepare()
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function handle($argarray=null)
|
||||
{
|
||||
parent::handle($argarray);
|
||||
|
||||
if ($this->isPost()) {
|
||||
$this->newBookmark();
|
||||
} else {
|
||||
$this->showPage();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new bookmark
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function newBookmark()
|
||||
{
|
||||
try {
|
||||
if (empty($this->title)) {
|
||||
throw new ClientException(_('Bookmark must have a title.'));
|
||||
}
|
||||
|
||||
if (empty($this->url)) {
|
||||
throw new ClientException(_('Bookmark must have an URL.'));
|
||||
}
|
||||
|
||||
|
||||
$saved = Bookmark::saveNew($this->user->getProfile(),
|
||||
$this->title,
|
||||
$this->url,
|
||||
$this->tags,
|
||||
$this->description);
|
||||
|
||||
} catch (ClientException $ce) {
|
||||
$this->error = $ce->getMessage();
|
||||
$this->showPage();
|
||||
return;
|
||||
}
|
||||
|
||||
common_redirect($saved->bestUrl(), 303);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the bookmark form
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function showContent()
|
||||
{
|
||||
if (!empty($this->error)) {
|
||||
$this->element('p', 'error', $this->error);
|
||||
}
|
||||
|
||||
$form = new BookmarkForm($this,
|
||||
$this->title,
|
||||
$this->url,
|
||||
$this->tags,
|
||||
$this->description);
|
||||
|
||||
$form->show();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if read only.
|
||||
*
|
||||
* MAY override
|
||||
*
|
||||
* @param array $args other arguments
|
||||
*
|
||||
* @return boolean is read only action?
|
||||
*/
|
||||
|
||||
function isReadOnly($args)
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'GET' ||
|
||||
$_SERVER['REQUEST_METHOD'] == 'HEAD') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
177
plugins/Bookmark/noticebyurl.php
Normal file
177
plugins/Bookmark/noticebyurl.php
Normal file
@ -0,0 +1,177 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2010, StatusNet, Inc.
|
||||
*
|
||||
* Notice stream of notices with a given attachment
|
||||
*
|
||||
* 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 Bookmark
|
||||
* @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')) {
|
||||
// This check helps protect against security problems;
|
||||
// your code file can't be executed directly from the web.
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* List notices that contain/link to/use a given URL
|
||||
*
|
||||
* @category Bookmark
|
||||
* @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 NoticebyurlAction extends Action
|
||||
{
|
||||
protected $url = null;
|
||||
protected $file = null;
|
||||
protected $notices = null;
|
||||
protected $page = null;
|
||||
|
||||
/**
|
||||
* For initializing members of the class.
|
||||
*
|
||||
* @param array $argarray misc. arguments
|
||||
*
|
||||
* @return boolean true
|
||||
*/
|
||||
|
||||
function prepare($argarray)
|
||||
{
|
||||
parent::prepare($argarray);
|
||||
|
||||
$this->file = File::staticGet('id', $this->trimmed('id'));
|
||||
|
||||
if (empty($this->file)) {
|
||||
throw new ClientException(_('Unknown URL'));
|
||||
}
|
||||
|
||||
$pageArg = $this->trimmed('page');
|
||||
|
||||
$this->page = (empty($pageArg)) ? 1 : intval($pageArg);
|
||||
|
||||
$this->notices = $this->file->stream(($this->page - 1) * NOTICES_PER_PAGE,
|
||||
NOTICES_PER_PAGE + 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Title of the page
|
||||
*
|
||||
* @return string page title
|
||||
*/
|
||||
|
||||
function title()
|
||||
{
|
||||
if ($this->page == 1) {
|
||||
return sprintf(_("Notices linking to %s"), $this->file->url);
|
||||
} else {
|
||||
return sprintf(_("Notices linking to %s, page %d"),
|
||||
$this->file->url,
|
||||
$this->page);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler method
|
||||
*
|
||||
* @param array $argarray is ignored since it's now passed in in prepare()
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function handle($argarray=null)
|
||||
{
|
||||
$this->showPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show main page content.
|
||||
*
|
||||
* Shows a list of the notices that link to the given URL
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function showContent()
|
||||
{
|
||||
$nl = new NoticeList($this->notices, $this);
|
||||
|
||||
$nl->show();
|
||||
|
||||
$cnt = $nl->show();
|
||||
|
||||
$this->pagination($this->page > 1,
|
||||
$cnt > NOTICES_PER_PAGE,
|
||||
$this->page,
|
||||
'noticebyurl',
|
||||
array('id' => $this->file->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if read only.
|
||||
*
|
||||
* MAY override
|
||||
*
|
||||
* @param array $args other arguments
|
||||
*
|
||||
* @return boolean is read only action?
|
||||
*/
|
||||
|
||||
function isReadOnly($args)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return last modified, if applicable.
|
||||
*
|
||||
* MAY override
|
||||
*
|
||||
* @return string last modified http header
|
||||
*/
|
||||
function lastModified()
|
||||
{
|
||||
// For comparison with If-Last-Modified
|
||||
// If not applicable, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return etag, if applicable.
|
||||
*
|
||||
* MAY override
|
||||
*
|
||||
* @return string etag http header
|
||||
*/
|
||||
|
||||
function etag()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
145
plugins/Bookmark/showbookmark.php
Normal file
145
plugins/Bookmark/showbookmark.php
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2010, StatusNet, Inc.
|
||||
*
|
||||
* Show a single bookmark
|
||||
*
|
||||
* 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 Bookmark
|
||||
* @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')) {
|
||||
// This check helps protect against security problems;
|
||||
// your code file can't be executed directly from the web.
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a single bookmark, with associated information
|
||||
*
|
||||
* @category Bookmark
|
||||
* @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 ShowbookmarkAction extends ShownoticeAction
|
||||
{
|
||||
protected $bookmark = null;
|
||||
|
||||
/**
|
||||
* For initializing members of the class.
|
||||
*
|
||||
* @param array $argarray misc. arguments
|
||||
*
|
||||
* @return boolean true
|
||||
*/
|
||||
|
||||
function prepare($argarray)
|
||||
{
|
||||
OwnerDesignAction::prepare($argarray);
|
||||
|
||||
$this->user = User::staticGet('id', $this->trimmed('user'));
|
||||
|
||||
if (empty($this->user)) {
|
||||
throw new ClientException(_('No such user.'), 404);
|
||||
}
|
||||
|
||||
$this->profile = $this->user->getProfile();
|
||||
|
||||
if (empty($this->profile)) {
|
||||
throw new ServerException(_('User without a profile.'));
|
||||
}
|
||||
|
||||
$this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
|
||||
|
||||
sscanf($this->trimmed('crc32'), '%08x', $crc32);
|
||||
|
||||
if (empty($crc32)) {
|
||||
throw new ClientException(_('No such URL.'), 404);
|
||||
}
|
||||
|
||||
$dt = new DateTime($this->trimmed('created'),
|
||||
new DateTimeZone('UTC'));
|
||||
|
||||
if (empty($dt)) {
|
||||
throw new ClientException(_('No such create date.'), 404);
|
||||
}
|
||||
|
||||
$bookmarks = Bookmark::getByCRC32($this->profile,
|
||||
$crc32);
|
||||
|
||||
foreach ($bookmarks as $bookmark) {
|
||||
$bdt = new DateTime($bookmark->created, new DateTimeZone('UTC'));
|
||||
if ($bdt->format('U') == $dt->format('U')) {
|
||||
$this->bookmark = $bookmark;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($this->bookmark)) {
|
||||
throw new ClientException(_('No such bookmark.'), 404);
|
||||
}
|
||||
|
||||
$this->notice = Notice::staticGet('uri', $this->bookmark->uri);
|
||||
|
||||
if (empty($this->notice)) {
|
||||
// Did we used to have it, and it got deleted?
|
||||
throw new ClientException(_('No such bookmark.'), 404);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Title of the page
|
||||
*
|
||||
* Used by Action class for layout.
|
||||
*
|
||||
* @return string page tile
|
||||
*/
|
||||
|
||||
function title()
|
||||
{
|
||||
return sprintf(_('%s\'s bookmark for "%s"'),
|
||||
$this->user->nickname,
|
||||
$this->bookmark->title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload page title display to show bookmark link
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function showPageTitle()
|
||||
{
|
||||
$this->elementStart('h1');
|
||||
$this->element('a',
|
||||
array('href' => $this->bookmark->url),
|
||||
$this->bookmark->title);
|
||||
$this->elementEnd('h1');
|
||||
}
|
||||
}
|
@ -145,12 +145,10 @@ class OStatusPlugin extends Plugin
|
||||
$user = $feed->getUser();
|
||||
$id = $user->id;
|
||||
$profile = $user->getProfile();
|
||||
$feed->setActivitySubject($profile->asActivityNoun('subject'));
|
||||
} else if ($feed instanceof AtomGroupNoticeFeed) {
|
||||
$salmonAction = 'groupsalmon';
|
||||
$group = $feed->getGroup();
|
||||
$id = $group->id;
|
||||
$feed->setActivitySubject($group->asActivitySubject());
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
@ -47,6 +47,9 @@ class GroupsalmonAction extends SalmonAction
|
||||
$this->clientError(_m('No such group.'));
|
||||
}
|
||||
|
||||
|
||||
$this->target = $this->group;
|
||||
|
||||
$oprofile = Ostatus_profile::staticGet('group_id', $id);
|
||||
if ($oprofile) {
|
||||
// TRANS: Client error.
|
||||
|
@ -43,6 +43,8 @@ class UsersalmonAction extends SalmonAction
|
||||
$this->clientError(_m('No such user.'));
|
||||
}
|
||||
|
||||
$this->target = $this->user;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -419,7 +419,8 @@ class Ostatus_profile extends Managed_DataObject
|
||||
{
|
||||
$activity = new Activity($entry, $feed);
|
||||
|
||||
if (Event::handle('StartHandleFeedEntry', array($activity))) {
|
||||
if (Event::handle('StartHandleFeedEntryWithProfile', array($activity, $this)) &&
|
||||
Event::handle('StartHandleFeedEntry', array($activity))) {
|
||||
|
||||
// @todo process all activity objects
|
||||
switch ($activity->objects[0]->type) {
|
||||
@ -441,6 +442,7 @@ class Ostatus_profile extends Managed_DataObject
|
||||
}
|
||||
|
||||
Event::handle('EndHandleFeedEntry', array($activity));
|
||||
Event::handle('EndHandleFeedEntryWithProfile', array($activity, $this));
|
||||
}
|
||||
}
|
||||
|
||||
@ -453,37 +455,11 @@ class Ostatus_profile extends Managed_DataObject
|
||||
*/
|
||||
public function processPost($activity, $method)
|
||||
{
|
||||
if ($this->isGroup()) {
|
||||
// A group feed will contain posts from multiple authors.
|
||||
// @fixme validate these profiles in some way!
|
||||
$oprofile = self::ensureActorProfile($activity);
|
||||
if ($oprofile->isGroup()) {
|
||||
// Groups can't post notices in StatusNet.
|
||||
common_log(LOG_WARNING, "OStatus: skipping post with group listed as author: $oprofile->uri in feed from $this->uri");
|
||||
$oprofile = $this->checkAuthorship($activity);
|
||||
|
||||
if (empty($oprofile)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$actor = $activity->actor;
|
||||
|
||||
if (empty($actor)) {
|
||||
// OK here! assume the default
|
||||
} else if ($actor->id == $this->uri || $actor->link == $this->uri) {
|
||||
$this->updateFromActivityObject($actor);
|
||||
} else if ($actor->id) {
|
||||
// We have an ActivityStreams actor with an explicit ID that doesn't match the feed owner.
|
||||
// This isn't what we expect from mainline OStatus person feeds!
|
||||
// Group feeds go down another path, with different validation...
|
||||
// Most likely this is a plain ol' blog feed of some kind which
|
||||
// doesn't match our expectations. We'll take the entry, but ignore
|
||||
// the <author> info.
|
||||
common_log(LOG_WARNING, "Got an actor '{$actor->title}' ({$actor->id}) on single-user feed for {$this->uri}");
|
||||
} else {
|
||||
// Plain <author> without ActivityStreams actor info.
|
||||
// We'll just ignore this info for now and save the update under the feed's identity.
|
||||
}
|
||||
|
||||
$oprofile = $this;
|
||||
}
|
||||
|
||||
// It's not always an ActivityObject::NOTE, but... let's just say it is.
|
||||
|
||||
@ -1772,6 +1748,45 @@ class Ostatus_profile extends Managed_DataObject
|
||||
}
|
||||
return $oprofile;
|
||||
}
|
||||
|
||||
function checkAuthorship($activity)
|
||||
{
|
||||
if ($this->isGroup()) {
|
||||
// A group feed will contain posts from multiple authors.
|
||||
// @fixme validate these profiles in some way!
|
||||
$oprofile = self::ensureActorProfile($activity);
|
||||
if ($oprofile->isGroup()) {
|
||||
// Groups can't post notices in StatusNet.
|
||||
common_log(LOG_WARNING,
|
||||
"OStatus: skipping post with group listed as author: ".
|
||||
"$oprofile->uri in feed from $this->uri");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$actor = $activity->actor;
|
||||
|
||||
if (empty($actor)) {
|
||||
// OK here! assume the default
|
||||
} else if ($actor->id == $this->uri || $actor->link == $this->uri) {
|
||||
$this->updateFromActivityObject($actor);
|
||||
} else if ($actor->id) {
|
||||
// We have an ActivityStreams actor with an explicit ID that doesn't match the feed owner.
|
||||
// This isn't what we expect from mainline OStatus person feeds!
|
||||
// Group feeds go down another path, with different validation...
|
||||
// Most likely this is a plain ol' blog feed of some kind which
|
||||
// doesn't match our expectations. We'll take the entry, but ignore
|
||||
// the <author> info.
|
||||
common_log(LOG_WARNING, "Got an actor '{$actor->title}' ({$actor->id}) on single-user feed for {$this->uri}");
|
||||
} else {
|
||||
// Plain <author> without ActivityStreams actor info.
|
||||
// We'll just ignore this info for now and save the update under the feed's identity.
|
||||
}
|
||||
|
||||
$oprofile = $this;
|
||||
}
|
||||
|
||||
return $oprofile;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,6 +30,7 @@ class SalmonAction extends Action
|
||||
{
|
||||
var $xml = null;
|
||||
var $activity = null;
|
||||
var $target = null;
|
||||
|
||||
function prepare($args)
|
||||
{
|
||||
@ -82,7 +83,8 @@ class SalmonAction extends Action
|
||||
StatusNet::setApi(true); // Send smaller error pages
|
||||
|
||||
common_log(LOG_DEBUG, "Got a " . $this->activity->verb);
|
||||
if (Event::handle('StartHandleSalmon', array($this->activity))) {
|
||||
if (Event::handle('StartHandleSalmonTarget', array($this->activity, $this->target)) &&
|
||||
Event::handle('StartHandleSalmon', array($this->activity))) {
|
||||
switch ($this->activity->verb)
|
||||
{
|
||||
case ActivityVerb::POST:
|
||||
@ -118,6 +120,7 @@ class SalmonAction extends Action
|
||||
throw new ClientException(_m("Unrecognized activity type."));
|
||||
}
|
||||
Event::handle('EndHandleSalmon', array($this->activity));
|
||||
Event::handle('EndHandleSalmonTarget', array($this->activity, $this->target));
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user