]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Radical differences in Bookmark storage
authorEvan Prodromou <evan@status.net>
Sat, 25 Dec 2010 04:34:15 +0000 (20:34 -0800)
committerEvan Prodromou <evan@status.net>
Sat, 25 Dec 2010 04:34:15 +0000 (20:34 -0800)
Had some problems with PuSH and Salmon use of Bookmarks; they were
being required to generate Atom versions of the bookmark _before_ the bookmark was saved.

So, I reversed the order of how things are saved, and associate notices and bookmarks
by URI rather than notice_id.

plugins/Bookmark/Bookmark.php [new file with mode: 0644]
plugins/Bookmark/BookmarkPlugin.php
plugins/Bookmark/Notice_bookmark.php [deleted file]
plugins/Bookmark/deliciousbookmarkimporter.php
plugins/Bookmark/newbookmark.php
plugins/Bookmark/showbookmark.php [new file with mode: 0644]

diff --git a/plugins/Bookmark/Bookmark.php b/plugins/Bookmark/Bookmark.php
new file mode 100644 (file)
index 0000000..aa9e9af
--- /dev/null
@@ -0,0 +1,312 @@
+<?php
+/**
+ * Data class to mark notices as bookmarks
+ *
+ * PHP version 5
+ *
+ * @category Data
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://status.net/
+ *
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+/**
+ * For storing the fact that a notice is a bookmark
+ *
+ * @category Bookmark
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://status.net/
+ *
+ * @see      DB_DataObject
+ */
+
+class Bookmark extends Memcached_DataObject
+{
+    public $__table = 'bookmark'; // table name
+    public $profile_id;           // int(4)  primary_key not_null
+    public $url;                  // varchar(255) primary_key not_null
+    public $title;                // varchar(255)
+    public $description;          // text
+    public $uri;                  // varchar(255)
+    public $url_crc32;            // int(4) not_null
+    public $created;              // datetime
+
+    /**
+     * Get an instance by key
+     *
+     * This is a utility method to get a single instance with a given key value.
+     *
+     * @param string $k Key to use to lookup (usually 'user_id' for this class)
+     * @param mixed  $v Value to lookup
+     *
+     * @return User_greeting_count object found, or null for no hits
+     *
+     */
+
+    function staticGet($k, $v=null)
+    {
+        return Memcached_DataObject::staticGet('Bookmark', $k, $v);
+    }
+
+    /**
+     * return table definition for DB_DataObject
+     *
+     * DB_DataObject needs to know something about the table to manipulate
+     * instances. This method provides all the DB_DataObject needs to know.
+     *
+     * @return array array of column definitions
+     */
+
+    function table()
+    {
+        return array('profile_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'url' => DB_DATAOBJECT_STR,
+                     'title' => DB_DATAOBJECT_STR,
+                     'description' => DB_DATAOBJECT_STR,
+                     'uri' => DB_DATAOBJECT_STR,
+                     'url_crc32' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
+    }
+
+    /**
+     * return key definitions for DB_DataObject
+     *
+     * @return array list of key field names
+     */
+
+    function keys()
+    {
+        return array_keys($this->keyTypes());
+    }
+
+    /**
+     * return key definitions for Memcached_DataObject
+     *
+     * @return array associative array of key definitions
+     */
+
+    function keyTypes()
+    {
+        return array('profile_id' => 'K',
+                     'url' => 'K',
+                     'uri' => 'U');
+    }
+
+    /**
+     * Magic formula for non-autoincrementing integer primary keys
+     *
+     * @return array magic three-false array that stops auto-incrementing.
+     */
+
+    function sequenceKey()
+    {
+        return array(false, false, false);
+    }
+
+    /**
+     * Get a bookmark based on a notice
+     * 
+     * @param Notice $notice Notice to check for
+     *
+     * @return Bookmark found bookmark or null
+     */
+    
+    function getByNotice($notice)
+    {
+        return self::staticGet('uri', $notice->uri);
+    }
+
+    /**
+     * Get the bookmark that a user made for an URL
+     *
+     * @param Profile $profile Profile to check for
+     * @param string  $url     URL to check for
+     *
+     * @return Bookmark bookmark found or null
+     */
+     
+    static function getByURL($profile, $url)
+    {
+        return self::pkeyGet(array('profile_id' => $profile->id,
+                                   'url' => $url));
+        return null;
+    }
+
+    /**
+     * Get the bookmark that a user made for an URL
+     *
+     * @param Profile $profile Profile to check for
+     * @param integer $crc32   CRC-32 of URL to check for
+     *
+     * @return array Bookmark objects found (usually 1 or 0)
+     */
+     
+    static function getByCRC32($profile, $crc32)
+    {
+        $bookmarks = array();
+
+        $nb = new Bookmark();
+        
+        $nb->profile_id = $profile->id;
+        $nb->url_crc32  = $crc32;
+
+        if ($nb->find()) {
+            while ($nb->fetch()) {
+                $bookmarks[] = clone($nb);
+            }
+        }
+
+        return $bookmarks;
+    }
+
+    /**
+     * Save a new notice bookmark
+     *
+     * @param Profile $profile     To save the bookmark for
+     * @param string  $title       Title of the bookmark
+     * @param string  $url         URL of the bookmark
+     * @param mixed   $rawtags     array of tags or string
+     * @param string  $description Description of the bookmark
+     * @param array   $options     Options for the Notice::saveNew()
+     *
+     * @return Notice saved notice
+     */
+
+    static function saveNew($profile, $title, $url, $rawtags, $description,
+                            $options=null)
+    {
+        $nb = self::getByURL($profile, $url);
+
+        if (!empty($nb)) {
+            throw new ClientException(_('Bookmark already exists.'));
+        }
+
+        if (empty($options)) {
+            $options = array();
+        }
+
+        if (is_string($rawtags)) {
+            $rawtags = preg_split('/[\s,]+/', $rawtags);
+        }
+
+        $nb = new Bookmark();
+
+        $nb->profile_id  = $profile->id;
+        $nb->url         = $url;
+        $nb->title       = $title;
+        $nb->description = $description;
+        $nb->url_crc32   = crc32($nb->url);
+        $nb->created     = common_sql_now();
+
+        if (array_key_exists('uri', $options)) {
+            $nb->uri = $options['uri'];
+        } else {
+            $dt = new DateTime($nb->created);
+            // 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.
+            $nb->uri = common_local_url('showbookmark',
+                                        array('user' => $profile->id,
+                                              'created' => $dt->format(DateTime::W3C),
+                                              'crc32' => sprintf('%08x', $nb->url_crc32)));
+        }
+
+        $nb->insert();
+
+        $tags    = array();
+        $replies = array();
+
+        // filter "for:nickname" tags
+
+        foreach ($rawtags as $tag) {
+            if (strtolower(mb_substr($tag, 0, 4)) == 'for:') {
+                $nickname = mb_substr($tag, 4);
+                $other    = common_relative_profile($profile,
+                                                    $nickname);
+                if (!empty($other)) {
+                    $replies[] = $other->getUri();
+                }
+            } else {
+                $tags[] = common_canonical_tag($tag);
+            }
+        }
+
+        // 
+
+        $hashtags = array();
+        $taglinks = array();
+
+        foreach ($tags as $tag) {
+            $hashtags[] = '#'.$tag;
+            $attrs      = array('href' => Notice_tag::url($tag),
+                                'rel'  => $tag,
+                                'class' => 'tag');
+            $taglinks[] = XMLStringer::estring('a', $attrs, $tag);
+        }
+
+        // Use user's preferences for short URLs, if possible
+
+        $user = User::staticGet('id', $profile->id);
+
+        $shortUrl = File_redirection::makeShort($url, 
+                                                empty($user) ? null : $user);
+
+        $content = sprintf(_('"%s" %s %s %s'),
+                           $title,
+                           $shortUrl,
+                           $description,
+                           implode(' ', $hashtags));
+
+        $rendered = sprintf(_('<span class="xfolkentry">'.
+                              '<a class="taggedlink" href="%s">%s</a> '.
+                              '<span class="description">%s</span> '.
+                              '<span class="meta">%s</span>'.
+                              '</span>'),
+                            htmlspecialchars($url),
+                            htmlspecialchars($title),
+                            htmlspecialchars($description),
+                            implode(' ', $taglinks));
+
+        $options = array_merge($options, array('urls' => array($url),
+                                               'rendered' => $rendered,
+                                               'tags' => $tags,
+                                               'replies' => $replies));
+
+        if (!array_key_exists('uri', $options)) {
+            $options['uri'] = $nb->uri;
+        }
+
+        $saved = Notice::saveNew($profile->id,
+                                 $content,
+                                 array_key_exists('source', $options) ?
+                                 $options['source'] : 'web',
+                                 $options);
+
+        return $saved;
+    }
+}
index e18ea25eaa100db123ea74d1024ec6799dc5b8ed..01dd6f1eaae87a60c658ccf7e60a417b4af7a22a 100644 (file)
@@ -63,23 +63,52 @@ class BookmarkPlugin extends Plugin
 
         // For storing user-submitted flags on profiles
 
-        $schema->ensureTable('notice_bookmark',
-                             array(new ColumnDef('notice_id',
+        $schema->ensureTable('bookmark',
+                             array(new ColumnDef('profile_id',
                                                  'integer',
                                                  null,
                                                  false,
                                                  'PRI'),
+                                   new ColumnDef('url',
+                                                 'varchar',
+                                                 255,
+                                                 false,
+                                                 'PRI'),
                                    new ColumnDef('title',
                                                  'varchar',
                                                  255),
                                    new ColumnDef('description',
-                                                 'text')));
+                                                 'text'),
+                                   new ColumnDef('uri',
+                                                 'varchar',
+                                                 255,
+                                                 false,
+                                                 'UNI'),
+                                   new ColumnDef('url_crc32',
+                                                 'integer',
+                                                 null,
+                                                 false,
+                                                 'MUL'),
+                                   new ColumnDef('created',
+                                                 'datetime',
+                                                 null,
+                                                 false,
+                                                 'MUL')));
+
+        try {
+            $schema->createIndex('bookmark', 
+                                 array('profile_id', 
+                                       'url_crc32'),
+                                 'bookmark_profile_url_idx');
+        } catch (Exception $e) {
+            common_log(LOG_ERR, $e->getMessage());
+        }
 
         return true;
     }
 
     /**
-     * When a notice is deleted, delete the related Notice_bookmark
+     * When a notice is deleted, delete the related Bookmark
      *
      * @param Notice $notice Notice being deleted
      * 
@@ -88,7 +117,7 @@ class BookmarkPlugin extends Plugin
 
     function onNoticeDeleteRelated($notice)
     {
-        $nb = Notice_bookmark::staticGet('notice_id', $notice->id);
+        $nb = Bookmark::getByNotice($notice);
 
         if (!empty($nb)) {
             $nb->delete();
@@ -129,7 +158,7 @@ class BookmarkPlugin extends Plugin
         case 'BookmarkpopupAction':
             include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
             return false;
-        case 'Notice_bookmark':
+        case 'Bookmark':
             include_once $dir.'/'.$cls.'.php';
             return false;
         case 'BookmarkForm':
@@ -158,6 +187,12 @@ class BookmarkPlugin extends Plugin
 
         $m->connect('main/bookmark/popup', array('action' => 'bookmarkpopup'));
 
+        $m->connect('bookmark/:user/:created/:crc32',
+                    array('action' => 'showbookmark'),
+                    array('user' => '[0-9]+',
+                          'created' => '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z',
+                          'crc32' => '[0-9A-F]{8}'));
+
         return true;
     }
 
@@ -171,8 +206,7 @@ class BookmarkPlugin extends Plugin
 
     function onStartShowNoticeItem($nli)
     {
-        $nb = Notice_bookmark::staticGet('notice_id',
-                                         $nli->notice->id);
+        $nb = Bookmark::getByNotice($nli->notice);
 
         if (!empty($nb)) {
 
@@ -279,8 +313,7 @@ class BookmarkPlugin extends Plugin
         common_log(LOG_INFO,
                    "Checking {$notice->uri} to see if it's a bookmark.");
 
-        $nb = Notice_bookmark::staticGet('notice_id',
-                                         $notice->id);
+        $nb = Bookmark::getByNotice($notice);
                                          
         if (!empty($nb)) {
 
@@ -485,7 +518,7 @@ class BookmarkPlugin extends Plugin
             }
         }
 
-        Notice_bookmark::saveNew($author->localProfile(),
+        Bookmark::saveNew($author->localProfile(),
                                  $bookmark->title,
                                  $url,
                                  $tags,
diff --git a/plugins/Bookmark/Notice_bookmark.php b/plugins/Bookmark/Notice_bookmark.php
deleted file mode 100644 (file)
index 3a7d0ed..0000000
+++ /dev/null
@@ -1,256 +0,0 @@
-<?php
-/**
- * Data class to mark notices as bookmarks
- *
- * PHP version 5
- *
- * @category Data
- * @package  StatusNet
- * @author   Evan Prodromou <evan@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link     http://status.net/
- *
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2009, StatusNet, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-if (!defined('STATUSNET')) {
-    exit(1);
-}
-
-/**
- * For storing the fact that a notice is a bookmark
- *
- * @category Bookmark
- * @package  StatusNet
- * @author   Evan Prodromou <evan@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link     http://status.net/
- *
- * @see      DB_DataObject
- */
-
-class Notice_bookmark extends Memcached_DataObject
-{
-    public $__table = 'notice_bookmark'; // table name
-    public $notice_id;                   // int(4)  primary_key not_null
-    public $title;                       // varchar(255)
-    public $description;                 // text
-
-    /**
-     * Get an instance by key
-     *
-     * This is a utility method to get a single instance with a given key value.
-     *
-     * @param string $k Key to use to lookup (usually 'user_id' for this class)
-     * @param mixed  $v Value to lookup
-     *
-     * @return User_greeting_count object found, or null for no hits
-     *
-     */
-
-    function staticGet($k, $v=null)
-    {
-        return Memcached_DataObject::staticGet('Notice_bookmark', $k, $v);
-    }
-
-    /**
-     * return table definition for DB_DataObject
-     *
-     * DB_DataObject needs to know something about the table to manipulate
-     * instances. This method provides all the DB_DataObject needs to know.
-     *
-     * @return array array of column definitions
-     */
-
-    function table()
-    {
-        return array('notice_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
-                     'title' => DB_DATAOBJECT_STR,
-                     'description' => DB_DATAOBJECT_STR);
-    }
-
-    /**
-     * return key definitions for DB_DataObject
-     *
-     * @return array list of key field names
-     */
-
-    function keys()
-    {
-        return array_keys($this->keyTypes());
-    }
-
-    /**
-     * return key definitions for Memcached_DataObject
-     *
-     * @return array associative array of key definitions
-     */
-
-    function keyTypes()
-    {
-        return array('notice_id' => 'K');
-    }
-
-    /**
-     * Magic formula for non-autoincrementing integer primary keys
-     *
-     * @return array magic three-false array that stops auto-incrementing.
-     */
-
-    function sequenceKey()
-    {
-        return array(false, false, false);
-    }
-
-    /**
-     * Get the bookmark that a user made for an URL
-     *
-     * @param Profile $profile Profile to check for
-     * @param string  $url     URL to check for
-     *
-     * @return Notice_bookmark bookmark found or null
-     */
-     
-    static function getByURL($profile, $url)
-    {
-        $file = File::staticGet('url', $url);
-        if (!empty($file)) {
-            $f2p = new File_to_post();
-
-            $f2p->file_id = $file->id;
-            if ($f2p->find()) {
-                while ($f2p->fetch()) {
-                    $n = Notice::staticGet('id', $f2p->post_id);
-                    if (!empty($n)) {
-                        if ($n->profile_id == $profile->id) {
-                            $nb = Notice_bookmark::staticGet('notice_id', $n->id);
-                            if (!empty($nb)) {
-                                return $nb;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Save a new notice bookmark
-     *
-     * @param Profile $profile     To save the bookmark for
-     * @param string  $title       Title of the bookmark
-     * @param string  $url         URL of the bookmark
-     * @param mixed   $rawtags     array of tags or string
-     * @param string  $description Description of the bookmark
-     * @param array   $options     Options for the Notice::saveNew()
-     *
-     * @return Notice saved notice
-     */
-
-    static function saveNew($profile, $title, $url, $rawtags, $description,
-                            $options=null)
-    {
-        $nb = self::getByURL($profile, $url);
-
-        if (!empty($nb)) {
-            throw new ClientException(_('Bookmark already exists.'));
-        }
-
-        if (empty($options)) {
-            $options = array();
-        }
-
-        if (is_string($rawtags)) {
-            $rawtags = preg_split('/[\s,]+/', $rawtags);
-        }
-
-        $tags    = array();
-        $replies = array();
-
-        // filter "for:nickname" tags
-
-        foreach ($rawtags as $tag) {
-            if (strtolower(mb_substr($tag, 0, 4)) == 'for:') {
-                $nickname = mb_substr($tag, 4);
-                $other    = common_relative_profile($profile,
-                                                    $nickname);
-                if (!empty($other)) {
-                    $replies[] = $other->getUri();
-                }
-            } else {
-                $tags[] = common_canonical_tag($tag);
-            }
-        }
-
-        $hashtags = array();
-        $taglinks = array();
-
-        foreach ($tags as $tag) {
-            $hashtags[] = '#'.$tag;
-            $attrs      = array('href' => Notice_tag::url($tag),
-                                'rel'  => $tag,
-                                'class' => 'tag');
-            $taglinks[] = XMLStringer::estring('a', $attrs, $tag);
-        }
-
-        // Use user's preferences for short URLs, if possible
-
-        $user = User::staticGet('id', $profile->id);
-
-        $shortUrl = File_redirection::makeShort($url, 
-                                                empty($user) ? null : $user);
-
-        $content = sprintf(_('"%s" %s %s %s'),
-                           $title,
-                           $shortUrl,
-                           $description,
-                           implode(' ', $hashtags));
-
-        $rendered = sprintf(_('<span class="xfolkentry">'.
-                              '<a class="taggedlink" href="%s">%s</a> '.
-                              '<span class="description">%s</span> '.
-                              '<span class="meta">%s</span>'.
-                              '</span>'),
-                            htmlspecialchars($url),
-                            htmlspecialchars($title),
-                            htmlspecialchars($description),
-                            implode(' ', $taglinks));
-
-        $options = array_merge($options, array('urls' => array($url),
-                                               'rendered' => $rendered,
-                                               'tags' => $tags,
-                                               'replies' => $replies));
-
-        $saved = Notice::saveNew($profile->id,
-                                 $content,
-                                 array_key_exists('source', $options) ?
-                                 $options['source'] : 'web',
-                                 $options);
-
-        if (!empty($saved)) {
-            $nb = new Notice_bookmark();
-
-            $nb->notice_id   = $saved->id;
-            $nb->title       = $title;
-            $nb->description = $description;
-            $nb->insert();
-        }
-
-        return $saved;
-    }
-}
index 22ad45882e27abc23793991922120e3557a379d3..061572d95a034ae10fa3a32dd082aceb8ce62956 100644 (file)
@@ -96,7 +96,7 @@ class DeliciousBookmarkImporter extends QueueHandler
         $addDate = $a->getAttribute('add_date');
         $created = common_sql_date(intval($addDate));
 
-        $saved = Notice_bookmark::saveNew($user->getProfile(),
+        $saved = Bookmark::saveNew($user->getProfile(),
                                           $title,
                                           $url,
                                           $tags,
index 63944c817659cef3624755e48e9a014dbddee524..a0cf3fffb297c2e33efa77ab68142783f1547126 100644 (file)
@@ -135,7 +135,7 @@ class NewbookmarkAction extends Action
             }
 
 
-            $saved = Notice_bookmark::saveNew($this->user->getProfile(),
+            $saved = Bookmark::saveNew($this->user->getProfile(),
                                               $this->title,
                                               $this->url,
                                               $this->tags,
diff --git a/plugins/Bookmark/showbookmark.php b/plugins/Bookmark/showbookmark.php
new file mode 100644 (file)
index 0000000..f6213ef
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * Show a single bookmark
+ * 
+ * PHP version 5
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Bookmark
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    // This check helps protect against security problems;
+    // your code file can't be executed directly from the web.
+    exit(1);
+}
+
+/**
+ * Show a single bookmark, with associated information
+ *
+ * @category  Bookmark
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+
+class ShowbookmarkAction extends ShownoticeAction
+{
+    protected $bookmark = null;
+
+    /**
+     * For initializing members of the class.
+     *
+     * @param array $argarray misc. arguments
+     *
+     * @return boolean true
+     */
+
+    function prepare($argarray)
+    {
+        OwnerDesignAction::prepare($argarray);
+
+        $this->user = User::staticGet('id', $this->trimmed('user'));
+
+        if (empty($this->user)) {
+            throw new ClientException(_('No such user.'), 404);
+        }
+
+        $this->profile = $this->user->getProfile();
+
+        if (empty($this->profile)) {
+            throw new ServerException(_('User without a profile.'));
+        }
+
+        $this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
+
+        $crc32 = pack("H*", $this->trimmed('crc32'));
+
+        if (empty($crc32)) {
+            throw new ClientException(_('No such URL.'), 404);
+        }
+        
+        $dt = DateTime::createFromFormat(DateTime::W3C,
+                                         $this->trimmed('created'),
+                                         new DateTimeZone('UTC'));
+
+        if (empty($dt)) {
+            throw new ClientException(_('No such create date.'), 404);
+        }
+
+        $bookmarks = Bookmark::getByCRC32($this->profile,
+                                                 $this->crc32);
+
+        foreach ($bookmarks as $bookmark) {
+            $bdt = new DateTime($bookmark->created);
+            if ($bdt->getTimestamp() == $dt->getTimestamp()) {
+                $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;
+    }
+}