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
- $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
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
=======

View File

@ -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

View File

@ -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());
}

View File

@ -263,11 +263,12 @@ class NoticeListItem extends Widget
function showStart()
{
// XXX: RDFa
// TODO: add notice_type class e.g., notice_video, notice_image
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()
{
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,12 +46,12 @@ 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 $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 $url_crc32; // int(4) not_null
public $created; // datetime
/**
@ -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,38 +168,18 @@ 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;
}
/**
* Save a new notice bookmark
*
@ -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();

View File

@ -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) || !empty($tags)) {
$out->elementStart('ul', array('class' => 'bookmark-tags'));
if (!empty($replies)) {
foreach ($replies as $reply) {
$other = Profile::staticGet('id', $reply);
$out->elementStart('li');
@ -291,9 +283,6 @@ class BookmarkPlugin extends Plugin
$out->elementEnd('li');
$out->text(' ');
}
}
$tags = $nli->notice->getTags();
foreach ($tags as $tag) {
$out->elementStart('li');
@ -306,29 +295,46 @@ class BookmarkPlugin extends Plugin
}
$out->elementEnd('ul');
}
if (!empty($nb->description)) {
$out->element('p',
array('class' => 'bookmark_description'),
array('class' => 'bookmark-description'),
$nb->description);
}
if (common_config('attachments', 'show_thumbs')) {
$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) ?
$out->element('img',
array('src' => ($avatar) ?
$avatar->displayUrl() :
Avatar::defaultImage(AVATAR_MINI_SIZE),
'class' => 'avatar photo bookmark_avatar',
'class' => 'avatar photo bookmark-avatar',
'width' => AVATAR_MINI_SIZE,
'height' => AVATAR_MINI_SIZE,
'alt' => $profile->getBestName()));
$out->raw('&nbsp;');
$out->element('a', array('href' => $profile->profileurl,
$out->element('a',
array('href' => $profile->profileurl,
'title' => $profile->getBestName()),
$profile->nickname);
@ -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);
}
}

View File

@ -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 }

View File

@ -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();
}});
}

View File

@ -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);
}
}
}

View File

@ -61,48 +61,28 @@ 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>."));
}
$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,
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;
}
return true;
}

View File

@ -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');
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();

View File

@ -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
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");
}
}