Merge branch 'revertversion' into 0.9.x

This commit is contained in:
Evan Prodromou 2011-01-03 10:51:42 -08:00
commit a7e5c58a82
15 changed files with 481 additions and 207 deletions

View File

@ -1045,3 +1045,15 @@ StartProfileSettingsActions: when we're showing account-management action list
EndProfileSettingsActions: when we're showing account-management action list EndProfileSettingsActions: when we're showing account-management action list
- $action: Action being shown (use for output) - $action: Action being shown (use for output)
StartOpenNoticeListItemElement: Before the opening <li> of a notice list element
- $nli: The notice list item being shown
EndOpenNoticeListItemElement: After the opening <li> of a notice list element
- $nli: The notice list item being shown
StartCloseNoticeListItemElement: Before the closing </li> of a notice list element
- $nli: The notice list item being shown
EndCloseNoticeListItemElement: After the closing </li> of a notice list element
- $nli: The notice list item being shown

16
README
View File

@ -1556,6 +1556,22 @@ cache: whether to cache the router in memcache (or another caching
router cached) or others who see strange behavior. You're unlikely router cached) or others who see strange behavior. You're unlikely
to need this unless you're a developer. to need this unless you're a developer.
http
----
Settings for the HTTP client.
ssl_cafile: location of the CA file for SSL. If not set, won't verify
SSL peers. Default unset.
curl: Use cURL <http://curl.haxx.se/> for doing HTTP calls. You must
have the PHP curl extension installed for this to work.
proxy_host: Host to use for proxying HTTP requests. If unset, doesn't
do any HTTP proxy stuff. Default unset.
proxy_port: Port to use to connect to HTTP proxy host. Default null.
proxy_user: Username to use for authenticating to the HTTP proxy. Default null.
proxy_password: Password to use for authenticating to the HTTP proxy. Default null.
proxy_auth_scheme: Scheme to use for authenticating to the HTTP proxy. Default null.
Plugins Plugins
======= =======

View File

@ -331,6 +331,11 @@ $default =
'http' => // HTTP client settings when contacting other sites 'http' => // HTTP client settings when contacting other sites
array('ssl_cafile' => false, // To enable SSL cert validation, point to a CA bundle (eg '/usr/lib/ssl/certs/ca-certificates.crt') array('ssl_cafile' => false, // To enable SSL cert validation, point to a CA bundle (eg '/usr/lib/ssl/certs/ca-certificates.crt')
'curl' => false, // Use CURL backend for HTTP fetches if available. (If not, PHP's socket streams will be used.) 'curl' => false, // Use CURL backend for HTTP fetches if available. (If not, PHP's socket streams will be used.)
'proxy_host' => null,
'proxy_port' => null,
'proxy_user' => null,
'proxy_password' => null,
'proxy_auth_scheme' => null,
), ),
'router' => 'router' =>
array('cache' => true), // whether to cache the router object. Defaults to true, turn off for devel array('cache' => true), // whether to cache the router object. Defaults to true, turn off for devel

View File

@ -149,6 +149,14 @@ class HTTPClient extends HTTP_Request2
$this->config['adapter'] = 'HTTP_Request2_Adapter_Curl'; $this->config['adapter'] = 'HTTP_Request2_Adapter_Curl';
} }
foreach (array('host', 'port', 'user', 'password', 'auth_scheme') as $cf) {
$k = 'proxy_'.$cf;
$v = common_config('http', $k);
if (!empty($v)) {
$this->config[$k] = $v;
}
}
parent::__construct($url, $method, $config); parent::__construct($url, $method, $config);
$this->setHeader('User-Agent', $this->userAgent()); $this->setHeader('User-Agent', $this->userAgent());
} }

View File

@ -263,11 +263,12 @@ class NoticeListItem extends Widget
function showStart() function showStart()
{ {
// XXX: RDFa if (Event::handle('StartOpenNoticeListItemElement', array($this))) {
// TODO: add notice_type class e.g., notice_video, notice_image $id = (empty($this->repeat)) ? $this->notice->id : $this->repeat->id;
$id = (empty($this->repeat)) ? $this->notice->id : $this->repeat->id; $this->out->elementStart('li', array('class' => 'hentry notice',
$this->out->elementStart('li', array('class' => 'hentry notice', 'id' => 'notice-' . $id));
'id' => 'notice-' . $id)); Event::handle('EndOpenNoticeListItemElement', array($this));
}
} }
/** /**
@ -706,6 +707,9 @@ class NoticeListItem extends Widget
function showEnd() function showEnd()
{ {
$this->out->elementEnd('li'); if (Event::handle('StartCloseNoticeListItemElement', array($this))) {
$this->out->elementEnd('li');
Event::handle('EndCloseNoticeListItemElement', array($this));
}
} }
} }

109
lib/uuid.php Normal file
View File

@ -0,0 +1,109 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc.
*
* UUID generation
*
* 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 UUID
* @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);
}
/**
* UUID generation
*
* @category General
* @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 UUID
{
protected $str = null;
/**
* Constructor for a UUID
*
* Uses gen() to create a new UUID
*/
function __construct()
{
$this->str = self::gen();
}
/**
* For serializing to a string
*
* @return string version of self
*/
function __toString()
{
return $this->str;
}
/**
* For serializing to a string
*
* @return string version of self
*/
function getString()
{
return $this->str;
}
/**
* Generate a new UUID
*
* @return 36-char v4 (random-ish) UUID
*/
static function gen()
{
return sprintf('%s-%s-%04x-%04x-%s',
// 32 bits for "time_low"
common_good_rand(4),
// 16 bits for "time_mid"
common_good_rand(2),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
(hexdec(common_good_rand(2)) & 0x0fff) | 0x4000,
// 16 bits, 8 bits for "clk_seq_hi_res",
// 8 bits for "clk_seq_low",
// two most significant bits holds zero and one
// for variant DCE1.1
(hexdec(common_good_rand(2)) & 0x3fff) | 0x8000,
// 48 bits for "node"
common_good_rand(6));
}
}

View File

@ -46,13 +46,13 @@ if (!defined('STATUSNET')) {
class Bookmark extends Memcached_DataObject class Bookmark extends Memcached_DataObject
{ {
public $__table = 'bookmark'; // table name public $__table = 'bookmark'; // table name
public $profile_id; // int(4) primary_key not_null public $id; // char(36) primary_key not_null
public $url; // varchar(255) primary_key not_null public $profile_id; // int(4) not_null
public $title; // varchar(255) public $url; // varchar(255) not_null
public $description; // text public $title; // varchar(255)
public $uri; // varchar(255) public $description; // text
public $url_crc32; // int(4) not_null public $uri; // varchar(255)
public $created; // datetime public $created; // datetime
/** /**
* Get an instance by key * Get an instance by key
@ -100,12 +100,12 @@ class Bookmark extends Memcached_DataObject
function table() function table()
{ {
return array('profile_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, return array('id' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
'profile_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
'url' => DB_DATAOBJECT_STR, 'url' => DB_DATAOBJECT_STR,
'title' => DB_DATAOBJECT_STR, 'title' => DB_DATAOBJECT_STR,
'description' => DB_DATAOBJECT_STR, 'description' => DB_DATAOBJECT_STR,
'uri' => DB_DATAOBJECT_STR, 'uri' => DB_DATAOBJECT_STR,
'url_crc32' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE +
DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL); DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
} }
@ -129,8 +129,7 @@ class Bookmark extends Memcached_DataObject
function keyTypes() function keyTypes()
{ {
return array('profile_id' => 'K', return array('id' => 'K',
'url' => 'K',
'uri' => 'U'); 'uri' => 'U');
} }
@ -169,36 +168,16 @@ class Bookmark extends Memcached_DataObject
static function getByURL($profile, $url) 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 = new Bookmark();
$nb->profile_id = $profile->id; $nb->profile_id = $profile->id;
$nb->url_crc32 = $crc32; $nb->url = $url;
if ($nb->find()) { if ($nb->find(true)) {
while ($nb->fetch()) { return $nb;
$bookmarks[] = clone($nb); } else {
} return null;
} }
return $bookmarks;
} }
/** /**
@ -240,11 +219,11 @@ class Bookmark extends Memcached_DataObject
$nb = new Bookmark(); $nb = new Bookmark();
$nb->id = UUID::gen();
$nb->profile_id = $profile->id; $nb->profile_id = $profile->id;
$nb->url = $url; $nb->url = $url;
$nb->title = $title; $nb->title = $title;
$nb->description = $description; $nb->description = $description;
$nb->url_crc32 = crc32($nb->url);
if (array_key_exists('created', $options)) { if (array_key_exists('created', $options)) {
$nb->created = $options['created']; $nb->created = $options['created'];
@ -255,22 +234,8 @@ class Bookmark extends Memcached_DataObject
if (array_key_exists('uri', $options)) { if (array_key_exists('uri', $options)) {
$nb->uri = $options['uri']; $nb->uri = $options['uri'];
} else { } 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', $nb->uri = common_local_url('showbookmark',
array('user' => $profile->id, array('id' => $nb->id));
'created' => $created,
'crc32' => $crc32));
} }
$nb->insert(); $nb->insert();

View File

@ -86,16 +86,21 @@ class BookmarkPlugin extends Plugin
// For storing user-submitted flags on profiles // For storing user-submitted flags on profiles
$schema->ensureTable('bookmark', $schema->ensureTable('bookmark',
array(new ColumnDef('profile_id', array(new ColumnDef('id',
'char',
36,
false,
'PRI'),
new ColumnDef('profile_id',
'integer', 'integer',
null, null,
false, false,
'PRI'), 'MUL'),
new ColumnDef('url', new ColumnDef('url',
'varchar', 'varchar',
255, 255,
false, false,
'PRI'), 'MUL'),
new ColumnDef('title', new ColumnDef('title',
'varchar', 'varchar',
255), 255),
@ -106,26 +111,12 @@ class BookmarkPlugin extends Plugin
255, 255,
false, false,
'UNI'), 'UNI'),
new ColumnDef('url_crc32',
'integer unsigned',
null,
false,
'MUL'),
new ColumnDef('created', new ColumnDef('created',
'datetime', 'datetime',
null, null,
false, false,
'MUL'))); '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; return true;
} }
@ -216,11 +207,9 @@ class BookmarkPlugin extends Plugin
$m->connect('main/bookmark/import', $m->connect('main/bookmark/import',
array('action' => 'importdelicious')); array('action' => 'importdelicious'));
$m->connect('bookmark/:user/:created/:crc32', $m->connect('bookmark/:id',
array('action' => 'showbookmark'), array('action' => 'showbookmark'),
array('user' => '[0-9]+', array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
'created' => '[0-9]{14}',
'crc32' => '[0-9a-f]{8}'));
$m->connect('notice/by-url/:id', $m->connect('notice/by-url/:id',
array('action' => 'noticebyurl'), array('action' => 'noticebyurl'),
@ -262,25 +251,28 @@ class BookmarkPlugin extends Plugin
} else { } else {
$out->elementStart('h3'); $out->elementStart('h3');
$out->element('a', $out->element('a',
array('href' => $att->url), array('href' => $att->url,
'class' => 'bookmark-title entry-title'),
$nb->title); $nb->title);
$out->elementEnd('h3'); $out->elementEnd('h3');
$countUrl = common_local_url('noticebyurl', $countUrl = common_local_url('noticebyurl',
array('id' => $att->id)); array('id' => $att->id));
$out->element('a', array('class' => 'bookmark_notice_count', $out->element('a', array('class' => 'bookmark-notice-count',
'href' => $countUrl), 'href' => $countUrl),
$att->noticeCount()); $att->noticeCount());
} }
$out->elementStart('ul', array('class' => 'bookmark_tags'));
// Replies look like "for:" tags // Replies look like "for:" tags
$replies = $nli->notice->getReplies(); $replies = $nli->notice->getReplies();
$tags = $nli->notice->getTags();
if (!empty($replies)) { if (!empty($replies) || !empty($tags)) {
$out->elementStart('ul', array('class' => 'bookmark-tags'));
foreach ($replies as $reply) { foreach ($replies as $reply) {
$other = Profile::staticGet('id', $reply); $other = Profile::staticGet('id', $reply);
$out->elementStart('li'); $out->elementStart('li');
@ -291,45 +283,59 @@ class BookmarkPlugin extends Plugin
$out->elementEnd('li'); $out->elementEnd('li');
$out->text(' '); $out->text(' ');
} }
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');
} }
$tags = $nli->notice->getTags(); if (!empty($nb->description)) {
$out->element('p',
foreach ($tags as $tag) { array('class' => 'bookmark-description'),
$out->elementStart('li'); $nb->description);
$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')) { if (common_config('attachments', 'show_thumbs')) {
$al = new InlineAttachmentList($notice, $out); $haveThumbs = false;
$al->show(); foreach ($atts as $check) {
$thumbnail = File_thumbnail::staticGet('file_id', $check->id);
if (!empty($thumbnail)) {
$haveThumbs = true;
break;
}
}
if ($haveThumbs) {
$al = new InlineAttachmentList($notice, $out);
$al->show();
}
} }
$out->elementStart('p', array('style' => 'float: left')); $out->elementStart('p', array('class' => 'bookmark-info'));
$avatar = $profile->getAvatar(AVATAR_MINI_SIZE); $avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
$out->element('img', array('src' => ($avatar) ? $out->element('img',
$avatar->displayUrl() : array('src' => ($avatar) ?
Avatar::defaultImage(AVATAR_MINI_SIZE), $avatar->displayUrl() :
'class' => 'avatar photo bookmark_avatar', Avatar::defaultImage(AVATAR_MINI_SIZE),
'width' => AVATAR_MINI_SIZE, 'class' => 'avatar photo bookmark-avatar',
'height' => AVATAR_MINI_SIZE, 'width' => AVATAR_MINI_SIZE,
'alt' => $profile->getBestName())); 'height' => AVATAR_MINI_SIZE,
'alt' => $profile->getBestName()));
$out->raw('&nbsp;'); $out->raw('&nbsp;');
$out->element('a', array('href' => $profile->profileurl,
'title' => $profile->getBestName()), $out->element('a',
array('href' => $profile->profileurl,
'title' => $profile->getBestName()),
$profile->nickname); $profile->nickname);
$nli->showNoticeLink(); $nli->showNoticeLink();
@ -642,6 +648,27 @@ class BookmarkPlugin extends Plugin
return true; return true;
} }
/**
* Output our CSS class for bookmark notice list elements
*
* @param NoticeListItem $nli The item being shown
*
* @return boolean hook value
*/
function onStartOpenNoticeListItemElement($nli)
{
$nb = Bookmark::getByNotice($nli->notice);
if (!empty($nb)) {
$id = (empty($nli->repeat)) ? $nli->notice->id : $nli->repeat->id;
$nli->out->elementStart('li', array('class' => 'hentry notice bookmark',
'id' => 'notice-' . $id));
Event::handle('EndOpenNoticeListItemElement', array($nli));
return false;
}
return true;
}
/** /**
* Save a remote bookmark (from Salmon or PuSH) * Save a remote bookmark (from Salmon or PuSH)
* *
@ -769,4 +796,3 @@ class BookmarkPlugin extends Plugin
$activity->objects[0]->type == ActivityObject::BOOKMARK); $activity->objects[0]->type == ActivityObject::BOOKMARK);
} }
} }

View File

@ -1,4 +1,6 @@
.bookmark_tags li { display: inline; } .bookmark-tags li { display: inline; }
.bookmark_mentions li { display: inline; } .bookmark-mentions li { display: inline; }
.bookmark_avatar { float: left } .bookmark-avatar { float: left; }
.bookmark_notice_count { float: right } .bookmark-notice-count { float: right; }
.bookmark-info { float: left; }
.bookmark-title { margin-left: 0px }

View File

@ -2,6 +2,13 @@ $(document).ready(
function() { function() {
var form = $('#form_new_bookmark'); var form = $('#form_new_bookmark');
form.append('<input type="hidden" name="ajax" value="1"/>'); form.append('<input type="hidden" name="ajax" value="1"/>');
function doClose() {
self.close();
// If in popup blocker situation, we'll have to redirect back.
setTimeout(function() {
window.location = $('#url').val();
}, 100);
}
form.ajaxForm({dataType: 'xml', form.ajaxForm({dataType: 'xml',
timeout: '60000', timeout: '60000',
beforeSend: function(formData) { beforeSend: function(formData) {
@ -11,12 +18,12 @@ $(document).ready(
error: function (xhr, textStatus, errorThrown) { error: function (xhr, textStatus, errorThrown) {
form.removeClass('processing'); form.removeClass('processing');
form.find('#submit').removeClass('disabled'); form.find('#submit').removeClass('disabled');
self.close(); doClose();
}, },
success: function(data, textStatus) { success: function(data, textStatus) {
form.removeClass('processing'); form.removeClass('processing');
form.find('#submit').removeClass('disabled'); form.find('#submit').removeClass('disabled');
self.close(); doClose();
}}); }});
} }

View File

@ -65,7 +65,7 @@ class DeliciousBackupImporter extends QueueHandler
* and import to StatusNet as Bookmark activities. * and import to StatusNet as Bookmark activities.
* *
* The document format is terrible. It consists of a <dl> with * The document format is terrible. It consists of a <dl> with
* a bunch of <dt>'s, occasionally with <dd>'s. * a bunch of <dt>'s, occasionally with <dd>'s adding descriptions.
* There are sometimes <p>'s lost inside. * There are sometimes <p>'s lost inside.
* *
* @param array $data pair of user, text * @param array $data pair of user, text
@ -99,6 +99,9 @@ class DeliciousBackupImporter extends QueueHandler
} }
switch (strtolower($child->tagName)) { switch (strtolower($child->tagName)) {
case 'dt': case 'dt':
// <dt> nodes contain primary information about a bookmark.
// We can't import the current one just yet though, since
// it may be followed by a <dd>.
if (!empty($dt)) { if (!empty($dt)) {
// No DD provided // No DD provided
$this->importBookmark($user, $dt); $this->importBookmark($user, $dt);
@ -109,10 +112,13 @@ class DeliciousBackupImporter extends QueueHandler
case 'dd': case 'dd':
$dd = $child; $dd = $child;
// This <dd> contains a description for the bookmark in
// the preceding <dt> node.
$saved = $this->importBookmark($user, $dt, $dd); $saved = $this->importBookmark($user, $dt, $dd);
$dt = null; $dt = null;
$dd = null; $dd = null;
break;
case 'p': case 'p':
common_log(LOG_INFO, 'Skipping the <p> in the <dl>.'); common_log(LOG_INFO, 'Skipping the <p> in the <dl>.');
break; break;
@ -126,6 +132,14 @@ class DeliciousBackupImporter extends QueueHandler
$dt = $dd = null; $dt = $dd = null;
} }
} }
if (!empty($dt)) {
// There was a final bookmark without a description.
try {
$this->importBookmark($user, $dt);
} catch (Exception $e) {
common_log(LOG_ERR, $e->getMessage());
}
}
return true; return true;
} }
@ -148,24 +162,38 @@ class DeliciousBackupImporter extends QueueHandler
function importBookmark($user, $dt, $dd = null) function importBookmark($user, $dt, $dd = null)
{ {
// We have to go squirrelling around in the child nodes $as = $dt->getElementsByTagName('a');
// on the off chance that we've received another <dt>
// as a child.
for ($i = 0; $i < $dt->childNodes->length; $i++) { if ($as->length == 0) {
$child = $dt->childNodes->item($i); throw new ClientException(_("No <A> tag in a <DT>."));
if ($child->nodeType == XML_ELEMENT_NODE) {
if ($child->tagName == 'dt' && !is_null($dd)) {
$this->importBookmark($user, $dt);
$this->importBookmark($user, $child, $dd);
return;
}
}
} }
$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;
}
$addDate = $a->getAttribute('add_date');
$data = array(
'profile_id' => $user->id,
'title' => $a->nodeValue,
'description' => $description,
'url' => $a->getAttribute('href'),
'tags' => $a->getAttribute('tags'),
'created' => common_sql_date(intval($addDate))
);
$qm = QueueManager::get(); $qm = QueueManager::get();
$qm->enqueue($data, 'dlcsbkmk');
$qm->enqueue(array($user, $dt, $dd), 'dlcsbkmk');
} }
/** /**
@ -188,9 +216,95 @@ class DeliciousBackupImporter extends QueueHandler
error_reporting($old); error_reporting($old);
if ($ok) { if ($ok) {
foreach ($dom->getElementsByTagName('body') as $node) {
$this->fixListsIn($node);
}
return $dom; return $dom;
} else { } else {
return null; return null;
} }
} }
function fixListsIn(DOMNode $body) {
$toFix = array();
foreach ($body->childNodes as $node) {
if ($node->nodeType == XML_ELEMENT_NODE) {
$el = strtolower($node->nodeName);
if ($el == 'dl') {
$toFix[] = $node;
}
}
}
foreach ($toFix as $node) {
$this->fixList($node);
}
}
function fixList(DOMNode $list) {
$toFix = array();
foreach ($list->childNodes as $node) {
if ($node->nodeType == XML_ELEMENT_NODE) {
$el = strtolower($node->nodeName);
if ($el == 'dt' || $el == 'dd') {
$toFix[] = $node;
}
if ($el == 'dl') {
// Sublist.
// Technically, these can only appear inside a <dd>...
$this->fixList($node);
}
}
}
foreach ($toFix as $node) {
$this->fixListItem($node);
}
}
function fixListItem(DOMNode $item) {
// The HTML parser in libxml2 doesn't seem to properly handle
// many cases of implied close tags, apparently because it doesn't
// understand the nesting rules specified in the HTML DTD.
//
// This leads to sequences of adjacent <dt>s or <dd>s being incorrectly
// interpreted as parent->child trees instead of siblings:
//
// When parsing this input: "<dt>aaa <dt>bbb"
// should be equivalent to: "<dt>aaa </dt><dt>bbb</dt>"
// but we're seeing instead: "<dt>aaa <dt>bbb</dt></dt>"
//
// It does at least know that going from dt to dd, or dd to dt,
// should make a break.
$toMove = array();
foreach ($item->childNodes as $node) {
if ($node->nodeType == XML_ELEMENT_NODE) {
$el = strtolower($node->nodeName);
if ($el == 'dt' || $el == 'dd') {
// dt & dd cannot contain each other;
// This node was incorrectly placed; move it up a level!
$toMove[] = $node;
}
if ($el == 'dl') {
// Sublist.
// Technically, these can only appear inside a <dd>.
$this->fixList($node);
}
}
}
$parent = $item->parentNode;
$next = $item->nextSibling;
foreach ($toMove as $node) {
$item->removeChild($node);
$parent->insertBefore($node, $next);
$this->fixListItem($node);
}
}
} }

View File

@ -61,49 +61,29 @@ class DeliciousBookmarkImporter extends QueueHandler
/** /**
* Handle the data * Handle the data
* *
* @param array $data array of user, dt, dd * @param array $data associative array of user & bookmark info from DeliciousBackupImporter::importBookmark()
* *
* @return boolean success value * @return boolean success value
*/ */
function handle($data) function handle($data)
{ {
list($user, $dt, $dd) = $data; $profile = Profile::staticGet('id', $data['profile_id']);
$as = $dt->getElementsByTagName('a'); try {
$saved = Bookmark::saveNew($profile,
if ($as->length == 0) { $data['title'],
throw new ClientException(_("No <A> tag in a <DT>.")); $data['url'],
$data['tags'],
$data['description'],
array('created' => $data['created'],
'distribute' => false));
} catch (ClientException $e) {
// Most likely a duplicate -- continue on with the rest!
common_log(LOG_ERR, "Error importing delicious bookmark to $data[url]: " . $e->getMessage());
return true;
} }
$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; return true;
} }
} }

View File

@ -48,6 +48,7 @@ if (!defined('STATUSNET')) {
class ImportdeliciousAction extends Action class ImportdeliciousAction extends Action
{ {
protected $success = false; protected $success = false;
private $inprogress = false;
/** /**
* Return the title of the page * Return the title of the page
@ -191,7 +192,13 @@ class ImportdeliciousAction extends Action
$qm = QueueManager::get(); $qm = QueueManager::get();
$qm->enqueue(array(common_current_user(), $html), 'dlcsback'); $qm->enqueue(array(common_current_user(), $html), 'dlcsback');
$this->success = true; if ($qm instanceof UnQueueManager) {
// No active queuing means we've actually just completed the job!
$this->success = true;
} else {
// We've fed data into background queues, and it's probably still running.
$this->inprogress = true;
}
$this->showPage(); $this->showPage();
@ -212,8 +219,10 @@ class ImportdeliciousAction extends Action
{ {
if ($this->success) { if ($this->success) {
$this->element('p', null, $this->element('p', null,
_('Feed will be restored. '. _('Bookmarks have been imported. Your bookmarks should now appear in search and your profile page.'));
'Please wait a few minutes for results.')); } else if ($this->inprogress) {
$this->element('p', null,
_('Bookmarks are being imported. Please wait a few minutes for results.'));
} else { } else {
$form = new ImportDeliciousForm($this); $form = new ImportDeliciousForm($this);
$form->show(); $form->show();

View File

@ -61,7 +61,22 @@ class ShowbookmarkAction extends ShownoticeAction
{ {
OwnerDesignAction::prepare($argarray); OwnerDesignAction::prepare($argarray);
$this->user = User::staticGet('id', $this->trimmed('user')); $this->id = $this->trimmed('id');
$this->bookmark = Bookmark::staticGet('id', $this->id);
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);
}
$this->user = User::staticGet('id', $this->bookmark->profile_id);
if (empty($this->user)) { if (empty($this->user)) {
throw new ClientException(_('No such user.'), 404); throw new ClientException(_('No such user.'), 404);
@ -75,41 +90,6 @@ class ShowbookmarkAction extends ShownoticeAction
$this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE); $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; return true;
} }

37
tests/UUIDTest.php Normal file
View File

@ -0,0 +1,37 @@
<?php
if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
print "This script must be run from the command line\n";
exit();
}
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
define('STATUSNET', true);
require_once INSTALLDIR . '/lib/common.php';
class UUIDTest extends PHPUnit_Framework_TestCase
{
public function testGenerate()
{
$result = UUID::gen();
$this->assertRegExp('/^[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}$/',
$result);
// Check version number
$this->assertEquals(0x4000, hexdec(substr($result, 14, 4)) & 0xF000);
$this->assertEquals(0x8000, hexdec(substr($result, 19, 4)) & 0xC000);
}
public function testUnique()
{
$reps = 100;
$ids = array();
for ($i = 0; $i < $reps; $i++) {
$ids[] = UUID::gen();
}
$this->assertEquals(count($ids), count(array_unique($ids)), "UUIDs must be unique");
}
}