Merge branch 'revertversion' into 0.9.x
This commit is contained in:
commit
a7e5c58a82
12
EVENTS.txt
12
EVENTS.txt
|
@ -1045,3 +1045,15 @@ StartProfileSettingsActions: when we're showing account-management action list
|
|||
|
||||
EndProfileSettingsActions: when we're showing account-management action list
|
||||
- $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
16
README
|
@ -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
|
||||
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
|
||||
=======
|
||||
|
||||
|
|
|
@ -331,6 +331,11 @@ $default =
|
|||
'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')
|
||||
'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' =>
|
||||
array('cache' => true), // whether to cache the router object. Defaults to true, turn off for devel
|
||||
|
|
|
@ -149,6 +149,14 @@ class HTTPClient extends HTTP_Request2
|
|||
$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);
|
||||
$this->setHeader('User-Agent', $this->userAgent());
|
||||
}
|
||||
|
|
|
@ -263,11 +263,12 @@ class NoticeListItem extends Widget
|
|||
|
||||
function showStart()
|
||||
{
|
||||
// XXX: RDFa
|
||||
// TODO: add notice_type class e.g., notice_video, notice_image
|
||||
$id = (empty($this->repeat)) ? $this->notice->id : $this->repeat->id;
|
||||
$this->out->elementStart('li', array('class' => 'hentry notice',
|
||||
'id' => 'notice-' . $id));
|
||||
if (Event::handle('StartOpenNoticeListItemElement', array($this))) {
|
||||
$id = (empty($this->repeat)) ? $this->notice->id : $this->repeat->id;
|
||||
$this->out->elementStart('li', array('class' => 'hentry notice',
|
||||
'id' => 'notice-' . $id));
|
||||
Event::handle('EndOpenNoticeListItemElement', array($this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -706,6 +707,9 @@ class NoticeListItem extends Widget
|
|||
|
||||
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
109
lib/uuid.php
Normal 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));
|
||||
}
|
||||
}
|
|
@ -46,13 +46,13 @@ if (!defined('STATUSNET')) {
|
|||
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
|
||||
public $id; // char(36) primary_key not_null
|
||||
public $profile_id; // int(4) not_null
|
||||
public $url; // varchar(255) not_null
|
||||
public $title; // varchar(255)
|
||||
public $description; // text
|
||||
public $uri; // varchar(255)
|
||||
public $created; // datetime
|
||||
|
||||
/**
|
||||
* Get an instance by key
|
||||
|
@ -100,12 +100,12 @@ class Bookmark extends Memcached_DataObject
|
|||
|
||||
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,
|
||||
'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);
|
||||
}
|
||||
|
@ -129,8 +129,7 @@ class Bookmark extends Memcached_DataObject
|
|||
|
||||
function keyTypes()
|
||||
{
|
||||
return array('profile_id' => 'K',
|
||||
'url' => 'K',
|
||||
return array('id' => 'K',
|
||||
'uri' => 'U');
|
||||
}
|
||||
|
||||
|
@ -169,36 +168,16 @@ class Bookmark extends Memcached_DataObject
|
|||
|
||||
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;
|
||||
$nb->url = $url;
|
||||
|
||||
if ($nb->find()) {
|
||||
while ($nb->fetch()) {
|
||||
$bookmarks[] = clone($nb);
|
||||
}
|
||||
if ($nb->find(true)) {
|
||||
return $nb;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $bookmarks;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -240,11 +219,11 @@ class Bookmark extends Memcached_DataObject
|
|||
|
||||
$nb = new Bookmark();
|
||||
|
||||
$nb->id = UUID::gen();
|
||||
$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'];
|
||||
|
@ -255,22 +234,8 @@ class Bookmark extends Memcached_DataObject
|
|||
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));
|
||||
array('id' => $nb->id));
|
||||
}
|
||||
|
||||
$nb->insert();
|
||||
|
|
|
@ -86,16 +86,21 @@ class BookmarkPlugin extends Plugin
|
|||
// For storing user-submitted flags on profiles
|
||||
|
||||
$schema->ensureTable('bookmark',
|
||||
array(new ColumnDef('profile_id',
|
||||
array(new ColumnDef('id',
|
||||
'char',
|
||||
36,
|
||||
false,
|
||||
'PRI'),
|
||||
new ColumnDef('profile_id',
|
||||
'integer',
|
||||
null,
|
||||
false,
|
||||
'PRI'),
|
||||
'MUL'),
|
||||
new ColumnDef('url',
|
||||
'varchar',
|
||||
255,
|
||||
false,
|
||||
'PRI'),
|
||||
'MUL'),
|
||||
new ColumnDef('title',
|
||||
'varchar',
|
||||
255),
|
||||
|
@ -106,26 +111,12 @@ class BookmarkPlugin extends Plugin
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -216,11 +207,9 @@ class BookmarkPlugin extends Plugin
|
|||
$m->connect('main/bookmark/import',
|
||||
array('action' => 'importdelicious'));
|
||||
|
||||
$m->connect('bookmark/:user/:created/:crc32',
|
||||
$m->connect('bookmark/:id',
|
||||
array('action' => 'showbookmark'),
|
||||
array('user' => '[0-9]+',
|
||||
'created' => '[0-9]{14}',
|
||||
'crc32' => '[0-9a-f]{8}'));
|
||||
array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
|
||||
|
||||
$m->connect('notice/by-url/:id',
|
||||
array('action' => 'noticebyurl'),
|
||||
|
@ -262,25 +251,28 @@ class BookmarkPlugin extends Plugin
|
|||
} else {
|
||||
$out->elementStart('h3');
|
||||
$out->element('a',
|
||||
array('href' => $att->url),
|
||||
array('href' => $att->url,
|
||||
'class' => 'bookmark-title entry-title'),
|
||||
$nb->title);
|
||||
$out->elementEnd('h3');
|
||||
|
||||
$countUrl = common_local_url('noticebyurl',
|
||||
array('id' => $att->id));
|
||||
|
||||
$out->element('a', array('class' => 'bookmark_notice_count',
|
||||
$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();
|
||||
$tags = $nli->notice->getTags();
|
||||
|
||||
if (!empty($replies)) {
|
||||
if (!empty($replies) || !empty($tags)) {
|
||||
|
||||
$out->elementStart('ul', array('class' => 'bookmark-tags'));
|
||||
|
||||
foreach ($replies as $reply) {
|
||||
$other = Profile::staticGet('id', $reply);
|
||||
$out->elementStart('li');
|
||||
|
@ -291,45 +283,59 @@ class BookmarkPlugin extends Plugin
|
|||
$out->elementEnd('li');
|
||||
$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();
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
$out->elementStart('li');
|
||||
$out->element('a',
|
||||
array('rel' => 'tag',
|
||||
'href' => Notice_tag::url($tag)),
|
||||
$tag);
|
||||
$out->elementEnd('li');
|
||||
$out->text(' ');
|
||||
if (!empty($nb->description)) {
|
||||
$out->element('p',
|
||||
array('class' => 'bookmark-description'),
|
||||
$nb->description);
|
||||
}
|
||||
|
||||
$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();
|
||||
$haveThumbs = false;
|
||||
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);
|
||||
|
||||
$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->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()),
|
||||
|
||||
$out->element('a',
|
||||
array('href' => $profile->profileurl,
|
||||
'title' => $profile->getBestName()),
|
||||
$profile->nickname);
|
||||
|
||||
$nli->showNoticeLink();
|
||||
|
@ -642,6 +648,27 @@ class BookmarkPlugin extends Plugin
|
|||
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)
|
||||
*
|
||||
|
@ -769,4 +796,3 @@ class BookmarkPlugin extends Plugin
|
|||
$activity->objects[0]->type == ActivityObject::BOOKMARK);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
.bookmark_tags li { display: inline; }
|
||||
.bookmark_mentions li { display: inline; }
|
||||
.bookmark_avatar { float: left }
|
||||
.bookmark_notice_count { float: right }
|
||||
.bookmark-tags li { display: inline; }
|
||||
.bookmark-mentions li { display: inline; }
|
||||
.bookmark-avatar { float: left; }
|
||||
.bookmark-notice-count { float: right; }
|
||||
.bookmark-info { float: left; }
|
||||
.bookmark-title { margin-left: 0px }
|
||||
|
|
|
@ -2,6 +2,13 @@ $(document).ready(
|
|||
function() {
|
||||
var form = $('#form_new_bookmark');
|
||||
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',
|
||||
timeout: '60000',
|
||||
beforeSend: function(formData) {
|
||||
|
@ -11,12 +18,12 @@ $(document).ready(
|
|||
error: function (xhr, textStatus, errorThrown) {
|
||||
form.removeClass('processing');
|
||||
form.find('#submit').removeClass('disabled');
|
||||
self.close();
|
||||
doClose();
|
||||
},
|
||||
success: function(data, textStatus) {
|
||||
form.removeClass('processing');
|
||||
form.find('#submit').removeClass('disabled');
|
||||
self.close();
|
||||
doClose();
|
||||
}});
|
||||
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ class DeliciousBackupImporter extends QueueHandler
|
|||
* 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.
|
||||
* a bunch of <dt>'s, occasionally with <dd>'s adding descriptions.
|
||||
* There are sometimes <p>'s lost inside.
|
||||
*
|
||||
* @param array $data pair of user, text
|
||||
|
@ -99,6 +99,9 @@ class DeliciousBackupImporter extends QueueHandler
|
|||
}
|
||||
switch (strtolower($child->tagName)) {
|
||||
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)) {
|
||||
// No DD provided
|
||||
$this->importBookmark($user, $dt);
|
||||
|
@ -109,10 +112,13 @@ class DeliciousBackupImporter extends QueueHandler
|
|||
case 'dd':
|
||||
$dd = $child;
|
||||
|
||||
// This <dd> contains a description for the bookmark in
|
||||
// the preceding <dt> node.
|
||||
$saved = $this->importBookmark($user, $dt, $dd);
|
||||
|
||||
$dt = null;
|
||||
$dd = null;
|
||||
break;
|
||||
case 'p':
|
||||
common_log(LOG_INFO, 'Skipping the <p> in the <dl>.');
|
||||
break;
|
||||
|
@ -126,6 +132,14 @@ class DeliciousBackupImporter extends QueueHandler
|
|||
$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;
|
||||
}
|
||||
|
@ -148,24 +162,38 @@ class DeliciousBackupImporter extends QueueHandler
|
|||
|
||||
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.
|
||||
$as = $dt->getElementsByTagName('a');
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
$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->enqueue(array($user, $dt, $dd), 'dlcsbkmk');
|
||||
$qm->enqueue($data, 'dlcsbkmk');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -188,9 +216,95 @@ class DeliciousBackupImporter extends QueueHandler
|
|||
error_reporting($old);
|
||||
|
||||
if ($ok) {
|
||||
foreach ($dom->getElementsByTagName('body') as $node) {
|
||||
$this->fixListsIn($node);
|
||||
}
|
||||
return $dom;
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -61,49 +61,29 @@ class DeliciousBookmarkImporter extends QueueHandler
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
function handle($data)
|
||||
{
|
||||
list($user, $dt, $dd) = $data;
|
||||
$profile = Profile::staticGet('id', $data['profile_id']);
|
||||
|
||||
$as = $dt->getElementsByTagName('a');
|
||||
|
||||
if ($as->length == 0) {
|
||||
throw new ClientException(_("No <A> tag in a <DT>."));
|
||||
try {
|
||||
$saved = Bookmark::saveNew($profile,
|
||||
$data['title'],
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ if (!defined('STATUSNET')) {
|
|||
class ImportdeliciousAction extends Action
|
||||
{
|
||||
protected $success = false;
|
||||
private $inprogress = false;
|
||||
|
||||
/**
|
||||
* Return the title of the page
|
||||
|
@ -191,7 +192,13 @@ class ImportdeliciousAction extends Action
|
|||
$qm = QueueManager::get();
|
||||
$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();
|
||||
|
||||
|
@ -212,8 +219,10 @@ class ImportdeliciousAction extends Action
|
|||
{
|
||||
if ($this->success) {
|
||||
$this->element('p', null,
|
||||
_('Feed will be restored. '.
|
||||
'Please wait a few minutes for results.'));
|
||||
_('Bookmarks have been imported. Your bookmarks should now appear in search and your profile page.'));
|
||||
} else if ($this->inprogress) {
|
||||
$this->element('p', null,
|
||||
_('Bookmarks are being imported. Please wait a few minutes for results.'));
|
||||
} else {
|
||||
$form = new ImportDeliciousForm($this);
|
||||
$form->show();
|
||||
|
|
|
@ -61,7 +61,22 @@ class ShowbookmarkAction extends ShownoticeAction
|
|||
{
|
||||
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)) {
|
||||
throw new ClientException(_('No such user.'), 404);
|
||||
|
@ -75,41 +90,6 @@ class ShowbookmarkAction extends ShownoticeAction
|
|||
|
||||
$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;
|
||||
}
|
||||
|
||||
|
|
37
tests/UUIDTest.php
Normal file
37
tests/UUIDTest.php
Normal 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");
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user