code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-07 08:48+0000\n"
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
"Language-Team: Tamil (http://www.transifex.com/projects/p/gnu-social/language/ta/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ta\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Server exception thrown when blogspam.net returns error status.
#. TRANS: %1$s is the base URL, %2$s is the error (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:87
#, php-format
msgid "Error from %1$s: %2$s"
msgstr ""

#. TRANS: Server exception thrown when blogspam.net returns spam status.
#. TRANS: Does not end with period because of unknown contents for %s (spam
#. match).
#: BlogspamNetPlugin.php:91
#, php-format
msgid "Spam checker results: %s"
msgstr ""

#. TRANS: Server exception thrown when blogspam.net returns an unexpected
#. status.
#. TRANS: %1$s is the base URL, %2$s is the response (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:97
#, php-format
msgid "Unexpected response from %1$s: %2$s"
msgstr ""

#. TRANS: Plugin description.
#: BlogspamNetPlugin.php:159
msgid "Plugin to check submitted notices with blogspam.net."
msgstr ""


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-06 15:04+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Telugu (http://www.transifex.com/projects/p/gnu-social/language/te/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: te\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Server exception thrown when blogspam.net returns error status.
#. TRANS: %1$s is the base URL, %2$s is the error (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:87
#, php-format
msgid "Error from %1$s: %2$s"
msgstr ""

#. TRANS: Server exception thrown when blogspam.net returns spam status.
#. TRANS: Does not end with period because of unknown contents for %s (spam
#. match).
#: BlogspamNetPlugin.php:91
#, php-format
msgid "Spam checker results: %s"
msgstr ""

#. TRANS: Server exception thrown when blogspam.net returns an unexpected
#. status.
#. TRANS: %1$s is the base URL, %2$s is the response (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:97
#, php-format
msgid "Unexpected response from %1$s: %2$s"
msgstr ""

#. TRANS: Plugin description.
#: BlogspamNetPlugin.php:159
msgid "Plugin to check submitted notices with blogspam.net."
msgstr ""

repo_name: piratas/tortuga

license: mit


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-06 15:13+0000\n"
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
"Language-Team: Tagalog (http://www.transifex.com/projects/p/gnu-social/language/tl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: tl\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

#. TRANS: Server exception thrown when blogspam.net returns error status.
#. TRANS: %1$s is the base URL, %2$s is the error (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:87
#, php-format
msgid "Error from %1$s: %2$s"
msgstr "Kamalian mula sa %1$s: %2$s"

#. TRANS: Server exception thrown when blogspam.net returns spam status.
#. TRANS: Does not end with period because of unknown contents for %s (spam
#. match).
#: BlogspamNetPlugin.php:91
#, php-format
msgid "Spam checker results: %s"
msgstr "Mga kinalabasan ng pangsuri ng basurang liham: %s"

#. TRANS: Server exception thrown when blogspam.net returns an unexpected
#. status.
#. TRANS: %1$s is the base URL, %2$s is the response (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:97
#, php-format
msgid "Unexpected response from %1$s: %2$s"
msgstr "Hindi inaasahang tugon mula sa %1$s: %2$s"

#. TRANS: Plugin description.
#: BlogspamNetPlugin.php:159
msgid "Plugin to check submitted notices with blogspam.net."
msgstr "Pamasak upang masuri ang ipinasang mga pabatid sa pamamagitan ng blogspam.net."


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-06 15:04+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Turkish (http://www.transifex.com/projects/p/gnu-social/language/tr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: tr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

#. TRANS: Server exception thrown when blogspam.net returns error status.
#. TRANS: %1$s is the base URL, %2$s is the error (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:87
#, php-format
msgid "Error from %1$s: %2$s"
msgstr ""

#. TRANS: Server exception thrown when blogspam.net returns spam status.
#. TRANS: Does not end with period because of unknown contents for %s (spam
#. match).
#: BlogspamNetPlugin.php:91
#, php-format
msgid "Spam checker results: %s"
msgstr ""

#. TRANS: Server exception thrown when blogspam.net returns an unexpected
#. status.
#. TRANS: %1$s is the base URL, %2$s is the response (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:97
#, php-format
msgid "Unexpected response from %1$s: %2$s"
msgstr ""

#. TRANS: Plugin description.
#: BlogspamNetPlugin.php:159
msgid "Plugin to check submitted notices with blogspam.net."
msgstr ""


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-06 15:13+0000\n"
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
"Language-Team: Ukrainian (http://www.transifex.com/projects/p/gnu-social/language/uk/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: uk\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"

#. TRANS: Server exception thrown when blogspam.net returns error status.
#. TRANS: %1$s is the base URL, %2$s is the error (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:87
#, php-format
msgid "Error from %1$s: %2$s"
msgstr "Помилка з %1$s: %2$s"

#. TRANS: Server exception thrown when blogspam.net returns spam status.
#. TRANS: Does not end with period because of unknown contents for %s (spam
#. match).
#: BlogspamNetPlugin.php:91
#, php-format
msgid "Spam checker results: %s"
msgstr "Результати перевірки спаму: %s"

#. TRANS: Server exception thrown when blogspam.net returns an unexpected
#. status.
#. TRANS: %1$s is the base URL, %2$s is the response (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:97
#, php-format
msgid "Unexpected response from %1$s: %2$s"
msgstr "Неочікувана відповідь від %1$s: %2$s"

#. TRANS: Plugin description.
#: BlogspamNetPlugin.php:159
msgid "Plugin to check submitted notices with blogspam.net."
msgstr "Додаток для перевірки вказаних повідомлень на blogspam.net."


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-06 15:04+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Urdu (Pakistan) (http://www.transifex.com/projects/p/gnu-social/language/ur_PK/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ur_PK\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Server exception thrown when blogspam.net returns error status.
#. TRANS: %1$s is the base URL, %2$s is the error (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:87
#, php-format
msgid "Error from %1$s: %2$s"
msgstr ""

#. TRANS: Server exception thrown when blogspam.net returns spam status.
#. TRANS: Does not end with period because of unknown contents for %s (spam
#. match).
#: BlogspamNetPlugin.php:91
#, php-format
msgid "Spam checker results: %s"
msgstr ""

#. TRANS: Server exception thrown when blogspam.net returns an unexpected
#. status.
#. TRANS: %1$s is the base URL, %2$s is the response (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:97
#, php-format
msgid "Unexpected response from %1$s: %2$s"
msgstr ""

#. TRANS: Plugin description.
#: BlogspamNetPlugin.php:159
msgid "Plugin to check submitted notices with blogspam.net."
msgstr ""


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-06 15:04+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Vietnamese (http://www.transifex.com/projects/p/gnu-social/language/vi/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: vi\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#. TRANS: Server exception thrown when blogspam.net returns error status.
#. TRANS: %1$s is the base URL, %2$s is the error (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:87
#, php-format
msgid "Error from %1$s: %2$s"
msgstr ""

#. TRANS: Server exception thrown when blogspam.net returns spam status.
#. TRANS: Does not end with period because of unknown contents for %s (spam
#. match).
#: BlogspamNetPlugin.php:91
#, php-format
msgid "Spam checker results: %s"
msgstr ""

#. TRANS: Server exception thrown when blogspam.net returns an unexpected
#. status.
#. TRANS: %1$s is the base URL, %2$s is the response (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:97
#, php-format
msgid "Unexpected response from %1$s: %2$s"
msgstr ""

#. TRANS: Plugin description.
#: BlogspamNetPlugin.php:159
msgid "Plugin to check submitted notices with blogspam.net."
msgstr ""


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-06 15:13+0000\n"
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
"Language-Team: Chinese (China) (http://www.transifex.com/projects/p/gnu-social/language/zh_CN/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#. TRANS: Server exception thrown when blogspam.net returns error status.
#. TRANS: %1$s is the base URL, %2$s is the error (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:87
#, php-format
msgid "Error from %1$s: %2$s"
msgstr ""

#. TRANS: Server exception thrown when blogspam.net returns spam status.
#. TRANS: Does not end with period because of unknown contents for %s (spam
#. match).
#: BlogspamNetPlugin.php:91
#, php-format
msgid "Spam checker results: %s"
msgstr ""

#. TRANS: Server exception thrown when blogspam.net returns an unexpected
#. status.
#. TRANS: %1$s is the base URL, %2$s is the response (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:97
#, php-format
msgid "Unexpected response from %1$s: %2$s"
msgstr ""

#. TRANS: Plugin description.
#: BlogspamNetPlugin.php:159
msgid "Plugin to check submitted notices with blogspam.net."
msgstr "通过 blogspam.net 检查发布的消息的插件。"


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-06 15:04+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p/gnu-social/language/zh_TW/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: zh_TW\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#. TRANS: Server exception thrown when blogspam.net returns error status.
#. TRANS: %1$s is the base URL, %2$s is the error (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:87
#, php-format
msgid "Error from %1$s: %2$s"
msgstr ""

#. TRANS: Server exception thrown when blogspam.net returns spam status.
#. TRANS: Does not end with period because of unknown contents for %s (spam
#. match).
#: BlogspamNetPlugin.php:91
#, php-format
msgid "Spam checker results: %s"
msgstr ""

#. TRANS: Server exception thrown when blogspam.net returns an unexpected
#. status.
#. TRANS: %1$s is the base URL, %2$s is the response (unknown contents; no
#. period).
#: BlogspamNetPlugin.php:97
#, php-format
msgid "Unexpected response from %1$s: %2$s"
msgstr ""

#. TRANS: Plugin description.
#: BlogspamNetPlugin.php:159
msgid "Plugin to check submitted notices with blogspam.net."
msgstr ""


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2010, StatusNet, Inc.
 *
 * A plugin to enable social-bookmarking functionality
 *
 * 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  SocialBookmark
 * @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')) {
    exit(1);
}

/**
 * Bookmark plugin main class
 *
 * @category  Bookmark
 * @package   StatusNet
 * @author    Brion Vibber <brionv@status.net>
 * @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 BookmarkPlugin extends MicroAppPlugin
{
    const VERSION         = '0.1';
    const IMPORTDELICIOUS = 'BookmarkPlugin:IMPORTDELICIOUS';

    var $oldSaveNew = true;

    /**
     * Authorization for importing delicious bookmarks
     *
     * By default, everyone can import bookmarks except silenced people.
     *
     * @param Profile $profile Person whose rights to check
     * @param string  $right   Right to check; const value
     * @param boolean &$result Result of the check, writeable
     *
     * @return boolean hook value
     */
    function onUserRightsCheck($profile, $right, &$result)
    {
        if ($right == self::IMPORTDELICIOUS) {
            $result = !$profile->isSilenced();
            return false;
        }
        return true;
    }

    /**
     * Database schema setup
     *
     * @see Schema
     * @see ColumnDef
     *
     * @return boolean hook value; true means continue processing, false means stop.
     */
    function onCheckSchema()
    {
        $schema = Schema::get();

        $schema->ensureTable('bookmark', Bookmark::schemaDef());

        return true;
    }

    /**
     * Show the CSS necessary for this plugin
     *
     * @param Action $action the action being run
     *
     * @return boolean hook value
     */
    function onEndShowStyles($action)
    {
        $action->cssLink($this->path('css/bookmark.css'));
        return true;
    }

    function onEndShowScripts($action)
    {
        $action->script($this->path('js/bookmark.js'));
        return true;
    }

    /**
     * Map URLs to actions
     *
     * @param URLMapper $m path-to-action mapper
     *
     * @return boolean hook value; true means continue processing, false means stop.
     */
    public function onRouterInitialized(URLMapper $m)
    {
        if (common_config('singleuser', 'enabled')) {
            $nickname = User::singleUserNickname();
            $m->connect('bookmarks',
                        array('action' => 'bookmarks', 'nickname' => $nickname));
            $m->connect('bookmarks/rss',
                        array('action' => 'bookmarksrss', 'nickname' => $nickname));
        } else {
            $m->connect(':nickname/bookmarks',
                        array('action' => 'bookmarks'),
                        array('nickname' => Nickname::DISPLAY_FMT));
            $m->connect(':nickname/bookmarks/rss',
                        array('action' => 'bookmarksrss'),
                        array('nickname' => Nickname::DISPLAY_FMT));
        }

        $m->connect('api/bookmarks/:id.:format',
                    array('action' => 'ApiTimelineBookmarks',
                          'id' => Nickname::INPUT_FMT,
                          'format' => '(xml|json|rss|atom|as)'));

        $m->connect('main/bookmark/new',
                    array('action' => 'newbookmark'),
                    array('id' => '[0-9]+'));

        $m->connect('main/bookmark/popup',
                    array('action' => 'bookmarkpopup'));

        $m->connect('main/bookmark/import',
                    array('action' => 'importdelicious'));

        $m->connect('main/bookmark/forurl',
                    array('action' => 'bookmarkforurl'));

        $m->connect('bookmark/:id',
                    array('action' => 'showbookmark'),
                    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'),
                    array('id' => '[0-9]+'));

        return true;
    }

    /**
     * Add our two queue handlers to the queue manager
     *
     * @param QueueManager $qm current queue manager
     *
     * @return boolean hook value
     */
    function onEndInitializeQueueManager($qm)
    {
        $qm->connect('dlcsback', 'DeliciousBackupImporter');
        $qm->connect('dlcsbkmk', 'DeliciousBookmarkImporter');
        return true;
    }

    /**
     * Plugin version data
     *
     * @param array &$versions array of version data
     *
     * @return value
     */
    function onPluginVersion(&$versions)
    {
        $versions[] = array('name' => 'Bookmark',
                            'version' => self::VERSION,
                            'author' => 'Evan Prodromou, Stephane Berube, Jean Baptiste Favre',
                            'homepage' => 'http://status.net/wiki/Plugin:Bookmark',
                            'description' =>
                            // TRANS: Plugin description.
                            _m('Simple extension for supporting bookmarks. ') .
                            'BookmarkList feature has been developped by Stephane Berube. ' .
                            'Integration has been done by Jean Baptiste Favre.');
        return true;
    }

    /**
     * Load our document if requested
     *
     * @param string &$title  Title to fetch
     * @param string &$output HTML to output
     *
     * @return boolean hook value
     */
    function onStartLoadDoc(&$title, &$output)
    {
        if ($title == 'bookmarklet') {
            $filename = INSTALLDIR.'/plugins/Bookmark/bookmarklet';

            $c      = file_get_contents($filename);
            $output = common_markup_to_html($c);
            return false; // success!
        }

        return true;
    }

    /**
     * Show a link to our delicious import page on profile settings form
     *
     * @param Action $action Profile settings action being shown
     *
     * @return boolean hook value
     */
    function onEndProfileSettingsActions($action)
    {
        $user = common_current_user();

        if (!empty($user) && $user->hasRight(self::IMPORTDELICIOUS)) {
            $action->elementStart('li');
            $action->element('a',
                             array('href' => common_local_url('importdelicious')),
                             // TRANS: Link text in proile leading to import form.
                             _m('Import del.icio.us bookmarks'));
            $action->elementEnd('li');
        }

        return true;
    }

    /**
     * Modify the default menu to link to our custom action
     *
     * Using event handlers, it's possible to modify the default UI for pages
     * almost without limit. In this method, we add a menu item to the default
     * primary menu for the interface to link to our action.
     *
     * The Action class provides a rich set of events to hook, as well as output
     * methods.
     *
     * @param Action $action The current action handler. Use this to
     * do any output.
     *
     * @return boolean hook value; true means continue processing, false means stop.
     *
     * @see Action
     */
    function onEndPersonalGroupNav(Menu $menu, Profile $target, Profile $scoped=null)
    {
        $menu->menuItem(common_local_url('bookmarks', array('nickname' => $target->getNickname())),
                          // TRANS: Menu item in sample plugin.
                          _m('Bookmarks'),
                          // TRANS: Menu item title in sample plugin.
                          _m('A list of your bookmarks'), false, 'nav_timeline_bookmarks');
        return true;
    }

    function types()
    {
        return array(ActivityObject::BOOKMARK);
    }

    /**
     * When a notice is deleted, delete the related Bookmark
     *
     * @param Notice $notice Notice being deleted
     *
     * @return boolean hook value
     */
    function deleteRelated(Notice $notice)
    {
    	if ($this->isMyNotice($notice)) {

        	$nb = Bookmark::getByNotice($notice);

        	if (!empty($nb)) {
            	$nb->delete();
        	}
    	}

        return true;
    }

    /**
     * Save a bookmark from an activity
     *
     * @param Activity $activity Activity to save
     * @param Profile  $actor    Profile to use as author
     * @param array    $options  Options to pass to bookmark-saving code
     *
     * @return Notice resulting notice
     */
    function saveNoticeFromActivity(Activity $activity, Profile $actor, array $options=array())
    {
        $bookmark = $activity->objects[0];

        $relLinkEls = ActivityUtils::getLinks($bookmark->element, 'related');

        if (count($relLinkEls) < 1) {
            // TRANS: Client exception thrown when a bookmark is formatted incorrectly.
            throw new ClientException(_m('Expected exactly 1 link '.
                                        'rel=related in a Bookmark.'));
        }

        if (count($relLinkEls) > 1) {
            common_log(LOG_WARNING,
                       "Got too many link rel=related in a Bookmark.");
        }

        $linkEl = $relLinkEls[0];

        $url = $linkEl->getAttribute('href');

        $tags = array();

        foreach ($activity->categories as $category) {
            $tags[] = common_canonical_tag($category->term);
        }

        if (!empty($activity->time)) {
            $options['created'] = common_sql_date($activity->time);
        }

        // Fill in location if available

        $location = $activity->context->location;

        if ($location) {
            $options['lat'] = $location->lat;
            $options['lon'] = $location->lon;
            if ($location->location_id) {
                $options['location_ns'] = $location->location_ns;
                $options['location_id'] = $location->location_id;
            }
        }

        $options['groups']  = array();
        $options['replies'] = array();  // TODO: context->attention

        foreach ($activity->context->attention as $attnUrl=>$type) {
            try {
                $other = Profile::fromUri($attnUrl);
                if ($other->isGroup()) {
                    $options['groups'][] = $other->id;
                } else {
                    $options['replies'][] = $attnUrl;
                }
            } catch (UnknownUriException $e) {
                // We simply don't know this URI, despite lookup attempts.
            }
        }

        // Maintain direct reply associations
        // @fixme what about conversation ID?

        if (!empty($activity->context->replyToID)) {
            $orig = Notice::getKV('uri',
                                      $activity->context->replyToID);
            if (!empty($orig)) {
                $options['reply_to'] = $orig->id;
            }
        }

        return Bookmark::saveNew($actor,
                                 $bookmark->title,
                                 $url,
                                 $tags,
                                 $bookmark->summary,
                                 $options);
    }

    function activityObjectFromNotice(Notice $notice)
    {
        assert($this->isMyNotice($notice));

        common_log(LOG_INFO,
                   "Formatting notice {$notice->uri} as a bookmark.");

        $object = new ActivityObject();
        $nb = Bookmark::getByNotice($notice);

        $object->id      = $notice->uri;
        $object->type    = ActivityObject::BOOKMARK;
        $object->title   = $nb->title;
        $object->summary = $nb->description;
        $object->link    = $notice->getUrl();

        // Attributes of the URL

        $attachments = $notice->attachments();

        if (count($attachments) != 1) {
            // TRANS: Server exception thrown when a bookmark has multiple attachments.
            throw new ServerException(_m('Bookmark notice with the '.
                                        'wrong number of attachments.'));
        }

        $target = $attachments[0];

        $attrs = array('rel' => 'related',
                       'href' => $target->getUrl());

        if (!empty($target->title)) {
            $attrs['title'] = $target->title;
        }

        $object->extra[] = array('link', $attrs, null);

        // Attributes of the thumbnail, if any

        try {
            $thumbnail = $target->getThumbnail();
            $tattrs = array('rel' => 'preview',
                            'href' => $thumbnail->getUrl());

            if (!empty($thumbnail->width)) {
                $tattrs['media:width'] = $thumbnail->width;
            }

            if (!empty($thumbnail->height)) {
                $tattrs['media:height'] = $thumbnail->height;
            }

            $object->extra[] = array('link', $tattrs, null);
        } catch (UnsupportedMediaException $e) {
            // No image thumbnail metadata available
        }

        return $object;
    }

    function entryForm($out)
    {
        return new InitialBookmarkForm($out);
    }

    function tag()
    {
        return 'bookmark';
    }

    function appTitle()
    {
        // TRANS: Application title.
        return _m('TITLE','Bookmark');
    }

    function onEndUpgrade()
    {
        // Version 0.9.x of the plugin didn't stamp notices
        // with verb and object-type (for obvious reasons). Update
        // those notices here.

        $notice = new Notice();

        $notice->whereAdd('exists (select uri from bookmark where bookmark.uri = notice.uri)');
        $notice->whereAdd('((object_type is null) or (object_type = "' .ActivityObject::NOTE.'"))');

        $notice->find();

        while ($notice->fetch()) {
            $original = clone($notice);
            $notice->verb        = ActivityVerb::POST;
            $notice->object_type = ActivityObject::BOOKMARK;
            $notice->update($original);
        }
    }

    public function activityObjectOutputJson(ActivityObject $obj, array &$out)
    {
        assert($obj->type == ActivityObject::BOOKMARK);

        $bm = Bookmark::getKV('uri', $obj->id);

        if (empty($bm)) {
            throw new ServerException("Unknown bookmark: " . $obj->id);
        }

        $out['displayName'] = $bm->title;
        $out['targetUrl']   = $bm->url;

        return true;
    }

    protected function showNoticeItemNotice(NoticeListItem $nli)
    {
        $nli->out->elementStart('div', 'entry-title');
        $nli->showAuthor();
        $nli->showContent();
        $nli->out->elementEnd('div');
    }

    protected function showNoticeContent(Notice $stored, HTMLOutputter $out, Profile $scoped=null)
    {
        $nb = Bookmark::getByNotice($stored);

        if (empty($nb)) {
            common_log(LOG_ERR, "No bookmark for notice {$stored->id}");
            parent::showContent();
            return;
        } else if (empty($nb->url)) {
            common_log(LOG_ERR, "No url for bookmark {$nb->id} for notice {$stored->id}");
            parent::showContent();
            return;
        }

        $profile = $stored->getProfile();

        // Whether to nofollow
        $attrs = array('href' => $nb->url, 'class' => 'bookmark-title');

        $nf = common_config('nofollow', 'external');

        if ($nf == 'never' || ($nf == 'sometimes' and $out instanceof ShowstreamAction)) {
            $attrs['rel'] = 'external';
        } else {
            $attrs['rel'] = 'nofollow external';
        }

        $out->elementStart('h3');
        $out->element('a', $attrs, $nb->title);
        $out->elementEnd('h3');

        // Replies look like "for:" tags
        $replies = $stored->getReplies();
        $tags = $stored->getTags();

        if (!empty($nb->description)) {
            $out->element('p',
                          array('class' => 'bookmark-description'),
                          $nb->description);
        }

        if (!empty($replies) || !empty($tags)) {

            $out->elementStart('ul', array('class' => 'bookmark-tags'));

            foreach ($replies as $reply) {
                $other = Profile::getKV('id', $reply);
                if (!empty($other)) {
                    $out->elementStart('li');
                    $out->element('a', array('rel' => 'tag',
                                             'href' => $other->profileurl,
                                             'title' => $other->getBestName()),
                                  sprintf('for:%s', $other->nickname));
                    $out->elementEnd('li');
                    $out->text(' ');
                }
            }

            foreach ($tags as $tag) {
                $tag = trim($tag);
                if (!empty($tag)) {
                    $out->elementStart('li');
                    $out->element('a',
                                  array('rel' => 'tag',
                                        'href' => Notice_tag::url($tag)),
                                  $tag);
                    $out->elementEnd('li');
                    $out->text(' ');
                }
            }

            $out->elementEnd('ul');
        }

    }
}


code: Depends on the oEmbed plugin (Oembed).

path: plugins/Bookmark/README


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Show a user's bookmark activities
 *
 * PHP version 5
 *
 * LICENCE: 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  API
 * @package   StatusNet
 * @author    Craig Andrews <candrews@integralblue.com>
 * @author    Evan Prodromou <evan@status.net>
 * @author    Zach Copley <zach@status.net>
 * @copyright 2009-2010 StatusNet, Inc.
 * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    exit(1);
}

/**
 * Returns the most recent bookmark activities for the authenticating user or user
 * specified by the ID parameter in the requested format.
 *
 * @category API
 * @package  StatusNet
 * @author   Craig Andrews <candrews@integralblue.com>
 * @author   Evan Prodromou <evan@status.net>
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class ApiTimelineBookmarksAction extends ApiBareAuthAction
{
    var $notices  = null;

    /**
     * Take arguments for running
     *
     * @param array $args $_REQUEST args
     *
     * @return boolean success flag
     */
    function prepare($args)
    {
        parent::prepare($args);

        $this->user = $this->getTargetUser($this->arg('id'));

        if (empty($this->user)) {
            // TRANS: Client error displayed when requesting most recent favourite notices by a user for a non-existing user.
            $this->clientError(_('No such user.'), 404, $this->format);
        }

        $this->notices = $this->getNotices();

        return true;
    }

    /**
     * Handle the request
     *
     * Just show the notices
     *
     * @param array $args $_REQUEST data (unused)
     *
     * @return void
     */
    function handle($args)
    {
        parent::handle($args);
        $this->showTimeline();
    }

    /**
     * Show the timeline of notices
     *
     * @return void
     */
    function showTimeline()
    {
        $profile  = $this->user->getProfile();

        $sitename = common_config('site', 'name');
        $title    = sprintf(
            // TRANS: Title for timeline of most recent favourite notices by a user.
            // TRANS: %1$s is the StatusNet sitename, %2$s is a user nickname.
            _('%1$s / Bookmarks from %2$s'),
            $sitename,
            $this->user->nickname
        );

        $taguribase = TagURI::base();
        $id         = "tag:$taguribase:Bookmarks:" . $this->user->id;

        $subtitle = sprintf(
            // TRANS: Subtitle for timeline of most recent favourite notices by a user.
            // TRANS: %1$s is the StatusNet sitename, %2$s is a user's full name,
            // TRANS: %3$s is a user nickname.
            _('%1$s updates bookmarked by %2$s / %3$s.'),
            $sitename,
            $profile->getBestName(),
            $this->user->nickname
        );

        $logo = $profile->avatarUrl(AVATAR_PROFILE_SIZE);
        $link = common_local_url('bookmarks',
                                array('nickname' => $this->user->nickname));
        $self = $this->getSelfUri();

        switch($this->format) {
        case 'xml':
            $this->showXmlTimeline($this->notices);
            break;
        case 'rss':
            $this->showRssTimeline(
                $this->notices,
                $title,
                $link,
                $subtitle,
                null,
                $logo,
                $self
            );
            break;
        case 'atom':
            header('Content-Type: application/atom+xml; charset=utf-8');

            $atom = new AtomNoticeFeed($this->auth_user);

            $atom->setId($id);
            $atom->setTitle($title);
            $atom->setSubtitle($subtitle);
            $atom->setLogo($logo);
            $atom->setUpdated('now');

            $atom->addLink($link);
            $atom->setSelfLink($self);

            $atom->addEntryFromNotices($this->notices);

            $this->raw($atom->getString());
            break;
        case 'json':
            $this->showJsonTimeline($this->notices);
            break;
        case 'as':
            header('Content-Type: ' . ActivityStreamJSONDocument::CONTENT_TYPE);
            $doc = new ActivityStreamJSONDocument($this->auth_user);
            $doc->setTitle($title);
            $doc->addLink($link,'alternate', 'text/html');
            $doc->addItemsFromNotices($this->notices);
            $this->raw($doc->asString());
            break;
        default:
            // TRANS: Client error displayed when coming across a non-supported API method.
            $this->clientError(_('API method not found.'), $code = 404);
        }
    }

    /**
     * Get notices
     *
     * @return array notices
     */
    function getNotices()
    {
        $notices = array();

        common_debug("since id = " . $this->since_id . " max id = " . $this->max_id);

        $notice = new BookmarksNoticeStream($this->user->id, true);
        $notice = $notice->getNotices(
            ($this->page-1) * $this->count,
            $this->count,
            $this->since_id,
            $this->max_id
        );

        while ($notice->fetch()) {
            $notices[] = clone($notice);
        }

        return $notices;
    }

    /**
     * Is this action read only?
     *
     * @param array $args other arguments
     *
     * @return boolean true
     */
    function isReadOnly($args)
    {
        return true;
    }

    /**
     * When was this feed last modified?
     *
     * @return string datestamp of the latest notice in the stream
     */
    function lastModified()
    {
        if (!empty($this->notices) && (count($this->notices) > 0)) {
            return strtotime($this->notices[0]->created);
        }

        return null;
    }

    /**
     * An entity tag for this stream
     *
     * Returns an Etag based on the action name, language, user ID, and
     * timestamps of the first and last notice in the timeline
     *
     * @return string etag
     */
    function etag()
    {
        if (!empty($this->notices) && (count($this->notices) > 0)) {

            $last = count($this->notices) - 1;

            return '"' . implode(
                ':',
                array($this->arg('action'),
                      common_user_cache_hash($this->auth_user),
                      common_language(),
                      $this->user->id,
                      strtotime($this->notices[0]->created),
                      strtotime($this->notices[$last]->created))
            )
            . '"';
        }

        return null;
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Returns a pre-filled bookmark form for a given URL
 *
 * 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 2011 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);
}

/**
 * Returns a prefilled bookmark form for a given URL
 *
 * @category  Bookmark
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

class BookmarkforurlAction extends Action
{
    protected $url        = null;
    protected $oembed     = null;
    protected $thumbnail  = null;
    protected $title      = null;

    /**
     * For initializing members of the class.
     *
     * @param array $args misc. arguments
     *
     * @return boolean true
     */
    function prepare($args)
    {
        parent::prepare($args);

        if (!$this->isPost()) {
            throw new ClientException(_('POST only'), 405);
        }

        $this->checkSessionToken();
        $this->url = $this->trimmed('url');

        if (empty($this->url)) {
            throw new ClientException(_('URL is required.'), 400);
        }

        if (!common_valid_http_url($this->url)) {
            throw new ClientException(_('Invalid URL.'), 400);
        }

        try {
            // processNew will first try to fetch a locally stored File entry
            $f = File::processNew($this->url);
        } catch (ServerException $e) {
            $f = null;
        }

        // How about now?

        if ($f instanceof File) {
            // FIXME: Use some File metadata Event instead
            $this->oembed    = File_oembed::getKV('file_id', $f->id);
            if ($this->oembed instanceof File_oembed) {
                $this->title = $this->oembed->title;
            }
            $this->thumbnail = File_thumbnail::getKV('file_id', $f->id);
        }

        return true;
    }

    /**
     * Handler method
     *
     * @param array $args is ignored since it's now passed in in prepare()
     *
     * @return void
     */

    function handle($args=null)
    {
        $this->startHTML('text/xml;charset=utf-8');
        $this->elementStart('head');
        $this->element('title', null, _('Bookmark form'));
        $this->elementEnd('head');
        $this->elementStart('body');
        $bf = new BookmarkForm($this, $this->title, $this->url, null, null, $this->thumbnail);
        $bf->show();
        $this->elementEnd('body');
        $this->endHTML();
    }

    /**
     * Return true if read only.
     *
     * MAY override
     *
     * @param array $args other arguments
     *
     * @return boolean is read only action?
     */

    function isReadOnly($args)
    {
        return false;
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Post a new bookmark in a popup window
 *
 * PHP version 5
 *
 * LICENCE: 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    Sarven Capadisli <csarven@status.net>
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2008-2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    exit(1);
}

/**
 * Action for posting a new bookmark
 *
 * @category Bookmark
 * @package  StatusNet
 * @author   Sarven Capadisli <csarven@status.net>
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link     http://status.net/
 */
class BookmarkpopupAction extends NewbookmarkAction
{
    /**
     * Show the title section of the window
     *
     * @return void
     */
    function showTitle()
    {
        $this->element('title',
                       // TRANS: Title for mini-posting window loaded from bookmarklet.
                       // TRANS: %s is the StatusNet site name.
                       null, sprintf(_m('Bookmark on %s'),
                                     common_config('site', 'name')));
    }

    /**
     * Show the header section of the page
     *
     * Shows a stub page and the bookmark form.
     *
     * @return void
     */
    function showHeader()
    {
        $this->elementStart('div', array('id' => 'header'));
        $this->elementStart('address');
        $this->element('a', array('class' => 'url',
                                  'href' => common_local_url('top')),
                         '');
        $this->elementEnd('address');
        if (common_logged_in()) {
            $form = new BookmarkForm($this,
                                     $this->title,
                                     $this->url);
            $form->show();
        }
        $this->elementEnd('div');
    }

    /**
     * Hide the core section of the page
     *
     * @return void
     */
    function showCore()
    {
    }

    /**
     * Hide the footer section of the page
     *
     * @return void
     */
    function showFooter()
    {
    }

    function showScripts()
    {
        parent::showScripts();
        $this->script(Plugin::staticPath('Bookmark', 'bookmarkpopup.js'));
    }
}


code:
<?php
/**
 * Give a warm greeting to our friendly user
 *
 * PHP version 5
 *
 * @category Bookmark
 * @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);
}

/**
 * List currently logged-in user's bookmakrs
 *
 * @category Bookmark
 * @package  StatusNet
 * @author   Stephane Berube <chimo@chromic.org>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     https://github.com/chimo/BookmarkList
 */
class BookmarksAction extends Action
{
    var $user = null;
    var $gc   = null;

    /**
     * Take arguments for running
     *
     * This method is called first, and it lets the action class get
     * all its arguments and validate them. It's also the time
     * to fetch any relevant data from the database.
     *
     * Action classes should run parent::prepare($args) as the first
     * line of this method to make sure the default argument-processing
     * happens.
     *
     * @param array $args $_REQUEST args
     *
     * @return boolean success flag
     */
    function prepare($args)
    {
        parent::prepare($args);

        if (common_config('singleuser', 'enabled')) {
            $nickname = User::singleUserNickname();
        } else {
            // PHP 5.4
            // $nickname = $this->returnToArgs()[1]['nickname'];

            // PHP < 5.4
            $nickname = $this->returnToArgs();
            $nickname = $nickname[1]['nickname'];
        }

        $this->user = User::getKV('nickname', $nickname);

        if (!$this->user) {
            // TRANS: Client error displayed when trying to display bookmarks for a non-existing user.
            $this->clientError(_('No such user.'));
        }

        $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;

        $stream = new BookmarksNoticeStream($this->user->id, true);
        $this->notices = $stream->getNotices(($this->page-1)*NOTICES_PER_PAGE,
                                                NOTICES_PER_PAGE + 1);

        if($this->page > 1 && $this->notices->N == 0) {
            throw new ClientException(_('No such page.'), 404);
        }

        return true;
    }

    /**
     * Handle request
     *
     * This is the main method for handling a request. Note that
     * most preparation should be done in the prepare() method;
     * by the time handle() is called the action should be
     * more or less ready to go.
     *
     * @param array $args $_REQUEST args; handled in prepare()
     *
     * @return void
     */
    function handle($args)
    {
        parent::handle($args);
        $this->showPage();
    }

    /**
     * Title of this page
     *
     * Override this method to show a custom title.
     *
     * @return string Title of the page
     */
    function title()
    {

        if (empty($this->user)) {
            // TRANS: Page title for sample plugin.
            return _m('Log in');
        } else {
            // TRANS: Page title for sample plugin. %s is a user nickname.
            return sprintf(_m('%s\'s bookmarks'), $this->user->nickname);
        }
    }

    /**
     * Feeds for the <head> section
     *
     * @return array Feed objects to show
     */
    function getFeeds()
    {
        return array(new Feed(Feed::JSON,
                              common_local_url('ApiTimelineBookmarks',
                                               array(
                                                    'id' => $this->user->nickname,
                                                    'format' => 'as')),
                              // TRANS: Feed link text. %s is a username.
                              sprintf(_('Feed for bookmarks of %s (Activity Streams JSON)'),
                                      $this->user->nickname)),
                     new Feed(Feed::RSS1,
                              common_local_url('bookmarksrss',
                                               array('nickname' => $this->user->nickname)),
                              // TRANS: Feed link text. %s is a username.
                              sprintf(_('Feed for bookmarks of %s (RSS 1.0)'),
                                      $this->user->nickname)),
                     new Feed(Feed::RSS2,
                              common_local_url('ApiTimelineBookmarks',
                                               array(
                                                    'id' => $this->user->nickname,
                                                    'format' => 'rss')),
                              // TRANS: Feed link text. %s is a username.
                              sprintf(_('Feed for bookmarks of %s (RSS 2.0)'),
                                      $this->user->nickname)),
                     new Feed(Feed::ATOM,
                              common_local_url('ApiTimelineBookmarks',
                                               array(
                                                    'id' => $this->user->nickname,
                                                    'format' => 'atom')),
                              // TRANS: Feed link text. %s is a username.
                              sprintf(_('Feed for bookmarks of %s (Atom)'),
                                      $this->user->nickname)));
    }

    /**
     * Show content in the content area
     *
     * The default StatusNet page has a lot of decorations: menus,
     * logos, tabs, all that jazz. This method is used to show
     * content in the content area of the page; it's the main
     * thing you want to overload.
     *
     * This method also demonstrates use of a plural localized string.
     *
     * @return void
     */
    function showContent()
    {

        $nl = new NoticeList($this->notices, $this);

        $cnt = $nl->show();

        if ($cnt == 0) {
            $this->showEmptyList();
        }

        $this->pagination($this->page > 1,
                $cnt > NOTICES_PER_PAGE,
                $this->page, 'bookmarks',
                array('nickname' => $this->user->nickname));
    }

    function showEmptyList() {
        $message = sprintf(_('This is %1$s\'s bookmark stream, but %1$s hasn\'t bookmarked anything yet.'), $this->user->nickname) . ' ';

        $this->elementStart('div', 'guide');
        $this->raw(common_markup_to_html($message));
        $this->elementEnd('div');
    }

    /**
     * Return true if read only.
     *
     * Some actions only read from the database; others read and write.
     * The simple database load-balancer built into StatusNet will
     * direct read-only actions to database mirrors (if they are configured),
     * and read-write actions to the master database.
     *
     * This defaults to false to avoid data integrity issues, but you
     * should make sure to overload it for performance gains.
     *
     * @param array $args other arguments, if RO/RW status depends on them.
     *
     * @return boolean is read only action?
     */
    function isReadOnly($args)
    {
        return true;
    }
}


code:
<?php
/**
 * RSS feed for user bookmarks action class.
 *
 * PHP version 5
 *
 * @category Action
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @author   Robin Millette <millette@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) 2008, 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') && !defined('LACONICA')) {
    exit(1);
}

require_once INSTALLDIR.'/lib/rssaction.php';

/**
 * RSS feed for user bookmarks action class.
 *
 * Formatting of RSS handled by Rss10Action
 *
 * @category Action
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @author   Robin Millette <millette@status.net>
 * @author   Zach Copley <zach@status.net>
 * @author   Stephane Berube <chimo@chromic.org> (modified 'favoritesrss.php' to show bookmarks instead)
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 */
class BookmarksrssAction extends Rss10Action
{
    /** The user whose bookmarks to display */

    var $user = null;

    /**
     * Find the user to display by supplied nickname
     *
     * @param array $args Arguments from $_REQUEST
     *
     * @return boolean success
     */
    function prepare($args)
    {
        parent::prepare($args);

        $nickname   = $this->trimmed('nickname');
        $this->user = User::getKV('nickname', $nickname);

        if (!$this->user) {
            // TRANS: Client error displayed when trying to get the RSS feed with bookmarks of a user that does not exist.
            $this->clientError(_('No such user.'));
        } else {
            $this->notices = $this->getNotices($this->limit);
            return true;
        }
    }

    /**
     * Get notices
     *
     * @param integer $limit max number of notices to return
     *
     * @return array notices
     */
    function getNotices($limit=0)
    {
        $user    = $this->user;

        $notice = new BookmarksNoticeStream($this->user->id, true);
        $notice = $notice->getNotices(0, NOTICES_PER_PAGE);

        $notices = array();
        while ($notice->fetch()) {
            $notices[] = clone($notice);
        }
        return $notices;
    }

     /**
     * Get channel.
     *
     * @return array associative array on channel information
     */
    function getChannel()
    {
        $user = $this->user;
        $c    = array('url' => common_local_url('bookmarksrss',
                                        array('nickname' =>
                                        $user->nickname)),
                   // TRANS: Title of RSS feed with bookmarks of a user.
                   // TRANS: %s is a user's nickname.
                   'title' => sprintf(_("%s's bookmarks"), $user->nickname),
                   'link' => common_local_url('bookmarks',
                                        array('nickname' =>
                                        $user->nickname)),
                   // TRANS: Desciption of RSS feed with bookmarks of a user.
                   // TRANS: %1$s is a user's nickname, %2$s is the name of the StatusNet site.
                   'description' => sprintf(_('Bookmarks posted by %1$s on %2$s!'),
                                        $user->nickname, common_config('site', 'name')));
        return $c;
    }

    /**
     * Get image.
     *
     * @return void
    */
    function getImage()
    {
        return null;
    }

}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2010, StatusNet, Inc.
 *
 * Import del.icio.us bookmarks backups
 *
 * 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);
}

/**
 * UI for importing del.icio.us bookmark backups
 *
 * @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 ImportdeliciousAction extends Action
{
    protected $success = false;
    private $inprogress = false;

    /**
     * Return the title of the page
     *
     * @return string page title
     */
    function title()
    {
        // TRANS: Title for page to import del.icio.us bookmark backups on.
        return _m("Import del.icio.us bookmarks");
    }

    /**
     * For initializing members of the class.
     *
     * @param array $argarray misc. arguments
     *
     * @return boolean true
     */
    function prepare($argarray)
    {
        parent::prepare($argarray);

        $cur = common_current_user();

        if (empty($cur)) {
            // TRANS: Client exception thrown when trying to import bookmarks without being logged in.
            throw new ClientException(_m('Only logged-in users can '.
                                        'import del.icio.us backups.'),
                                      403);
        }

        if (!$cur->hasRight(BookmarkPlugin::IMPORTDELICIOUS)) {
            // TRANS: Client exception thrown when trying to import bookmarks without having the rights to do so.
            throw new ClientException(_m('You may not restore your account.'), 403);
        }

        return true;
    }

    /**
     * Handler method
     *
     * @param array $argarray is ignored since it's now passed in in prepare()
     *
     * @return void
     */
    function handle($argarray=null)
    {
        parent::handle($argarray);

        if ($this->isPost()) {
            $this->importDelicious();
        } else {
            $this->showPage();
        }
        return;
    }

    /**
     * Queue a file for importation
     *
     * Uses the DeliciousBackupImporter class; may take a long time!
     *
     * @return void
     */
    function importDelicious()
    {
        $this->checkSessionToken();

        if (!isset($_FILES[ImportDeliciousForm::FILEINPUT]['error'])) {
            // TRANS: Client exception thrown when trying to import bookmarks and upload fails.
            throw new ClientException(_m('No uploaded file.'));
        }

        switch ($_FILES[ImportDeliciousForm::FILEINPUT]['error']) {
        case UPLOAD_ERR_OK: // success, jump out
            break;
        case UPLOAD_ERR_INI_SIZE:
            // TRANS: Client exception thrown when an uploaded file is too large.
            throw new ClientException(_m('The uploaded file exceeds the ' .
                'upload_max_filesize directive in php.ini.'));
            return;
        case UPLOAD_ERR_FORM_SIZE:
            throw new ClientException(
            // TRANS: Client exception thrown when an uploaded file is too large.
                _m('The uploaded file exceeds the MAX_FILE_SIZE directive' .
                ' that was specified in the HTML form.'));
            return;
        case UPLOAD_ERR_PARTIAL:
            @unlink($_FILES[ImportDeliciousForm::FILEINPUT]['tmp_name']);
            // TRANS: Client exception thrown when a file was only partially uploaded.
            throw new ClientException(_m('The uploaded file was only' .
                ' partially uploaded.'));
            return;
        case UPLOAD_ERR_NO_FILE:
            // No file; probably just a non-AJAX submission.
            // TRANS: Client exception thrown when a file upload has failed.
            throw new ClientException(_m('No uploaded file.'));
            return;
        case UPLOAD_ERR_NO_TMP_DIR:
            // TRANS: Client exception thrown when a temporary folder is not present.
            throw new ClientException(_m('Missing a temporary folder.'));
            return;
        case UPLOAD_ERR_CANT_WRITE:
            // TRANS: Client exception thrown when writing to disk is not possible.
            throw new ClientException(_m('Failed to write file to disk.'));
            return;
        case UPLOAD_ERR_EXTENSION:
            // TRANS: Client exception thrown when a file upload has been stopped.
            throw new ClientException(_m('File upload stopped by extension.'));
            return;
        default:
            common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " .
                $_FILES[ImportDeliciousForm::FILEINPUT]['error']);
            // TRANS: Client exception thrown when a file upload operation has failed.
            throw new ClientException(_m('System error uploading file.'));
            return;
        }

        $filename = $_FILES[ImportDeliciousForm::FILEINPUT]['tmp_name'];

        try {
            if (!file_exists($filename)) {
                // TRANS: Server exception thrown when a file upload cannot be found.
                // TRANS: %s is the file that could not be found.
                throw new ServerException(sprintf(_m('No such file "%s".'),$filename));
            }

            if (!is_file($filename)) {
                // TRANS: Server exception thrown when a file upload is incorrect.
                // TRANS: %s is the irregular file.
                throw new ServerException(sprintf(_m('Not a regular file: "%s".'),$filename));
            }

            if (!is_readable($filename)) {
                // TRANS: Server exception thrown when a file upload is not readable.
                // TRANS: %s is the file that could not be read.
                throw new ServerException(sprintf(_m('File "%s" not readable.'),$filename));
            }

            common_debug(sprintf("Getting backup from file '%s'.", $filename));

            $html = file_get_contents($filename);

            // Enqueue for processing.

            $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();

        } catch (Exception $e) {
            // Delete the file and re-throw
            @unlink($_FILES[ImportDeliciousForm::FILEINPUT]['tmp_name']);
            throw $e;
        }
    }

    /**
     * Show the content of the page
     *
     * @return void
     */
    function showContent()
    {
        if ($this->success) {
            $this->element('p', null,
                           // TRANS: Success message after importing bookmarks.
                           _m('Bookmarks have been imported. Your bookmarks should now appear in search and your profile page.'));
        } else if ($this->inprogress) {
            $this->element('p', null,
                           // TRANS: Busy message for importing bookmarks.
                           _m('Bookmarks are being imported. Please wait a few minutes for results.'));
        } else {
            $form = new ImportDeliciousForm($this);
            $form->show();
        }
    }

    /**
     * Return true if read only.
     *
     * MAY override
     *
     * @param array $args other arguments
     *
     * @return boolean is read only action?
     */
    function isReadOnly($args)
    {
        return !$this->isPost();
    }
}

/**
 * A form for backing up the account.
 *
 * @category  Account
 * @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 ImportDeliciousForm extends Form
{
    const FILEINPUT = 'deliciousbackupfile';

    /**
     * Constructor
     *
     * Set the encoding type, since this is a file upload.
     *
     * @param HTMLOutputter $out output channel
     *
     * @return ImportDeliciousForm this
     */
    function __construct($out=null)
    {
        parent::__construct($out);
        $this->enctype = 'multipart/form-data';
    }

    /**
     * Class of the form.
     *
     * @return string the form's class
     */
    function formClass()
    {
        return 'form_import_delicious';
    }

    /**
     * URL the form posts to
     *
     * @return string the form's action URL
     */
    function action()
    {
        return common_local_url('importdelicious');
    }

    /**
     * Output form data
     *
     * Really, just instructions for doing a backup.
     *
     * @return void
     */
    function formData()
    {
        $this->out->elementStart('p', 'instructions');

        // TRANS: Form instructions for importing bookmarks.
        $this->out->raw(_m('You can upload a backed-up '.
                          'delicious.com bookmarks file.'));

        $this->out->elementEnd('p');

        $this->out->elementStart('ul', 'form_data');

        $this->out->elementStart('li', array ('id' => 'settings_attach'));
        $this->out->element('input', array('name' => self::FILEINPUT,
                                           'type' => 'file',
                                           'id' => self::FILEINPUT));
        $this->out->elementEnd('li');

        $this->out->elementEnd('ul');
    }

    /**
     * Buttons for the form
     *
     * In this case, a single submit button
     *
     * @return void
     */

    function formActions()
    {
        $this->out->submit('submit',
                           // TRANS: Button text on form to import bookmarks.
                           _m('BUTTON', 'Upload'),
                           'submit',
                           null,
                           // TRANS: Button title on form to import bookmarks.
                           _m('Upload the file.'));
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2010, StatusNet, Inc.
 *
 * Add a new 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);
}

/**
 * Add a new bookmark
 *
 * @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 NewbookmarkAction extends Action
{
    protected $user        = null;
    protected $error       = null;
    protected $complete    = null;
    protected $title       = null;
    protected $url         = null;
    protected $tags        = null;
    protected $description = null;

    /**
     * Returns the title of the action
     *
     * @return string Action title
     */
    function title()
    {
        // TRANS: Title for action to create a new bookmark.
        return _m('New bookmark');
    }

    /**
     * For initializing members of the class.
     *
     * @param array $argarray misc. arguments
     *
     * @return boolean true
     */
    function prepare($argarray)
    {
        parent::prepare($argarray);

        if ($this->boolean('ajax')) {
            GNUsocial::setApi(true);
        }

        $this->user = common_current_user();

        if (empty($this->user)) {
            // TRANS: Client exception thrown when trying to create a new bookmark while not logged in.
            throw new ClientException(_m('Must be logged in to post a bookmark.'),
                                      403);
        }

        if ($this->isPost()) {
            $this->checkSessionToken();
        }

        $this->title       = $this->trimmed('title');
        $this->url         = $this->trimmed('url');
        $this->tags        = $this->trimmed('tags');
        $this->description = $this->trimmed('description');

        return true;
    }

    /**
     * Handler method
     *
     * @param array $argarray is ignored since it's now passed in in prepare()
     *
     * @return void
     */
    function handle($argarray=null)
    {
        parent::handle($argarray);

        if ($this->isPost()) {
            $this->newBookmark();
        } else {
            $this->showPage();
        }

        return;
    }

    /**
     * Add a new bookmark
     *
     * @return void
     */
    function newBookmark()
    {
        try {
            if (empty($this->title)) {
                // TRANS: Client exception thrown when trying to create a new bookmark without a title.
                throw new ClientException(_m('Bookmark must have a title.'));
            }

            if (empty($this->url)) {
                // TRANS: Client exception thrown when trying to create a new bookmark without a URL.
                throw new ClientException(_m('Bookmark must have an URL.'));
            }

            $options = array();

            ToSelector::fillOptions($this, $options);

            $saved = Bookmark::saveNew($this->user->getProfile(),
                                       $this->title,
                                       $this->url,
                                       $this->tags,
                                       $this->description,
                                       $options);

        } catch (ClientException $ce) {
            if ($this->boolean('ajax')) {
                $this->startHTML('text/xml;charset=utf-8');
                $this->elementStart('head');
                // TRANS: Page title after an AJAX error occurs
                $this->element('title', null, _('Ajax Error'));
                $this->elementEnd('head');
                $this->elementStart('body');
                $this->element('p', array('id' => 'error'), $ce->getMessage());
                $this->elementEnd('body');
                $this->endHTML();
                return;
            } else {
                $this->error = $ce->getMessage();
                $this->showPage();
                return;
            }
        }

        if ($this->boolean('ajax')) {
            $this->startHTML('text/xml;charset=utf-8');
            $this->elementStart('head');
            // TRANS: Page title after posting a bookmark.
            $this->element('title', null, _m('Bookmark posted'));
            $this->elementEnd('head');
            $this->elementStart('body');
            $this->showNotice($saved);
            $this->elementEnd('body');
            $this->endHTML();
        } else {
            common_redirect($saved->getUrl(), 303);
        }
    }

    /**
     * Output a notice
     *
     * Used to generate the notice code for Ajax results.
     *
     * @param Notice $notice Notice that was saved
     *
     * @return void
     */
    function showNotice(Notice $notice)
    {
        class_exists('NoticeList'); // @fixme hack for autoloader
        $nli = new NoticeListItem($notice, $this);
        $nli->show();
    }

    /**
     * Show the bookmark form
     *
     * @return void
     */
    function showContent()
    {
        if (!empty($this->error)) {
            $this->element('p', 'error', $this->error);
        }

        $form = new BookmarkForm($this,
                                 $this->title,
                                 $this->url,
                                 $this->tags,
                                 $this->description);

        $form->show();

        return;
    }

    /**
     * Return true if read only.
     *
     * MAY override
     *
     * @param array $args other arguments
     *
     * @return boolean is read only action?
     */
    function isReadOnly($args)
    {
        if ($_SERVER['REQUEST_METHOD'] == 'GET' ||
            $_SERVER['REQUEST_METHOD'] == 'HEAD') {
            return true;
        } else {
            return false;
        }
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2010, StatusNet, Inc.
 *
 * Notice stream of notices with a given attachment
 *
 * 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);
}

/**
 * List notices that contain/link to/use a given URL
 *
 * @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 NoticebyurlAction extends Action
{
    protected $url     = null;
    protected $file    = null;
    protected $notices = null;
    protected $page    = null;

    /**
     * For initializing members of the class.
     *
     * @param array $argarray misc. arguments
     *
     * @return boolean true
     */
    function prepare($argarray)
    {
        parent::prepare($argarray);

        $this->file = File::getKV('id', $this->trimmed('id'));

        if (empty($this->file)) {
            // TRANS: Client exception thrown when an unknown URL is provided.
            throw new ClientException(_m('Unknown URL.'));
        }

        $pageArg = $this->trimmed('page');

        $this->page = (empty($pageArg)) ? 1 : intval($pageArg);

        $this->notices = $this->file->stream(($this->page - 1) * NOTICES_PER_PAGE,
                                             NOTICES_PER_PAGE + 1);

        return true;
    }

    /**
     * Title of the page
     *
     * @return string page title
     */
    function title()
    {
        if ($this->page == 1) {
            // TRANS: Title of notice stream of notices with a given attachment (first page).
            // TRANS: %s is the URL.
            return sprintf(_m('Notices linking to %s'), $this->file->url);
        } else {
            // TRANS: Title of notice stream of notices with a given attachment (all but first page).
            // TRANS: %1$s is the URL, %2$s is the page number.
            return sprintf(_m('Notices linking to %1$s, page %2$d'),
                           $this->file->url,
                           $this->page);
        }
    }

    /**
     * Handler method
     *
     * @param array $argarray is ignored since it's now passed in in prepare()
     *
     * @return void
     */
    function handle($argarray=null)
    {
        $this->showPage();
    }

    /**
     * Show main page content.
     *
     * Shows a list of the notices that link to the given URL
     *
     * @return void
     */
    function showContent()
    {
        $nl = new NoticeList($this->notices, $this);

        $nl->show();

        $cnt = $nl->show();

        $this->pagination($this->page > 1,
                          $cnt > NOTICES_PER_PAGE,
                          $this->page,
                          'noticebyurl',
                          array('id' => $this->file->id));
    }

    /**
     * Return true if read only.
     *
     * MAY override
     *
     * @param array $args other arguments
     *
     * @return boolean is read only action?
     */
    function isReadOnly($args)
    {
        return true;
    }

    /**
     * Return last modified, if applicable.
     *
     * MAY override
     *
     * @return string last modified http header
     */
    function lastModified()
    {
        // For comparison with If-Last-Modified
        // If not applicable, return null
        return null;
    }

    /**
     * Return etag, if applicable.
     *
     * MAY override
     *
     * @return string etag http header
     */
    function etag()
    {
        return null;
    }
}


code:
<?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;

    function getNotice()
    {
        $this->id = $this->trimmed('id');

        $this->bookmark = Bookmark::getKV('id', $this->id);

        if (empty($this->bookmark)) {
            // TRANS: Client exception thrown when referring to a non-existing bookmark.
            throw new ClientException(_m('No such bookmark.'), 404);
        }

        $notice = Notice::getKV('uri', $this->bookmark->uri);

        if (empty($notice)) {
            // Did we used to have it, and it got deleted?
            // TRANS: Client exception thrown when referring to a non-existing bookmark.
            throw new ClientException(_m('No such bookmark.'), 404);
        }

        return $notice;
    }

    /**
     * Title of the page
     *
     * Used by Action class for layout.
     *
     * @return string page tile
     */
    function title()
    {
        // TRANS: Title for bookmark.
        // TRANS: %1$s is a user nickname, %2$s is a bookmark title.
        return sprintf(_m('%1$s\'s bookmark for "%2$s"'),
                       $this->user->nickname,
                       $this->bookmark->title);
    }

    /**
     * Overload page title display to show bookmark link
     *
     * @return void
     */
    function showPageTitle()
    {
        $this->elementStart('h1');
        $this->element('a',
                       array('href' => $this->bookmark->url),
                       $this->bookmark->title);
        $this->elementEnd('h1');
    }
}


code:
A bookmarklet is a small piece of javascript code used as a bookmark. This one will let you post to %%site.name%% simply by selecting some text on a page and pressing the bookmarklet.

Drag-and-drop the following link to your bookmarks bar or right-click it and add it to your browser favorites to keep it handy.

Bookmark on %%site.name%%

path: plugins/Bookmark/bookmarklet


code:
$(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) {
			   form.addClass('processing');
			   form.find('#submit').addClass('disabled');
		       },
                       error: function (xhr, textStatus, errorThrown) {
			   form.removeClass('processing');
			   form.find('#submit').removeClass('disabled');
               doClose();
		       },
                       success: function(data, textStatus) {
			   form.removeClass('processing');
			   form.find('#submit').removeClass('disabled');
                           doClose();
                       }});

    }
);


code:
<?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 Managed_DataObject
{
    public $__table = 'bookmark'; // table name
    public $id;          // char(36) primary_key not_null
    public $profile_id;  // int(4) not_null
    public $url;         // varchar(191) not_null   not 255 because utf8mb4 takes more space
    public $title;       // varchar(191)   not 255 because utf8mb4 takes more space
    public $uri;         // varchar(191)   not 255 because utf8mb4 takes more space
    public $description; // text
    public $created;     // datetime

    public static function schemaDef()
    {
        return array(
            'fields' => array(
                'id' => array('type' => 'char',
                            'length' => 36,
                            'not null' => true),
                'profile_id' => array('type' => 'int', 'not null' => true),
                'uri' => array('type' => 'varchar',
                            'length' => 191,
                            'not null' => true),
                'url' => array('type' => 'varchar',
                            'length' => 191,
                            'not null' => true),
                'title' => array('type' => 'varchar', 'length' => 191),
                'description' => array('type' => 'text'),
                'created' => array('type' => 'datetime', 'not null' => true),
            ),
            'primary key' => array('id'),
            'unique keys' => array(
                'bookmark_uri_key' => array('uri'),
            ),
            'foreign keys' => array(
                'bookmark_profile_id_fkey' => array('profile', array('profile_id' => 'id'))
            ),
            'indexes' => array('bookmark_created_idx' => array('created'),
                            'bookmark_url_idx' => array('url'),
                            'bookmark_profile_id_idx' => array('profile_id'),
            ),
        );
    }

    /**
     * Get a bookmark based on a notice
     *
     * @param Notice $notice Notice to check for
     *
     * @return Bookmark found bookmark or null
     */
    static function getByNotice($notice)
    {
        return self::getKV('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)
    {
        $nb = new Bookmark();

        $nb->profile_id = $profile->id;
        $nb->url        = $url;

        if ($nb->find(true)) {
            return $nb;
        } else {
            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)
    {
        if (!common_valid_http_url($url)) {
            throw new ClientException(_m('Only web bookmarks can be posted (HTTP or HTTPS).'));
        }

        $nb = self::getByURL($profile, $url);

        if (!empty($nb)) {
            // TRANS: Client exception thrown when trying to save a new bookmark that already exists.
            throw new ClientException(_m('Bookmark already exists.'));
        }

        if (empty($options)) {
            $options = array();
        }

        if (array_key_exists('uri', $options)) {
            $other = Bookmark::getKV('uri', $options['uri']);
            if (!empty($other)) {
                // TRANS: Client exception thrown when trying to save a new bookmark that already exists.
                throw new ClientException(_m('Bookmark already exists.'));
            }
        }

        if (is_string($rawtags)) {
            if (empty($rawtags)) {
                $rawtags = array();
            } else {
                $rawtags = preg_split('/[\s,]+/', $rawtags);
            }
        }

        $nb = new Bookmark();

        $nb->id          = UUID::gen();
        $nb->profile_id  = $profile->id;
        $nb->url         = $url;
        $nb->title       = $title;
        $nb->description = $description;

        if (array_key_exists('created', $options)) {
            $nb->created = $options['created'];
        } else {
            $nb->created = common_sql_now();
        }

        if (array_key_exists('uri', $options)) {
            $nb->uri = $options['uri'];
        } else {
            // FIXME: hacks to work around router bugs in
            // queue daemons

            $r = Router::get();

            $path = $r->build('showbookmark',
                              array('id' => $nb->id));

            if (empty($path)) {
                $nb->uri = common_path('bookmark/'.$nb->id, false, false);
            } else {
                $nb->uri = common_local_url('showbookmark',
                                            array('id' => $nb->id),
                                            null,
                                            null,
                                            false);
            }
        }

        $nb->insert();

        $tags    = array();
        $replies = array();

        // filter "for:nickname" tags

        foreach ($rawtags as $tag) {
            if (strtolower(mb_substr($tag, 0, 4)) == 'for:') {
                // skip if done by caller
                if (!array_key_exists('replies', $options)) {
                    $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

        try {
            $user = User::getKV('id', $profile->id);

            $shortUrl = File_redirection::makeShort($url,
                                                    empty($user) ? null : $user);
        } catch (Exception $e) {
            // Don't let this stop us.
            $shortUrl = $url;
        }

        // TRANS: Bookmark content.
        // TRANS: %1$s is a title, %2$s is a short URL, %3$s is the bookmark description,
	// TRANS: %4$s is space separated list of hash tags.
        $content = sprintf(_m('"%1$s" %2$s %3$s %4$s'),
                           $title,
                           $shortUrl,
                           $description,
                           implode(' ', $hashtags));

        // TRANS: Rendered bookmark content.
        // TRANS: %1$s is a URL, %2$s the bookmark title, %3$s is the bookmark description,
	// TRANS: %4$s is space separated list of hash tags.
        $rendered = sprintf(_m(''.
                              '%2$s '.
                              '%3$s '.
                              '%4$s'.
                              ''),
                            htmlspecialchars($url),
                            htmlspecialchars($title),
                            htmlspecialchars($description),
                            implode(' ', $taglinks));

        $options = array_merge(array('urls' => array($url),
                                     'rendered' => $rendered,
                                     'tags' => $tags,
                                     'replies' => $replies,
                                     'object_type' => ActivityObject::BOOKMARK),
                               $options);

        if (!array_key_exists('uri', $options)) {
            $options['uri'] = $nb->uri;
        }

        try {
            $saved = Notice::saveNew($profile->id,
                                     $content,
                                     array_key_exists('source', $options) ?
                                     $options['source'] : 'web',
                                     $options);
        } catch (Exception $e) {
            $nb->delete();
            throw $e;
        }

        if (empty($saved)) {
            $nb->delete();
        }

        return $saved;
    }
}


code:
/* Bookmark specific styles */

.bookmark-tags li { display: inline; }

.bookmark h3 {
    margin: 4px 0px 8px 0px;
}

.bookmark-notice-count {
    border-radius: 4px;
	-moz-border-radius: 4px;
	-webkit-border-radius: 4px;
    padding: 1px 6px;
    font-size: 1.2em;
    line-height: 1.2em;
    background: #fff;
    border: 1px solid #7b8dbb;
    color: #3e3e8c !important;
    position: relative;
    right: 4px;
    margin-left: 10px;
}

.bookmark-notice-count:hover {
    text-decoration: none;
    background: #f2f2f2;
    border: 1px solid #7b8dbb;
    text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.5);
}

.notice .bookmark-description {
    clear: both;
    margin-left: 0px;
    margin-bottom: 0px;
}

.notice .bookmark-author {
    margin-left: 0px;
    float: left;
}

.bookmark-tags {
    clear: both;
    margin-bottom: 4px;
    line-height: 1.6em;
}

ul.bookmark-tags a {
    border-radius: 4px;
	-moz-border-radius: 4px;
	-webkit-border-radius: 4px;
    padding: 1px 6px;
    background: #f2f2f2;
    color: #3e3e8c !important;
    text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.5);
    font-size: 0.88em;
}

ul.bookmark-tags a:hover {
    background-color: #cdd1dd;
    text-decoration: none;
}

.bookmark-avatar {
    float: none !important;
    position: relative;
    top: 2px;
}

.bookmark div.e-content {
    font-size: 0.88em;
    line-height: 1.2em;
    margin-top: 6px;
    opacity: 0.6;
    margin-bottom: 0px;
}

.bookmark:hover div.e-content {
    opacity: 1;
}

#bookmarkpopup {
    min-width: 600px;
    margin-top: 0px;
    height: 100%;
    border: 10px solid #364A84;
    background: #364A84;
}

#bookmarkpopup #wrap {
    width: auto;
    min-width: 560px;
    padding: 40px 0px 25px 0px;
    margin-right: 2px;
    background: #fff url(../mobilelogo.png) no-repeat 6px 6px;
}

#bookmarkpopup #header {
    width: auto;
    padding: 0px 10px;
}

#bookmarkpopup .form_settings label {
    margin-top: 2px;
    text-align: right;
    width: 24%;
    font-size: 1.2em;
}

#bookmarkpopup .form_settings .form_data input {
    width: 60%;
}

#bookmarkpopup .form_guide {
    color: #777;
}

#bookmarkpopup #bookmark-submit {
    min-width: 100px;
}

#bookmarkpopup fieldset fieldset {
    margin-bottom: 10px;
}

#form_initial_bookmark.form_settings .form_data li {
    margin-bottom: 0px;
}

#form_new_bookmark.form_settings .bookmarkform-thumbnail {
    position: absolute;
    top: 50px;
    right: 0px;
}