From 3d62c1cf5114760c0a7b97cd69ba363ac059e215 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 29 Dec 2010 16:15:57 -0800 Subject: [PATCH 01/15] 0.9.7alpha1 --- lib/common.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common.php b/lib/common.php index 5505dc39da..22890b5cb7 100644 --- a/lib/common.php +++ b/lib/common.php @@ -23,7 +23,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; } define('STATUSNET_BASE_VERSION', '0.9.7'); -define('STATUSNET_LIFECYCLE', 'dev'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release' +define('STATUSNET_LIFECYCLE', 'alpha1'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release' define('STATUSNET_VERSION', STATUSNET_BASE_VERSION . STATUSNET_LIFECYCLE); define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility From 3b5c3d2c8428e1072a2944f0b8adfa79574bf1f6 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 30 Dec 2010 12:15:34 -0800 Subject: [PATCH 02/15] UUID-generation tools --- lib/uuid.php | 109 +++++++++++++++++++++++++++++++++++++++++++++ tests/UUIDTest.php | 25 +++++++++++ 2 files changed, 134 insertions(+) create mode 100644 lib/uuid.php create mode 100644 tests/UUIDTest.php diff --git a/lib/uuid.php b/lib/uuid.php new file mode 100644 index 0000000000..93153504f2 --- /dev/null +++ b/lib/uuid.php @@ -0,0 +1,109 @@ +. + * + * @category UUID + * @package StatusNet + * @author Evan Prodromou + * @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 + * @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)); + } +} diff --git a/tests/UUIDTest.php b/tests/UUIDTest.php new file mode 100644 index 0000000000..de256ffa1f --- /dev/null +++ b/tests/UUIDTest.php @@ -0,0 +1,25 @@ +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); + } +} + From 6fc7e5b05b1106cfcb2bf61cb788dd3aa38fa6f7 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 30 Dec 2010 13:21:14 -0800 Subject: [PATCH 03/15] Use UUIDs for Bookmark unique ID I was trying to generate URIs for Bookmarks based on (profile, crc32(url), created). I failed at that. CRC32s are unsigned ints, and our schema code didn't like that. On top of that, my code to encode and restore created timestamps was problematic. So, I switched back to using a meaningless unique ID for Bookmarks. One way to do this would be to use an auto-incrementing integer ID. However, we've been kind of crabbed out a few times for exposing auto-incrementing integer IDs as URIs, so I thought maybe using a random UUID would be a better way to do it. So, this patch sets random UUIDs for URIs of bookmarks. --- plugins/Bookmark/Bookmark.php | 69 +++++++---------------------- plugins/Bookmark/BookmarkPlugin.php | 31 +++++-------- plugins/Bookmark/showbookmark.php | 52 +++++++--------------- 3 files changed, 43 insertions(+), 109 deletions(-) diff --git a/plugins/Bookmark/Bookmark.php b/plugins/Bookmark/Bookmark.php index 61fe3c5b97..4ee287fb65 100644 --- a/plugins/Bookmark/Bookmark.php +++ b/plugins/Bookmark/Bookmark.php @@ -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(); diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 4f31349801..99f25c64b8 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -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'), diff --git a/plugins/Bookmark/showbookmark.php b/plugins/Bookmark/showbookmark.php index e9e656f84c..6bebffb68e 100644 --- a/plugins/Bookmark/showbookmark.php +++ b/plugins/Bookmark/showbookmark.php @@ -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; } From b00a3cd4e4b279b075e7786d850f63dacdd9ee98 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 30 Dec 2010 13:37:05 -0800 Subject: [PATCH 04/15] Make sure the UUIDs are unique too :) --- tests/UUIDTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/UUIDTest.php b/tests/UUIDTest.php index de256ffa1f..e78d5ce1bc 100644 --- a/tests/UUIDTest.php +++ b/tests/UUIDTest.php @@ -21,5 +21,17 @@ class UUIDTest extends PHPUnit_Framework_TestCase $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"); + } } From b71a09a1a9a8eedcd0e2214c5c998b0707a2ee70 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 30 Dec 2010 16:14:41 -0800 Subject: [PATCH 05/15] Tweak the post-form return on bookmarklet if we're not in a popup that we can close --- plugins/Bookmark/bookmarkpopup.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/Bookmark/bookmarkpopup.js b/plugins/Bookmark/bookmarkpopup.js index 29f314ed06..4904b07e24 100644 --- a/plugins/Bookmark/bookmarkpopup.js +++ b/plugins/Bookmark/bookmarkpopup.js @@ -2,6 +2,13 @@ $(document).ready( function() { var form = $('#form_new_bookmark'); form.append(''); + 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(); }}); } From 682e11bb8b09588d92960d45c08c2a828be0a374 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 30 Dec 2010 16:21:22 -0800 Subject: [PATCH 06/15] don't show some bookmark elements if empty --- plugins/Bookmark/BookmarkPlugin.php | 79 +++++++++++++++++------------ plugins/Bookmark/bookmark.css | 1 + 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 99f25c64b8..e6590bce34 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -263,13 +263,15 @@ class BookmarkPlugin extends Plugin $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'); @@ -280,45 +282,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(); @@ -758,4 +774,3 @@ class BookmarkPlugin extends Plugin $activity->objects[0]->type == ActivityObject::BOOKMARK); } } - diff --git a/plugins/Bookmark/bookmark.css b/plugins/Bookmark/bookmark.css index b86e749fd9..22e7f99ce4 100644 --- a/plugins/Bookmark/bookmark.css +++ b/plugins/Bookmark/bookmark.css @@ -2,3 +2,4 @@ .bookmark_mentions li { display: inline; } .bookmark_avatar { float: left } .bookmark_notice_count { float: right } +.bookmark_info { float: left } From 5d7f5212f0147a5d2b98fbeda9e97ede0a1cdfc6 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 30 Dec 2010 16:54:01 -0800 Subject: [PATCH 07/15] switch bookmark CSS classes to use dash instead of underscore --- plugins/Bookmark/BookmarkPlugin.php | 13 +++++++------ plugins/Bookmark/bookmark.css | 11 ++++++----- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index e6590bce34..faddf5aafa 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -251,14 +251,15 @@ 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()); } @@ -270,7 +271,7 @@ class BookmarkPlugin extends Plugin if (!empty($replies) || !empty($tags)) { - $out->elementStart('ul', array('class' => 'bookmark_tags')); + $out->elementStart('ul', array('class' => 'bookmark-tags')); foreach ($replies as $reply) { $other = Profile::staticGet('id', $reply); @@ -298,7 +299,7 @@ class BookmarkPlugin extends Plugin if (!empty($nb->description)) { $out->element('p', - array('class' => 'bookmark_description'), + array('class' => 'bookmark-description'), $nb->description); } @@ -317,7 +318,7 @@ class BookmarkPlugin extends Plugin } } - $out->elementStart('p', array('class' => 'bookmark_info')); + $out->elementStart('p', array('class' => 'bookmark-info')); $avatar = $profile->getAvatar(AVATAR_MINI_SIZE); @@ -325,7 +326,7 @@ class BookmarkPlugin extends Plugin 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())); diff --git a/plugins/Bookmark/bookmark.css b/plugins/Bookmark/bookmark.css index 22e7f99ce4..6743622d2f 100644 --- a/plugins/Bookmark/bookmark.css +++ b/plugins/Bookmark/bookmark.css @@ -1,5 +1,6 @@ -.bookmark_tags li { display: inline; } -.bookmark_mentions li { display: inline; } -.bookmark_avatar { float: left } -.bookmark_notice_count { float: right } -.bookmark_info { float: left } +.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 } From 66f6b2a3427212de020694cde1385eeb44f1ccd6 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 30 Dec 2010 16:57:05 -0800 Subject: [PATCH 08/15] Events to hook for NoticeListElement
  • generation --- EVENTS.txt | 12 ++++++++++++ lib/noticelist.php | 16 ++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index f7cc7df67c..6719ba737a 100644 --- a/EVENTS.txt +++ b/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
  • of a notice list element +- $nli: The notice list item being shown + +EndOpenNoticeListItemElement: After the opening
  • of a notice list element +- $nli: The notice list item being shown + +StartCloseNoticeListItemElement: Before the closing
  • of a notice list element +- $nli: The notice list item being shown + +EndCloseNoticeListItemElement: After the closing of a notice list element +- $nli: The notice list item being shown diff --git a/lib/noticelist.php b/lib/noticelist.php index c6f964662f..7b2fbb1e7c 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -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)); + } } } From 5dfc9e1b185d841cbc4b3b85e984bf63dfa6508d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 30 Dec 2010 16:57:28 -0800 Subject: [PATCH 09/15] Generated an extra class on bookmark notice
  • s --- plugins/Bookmark/BookmarkPlugin.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index faddf5aafa..8eef609751 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -648,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) * From 56875318489b11c96d0360f7458bb3c6f1a25dc9 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 31 Dec 2010 12:09:15 -0800 Subject: [PATCH 10/15] Bookmark plugin: graceful error out for failure to import a delicious bookmark due to it being already bookmarked --- .../Bookmark/deliciousbookmarkimporter.php | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/plugins/Bookmark/deliciousbookmarkimporter.php b/plugins/Bookmark/deliciousbookmarkimporter.php index 297ef81246..545c336860 100644 --- a/plugins/Bookmark/deliciousbookmarkimporter.php +++ b/plugins/Bookmark/deliciousbookmarkimporter.php @@ -96,13 +96,19 @@ class DeliciousBookmarkImporter extends QueueHandler $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)); + try { + $saved = Bookmark::saveNew($user->getProfile(), + $title, + $url, + $tags, + $description, + array('created' => $created, + 'distribute' => false)); + } catch (ClientException $e) { + // Most likely a duplicate -- continue on with the rest! + common_log(LOG_ERR, "Error importing delicious bookmark to $url: " . $e->getMessage()); + return true; + } return true; } From fedfde9bbb3c898328c6395a41c3243d6e97a2bf Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 31 Dec 2010 12:09:54 -0800 Subject: [PATCH 11/15] Bookmark plugin: fixes for bad DOM element nesting in delicious import data delicious bookmark exports use the godawful HTML bookmark file format that ancient versions of Netscape used (and has thus been the common import/export format for bookmarks since the dark ages of the web :) This arranges bookmark entries as an HTML definition list, using a lot of implied close tags (leaving off the and ). DOMDocument->loadHTML() uses libxml2's HTML mode, which generally does ok with muddling through things but apparently is really, really bad about handling those implied close tags. Sequences of adjacent
    elements (eg bookmark without a description, followed by another bookmark "
    "), end up interpreted as nested ("
    ") instead of as siblings ("
    "). The first round of code tried to resolve the nesting inline, but ended up a bit funky in places. I've replaced this with a standalone run through the data to re-order the elements, based on our knowing that
    and
    cannot directly contain one another; once that's done, our main logic loop can be a bit cleaner. I'm not 100% sure it's doing nested sublists correctly, but these don't seem to show up in delicious export (and even if they do, with the way we flatten the input it shouldn't make a difference). Also fixed a clearer edge case where some bookmarks didn't get imported when missing descriptions. --- plugins/Bookmark/deliciousbackupimporter.php | 117 ++++++++++++++++--- 1 file changed, 101 insertions(+), 16 deletions(-) diff --git a/plugins/Bookmark/deliciousbackupimporter.php b/plugins/Bookmark/deliciousbackupimporter.php index 1b55115d6d..bc5a91be80 100644 --- a/plugins/Bookmark/deliciousbackupimporter.php +++ b/plugins/Bookmark/deliciousbackupimporter.php @@ -65,7 +65,7 @@ class DeliciousBackupImporter extends QueueHandler * and import to StatusNet as Bookmark activities. * * The document format is terrible. It consists of a
    with - * a bunch of
    's, occasionally with
    's. + * a bunch of
    's, occasionally with
    's adding descriptions. * There are sometimes

    's lost inside. * * @param array $data pair of user, text @@ -99,6 +99,9 @@ class DeliciousBackupImporter extends QueueHandler } switch (strtolower($child->tagName)) { case '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
    . if (!empty($dt)) { // No DD provided $this->importBookmark($user, $dt); @@ -109,10 +112,13 @@ class DeliciousBackupImporter extends QueueHandler case 'dd': $dd = $child; + // This
    contains a description for the bookmark in + // the preceding
    node. $saved = $this->importBookmark($user, $dt, $dd); $dt = null; $dd = null; + break; case 'p': common_log(LOG_INFO, 'Skipping the

    in the

    .'); 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,21 +162,6 @@ 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
    - // 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'); @@ -188,9 +187,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
    ... + $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
    s or
    s being incorrectly + // interpreted as parent->child trees instead of siblings: + // + // When parsing this input: "
    aaa
    bbb" + // should be equivalent to: "
    aaa
    bbb
    " + // but we're seeing instead: "
    aaa
    bbb
    " + // + // 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
    . + $this->fixList($node); + } + } + } + + $parent = $item->parentNode; + $next = $item->nextSibling; + foreach ($toMove as $node) { + $item->removeChild($node); + $parent->insertBefore($node, $next); + $this->fixListItem($node); + } + } + } From 3368c33be776e41d603ab1c2149fe8b111877beb Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 31 Dec 2010 12:33:51 -0800 Subject: [PATCH 12/15] Bookmark plugin: fix for delicious import with queues enabled We were passing DOM nodes directly into the queues for the final bookmark import stage; unfortunately these don't actually survive serialization. Moved the extraction of properties from the HTML up to the first-stage handler, so now we don't have to worry about moving DOM nodes from one handler to the next. Instead passing an associative array of properties, which is fed into the Bookmark::saveNew by the per-bookmark handler. --- plugins/Bookmark/deliciousbackupimporter.php | 33 +++++++++++++- .../Bookmark/deliciousbookmarkimporter.php | 44 ++++--------------- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/plugins/Bookmark/deliciousbackupimporter.php b/plugins/Bookmark/deliciousbackupimporter.php index bc5a91be80..197c7a143b 100644 --- a/plugins/Bookmark/deliciousbackupimporter.php +++ b/plugins/Bookmark/deliciousbackupimporter.php @@ -162,9 +162,38 @@ class DeliciousBackupImporter extends QueueHandler function importBookmark($user, $dt, $dd = null) { + $as = $dt->getElementsByTagName('a'); + + if ($as->length == 0) { + throw new ClientException(_("No tag in a
    .")); + } + + $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'); } /** diff --git a/plugins/Bookmark/deliciousbookmarkimporter.php b/plugins/Bookmark/deliciousbookmarkimporter.php index 545c336860..018239f49d 100644 --- a/plugins/Bookmark/deliciousbookmarkimporter.php +++ b/plugins/Bookmark/deliciousbookmarkimporter.php @@ -61,52 +61,26 @@ 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; - - $as = $dt->getElementsByTagName('a'); - - if ($as->length == 0) { - throw new ClientException(_("No tag in a
    .")); - } - - $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)); + $profile = Profile::staticGet('id', $data['profile_id']); try { - $saved = Bookmark::saveNew($user->getProfile(), - $title, - $url, - $tags, - $description, - array('created' => $created, + $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 $url: " . $e->getMessage()); + common_log(LOG_ERR, "Error importing delicious bookmark to $data[url]: " . $e->getMessage()); return true; } From ae59046b1e5dd2c55e074e8e3d24e04ce8001b8e Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 31 Dec 2010 12:42:26 -0800 Subject: [PATCH 13/15] Bookmark plugin: tweak post-upload success message to distinguish between "already done" (UnQueueManager) and "started, should finish eventually" (other queue manager) --- plugins/Bookmark/importdelicious.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/plugins/Bookmark/importdelicious.php b/plugins/Bookmark/importdelicious.php index f8529cc914..b98b215717 100644 --- a/plugins/Bookmark/importdelicious.php +++ b/plugins/Bookmark/importdelicious.php @@ -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(); From 98a0d7f538bad0b365adb1ec7eacb8c86c15384b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 3 Jan 2011 10:38:32 -0800 Subject: [PATCH 14/15] Configuration options for using an HTTP proxy We can make a lot of HTTP requests from the server side. This change adds some configuration options for using an HTTP proxy, which can cache hits from multiple sites (good for status.net-like services, for example). --- README | 16 ++++++++++++++++ lib/default.php | 5 +++++ lib/httpclient.php | 8 ++++++++ 3 files changed, 29 insertions(+) diff --git a/README b/README index e2e4c580ef..d972bf5676 100644 --- a/README +++ b/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 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 ======= diff --git a/lib/default.php b/lib/default.php index 6d57c4ef02..ce61de5ea5 100644 --- a/lib/default.php +++ b/lib/default.php @@ -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 diff --git a/lib/httpclient.php b/lib/httpclient.php index 514a5afeb2..04e2b9ac65 100644 --- a/lib/httpclient.php +++ b/lib/httpclient.php @@ -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()); } From fd205546512b75016b1e055aec24eab586ae818b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 3 Jan 2011 10:51:29 -0800 Subject: [PATCH 15/15] Revert "0.9.7alpha1" This reverts commit 3d62c1cf5114760c0a7b97cd69ba363ac059e215. --- lib/common.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common.php b/lib/common.php index 22890b5cb7..5505dc39da 100644 --- a/lib/common.php +++ b/lib/common.php @@ -23,7 +23,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; } define('STATUSNET_BASE_VERSION', '0.9.7'); -define('STATUSNET_LIFECYCLE', 'alpha1'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release' +define('STATUSNET_LIFECYCLE', 'dev'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release' define('STATUSNET_VERSION', STATUSNET_BASE_VERSION . STATUSNET_LIFECYCLE); define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility