]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch 'quitagram' into nightly
authorMikael Nordfeldth <mmn@hethane.se>
Wed, 22 Apr 2015 20:17:50 +0000 (22:17 +0200)
committerMikael Nordfeldth <mmn@hethane.se>
Wed, 22 Apr 2015 20:17:50 +0000 (22:17 +0200)
Fixes that make quitagram work better amongst other things.

102 files changed:
actions/apistatusesretweet.php [deleted file]
actions/apistatusesretweets.php [deleted file]
actions/apitimelineretweetedbyme.php [deleted file]
actions/apitimelineretweetedtome.php [deleted file]
actions/apitimelineretweetsofme.php [deleted file]
actions/attachment_thumbnail.php
actions/avatarsettings.php
actions/grouplogo.php
actions/login.php
actions/newapplication.php
actions/repeat.php [deleted file]
actions/showstream.php
classes/Avatar.php
classes/File.php
classes/File_thumbnail.php
classes/File_to_post.php
classes/Notice.php
classes/Oauth_application.php
classes/Profile.php
classes/Queue_item.php
classes/User_group.php
extlib/php-gettext/ChangeLog
extlib/php-gettext/README
extlib/php-gettext/gettext.inc
extlib/php-gettext/gettext.php
extlib/php-gettext/streams.php
js/util.js
lib/action.php
lib/activityhandlerplugin.php
lib/activityverb.php
lib/apiaction.php
lib/attachmentlistitem.php
lib/command.php
lib/commandinterpreter.php
lib/dbqueuemanager.php
lib/default.php
lib/formaction.php
lib/framework.php
lib/htmloutputter.php
lib/imagefile.php
lib/implugin.php
lib/installer.php
lib/noticelist.php
lib/noticelistitem.php
lib/queuemanager.php
lib/repeatedbymenoticestream.php [deleted file]
lib/repeatform.php [deleted file]
lib/repeatsofmenoticestream.php [deleted file]
lib/router.php
lib/schema.php
lib/schemaupdater.php
lib/threadednoticelist.php
lib/util.php
plugins/Activity/locale/sv/LC_MESSAGES/Activity.po
plugins/ActivityVerb/ActivityVerbPlugin.php [new file with mode: 0644]
plugins/ActivityVerb/actions/activityverb.php [new file with mode: 0644]
plugins/ActivityVerb/lib/activityverbhandlerplugin.php [new file with mode: 0644]
plugins/AnonymousFave/actions/anonfavor.php
plugins/DefaultLayout/DefaultLayoutPlugin.php
plugins/FacebookBridge/actions/facebookfinishlogin.php
plugins/Favorite/FavoritePlugin.php
plugins/Favorite/actions/apifavoritecreate.php
plugins/Favorite/actions/disfavor.php [deleted file]
plugins/Favorite/actions/favor.php [deleted file]
plugins/Favorite/classes/Fave.php
plugins/Favorite/forms/disfavor.php
plugins/Favorite/forms/favor.php
plugins/Favorite/lib/favcommand.php
plugins/GNUsocialProfileExtensions/GNUsocialProfileExtensionsPlugin.php
plugins/InfiniteScroll/InfiniteScrollPlugin.php
plugins/MobileProfile/MobileProfilePlugin.php
plugins/ModPlus/actions/remoteprofile.php
plugins/OStatus/classes/Ostatus_profile.php
plugins/Oembed/OembedPlugin.php
plugins/Oembed/actions/oembed.php
plugins/Oembed/lib/oembedhelper.php
plugins/OpportunisticQM/lib/opportunisticqueuemanager.php
plugins/QnA/QnAPlugin.php
plugins/Share/SharePlugin.php [new file with mode: 0644]
plugins/Share/actions/apistatusesretweet.php [new file with mode: 0644]
plugins/Share/actions/apistatusesretweets.php [new file with mode: 0644]
plugins/Share/actions/apitimelineretweetedbyme.php [new file with mode: 0644]
plugins/Share/actions/apitimelineretweetedtome.php [new file with mode: 0644]
plugins/Share/actions/apitimelineretweetsofme.php [new file with mode: 0644]
plugins/Share/actions/repeat.php [new file with mode: 0644]
plugins/Share/forms/repeat.php [new file with mode: 0644]
plugins/Share/lib/repeatcommand.php [new file with mode: 0644]
plugins/Share/lib/repeatedbymenoticestream.php [new file with mode: 0644]
plugins/Share/lib/repeatsofmenoticestream.php [new file with mode: 0644]
plugins/Share/lib/threadednoticelistinlinerepeatsitem.php [new file with mode: 0644]
plugins/Share/lib/threadednoticelistrepeatsitem.php [new file with mode: 0644]
plugins/SiteNoticeInSidebar/lib/sitenoticesection.php
plugins/WikiHowProfile/WikiHowProfilePlugin.php
scripts/commandline.inc
scripts/deleteprofile.php [new file with mode: 0755]
scripts/deleteuser.php [deleted file]
scripts/remove_duplicate_file_urls.php [new file with mode: 0755]
scripts/upgrade.php
theme/base/css/display.css
theme/neo-gnu/css/display.css
theme/neo-quitter/css/display.css
theme/neo/css/display.css

diff --git a/actions/apistatusesretweet.php b/actions/apistatusesretweet.php
deleted file mode 100644 (file)
index 922d395..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Repeat a notice through the API
- *
- * 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    Evan Prodromou <evan@status.net>
- * @copyright 2009 StatusNet, Inc.
- * @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('GNUSOCIAL')) { exit(1); }
-
-/**
- * Repeat a notice through the API
- *
- * @category API
- * @package  StatusNet
- * @author   Evan Prodromou <evan@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 ApiStatusesRetweetAction extends ApiAuthAction
-{
-    protected $needPost = true;
-
-    var $original = null;
-
-    /**
-     * Take arguments for running
-     *
-     * @param array $args $_REQUEST args
-     *
-     * @return boolean success flag
-     */
-    protected function prepare(array $args=array())
-    {
-        parent::prepare($args);
-
-        $id = $this->trimmed('id');
-
-        $this->original = Notice::getKV('id', $id);
-
-        if (!$this->original instanceof Notice) {
-            // TRANS: Client error displayed trying to repeat a non-existing notice through the API.
-            $this->clientError(_('No such notice.'), 400, $this->format);
-        }
-
-        return true;
-    }
-
-    /**
-     * Handle the request
-     *
-     * Make a new notice for the update, save it, and show it
-     *
-     * @param array $args $_REQUEST data (unused)
-     *
-     * @return void
-     */
-    protected function handle()
-    {
-        parent::handle();
-
-        $repeat = $this->original->repeat($this->scoped, $this->source);
-
-        $this->showNotice($repeat);
-    }
-
-    /**
-     * Show the resulting notice
-     *
-     * @return void
-     */
-    function showNotice($notice)
-    {
-        if (!empty($notice)) {
-            if ($this->format == 'xml') {
-                $this->showSingleXmlStatus($notice);
-            } elseif ($this->format == 'json') {
-                $this->show_single_json_status($notice);
-            }
-        }
-    }
-}
diff --git a/actions/apistatusesretweets.php b/actions/apistatusesretweets.php
deleted file mode 100644 (file)
index 7af4cd3..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Show up to 100 repeats of a notice
- *
- * 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    Evan Prodromou <evan@status.net>
- * @copyright 2009 StatusNet, Inc.
- * @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);
-}
-
-/**
- * Show up to 100 repeats of a notice
- *
- * @category API
- * @package  StatusNet
- * @author   Evan Prodromou <evan@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 ApiStatusesRetweetsAction extends ApiAuthAction
-{
-    const MAXCOUNT = 100;
-
-    var $original = null;
-    var $cnt      = self::MAXCOUNT;
-
-    /**
-     * Take arguments for running
-     *
-     * @param array $args $_REQUEST args
-     *
-     * @return boolean success flag
-     */
-    function prepare($args)
-    {
-        parent::prepare($args);
-
-        $id = $this->trimmed('id');
-
-        $this->original = Notice::getKV('id', $id);
-
-        if (empty($this->original)) {
-            // TRANS: Client error displayed trying to display redents of a non-exiting notice.
-            $this->clientError(_('No such notice.'),
-                               400, $this->format);
-            return false;
-        }
-
-        $cnt = $this->trimmed('count');
-
-        if (empty($cnt) || !is_integer($cnt)) {
-            $cnt = 100;
-        } else {
-            $this->cnt = min((int)$cnt, self::MAXCOUNT);
-        }
-
-        return true;
-    }
-
-    /**
-     * Handle the request
-     *
-     * Make a new notice for the update, save it, and show it
-     *
-     * @param array $args $_REQUEST data (unused)
-     *
-     * @return void
-     */
-    function handle($args)
-    {
-        parent::handle($args);
-
-        $strm = $this->original->repeatStream($this->cnt);
-
-        switch ($this->format) {
-        case 'xml':
-            $this->showXmlTimeline($strm);
-            break;
-        case 'json':
-            $this->showJsonTimeline($strm);
-            break;
-        default:
-            // TRANS: Client error displayed when coming across a non-supported API method.
-            $this->clientError(_('API method not found.'), $code = 404);
-            break;
-        }
-    }
-
-    /**
-     * Return true if read only.
-     *
-     * MAY override
-     *
-     * @param array $args other arguments
-     *
-     * @return boolean is read only action?
-     */
-
-    function isReadOnly($args)
-    {
-        return true;
-    }
-}
diff --git a/actions/apitimelineretweetedbyme.php b/actions/apitimelineretweetedbyme.php
deleted file mode 100644 (file)
index 01f3fe3..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Show authenticating user's most recent repeats
- *
- * 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    Evan Prodromou <evan@status.net>
- * @copyright 2009 StatusNet, Inc.
- * @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);
-}
-
-/**
- * Show authenticating user's most recent repeats
- *
- * @category API
- * @package  StatusNet
- * @author   Evan Prodromou <evan@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 ApiTimelineRetweetedByMeAction extends ApiAuthAction
-{
-    const DEFAULTCOUNT = 20;
-    const MAXCOUNT     = 200;
-    const MAXNOTICES   = 3200;
-
-    var $repeats  = null;
-    var $cnt      = self::DEFAULTCOUNT;
-    var $page     = 1;
-    var $since_id = null;
-    var $max_id   = null;
-
-    /**
-     * Take arguments for running
-     *
-     * @param array $args $_REQUEST args
-     *
-     * @return boolean success flag
-     *
-     */
-    function prepare($args)
-    {
-        parent::prepare($args);
-
-        // TRANS: Server error displayed calling unimplemented API method for 'retweeted by me'.
-        $this->serverError(_('Unimplemented.'), 503);
-
-        return false;
-    }
-
-    /**
-     * Return true if read only.
-     *
-     * @param array $args other arguments
-     *
-     * @return boolean is read only action?
-     */
-    function isReadOnly($args)
-    {
-        return true;
-    }
-}
diff --git a/actions/apitimelineretweetedtome.php b/actions/apitimelineretweetedtome.php
deleted file mode 100644 (file)
index 92d4b35..0000000
+++ /dev/null
@@ -1,162 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Show most recent notices that are repeats in user's inbox
- *
- * 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    Evan Prodromou <evan@status.net>
- * @copyright 2009 StatusNet, Inc.
- * @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('GNUSOCIAL')) { exit(1); }
-
-/**
- * Show most recent notices that are repeats in user's inbox
- *
- * @category API
- * @package  StatusNet
- * @author   Evan Prodromou <evan@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 ApiTimelineRetweetedToMeAction extends ApiAuthAction
-{
-    const DEFAULTCOUNT = 20;
-    const MAXCOUNT     = 200;
-    const MAXNOTICES   = 3200;
-
-    var $repeats  = null;
-    var $cnt      = self::DEFAULTCOUNT;
-    var $page     = 1;
-    var $since_id = null;
-    var $max_id   = null;
-
-    /**
-     * Take arguments for running
-     *
-     * @param array $args $_REQUEST args
-     *
-     * @return boolean success flag
-     */
-    protected function prepare(array $args=array())
-    {
-        parent::prepare($args);
-
-        $cnt = $this->int('count', self::DEFAULTCOUNT, self::MAXCOUNT, 1);
-
-        $page = $this->int('page', 1, (self::MAXNOTICES/$this->cnt));
-
-        $since_id = $this->int('since_id');
-
-        $max_id = $this->int('max_id');
-
-        return true;
-    }
-
-    /**
-     * Handle the request
-     *
-     * show a timeline of the user's repeated notices
-     *
-     * @return void
-     */
-    protected function handle()
-    {
-        parent::handle();
-
-        $offset = ($this->page-1) * $this->cnt;
-        $limit  = $this->cnt;
-
-        // TRANS: Title for Atom feed "repeated to me". %s is the user nickname.
-        $title      = sprintf(_("Repeated to %s"), $this->scoped->getNickname());
-        $subtitle   = sprintf(
-            // @todo FIXME: $profile is not defined.
-            // TRANS: Subtitle for API action that shows most recent notices that are repeats in user's inbox.
-            // TRANS: %1$s is the sitename, %2$s is a user nickname, %3$s is a user profile name.
-            _('%1$s notices that were to repeated to %2$s / %3$s.'),
-            $sitename, $this->scoped->getNickname(), $profile->getBestName()
-        );
-        $taguribase = TagURI::base();
-        $id         = "tag:$taguribase:RepeatedToMe:" . $this->scoped->id;
-
-        $link = common_local_url(
-            'all',
-             array('nickname' => $this->scoped->getNickname())
-        );
-
-        $strm = $this->scoped->repeatedToMe($offset, $limit, $this->since_id, $this->max_id);
-
-        switch ($this->format) {
-        case 'xml':
-            $this->showXmlTimeline($strm);
-            break;
-        case 'json':
-            $this->showJsonTimeline($strm);
-            break;
-        case 'atom':
-            header('Content-Type: application/atom+xml; charset=utf-8');
-
-            $atom = new AtomNoticeFeed($this->scoped->getUser());
-
-            $atom->setId($id);
-            $atom->setTitle($title);
-            $atom->setSubtitle($subtitle);
-            $atom->setUpdated('now');
-            $atom->addLink($link);
-
-            $id = $this->arg('id');
-
-            $atom->setSelfLink($self);
-            $atom->addEntryFromNotices($strm);
-
-            $this->raw($atom->getString());
-
-            break;
-        case 'as':
-            header('Content-Type: ' . ActivityStreamJSONDocument::CONTENT_TYPE);
-            $doc = new ActivityStreamJSONDocument($this->scoped->getUser());
-            $doc->setTitle($title);
-            $doc->addLink($link, 'alternate', 'text/html');
-            $doc->addItemsFromNotices($strm);
-            $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);
-            break;
-        }
-    }
-
-    /**
-     * Return true if read only.
-     *
-     * MAY override
-     *
-     * @param array $args other arguments
-     *
-     * @return boolean is read only action?
-     */
-    function isReadOnly($args)
-    {
-        return true;
-    }
-}
diff --git a/actions/apitimelineretweetsofme.php b/actions/apitimelineretweetsofme.php
deleted file mode 100644 (file)
index fe90213..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Show authenticating user's most recent notices that have been repeated
- *
- * 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    Evan Prodromou <evan@status.net>
- * @copyright 2009 StatusNet, Inc.
- * @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);
-}
-
-/**
- * Show authenticating user's most recent notices that have been repeated
- *
- * @category API
- * @package  StatusNet
- * @author   Evan Prodromou <evan@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 ApiTimelineRetweetsOfMeAction extends ApiAuthAction
-{
-    const DEFAULTCOUNT = 20;
-    const MAXCOUNT     = 200;
-    const MAXNOTICES   = 3200;
-
-    var $repeats  = null;
-    var $cnt      = self::DEFAULTCOUNT;
-    var $page     = 1;
-    var $since_id = null;
-    var $max_id   = null;
-
-    /**
-     * Take arguments for running
-     *
-     * @param array $args $_REQUEST args
-     *
-     * @return boolean success flag
-     */
-    function prepare($args)
-    {
-        parent::prepare($args);
-
-        $cnt = $this->int('count', self::DEFAULTCOUNT, self::MAXCOUNT, 1);
-
-        $page = $this->int('page', 1, (self::MAXNOTICES/$this->cnt));
-
-        $since_id = $this->int('since_id');
-
-        $max_id = $this->int('max_id');
-
-        return true;
-    }
-
-    /**
-     * Handle the request
-     *
-     * show a timeline of the user's repeated notices
-     *
-     * @param array $args $_REQUEST data (unused)
-     *
-     * @return void
-     */
-    function handle($args)
-    {
-        parent::handle($args);
-
-        $offset = ($this->page-1) * $this->cnt;
-        $limit  = $this->cnt;
-
-        // TRANS: Title of list of repeated notices of the logged in user.
-        // TRANS: %s is the nickname of the logged in user.
-        $title      = sprintf(_("Repeats of %s"), $this->auth_user->nickname);
-        $sitename   = common_config('site', 'name');
-
-        $profile = $this->auth_user->getProfile();
-
-        $subtitle   = sprintf(
-            // TRANS: Subtitle of API time with retweets of me.
-            // TRANS: %1$s is the StatusNet sitename, %2$s is the user nickname, %3$s is the user profile name.
-            _('%1$s notices that %2$s / %3$s has repeated.'),
-            $sitename, $this->auth_user->nickname, $profile->getBestName()
-        );
-
-        $taguribase = TagURI::base();
-        $id         = "tag:$taguribase:RepeatsOfMe:" . $this->auth_user->id;
-
-        $link = common_local_url(
-            'all',
-             array('nickname' => $this->auth_user->nickname)
-        );
-
-        // This is a really bad query for some reason
-
-        if (!common_config('performance', 'high')) {
-            $strm = $this->auth_user->repeatsOfMe($offset, $limit, $this->since_id, $this->max_id);
-        } else {
-            $strm = new Notice();
-            $strm->whereAdd('0 = 1');
-            $strm->find();
-        }
-
-        switch ($this->format) {
-        case 'xml':
-            $this->showXmlTimeline($strm);
-            break;
-        case 'json':
-            $this->showJsonTimeline($strm);
-            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->setUpdated('now');
-            $atom->addLink($link);
-            $atom->setSelfLink($this->getSelfUri());
-            $atom->addEntryFromNotices($strm);
-            $this->raw($atom->getString());
-            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($strm);
-            $this->raw($doc->asString());
-            break;
-        default:
-            // TRANS: Client error displayed when coming across a non-supported API method.
-            $this->clientError(_('API method not found.'), 404);
-            break;
-        }
-    }
-
-    /**
-     * Return true if read only.
-     *
-     * MAY override
-     *
-     * @param array $args other arguments
-     *
-     * @return boolean is read only action?
-     */
-    function isReadOnly($args)
-    {
-        return true;
-    }
-}
index c85029dcf609e871a57b4c5d72bfcc929b4e4284..e8f89ffc360d45c505aefd5198e4927e9b04a8ec 100644 (file)
@@ -62,6 +62,6 @@ class Attachment_thumbnailAction extends AttachmentAction
             common_redirect($e->file->getUrl());
         }
 
-        common_redirect($thumbnail->getUrl());
+        common_redirect(File_thumbnail::url($thumbnail->filename));
     }
 }
index 7373e770477b4a8def01b0ea7ac9db5cdeca3ea6..1f31cbdafedc9975b80f0feb1b537d6206b33b64 100644 (file)
  * @link      http://status.net/
  */
 
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-
-
-define('MAX_ORIGINAL', 480);
+if (!defined('GNUSOCIAL')) { exit(1); }
 
 /**
  * Upload an avatar
@@ -369,13 +363,27 @@ class AvatarsettingsAction extends SettingsAction
         $dest_y = $this->arg('avatar_crop_y') ? $this->arg('avatar_crop_y'):0;
         $dest_w = $this->arg('avatar_crop_w') ? $this->arg('avatar_crop_w'):$file_d;
         $dest_h = $this->arg('avatar_crop_h') ? $this->arg('avatar_crop_h'):$file_d;
-        $size = intval(min($dest_w, $dest_h, MAX_ORIGINAL));
+        $size = intval(min($dest_w, $dest_h, common_config('avatar', 'maxsize')));
+
+        $box = array('width' => $size, 'height' => $size,
+                     'x' => $dest_x,   'y' => $dest_y,
+                     'w' => $dest_w,   'h' => $dest_h);
 
         $user = common_current_user();
         $profile = $user->getProfile();
 
-        $imagefile = new ImageFile($user->id, $filedata['filepath']);
-        $filename = $imagefile->resize($size, $dest_x, $dest_y, $dest_w, $dest_h);
+        $imagefile = new ImageFile(null, $filedata['filepath']);
+        $filename = Avatar::filename($profile->getID(), image_type_to_extension($imagefile->preferredType()),
+                                     $size, common_timestamp());
+        try {
+            $imagefile->resizeTo(Avatar::path($filename), $box);
+        } catch (UseFileAsThumbnailException $e) {
+            common_debug('Using uploaded avatar directly without resizing, copying it to: '.$filename);
+            if (!copy($filedata['filepath'], Avatar::path($filename))) {
+                common_debug('Tried to copy image file '.$filedata['filepath'].' to destination '.Avatar::path($filename));
+                throw new ServerException('Could not copy file to destination.');
+            }
+        }
 
         if ($profile->setOriginal($filename)) {
             @unlink($filedata['filepath']);
index 10fd33088d6a60fa3445b1339febd442e595d4be..0d9c135785ad5264cccc398ae574fd0233746404 100644 (file)
  * @link      http://status.net/
  */
 
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-
-
-define('MAX_ORIGINAL', 480);
+if (!defined('GNUSOCIAL')) { exit(1); }
 
 /**
  * Upload an avatar
@@ -390,13 +384,20 @@ class GrouplogoAction extends GroupAction
         $dest_y = $this->arg('avatar_crop_y') ? $this->arg('avatar_crop_y'):0;
         $dest_w = $this->arg('avatar_crop_w') ? $this->arg('avatar_crop_w'):$filedata['width'];
         $dest_h = $this->arg('avatar_crop_h') ? $this->arg('avatar_crop_h'):$filedata['height'];
-        $size = min($dest_w, $dest_h);
-        $size = ($size > MAX_ORIGINAL) ? MAX_ORIGINAL:$size;
+        $size = min($dest_w, $dest_h, common_config('avatar', 'maxsize'));
+        $box = array('width' => $size, 'height' => $size,
+                     'x' => $dest_x,   'y' => $dest_y,
+                     'w' => $dest_w,   'h' => $dest_h);
+
+        $profile = $this->group->getProfile();
+
+        $imagefile = new ImageFile(null, $filedata['filepath']);
+        $filename = Avatar::filename($profile->getID(), image_type_to_extension($imagefile->preferredType()),
+                                     $size, common_timestamp());
 
-        $imagefile = new ImageFile($this->group->id, $filedata['filepath']);
-        $filename = $imagefile->resize($size, $dest_x, $dest_y, $dest_w, $dest_h);
+        $imagefile->resizeTo(Avatar::path($filename), $box);
 
-        if ($this->group->setOriginal($filename)) {
+        if ($profile->setOriginal($filename)) {
             @unlink($filedata['filepath']);
             unset($_SESSION['FILEDATA']);
             $this->mode = 'upload';
index 10563dc56e03d2deb7941b11200c6539fae2a75a..f9fe71ed40503744562639af8676d8fbcf10fae9 100644 (file)
@@ -172,7 +172,7 @@ class LoginAction extends FormAction
      *
      * @return void
      */
-    function getInstructions()
+    protected function getInstructions()
     {
         if (common_logged_in() && !common_is_real_login() &&
             common_get_returnto()) {
index c3e6f2d2449d6fd37c386c73989adcd97be57f00..37bede0d72f3c547a3a4e3eb15bde6253c831d00 100644 (file)
@@ -66,7 +66,7 @@ class NewApplicationAction extends FormAction
         return new ApplicationEditForm($this);
     }
 
-    public function getInstructions()
+    protected function getInstructions()
     {
         // TRANS: Form instructions for registering a new application.
         return _('Use this form to register a new application.');
diff --git a/actions/repeat.php b/actions/repeat.php
deleted file mode 100644 (file)
index 49132e9..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php
-/**
- * Repeat action.
- *
- * PHP version 5
- *
- * @category Action
- * @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) 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('GNUSOCIAL')) { exit(1); }
-
-/**
- * Repeat action
- *
- * @category Action
- * @package  StatusNet
- * @author   Evan Prodromou <evan@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link     http://status.net/
- */
-class RepeatAction extends FormAction
-{
-    protected $notice = null;   // Notice that is being repeated.
-    protected $repeat = null;   // The resulting repeat object/notice.
-
-    function title()
-    {
-        return _m('TITLE', 'Repeat notice');
-    }
-
-    protected function doPreparation()
-    {
-        $id = $this->trimmed('notice');
-
-        if (empty($id)) {
-            // TRANS: Client error displayed when trying to repeat a notice while not providing a notice ID.
-            $this->clientError(_('No notice specified.'));
-        }
-
-        $this->notice = Notice::getKV('id', $id);
-
-        if (!$this->notice instanceof Notice) {
-            // TRANS: Client error displayed when trying to repeat a non-existing notice.
-            $this->clientError(_('Notice not found.'));
-        }
-
-        $this->repeat = $this->notice->repeat($this->scoped, 'web');
-        if (!$this->repeat instanceof Notice) {
-            // TRANS: Error when unable to repeat a notice for unknown reason.
-            $this->clientError(_('Could not repeat notice for unknown reason. Please contact the webmaster!'));
-        }
-
-        return true;
-    }
-
-    /**
-     * Class handler.
-     *
-     * @param array $args query arguments
-     *
-     * @return void
-     */
-    protected function showContent()
-    {
-        $this->element('p', array('id' => 'repeat_response',
-                                  'class' => 'repeated'),
-                            // TRANS: Confirmation text after repeating a notice.
-                            _('Repeated!'));
-    }
-}
index b1271dea5465f0ca886ff99acf747dbae935767a..64d85e63be2ee559a2515dc519fa49df1a2cd10c 100644 (file)
  * @link      http://status.net/
  */
 
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once INSTALLDIR.'/lib/personalgroupnav.php';
-require_once INSTALLDIR.'/lib/noticelist.php';
-require_once INSTALLDIR.'/lib/profileminilist.php';
-require_once INSTALLDIR.'/lib/groupminilist.php';
-require_once INSTALLDIR.'/lib/feedlist.php';
+if (!defined('GNUSOCIAL')) { exit(1); }
 
 /**
  * User profile page
@@ -231,10 +223,7 @@ class ShowstreamAction extends ProfileAction
 
     function showNotices()
     {
-        $pnl = null;
-        if (Event::handle('ShowStreamNoticeList', array($this->notice, $this, &$pnl))) {
-            $pnl = new ProfileNoticeList($this->notice, $this);
-        }
+        $pnl = new NoticeList($this->notice, $this);
         $cnt = $pnl->show();
         if (0 == $cnt) {
             $this->showEmptyListMessage();
@@ -290,60 +279,3 @@ class ShowstreamAction extends ProfileAction
         return $options;
     }
 }
-
-// We don't show the author for a profile, since we already know who it is!
-
-/**
- * Slightly modified from standard list; the author & avatar are hidden
- * in CSS. We used to remove them here too, but as it turns out that
- * confuses the inline reply code... and we hide them in CSS anyway
- * since realtime updates come through in original form.
- *
- * Remaining customization right now is for the repeat marker, where
- * it'll list who the original poster was instead of who did the repeat
- * (since the repeater is you, and the repeatee isn't shown!)
- * This will remain inconsistent if realtime updates come through,
- * since those'll get rendered as a regular NoticeListItem.
- */
-class ProfileNoticeList extends NoticeList
-{
-    function newListItem($notice)
-    {
-        return new ProfileNoticeListItem($notice, $this->out);
-    }
-}
-
-class ProfileNoticeListItem extends DoFollowListItem
-{
-    /**
-     * show a link to the author of repeat
-     *
-     * @return void
-     */
-    function showRepeat()
-    {
-        if (!empty($this->repeat)) {
-
-            // FIXME: this code is almost identical to default; need to refactor
-
-            $attrs = array();
-            if (!empty($this->target->fullname)) {
-                $attrs['title'] = $this->target->getFullname();
-            }
-
-            try {
-                $attrs = array('href' => $this->target->getUrl(),
-                               'class' => 'url');
-                $text_tag = 'a';
-            } catch (InvalidUrlException $e) {
-                $text_tag = 'abbr';
-            }
-
-            $this->out->elementStart('span', 'repeat');
-            $text_link = XMLStringer::estring($text_tag, $attrs, $this->target->getNickname());
-            // TRANS: Link to the author of a repeated notice. %s is a linked nickname.
-            $this->out->raw(sprintf(_('Repeat of %s'), $text_link));
-            $this->out->elementEnd('span');
-        }
-    }
-}
index 420f384e13e185e97813077dc43c87cf44fc4513..1722b85b6b926be5a1041b9f2ef981f2ac34e53c 100644 (file)
@@ -241,16 +241,21 @@ class Avatar extends Managed_DataObject
             // TRANS: An error message when avatar size is unreasonable
             throw new Exception(_m('Avatar size too large'));
         }
+        // So far we only have square avatars and I don't have time to
+        // rewrite support for non-square ones right now ;)
+        $height = $width;
 
         $original = Avatar::getUploaded($target);
 
-        $imagefile = new ImageFile($target->id, Avatar::path($original->filename));
-        $filename = $imagefile->resize($width);
+        $imagefile = new ImageFile(null, Avatar::path($original->filename));
+        $filename = Avatar::filename($target->getID(), image_type_to_extension($imagefile->preferredType()),
+                                     $width, common_timestamp());
+        $imagefile->resizeTo(Avatar::path($filename), array('width'=>$width, 'height'=>$height));
 
         $scaled = clone($original);
         $scaled->original = false;
         $scaled->width = $width;
-        $scaled->height = $width;
+        $scaled->height = $height;
         $scaled->url = Avatar::url($filename);
         $scaled->filename = $filename;
         $scaled->created = common_sql_now();
index 242da159c23e5fbb438f4967b4503d1f4dd40bce..1e296242b79f350c610fa327819ed2cfa4ed481a 100644 (file)
@@ -569,6 +569,14 @@ class File extends Managed_DataObject
                     $thumbs->delete();
                 }
             }
+
+            $f2p = new File_to_post();
+            $f2p->file_id = $this->id;
+            if ($f2p->find()) {
+                while ($f2p->fetch()) {
+                    $f2p->delete();
+                }
+            }
         }
 
         // And finally remove the entry from the database
index 4c44e10fc1435f9afe725ca115394d48c55ecf20..a2e633249f950c345b54b9bbd80ce713200929a4 100644 (file)
@@ -28,7 +28,7 @@ class File_thumbnail extends Managed_DataObject
     public $__table = 'file_thumbnail';                  // table name
     public $file_id;                         // int(4)  primary_key not_null
     public $url;                             // text
-    public $filename;                        // varchar(191)   not 255 because utf8mb4 takes more space
+    public $filename;                        // text
     public $width;                           // int(4)  primary_key
     public $height;                          // int(4)  primary_key
     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
@@ -39,7 +39,7 @@ class File_thumbnail extends Managed_DataObject
             'fields' => array(
                 'file_id' => array('type' => 'int', 'not null' => true, 'description' => 'thumbnail for what URL/file'),
                 'url' => array('type' => 'text', 'not null' => false, 'description' => 'URL of thumbnail'),
-                'filename' => array('type' => 'varchar', 'length' => 191, 'description' => 'if stored locally, filename is put here'),
+                'filename' => array('type' => 'text', 'description' => 'if stored locally, filename is put here'),
                 'width' => array('type' => 'int', 'description' => 'width of thumbnail'),
                 'height' => array('type' => 'int', 'description' => 'height of thumbnail'),
                 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
@@ -142,7 +142,11 @@ class File_thumbnail extends Managed_DataObject
                 $this->url = null;
                 $this->update($orig);
             }
-            return self::url($this->filename);
+            $url = common_local_url('attachment_thumbnail', array('attachment'=>$this->file_id));
+            if (strpos($url, '?') === false) {
+                $url .= '?';
+            }
+            return $url . http_build_query(array('w'=>$this->width, 'h'=>$this->height));
         }
 
         // No local filename available, return the URL we have stored
index 1d2733738fb277f2d13f9b1e392cf390dafae678..4c751ae4f399d78a70ad9266f4f89ee95540bea2 100644 (file)
@@ -52,6 +52,7 @@ class File_to_post extends Managed_DataObject
                 'file_to_post_post_id_fkey' => array('notice', array('post_id' => 'id')),
             ),
             'indexes' => array(
+                'file_id_idx' => array('file_id'),
                 'post_id_idx' => array('post_id'),
             ),
         );
@@ -87,7 +88,7 @@ class File_to_post extends Managed_DataObject
     function delete($useWhere=false)
     {
         $f = File::getKV('id', $this->file_id);
-        if (!empty($f)) {
+        if ($f instanceof File) {
             $f->blowCache();
         }
         return parent::delete($useWhere);
index c631c1fcc6768dd690bb449c8115096c1806f644..38e31cb2745189ff0a3b41ee1711f48803318e0e 100644 (file)
@@ -313,6 +313,16 @@ class Notice extends Managed_DataObject
         return $notice;
     }
 
+    public static function getById($id)
+    {
+        $notice = new Notice();
+        $notice->id = $id;
+        if (!$notice->find(true)) {
+            throw new NoResultException($notice);
+        }
+        return $notice;
+    }
+
     /**
      * Extract #hashtags from this notice's content and save them to the database.
      */
@@ -892,6 +902,12 @@ class Notice extends Managed_DataObject
                 $stored->insert();    // throws exception on error
                 $orig = clone($stored); // for updating later in this try clause
 
+                $object = null;
+                Event::handle('StoreActivityObject', array($act, $stored, $options, &$object));
+                if (empty($object)) {
+                    throw new ServerException('Unsuccessful call to StoreActivityObject '.$stored->uri . ': '.$act->asString());
+                }
+
                 // If it's not part of a conversation, it's
                 // the beginning of a new conversation.
                 if (empty($stored->conversation)) {
@@ -900,12 +916,6 @@ class Notice extends Managed_DataObject
                     $stored->conversation = $conv->id;
                 }
 
-                $object = null;
-                Event::handle('StoreActivityObject', array($act, $stored, $options, &$object));
-                if (empty($object)) {
-                    throw new ServerException('No object from StoreActivityObject '.$stored->uri . ': '.$act->asString());
-                }
-                $stored->object_type = ActivityUtils::resolveUri($object->getObjectType(), true);
                 $stored->update($orig);
             } catch (Exception $e) {
                 if (empty($stored->id)) {
@@ -1808,17 +1818,7 @@ class Notice extends Managed_DataObject
 
             $act->verb = $this->verb;
 
-            if ($this->repeat_of) {
-                $repeated = Notice::getKV('id', $this->repeat_of);
-                if ($repeated instanceof Notice) {
-                    // TRANS: A repeat activity's title. %1$s is repeater's nickname
-                    //        and %2$s is the repeated user's nickname.
-                    $act->title = sprintf(_('%1$s repeated a notice by %2$s'),
-                                          $this->getProfile()->getNickname(),
-                                          $repeated->getProfile()->getNickname());
-                    $act->objects[] = $repeated->asActivity($scoped);
-                }
-            } else {
+            if (!$this->repeat_of) {
                 $act->objects[] = $this->asActivityObject();
             }
 
@@ -1841,9 +1841,9 @@ class Notice extends Managed_DataObject
             $attachments = $this->attachments();
 
             foreach ($attachments as $attachment) {
-                // Save local attachments
+                // Include local attachments in Activity
                 if (!empty($attachment->filename)) {
-                    $act->attachments[] = ActivityObject::fromFile($attachment);
+                    $act->enclosures[] = $attachment->getEnclosure();
                 }
             }
 
@@ -2901,31 +2901,4 @@ class Notice extends Managed_DataObject
             $notice->_setReplies($ids);
         }
     }
-
-    protected $_repeats = array();
-
-    function getRepeats()
-    {
-        if (isset($this->_repeats[$this->id])) {
-            return $this->_repeats[$this->id];
-        }
-        $repeatMap = Notice::listGet('repeat_of', array($this->id));
-        $this->_repeats[$this->id] = $repeatMap[$this->id];
-        return $this->_repeats[$this->id];
-    }
-
-    function _setRepeats($repeats)
-    {
-        $this->_repeats[$this->id] = $repeats;
-    }
-
-    static function fillRepeats(&$notices)
-    {
-        $ids = self::_idsOf($notices);
-        $repeatMap = Notice::listGet('repeat_of', $ids);
-        foreach ($notices as $notice) {
-               $repeats = $repeatMap[$notice->id];
-            $notice->_setRepeats($repeats);
-        }
-    }
 }
index 9cf3f5e108849b859018c6520b10c5fc0967b6e8..d5d38d0d662be231d5a5b9c2467039cd51b8a773 100644 (file)
@@ -80,7 +80,7 @@ class Oauth_application extends Managed_DataObject
 
     function setOriginal($filename)
     {
-        $imagefile = new ImageFile($this->id, Avatar::path($filename));
+        $imagefile = new ImageFile(null, Avatar::path($filename));
 
         // XXX: Do we want to have a bunch of different size icons? homepage, stream, mini?
         // or just one and control size via CSS? --Zach
index 480606890d2032e3b53a5564ccba028c6c5b5e7a..6eb09782b18785574ce54f28585bae3e37480724 100644 (file)
@@ -160,7 +160,7 @@ class Profile extends Managed_DataObject
             return $this->getGroup()->setOriginal($filename);
         }
 
-        $imagefile = new ImageFile($this->id, Avatar::path($filename));
+        $imagefile = new ImageFile(null, Avatar::path($filename));
 
         $avatar = new Avatar();
         $avatar->profile_id = $this->id;
index ce7762646d97098a9efe7fd22ccefc02d26c73f4..0d6fd56af278df6adff8654ae4302d4850a94b96 100644 (file)
@@ -82,7 +82,7 @@ class Queue_item extends Managed_DataObject
     /**
      * Release a claimed item.
      */
-    function releaseCLaim()
+    function releaseClaim()
     {
         // DB_DataObject doesn't let us save nulls right now
         $sql = sprintf("UPDATE queue_item SET claimed=NULL WHERE id=%d", $this->id);
index 3dc5fd4b2d8e0e1e799fad943b59301621a05ee3..df54b7987c7d7228ca507cd9b842a159b095005a 100644 (file)
@@ -312,13 +312,21 @@ class User_group extends Managed_DataObject
 
     function setOriginal($filename)
     {
-        $imagefile = new ImageFile($this->id, Avatar::path($filename));
+        // This should be handled by the Profile->setOriginal function so user and group avatars are handled the same
+        $imagefile = new ImageFile(null, Avatar::path($filename));
+
+        $sizes = array('homepage_logo' => AVATAR_PROFILE_SIZE,
+                       'stream_logo' => AVATAR_STREAM_SIZE,
+                       'mini_logo' => AVATAR_MINI_SIZE);
 
         $orig = clone($this);
         $this->original_logo = Avatar::url($filename);
-        $this->homepage_logo = Avatar::url($imagefile->resize(AVATAR_PROFILE_SIZE));
-        $this->stream_logo = Avatar::url($imagefile->resize(AVATAR_STREAM_SIZE));
-        $this->mini_logo = Avatar::url($imagefile->resize(AVATAR_MINI_SIZE));
+        foreach ($sizes as $name=>$size) {
+            $filename = Avatar::filename($this->profile_id, image_type_to_extension($imagefile->preferredType()),
+                                         $size, common_timestamp());
+            $imagefile->resizeTo(Avatar::path($filename), array('width'=>$size, 'height'=>$size));
+            $this->$name = Avatar::url($filename);
+        }
         common_debug(common_log_objstring($this));
         return $this->update($orig);
     }
index ab77d808102a766a87aa86a4a5ff4eba3d6b9805..8923bc304c060ee27adf1b534655b753b1b6978c 100644 (file)
@@ -1,3 +1,7 @@
+2009-11-27  Danilo Šegan <danilo@gnome.org>
+
+       * Makefile: discontinue use of ChangeLog with 1.0.8.
+
 2006-02-28  Danilo Šegan  <danilo@gnome.org>
 
        * gettext.php: Added some comments about these workarounds for
@@ -6,7 +10,7 @@
 2006-02-28  Danilo Šegan  <danilo@gnome.org>
 
        Fixes bug #15923.
-       
+
        * gettext.php (gettext_reader): make magic check work on 64-bit
        platforms as well (by Steffen Pingel).
 
@@ -27,7 +31,7 @@
 2006-02-03  Danilo Šegan  <danilo@gnome.org>
 
        Added setlocale() emulation as well.
-       
+
        * examples/pigs_dropin.php: Use T_setlocale() and locale_emulation().
        * examples/pigs_fallback.php: Use T_setlocale() and locale_emulation().
 
index c7525e29c9005fb793b0954dbbe1fc9a949f6a6c..bca4f916d5fe731f805d82fe82da30bcae7f62cf 100644 (file)
@@ -1,9 +1,9 @@
-PHP-gettext 1.0
+PHP-gettext 1.0 (https://launchpad.net/php-gettext)
 
-Copyright 2003, 2006 -- Danilo "angry with PHP[1]" Segan
+Copyright 2003, 2006, 2009 -- Danilo "angry with PHP[1]" Segan
 Licensed under GPLv2 (or any later version, see COPYING)
 
-[1] PHP is actually cyrillic, and translates roughly to 
+[1] PHP is actually cyrillic, and translates roughly to
     "works-doesn't-work" (UTF-8: Ради-Не-Ради)
 
 
@@ -50,36 +50,16 @@ Features
     file data, I used imaginary abstract class StreamReader to do all
     the input (check streams.php). For your convenience, I've already
     provided two classes for reading files: FileReader and
-    StringReader (CachedFileReader is a combination of the two: it 
-    loads entire file contents into a string, and then works on that). 
-    See example below for usage. You can for instance use StringReader 
-    when you read in data from a database, or you can create your own 
-    derivative of StreamReader for anything you like. 
-    
+    StringReader (CachedFileReader is a combination of the two: it
+    loads entire file contents into a string, and then works on that).
+    See example below for usage. You can for instance use StringReader
+    when you read in data from a database, or you can create your own
+    derivative of StreamReader for anything you like.
 
-Bugs
-
-    Plural-forms field in MO header (translation for empty string,
-    i.e. "") is treated according to PHP syntactic rules (it's
-    eval()ed). Since these should actually follow C syntax, there are
-    some problems.
 
-    For instance, I'm used to using this:
-      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;
-    but it fails with PHP (it sets $plural=2 instead of 0 for $n==1).
-
-    The fix is usually simple, but I'm lazy to go into the details of
-    PHP operator precedence, and maybe try to fix it. In here, I had
-    to put everything after the first ':' in parenthesis:
-      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);
-    That works, and I'm satisfied.
+Bugs
 
-    Besides this one, there are probably a bunch of other bugs, since
-    I hate PHP (did I mention it already? no? strange), and don't
-    know it very well. So, feel free to fix any of those and report
-    them back to me at <danilo@kvota.net>.
+    Report them on https://bugs.launchpad.net/php-gettext
 
 Usage
 
@@ -94,19 +74,19 @@ Usage
     Then, use that as a parameter to gettext_reader constructor:
       $wohoo = new gettext_reader($streamer);
 
-    If you want to disable pre-loading of entire message catalog in 
-    memory (if, for example, you have a multi-thousand message catalog 
-    which you'll use only occasionally), use "false" for second 
+    If you want to disable pre-loading of entire message catalog in
+    memory (if, for example, you have a multi-thousand message catalog
+    which you'll use only occasionally), use "false" for second
     parameter to gettext_reader constructor:
       $wohoo = new gettext_reader($streamer, false);
 
     From now on, you have all the benefits of gettext data at your
-    disposal, so may run: 
+    disposal, so may run:
       print $wohoo->translate("This is a test");
       print $wohoo->ngettext("%d bird", "%d birds", $birds);
 
     You might need to pass parameter "-k" to xgettext to make it
-    extract all the strings. In above example, try with 
+    extract all the strings. In above example, try with
       xgettext -ktranslate -kngettext:1,2 file.php
     what should create messages.po which contains two messages for
     translation.
@@ -118,8 +98,8 @@ Usage
 
 Usage with gettext.inc (standard gettext interfaces emulation)
 
-    Check example in examples/pig_dropin.php, basically you include 
-    gettext.inc and use all the standard gettext interfaces as 
+    Check example in examples/pig_dropin.php, basically you include
+    gettext.inc and use all the standard gettext interfaces as
     documented on:
 
        http://www.php.net/gettext
@@ -137,20 +117,12 @@ Example
     There is also simple "update" script that can be used to generate
     POT file and to update the translation using msgmerge.
 
-Interesting TODO:
+TODO:
 
-  o Try to parse "plural-forms" header field, and to follow C syntax
-    rules. This won't be easy.
+  o Improve speed to be even more comparable to the native gettext
+    implementation.
 
-Boring TODO:
-
-  o Learn PHP and fix bugs, slowness and other stuff resulting from
-    my lack of knowledge (but *maybe*, it's not my knowledge that is
-    bad, but PHP itself ;-).  
-
-    (This is mostly done thanks to Nico Kaiser.)
-
-  o Try to use hash tables in MO files: with pre-loading, would it 
+  o Try to use hash tables in MO files: with pre-loading, would it
     be useful at all?
 
 Never-asked-questions:
@@ -160,7 +132,7 @@ Never-asked-questions:
 
     Well, it's quite simple. I consider that the first released thing
     should be labeled "version 1" (first, right?). Zero is there to
-    indicate that there's zero improvement and/or change compared to 
+    indicate that there's zero improvement and/or change compared to
     "version 1".
 
     I plan to use version numbers 1.0.* for small bugfixes, and to
@@ -173,7 +145,7 @@ Never-asked-questions:
     Mozart's 40th Symphony (there is one like that, right?).
 
   o Can I...?
-    
+
     Yes, you can. This is free software (as in freedom, free speech),
     and you might do whatever you wish with it, provided you do not
     limit freedom of others (GPL).
index 7c95e40ec226660cf4e314de34fb3344ddad8ac4..c9f7dc01681a26f2a53d9219edfaf3d6eb807b0a 100644 (file)
@@ -1,9 +1,10 @@
 <?php
 /*
    Copyright (c) 2005 Steven Armstrong <sa at c-area dot ch>
-   
+   Copyright (c) 2009 Danilo Segan <danilo@kvota.net>
+
    Drop in replacement for native gettext.
-   
+
    This file is part of PHP-gettext.
 
    PHP-gettext is free software; you can redistribute it and/or modify
 
 */
 /*
-LC_CTYPE               0
-LC_NUMERIC     1
-LC_TIME                        2
-LC_COLLATE     3
-LC_MONETARY    4
-LC_MESSAGES    5
-LC_ALL                 6
+LC_CTYPE        0
+LC_NUMERIC      1
+LC_TIME         2
+LC_COLLATE      3
+LC_MONETARY     4
+LC_MESSAGES     5
+LC_ALL          6
 */
 
+// LC_MESSAGES is not available if php-gettext is not loaded
+// while the other constants are already available from session extension.
+if (!defined('LC_MESSAGES')) {
+  define('LC_MESSAGES',        5);
+}
+
 require('streams.php');
 require('gettext.php');
 
@@ -44,29 +51,96 @@ $LC_CATEGORIES = array('LC_CTYPE', 'LC_NUMERIC', 'LC_TIME', 'LC_COLLATE', 'LC_MO
 $EMULATEGETTEXT = 0;
 $CURRENTLOCALE = '';
 
+/* Class to hold a single domain included in $text_domains. */
+class domain {
+  var $l10n;
+  var $path;
+  var $codeset;
+}
 
 // Utility functions
 
+/**
+ * Return a list of locales to try for any POSIX-style locale specification.
+ */
+function get_list_of_locales($locale) {
+  /* Figure out all possible locale names and start with the most
+   * specific ones.  I.e. for sr_CS.UTF-8@latin, look through all of
+   * sr_CS.UTF-8@latin, sr_CS@latin, sr@latin, sr_CS.UTF-8, sr_CS, sr.
+   */
+  $locale_names = array();
+  $lang = NULL;
+  $country = NULL;
+  $charset = NULL;
+  $modifier = NULL;
+  if ($locale) {
+    if (preg_match("/^(?P<lang>[a-z]{2,3})"              // language code
+                   ."(?:_(?P<country>[A-Z]{2}))?"           // country code
+                   ."(?:\.(?P<charset>[-A-Za-z0-9_]+))?"    // charset
+                   ."(?:@(?P<modifier>[-A-Za-z0-9_]+))?$/",  // @ modifier
+                   $locale, $matches)) {
+
+      if (isset($matches["lang"])) $lang = $matches["lang"];
+      if (isset($matches["country"])) $country = $matches["country"];
+      if (isset($matches["charset"])) $charset = $matches["charset"];
+      if (isset($matches["modifier"])) $modifier = $matches["modifier"];
+
+      if ($modifier) {
+        if ($country) {
+          if ($charset)
+            array_push($locale_names, "${lang}_$country.$charset@$modifier");
+          array_push($locale_names, "${lang}_$country@$modifier");
+        } elseif ($charset)
+            array_push($locale_names, "${lang}.$charset@$modifier");
+        array_push($locale_names, "$lang@$modifier");
+      }
+      if ($country) {
+        if ($charset)
+          array_push($locale_names, "${lang}_$country.$charset");
+        array_push($locale_names, "${lang}_$country");
+      } elseif ($charset)
+          array_push($locale_names, "${lang}.$charset");
+      array_push($locale_names, $lang);
+    }
+
+    // If the locale name doesn't match POSIX style, just include it as-is.
+    if (!in_array($locale, $locale_names))
+      array_push($locale_names, $locale);
+  }
+  return $locale_names;
+}
+
 /**
  * Utility function to get a StreamReader for the given text domain.
  */
 function _get_reader($domain=null, $category=5, $enable_cache=true) {
-       global $text_domains, $default_domain, $LC_CATEGORIES;
-       if (!isset($domain)) $domain = $default_domain;
-       if (!isset($text_domains[$domain]->l10n)) {
-               // get the current locale
-               $locale = _setlocale(LC_MESSAGES, 0);
-               $p = isset($text_domains[$domain]->path) ? $text_domains[$domain]->path : './';
-               $path = $p . "$locale/". $LC_CATEGORIES[$category] ."/$domain.mo";
-               if (file_exists($path)) {
-                       $input = new FileReader($path);
-               }
-               else {
-                       $input = null;
-               }
-               $text_domains[$domain]->l10n = new gettext_reader($input, $enable_cache);
-       }
-       return $text_domains[$domain]->l10n;
+    global $text_domains, $default_domain, $LC_CATEGORIES;
+    if (!isset($domain)) $domain = $default_domain;
+    if (!isset($text_domains[$domain]->l10n)) {
+        // get the current locale
+        $locale = _setlocale(LC_MESSAGES, 0);
+        $bound_path = isset($text_domains[$domain]->path) ?
+          $text_domains[$domain]->path : './';
+        $subpath = $LC_CATEGORIES[$category] ."/$domain.mo";
+
+        $locale_names = get_list_of_locales($locale);
+        $input = null;
+        foreach ($locale_names as $locale) {
+          $full_path = $bound_path . $locale . "/" . $subpath;
+          if (file_exists($full_path)) {
+            $input = new FileReader($full_path);
+            break;
+          }
+        }
+
+        if (!array_key_exists($domain, $text_domains)) {
+          // Initialize an empty domain object.
+          $text_domains[$domain] = new domain();
+        }
+        $text_domains[$domain]->l10n = new gettext_reader($input,
+                                                          $enable_cache);
+    }
+    return $text_domains[$domain]->l10n;
 }
 
 /**
@@ -80,8 +154,10 @@ function locale_emulation() {
 /**
  * Checks if the current locale is supported on this system.
  */
-function _check_locale() {
+function _check_locale_and_function($function=false) {
     global $EMULATEGETTEXT;
+    if ($function and !function_exists($function))
+        return false;
     return !$EMULATEGETTEXT;
 }
 
@@ -89,55 +165,70 @@ function _check_locale() {
  * Get the codeset for the given domain.
  */
 function _get_codeset($domain=null) {
-       global $text_domains, $default_domain, $LC_CATEGORIES;
-       if (!isset($domain)) $domain = $default_domain;
-       return (isset($text_domains[$domain]->codeset))? $text_domains[$domain]->codeset : ini_get('mbstring.internal_encoding');
+    global $text_domains, $default_domain, $LC_CATEGORIES;
+    if (!isset($domain)) $domain = $default_domain;
+    return (isset($text_domains[$domain]->codeset))? $text_domains[$domain]->codeset : ini_get('mbstring.internal_encoding');
 }
 
 /**
  * Convert the given string to the encoding set by bind_textdomain_codeset.
  */
 function _encode($text) {
-       $source_encoding = mb_detect_encoding($text);
-       $target_encoding = _get_codeset();
-       if ($source_encoding != $target_encoding) {
-               return mb_convert_encoding($text, $target_encoding, $source_encoding);
-       }
-       else {
-               return $text;
-       }
+  $target_encoding = _get_codeset();
+  if (function_exists("mb_detect_encoding")) {
+    $source_encoding = mb_detect_encoding($text);
+    if ($source_encoding != $target_encoding)
+      $text = mb_convert_encoding($text, $target_encoding, $source_encoding);
+  }
+  return $text;
 }
 
 
-
-
 // Custom implementation of the standard gettext related functions
 
+/**
+ * Returns passed in $locale, or environment variable $LANG if $locale == ''.
+ */
+function _get_default_locale($locale) {
+  if ($locale == '') // emulate variable support
+    return getenv('LANG');
+  else
+    return $locale;
+}
+
 /**
  * Sets a requested locale, if needed emulates it.
  */
 function _setlocale($category, $locale) {
     global $CURRENTLOCALE, $EMULATEGETTEXT;
     if ($locale === 0) { // use === to differentiate between string "0"
-        if ($CURRENTLOCALE != '') 
+        if ($CURRENTLOCALE != '')
             return $CURRENTLOCALE;
-        else 
+        else
             // obey LANG variable, maybe extend to support all of LC_* vars
             // even if we tried to read locale without setting it first
             return _setlocale($category, $CURRENTLOCALE);
     } else {
-        $ret = 0;
-        if (function_exists('setlocale')) // I don't know if this ever happens ;)
-           $ret = setlocale($category, $locale);
-        if ($ret and ($locale == '' or $ret == $locale)) {
-            $EMULATEGETTEXT = 0;
+        if (function_exists('setlocale')) {
+          $ret = setlocale($category, $locale);
+          if (($locale == '' and !$ret) or // failed setting it by env
+              ($locale != '' and $ret != $locale)) { // failed setting it
+            // Failed setting it according to environment.
+            $CURRENTLOCALE = _get_default_locale($locale);
+            $EMULATEGETTEXT = 1;
+          } else {
             $CURRENTLOCALE = $ret;
+            $EMULATEGETTEXT = 0;
+          }
         } else {
-           if ($locale == '') // emulate variable support
-               $CURRENTLOCALE = getenv('LANG');
-           else
-               $CURRENTLOCALE = $locale;
-            $EMULATEGETTEXT = 1;
+          // No function setlocale(), emulate it all.
+          $CURRENTLOCALE = _get_default_locale($locale);
+          $EMULATEGETTEXT = 1;
+        }
+        // Allow locale to be changed on the go for one translation domain.
+        global $text_domains, $default_domain;
+        if (array_key_exists($default_domain, $text_domains)) {
+            unset($text_domains[$default_domain]->l10n);
         }
         return $CURRENTLOCALE;
     }
@@ -147,135 +238,241 @@ function _setlocale($category, $locale) {
  * Sets the path for a domain.
  */
 function _bindtextdomain($domain, $path) {
-       global $text_domains;
-       // ensure $path ends with a slash
-       if ($path[strlen($path) - 1] != '/') $path .= '/';
-       elseif ($path[strlen($path) - 1] != '\\') $path .= '\\';
-       $text_domains[$domain]->path = $path;
+    global $text_domains;
+    // ensure $path ends with a slash ('/' should work for both, but lets still play nice)
+    if (substr(php_uname(), 0, 7) == "Windows") {
+      if ($path[strlen($path)-1] != '\\' and $path[strlen($path)-1] != '/')
+        $path .= '\\';
+    } else {
+      if ($path[strlen($path)-1] != '/')
+        $path .= '/';
+    }
+    if (!array_key_exists($domain, $text_domains)) {
+      // Initialize an empty domain object.
+      $text_domains[$domain] = new domain();
+    }
+    $text_domains[$domain]->path = $path;
 }
 
 /**
  * Specify the character encoding in which the messages from the DOMAIN message catalog will be returned.
  */
 function _bind_textdomain_codeset($domain, $codeset) {
-       global $text_domains;
-       $text_domains[$domain]->codeset = $codeset;
+    global $text_domains;
+    $text_domains[$domain]->codeset = $codeset;
 }
 
 /**
  * Sets the default domain.
  */
 function _textdomain($domain) {
-       global $default_domain;
-       $default_domain = $domain;
+    global $default_domain;
+    $default_domain = $domain;
 }
 
 /**
  * Lookup a message in the current domain.
  */
 function _gettext($msgid) {
-       $l10n = _get_reader();
-       //return $l10n->translate($msgid);
-       return _encode($l10n->translate($msgid));
+    $l10n = _get_reader();
+    return _encode($l10n->translate($msgid));
 }
+
 /**
  * Alias for gettext.
  */
 function __($msgid) {
-       return _gettext($msgid);
+    return _gettext($msgid);
 }
+
 /**
  * Plural version of gettext.
  */
-function _ngettext($single, $plural, $number) {
-       $l10n = _get_reader();
-       //return $l10n->ngettext($single, $plural, $number);
-       return _encode($l10n->ngettext($single, $plural, $number));
+function _ngettext($singular, $plural, $number) {
+    $l10n = _get_reader();
+    return _encode($l10n->ngettext($singular, $plural, $number));
 }
 
 /**
  * Override the current domain.
  */
 function _dgettext($domain, $msgid) {
-       $l10n = _get_reader($domain);
-       //return $l10n->translate($msgid);
-       return _encode($l10n->translate($msgid));
+    $l10n = _get_reader($domain);
+    return _encode($l10n->translate($msgid));
 }
+
 /**
  * Plural version of dgettext.
  */
-function _dngettext($domain, $single, $plural, $number) {
-       $l10n = _get_reader($domain);
-       //return $l10n->ngettext($single, $plural, $number);
-       return _encode($l10n->ngettext($single, $plural, $number));
+function _dngettext($domain, $singular, $plural, $number) {
+    $l10n = _get_reader($domain);
+    return _encode($l10n->ngettext($singular, $plural, $number));
 }
 
 /**
  * Overrides the domain and category for a single lookup.
  */
 function _dcgettext($domain, $msgid, $category) {
-       $l10n = _get_reader($domain, $category);
-       //return $l10n->translate($msgid);
-       return _encode($l10n->translate($msgid));
+    $l10n = _get_reader($domain, $category);
+    return _encode($l10n->translate($msgid));
 }
 /**
  * Plural version of dcgettext.
  */
-function _dcngettext($domain, $single, $plural, $number, $category) {
-       $l10n = _get_reader($domain, $category);
-       //return $l10n->ngettext($single, $plural, $number);
-       return _encode($l10n->ngettext($single, $plural, $number));
+function _dcngettext($domain, $singular, $plural, $number, $category) {
+    $l10n = _get_reader($domain, $category);
+    return _encode($l10n->ngettext($singular, $plural, $number));
+}
+
+/**
+ * Context version of gettext.
+ */
+function _pgettext($context, $msgid) {
+    $l10n = _get_reader();
+    return _encode($l10n->pgettext($context, $msgid));
+}
+
+/**
+ * Override the current domain in a context gettext call.
+ */
+function _dpgettext($domain, $context, $msgid) {
+    $l10n = _get_reader($domain);
+    return _encode($l10n->pgettext($context, $msgid));
+}
+
+/**
+ * Overrides the domain and category for a single context-based lookup.
+ */
+function _dcpgettext($domain, $context, $msgid, $category) {
+    $l10n = _get_reader($domain, $category);
+    return _encode($l10n->pgettext($context, $msgid));
+}
+
+/**
+ * Context version of ngettext.
+ */
+function _npgettext($context, $singular, $plural) {
+    $l10n = _get_reader();
+    return _encode($l10n->npgettext($context, $singular, $plural));
+}
+
+/**
+ * Override the current domain in a context ngettext call.
+ */
+function _dnpgettext($domain, $context, $singular, $plural) {
+    $l10n = _get_reader($domain);
+    return _encode($l10n->npgettext($context, $singular, $plural));
+}
+
+/**
+ * Overrides the domain and category for a plural context-based lookup.
+ */
+function _dcnpgettext($domain, $context, $singular, $plural, $category) {
+    $l10n = _get_reader($domain, $category);
+    return _encode($l10n->npgettext($context, $singular, $plural));
 }
 
 
 
-// Wrappers to use if the standard gettext functions are available, but the current locale is not supported by the system.
-// Use the standard impl if the current locale is supported, use the custom impl otherwise.
+// Wrappers to use if the standard gettext functions are available,
+// but the current locale is not supported by the system.
+// Use the standard impl if the current locale is supported, use the
+// custom impl otherwise.
 
 function T_setlocale($category, $locale) {
     return _setlocale($category, $locale);
 }
 
 function T_bindtextdomain($domain, $path) {
-       if (_check_locale()) return bindtextdomain($domain, $path);
-       else return _bindtextdomain($domain, $path);
+    if (_check_locale_and_function()) return bindtextdomain($domain, $path);
+    else return _bindtextdomain($domain, $path);
 }
 function T_bind_textdomain_codeset($domain, $codeset) {
     // bind_textdomain_codeset is available only in PHP 4.2.0+
-       if (_check_locale() and function_exists('bind_textdomain_codeset')) return bind_textdomain_codeset($domain, $codeset);
-       else return _bind_textdomain_codeset($domain, $codeset);
+    if (_check_locale_and_function('bind_textdomain_codeset'))
+        return bind_textdomain_codeset($domain, $codeset);
+    else return _bind_textdomain_codeset($domain, $codeset);
 }
 function T_textdomain($domain) {
-       if (_check_locale()) return textdomain($domain);
-       else return _textdomain($domain);
+    if (_check_locale_and_function()) return textdomain($domain);
+    else return _textdomain($domain);
 }
 function T_gettext($msgid) {
-       if (_check_locale()) return gettext($msgid);
-       else return _gettext($msgid);
+    if (_check_locale_and_function()) return gettext($msgid);
+    else return _gettext($msgid);
 }
 function T_($msgid) {
-       if (_check_locale()) return _($msgid);
-       return __($msgid);
+    if (_check_locale_and_function()) return _($msgid);
+    return __($msgid);
 }
-function T_ngettext($single, $plural, $number) {
-       if (_check_locale()) return ngettext($single, $plural, $number);
-       else return _ngettext($single, $plural, $number);
+function T_ngettext($singular, $plural, $number) {
+    if (_check_locale_and_function())
+        return ngettext($singular, $plural, $number);
+    else return _ngettext($singular, $plural, $number);
 }
 function T_dgettext($domain, $msgid) {
-       if (_check_locale()) return dgettext($domain, $msgid);
-       else return _dgettext($domain, $msgid);
+    if (_check_locale_and_function()) return dgettext($domain, $msgid);
+    else return _dgettext($domain, $msgid);
 }
-function T_dngettext($domain, $single, $plural, $number) {
-       if (_check_locale()) return dngettext($domain, $single, $plural, $number);
-       else return _dngettext($domain, $single, $plural, $number);
+function T_dngettext($domain, $singular, $plural, $number) {
+    if (_check_locale_and_function())
+        return dngettext($domain, $singular, $plural, $number);
+    else return _dngettext($domain, $singular, $plural, $number);
 }
 function T_dcgettext($domain, $msgid, $category) {
-       if (_check_locale()) return dcgettext($domain, $msgid, $category);
-       else return _dcgettext($domain, $msgid, $category);
+    if (_check_locale_and_function())
+        return dcgettext($domain, $msgid, $category);
+    else return _dcgettext($domain, $msgid, $category);
+}
+function T_dcngettext($domain, $singular, $plural, $number, $category) {
+    if (_check_locale_and_function())
+      return dcngettext($domain, $singular, $plural, $number, $category);
+    else return _dcngettext($domain, $singular, $plural, $number, $category);
 }
-function T_dcngettext($domain, $single, $plural, $number, $category) {
-       if (_check_locale()) return dcngettext($domain, $single, $plural, $number, $category);
-       else return _dcngettext($domain, $single, $plural, $number, $category);
+
+function T_pgettext($context, $msgid) {
+  if (_check_locale_and_function('pgettext'))
+      return pgettext($context, $msgid);
+  else
+      return _pgettext($context, $msgid);
+}
+
+function T_dpgettext($domain, $context, $msgid) {
+  if (_check_locale_and_function('dpgettext'))
+      return dpgettext($domain, $context, $msgid);
+  else
+      return _dpgettext($domain, $context, $msgid);
+}
+
+function T_dcpgettext($domain, $context, $msgid, $category) {
+  if (_check_locale_and_function('dcpgettext'))
+      return dcpgettext($domain, $context, $msgid, $category);
+  else
+      return _dcpgettext($domain, $context, $msgid, $category);
+}
+
+function T_npgettext($context, $singular, $plural, $number) {
+    if (_check_locale_and_function('npgettext'))
+        return npgettext($context, $singular, $plural, $number);
+    else
+        return _npgettext($context, $singular, $plural, $number);
+}
+
+function T_dnpgettext($domain, $context, $singular, $plural, $number) {
+  if (_check_locale_and_function('dnpgettext'))
+      return dnpgettext($domain, $context, $singular, $plural, $number);
+  else
+      return _dnpgettext($domain, $context, $singular, $plural, $number);
+}
+
+function T_dcnpgettext($domain, $context, $singular, $plural,
+                       $number, $category) {
+    if (_check_locale_and_function('dcnpgettext'))
+        return dcnpgettext($domain, $context, $singular,
+                           $plural, $number, $category);
+    else
+        return _dcnpgettext($domain, $context, $singular,
+                            $plural, $number, $category);
 }
 
 
@@ -283,36 +480,56 @@ function T_dcngettext($domain, $single, $plural, $number, $category) {
 // Wrappers used as a drop in replacement for the standard gettext functions
 
 if (!function_exists('gettext')) {
-       function bindtextdomain($domain, $path) {
-               return _bindtextdomain($domain, $path);
-       }
-       function bind_textdomain_codeset($domain, $codeset) {
-               return _bind_textdomain_codeset($domain, $codeset);
-       }
-       function textdomain($domain) {
-               return _textdomain($domain);
-       }
-       function gettext($msgid) {
-               return _gettext($msgid);
-       }
-       function _($msgid) {
-               return __($msgid);
-       }
-       function ngettext($single, $plural, $number) {
-               return _ngettext($single, $plural, $number);
-       }
-       function dgettext($domain, $msgid) {
-               return _dgettext($domain, $msgid);
-       }
-       function dngettext($domain, $single, $plural, $number) {
-               return _dngettext($domain, $single, $plural, $number);
-       }
-       function dcgettext($domain, $msgid, $category) {
-               return _dcgettext($domain, $msgid, $category);
-       }
-       function dcngettext($domain, $single, $plural, $number, $category) {
-               return _dcngettext($domain, $single, $plural, $number, $category);
-       }
-}
-
-?>
\ No newline at end of file
+    function bindtextdomain($domain, $path) {
+        return _bindtextdomain($domain, $path);
+    }
+    function bind_textdomain_codeset($domain, $codeset) {
+        return _bind_textdomain_codeset($domain, $codeset);
+    }
+    function textdomain($domain) {
+        return _textdomain($domain);
+    }
+    function gettext($msgid) {
+        return _gettext($msgid);
+    }
+    function _($msgid) {
+        return __($msgid);
+    }
+    function ngettext($singular, $plural, $number) {
+        return _ngettext($singular, $plural, $number);
+    }
+    function dgettext($domain, $msgid) {
+        return _dgettext($domain, $msgid);
+    }
+    function dngettext($domain, $singular, $plural, $number) {
+        return _dngettext($domain, $singular, $plural, $number);
+    }
+    function dcgettext($domain, $msgid, $category) {
+        return _dcgettext($domain, $msgid, $category);
+    }
+    function dcngettext($domain, $singular, $plural, $number, $category) {
+        return _dcngettext($domain, $singular, $plural, $number, $category);
+    }
+    function pgettext($context, $msgid) {
+        return _pgettext($context, $msgid);
+    }
+    function npgettext($context, $singular, $plural, $number) {
+        return _npgettext($context, $singular, $plural, $number);
+    }
+    function dpgettext($domain, $context, $msgid) {
+        return _dpgettext($domain, $context, $msgid);
+    }
+    function dnpgettext($domain, $context, $singular, $plural, $number) {
+        return _dnpgettext($domain, $context, $singular, $plural, $number);
+    }
+    function dcpgettext($domain, $context, $msgid, $category) {
+        return _dcpgettext($domain, $context, $msgid, $category);
+    }
+    function dcnpgettext($domain, $context, $singular, $plural,
+                         $number, $category) {
+      return _dcnpgettext($domain, $context, $singular, $plural,
+                          $number, $category);
+    }
+}
+
+?>
index cd080444ca0b782a1ec803145f165c815867400e..5064047cbd2427248c2a058e8ac09640bcb6c503 100644 (file)
@@ -1,8 +1,8 @@
 <?php
 /*
-   Copyright (c) 2003 Danilo Segan <danilo@kvota.net>.
+   Copyright (c) 2003, 2009 Danilo Segan <danilo@kvota.net>.
    Copyright (c) 2005 Nico Kaiser <nico@siriux.net>
-   
+
    This file is part of PHP-gettext.
 
    PHP-gettext is free software; you can redistribute it and/or modify
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 */
+
 /**
  * Provides a simple gettext replacement that works independently from
  * the system's gettext abilities.
  * It can read MO files and use them for translating strings.
  * The files are passed to gettext_reader as a Stream (see streams.php)
- * 
+ *
  * This version has the ability to cache all strings and translations to
  * speed up the string lookup.
  * While the cache is enabled by default, it can be switched off with the
@@ -36,7 +36,7 @@
 class gettext_reader {
   //public:
    var $error = 0; // public variable that holds error code (0 if no error)
-   
+
    //private:
   var $BYTEORDER = 0;        // 0: low endian, 1: big endian
   var $STREAM = NULL;
@@ -52,27 +52,33 @@ class gettext_reader {
 
 
   /* Methods */
-  
-    
+
+
   /**
    * Reads a 32bit Integer from the Stream
-   * 
+   *
    * @access private
    * @return Integer from the Stream
    */
   function readint() {
       if ($this->BYTEORDER == 0) {
         // low endian
-        return array_shift(unpack('V', $this->STREAM->read(4)));
+        $input=unpack('V', $this->STREAM->read(4));
+        return array_shift($input);
       } else {
         // big endian
-        return array_shift(unpack('N', $this->STREAM->read(4)));
+        $input=unpack('N', $this->STREAM->read(4));
+        return array_shift($input);
       }
     }
 
+  function read($bytes) {
+    return $this->STREAM->read($bytes);
+  }
+
   /**
    * Reads an array of Integers from the Stream
-   * 
+   *
    * @param int count How many elements should be read
    * @return Array of Integers
    */
@@ -85,10 +91,10 @@ class gettext_reader {
         return unpack('N'.$count, $this->STREAM->read(4 * $count));
       }
   }
-  
+
   /**
    * Constructor
-   * 
+   *
    * @param object Reader the StreamReader object
    * @param boolean enable_cache Enable or disable caching of strings (default on)
    */
@@ -98,39 +104,37 @@ class gettext_reader {
       $this->short_circuit = true;
       return;
     }
-    
+
     // Caching can be turned off
     $this->enable_cache = $enable_cache;
 
-    // $MAGIC1 = (int)0x950412de; //bug in PHP 5.0.2, see https://savannah.nongnu.org/bugs/?func=detailitem&item_id=10565
-    $MAGIC1 = (int) - 1794895138;
-    // $MAGIC2 = (int)0xde120495; //bug
-    $MAGIC2 = (int) - 569244523;
+    $MAGIC1 = "\x95\x04\x12\xde";
+    $MAGIC2 = "\xde\x12\x04\x95";
 
     $this->STREAM = $Reader;
-    $magic = $this->readint();
-    if ($magic == ($MAGIC1 & 0xFFFFFFFF)) { // to make sure it works for 64-bit platforms
-      $this->BYTEORDER = 0;
-    } elseif ($magic == ($MAGIC2 & 0xFFFFFFFF)) {
+    $magic = $this->read(4);
+    if ($magic == $MAGIC1) {
       $this->BYTEORDER = 1;
+    } elseif ($magic == $MAGIC2) {
+      $this->BYTEORDER = 0;
     } else {
       $this->error = 1; // not MO file
       return false;
     }
-    
+
     // FIXME: Do we care about revision? We should.
     $revision = $this->readint();
-    
+
     $this->total = $this->readint();
     $this->originals = $this->readint();
     $this->translations = $this->readint();
   }
-  
+
   /**
    * Loads the translation tables from the MO file into the cache
    * If caching is enabled, also loads all strings into a cache
    * to speed up translation lookups
-   * 
+   *
    * @access private
    */
   function load_tables() {
@@ -138,13 +142,17 @@ class gettext_reader {
       is_array($this->table_originals) &&
       is_array($this->table_translations))
       return;
-    
+
     /* get original and translations tables */
-    $this->STREAM->seekto($this->originals);
-    $this->table_originals = $this->readintarray($this->total * 2);
-    $this->STREAM->seekto($this->translations);
-    $this->table_translations = $this->readintarray($this->total * 2);
-    
+    if (!is_array($this->table_originals)) {
+      $this->STREAM->seekto($this->originals);
+      $this->table_originals = $this->readintarray($this->total * 2);
+    }
+    if (!is_array($this->table_translations)) {
+      $this->STREAM->seekto($this->translations);
+      $this->table_translations = $this->readintarray($this->total * 2);
+    }
+
     if ($this->enable_cache) {
       $this->cache_translations = array ();
       /* read all strings in the cache */
@@ -157,10 +165,10 @@ class gettext_reader {
       }
     }
   }
-  
+
   /**
    * Returns a string from the "originals" table
-   * 
+   *
    * @access private
    * @param int num Offset number of original string
    * @return string Requested string if found, otherwise ''
@@ -174,10 +182,10 @@ class gettext_reader {
     $data = $this->STREAM->read($length);
     return (string)$data;
   }
-  
+
   /**
    * Returns a string from the "translations" table
-   * 
+   *
    * @access private
    * @param int num Offset number of original string
    * @return string Requested string if found, otherwise ''
@@ -191,10 +199,10 @@ class gettext_reader {
     $data = $this->STREAM->read($length);
     return (string)$data;
   }
-  
+
   /**
    * Binary search for string
-   * 
+   *
    * @access private
    * @param string string
    * @param int start (internally used in recursive function)
@@ -232,10 +240,10 @@ class gettext_reader {
         return $this->find_string($string, $half, $end);
     }
   }
-  
+
   /**
    * Translates a string
-   * 
+   *
    * @access public
    * @param string string to be translated
    * @return string translated string (or original, if not found)
@@ -243,8 +251,8 @@ class gettext_reader {
   function translate($string) {
     if ($this->short_circuit)
       return $string;
-    $this->load_tables();     
-    
+    $this->load_tables();
+
     if ($this->enable_cache) {
       // Caching enabled, get translated string from cache
       if (array_key_exists($string, $this->cache_translations))
@@ -261,17 +269,66 @@ class gettext_reader {
     }
   }
 
+  /**
+   * Sanitize plural form expression for use in PHP eval call.
+   *
+   * @access private
+   * @return string sanitized plural form expression
+   */
+  function sanitize_plural_expression($expr) {
+    // Get rid of disallowed characters.
+    $expr = preg_replace('@[^a-zA-Z0-9_:;\(\)\?\|\&=!<>+*/\%-]@', '', $expr);
+
+    // Add parenthesis for tertiary '?' operator.
+    $expr .= ';';
+    $res = '';
+    $p = 0;
+    for ($i = 0; $i < strlen($expr); $i++) {
+      $ch = $expr[$i];
+      switch ($ch) {
+      case '?':
+        $res .= ' ? (';
+        $p++;
+        break;
+      case ':':
+        $res .= ') : (';
+        break;
+      case ';':
+        $res .= str_repeat( ')', $p) . ';';
+        $p = 0;
+        break;
+      default:
+        $res .= $ch;
+      }
+    }
+    return $res;
+  }
+
+  /**
+   * Parse full PO header and extract only plural forms line.
+   *
+   * @access private
+   * @return string verbatim plural form header field
+   */
+  function extract_plural_forms_header_from_po_header($header) {
+    if (preg_match("/(^|\n)plural-forms: ([^\n]*)\n/i", $header, $regs))
+      $expr = $regs[2];
+    else
+      $expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
+    return $expr;
+  }
+
   /**
    * Get possible plural forms from MO header
-   * 
+   *
    * @access private
    * @return string plural form header
    */
   function get_plural_forms() {
-    // lets assume message number 0 is header  
+    // lets assume message number 0 is header
     // this is true, right?
     $this->load_tables();
-    
+
     // cache header field for plural forms
     if (! is_string($this->pluralheader)) {
       if ($this->enable_cache) {
@@ -279,18 +336,15 @@ class gettext_reader {
       } else {
         $header = $this->get_translation_string(0);
       }
-      if (eregi("plural-forms: ([^\n]*)\n", $header, $regs))
-        $expr = $regs[1];
-      else
-        $expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
-      $this->pluralheader = $expr;
+      $expr = $this->extract_plural_forms_header_from_po_header($header);
+      $this->pluralheader = $this->sanitize_plural_expression($expr);
     }
     return $this->pluralheader;
   }
 
   /**
    * Detects which plural form to take
-   * 
+   *
    * @access private
    * @param n count
    * @return int array index of the right plural form
@@ -300,7 +354,7 @@ class gettext_reader {
     $string = str_replace('nplurals',"\$total",$string);
     $string = str_replace("n",$n,$string);
     $string = str_replace('plural',"\$plural",$string);
-    
+
     $total = 0;
     $plural = 0;
 
@@ -311,7 +365,7 @@ class gettext_reader {
 
   /**
    * Plural version of gettext
-   * 
+   *
    * @access public
    * @param string single
    * @param string plural
@@ -327,12 +381,12 @@ class gettext_reader {
     }
 
     // find out the appropriate form
-    $select = $this->select_string($number); 
-    
+    $select = $this->select_string($number);
+
     // this should contains all strings separated by NULLs
-    $key = $single.chr(0).$plural;
-    
-    
+    $key = $single . chr(0) . $plural;
+
+
     if ($this->enable_cache) {
       if (! array_key_exists($key, $this->cache_translations)) {
         return ($number != 1) ? $plural : $single;
@@ -353,6 +407,26 @@ class gettext_reader {
     }
   }
 
+  function pgettext($context, $msgid) {
+    $key = $context . chr(4) . $msgid;
+    $ret = $this->translate($key);
+    if (strpos($ret, "\004") !== FALSE) {
+      return $msgid;
+    } else {
+      return $ret;
+    }
+  }
+
+  function npgettext($context, $singular, $plural, $number) {
+    $key = $context . chr(4) . $singular;
+    $ret = $this->ngettext($key, $plural, $number);
+    if (strpos($ret, "\004") !== FALSE) {
+      return $singular;
+    } else {
+      return $ret;
+    }
+
+  }
 }
 
 ?>
index 3eafa7482828303b6c6713878e1d28c7d45a7381..3cdc1584e1fa57451596c59a55f1dd03dc1a7939 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-   Copyright (c) 2003, 2005 Danilo Segan <danilo@kvota.net>.
+   Copyright (c) 2003, 2005, 2006, 2009 Danilo Segan <danilo@kvota.net>.
 
    This file is part of PHP-gettext.
 
 */
 
 
-// Simple class to wrap file streams, string streams, etc.
-// seek is essential, and it should be byte stream
+  // Simple class to wrap file streams, string streams, etc.
+  // seek is essential, and it should be byte stream
 class StreamReader {
   // should return a string [FIXME: perhaps return array of bytes?]
   function read($bytes) {
     return false;
   }
-  
+
   // should return new position
   function seekto($position) {
     return false;
   }
-  
+
   // returns current position
   function currentpos() {
     return false;
   }
-  
+
   // returns length of entire stream (limit for seekto()s)
   function length() {
     return false;
   }
-}
+};
 
 class StringReader {
   var $_pos;
@@ -78,7 +78,7 @@ class StringReader {
     return strlen($this->_str);
   }
 
-}
+};
 
 
 class FileReader {
@@ -93,8 +93,8 @@ class FileReader {
       $this->_pos = 0;
       $this->_fd = fopen($filename,'rb');
       if (!$this->_fd) {
-       $this->error = 3; // Cannot read file, probably permissions
-       return false;
+        $this->error = 3; // Cannot read file, probably permissions
+        return false;
       }
     } else {
       $this->error = 2; // File doesn't exist
@@ -115,7 +115,7 @@ class FileReader {
         $bytes -= strlen($chunk);
       }
       $this->_pos = ftell($this->_fd);
-      
+
       return $data;
     } else return '';
   }
@@ -138,9 +138,9 @@ class FileReader {
     fclose($this->_fd);
   }
 
-}
+};
 
-// Preloads entire file in memory first, then creates a StringReader 
+// Preloads entire file in memory first, then creates a StringReader
 // over it (it assumes knowledge of StringReader internals)
 class CachedFileReader extends StringReader {
   function CachedFileReader($filename) {
@@ -150,8 +150,8 @@ class CachedFileReader extends StringReader {
       $fd = fopen($filename,'rb');
 
       if (!$fd) {
-       $this->error = 3; // Cannot read file, probably permissions
-       return false;
+        $this->error = 3; // Cannot read file, probably permissions
+        return false;
       }
       $this->_str = fread($fd, $length);
       fclose($fd);
@@ -161,7 +161,7 @@ class CachedFileReader extends StringReader {
       return false;
     }
   }
-}
+};
 
 
-?>
\ No newline at end of file
+?>
index cee238836a3515d093174b8483c3db48f8b3aa65..190c1e4f43647724d313ddc417cce8851bcc7bff 100644 (file)
@@ -57,6 +57,9 @@ var SN = { // StatusNet
         }
     },
 
+    V: {    // Variables
+    },
+
     /**
      * Map of localized message strings exported to script from the PHP
      * side via Action::getScriptMessages().
@@ -216,6 +219,27 @@ var SN = { // StatusNet
             return url;
         },
 
+        FormNoticeUniqueID: function (form) {
+            var oldId = form.attr('id');
+            var newId = 'form_notice_' + Math.floor(Math.random()*999999999);
+            var attrs = ['name', 'for', 'id'];
+            for (var key in attrs) {
+                if (form.attr(attrs[key]) === undefined) {
+                    continue;
+                }
+                form.attr(attrs[key], form.attr(attrs[key]).replace(oldId, newId));
+            }
+            for (var key in attrs) {
+                form.find("[" + attrs[key] + "*='" + oldId + "']").each(function () {
+                        if ($(this).attr(attrs[key]) === undefined) {
+                            return; // since we're inside the each(function () { ... });
+                        }
+                        var newAttr = $(this).attr(attrs[key]).replace(oldId, newId);
+                        $(this).attr(attrs[key], newAttr);
+                    });
+            }
+        },
+
         /**
          * Grabs form data and submits it asynchronously, with 'ajax=1'
          * parameter added to the rest.
@@ -699,27 +723,38 @@ var SN = { // StatusNet
             var replyItem = $('li.notice-reply', list);
             if (replyItem.length == 0) {
                 replyItem = $('<li class="notice-reply"></li>');
+            }
+            replyForm = replyItem.children('form');
+            if (replyForm.length == 0) {
+                // Let's try another trick to avoid fetching by URL
+                var noticeForm = $('#input_form_status > form');
+                if (noticeForm.length == 0) {
+                    // No notice form found on the page, so let's just
+                    // fetch a fresh copy of the notice form over AJAX.
+                    $.ajax({
+                        url: SN.V.urlNewNotice,
+                        data: {ajax: 1, inreplyto: id},
+                        success: function (data, textStatus, xhr) {
+                            var formEl = document._importNode($('form', data)[0], true);
+                            replyForm = $(formEl);
+                            replyItem.append(replyForm);
+                            list.append(replyItem);
 
-                // Fetch a fresh copy of the notice form over AJAX.
-                var url = $('#input_form_status > form').attr('action');
-                $.ajax({
-                    url: url,
-                    data: {ajax: 1, inreplyto: id},
-                    success: function (data, textStatus, xhr) {
-                        var formEl = document._importNode($('form', data)[0], true);
-                        replyForm = $(formEl);
-                        replyItem.append(replyForm);
-                        list.append(replyItem);
-
-                        SN.Init.NoticeFormSetup(replyForm);
-                        nextStep();
-                    },
-                });
-            } else {
-                replyForm = replyItem.children('form');
+                            SN.Init.NoticeFormSetup(replyForm);
+                            nextStep();
+                        },
+                    });
+                    // We do everything relevant in 'success' above
+                    return;
+                }
+                replyForm = noticeForm.clone();
                 SN.Init.NoticeFormSetup(replyForm);
-                nextStep();
+                replyItem.append(replyForm);
+                list.append(replyItem);
             }
+            // replyForm is set, we're not fetching by URL...
+            // Next setp is to configure in-reply-to etc.
+            nextStep();
         },
 
         /**
@@ -1466,6 +1501,7 @@ var SN = { // StatusNet
                 return false;
             }
             SN.U.NoticeLocationAttach(form);
+            SN.U.FormNoticeUniqueID(form);
             SN.U.FormNoticeXHR(form);
             SN.U.FormNoticeEnhancements(form);
             SN.U.NoticeDataAttach(form);
index 14d0fe80579f70eab7915ce7ff31635b93736d66..a9cc99221da1b2eb573bb309aac6f323e4352a21 100644 (file)
@@ -431,6 +431,7 @@ class Action extends HTMLOutputter // lawsuit
                 $this->inlineScript('var _peopletagAC = "' .
                                     common_local_url('peopletagautocomplete') . '";');
                 $this->showScriptMessages();
+                $this->showScriptVariables();
                 // Anti-framing code to avoid clickjacking attacks in older browsers.
                 // This will show a blank page if the page is being framed, which is
                 // consistent with the behavior of the 'X-Frame-Options: SAMEORIGIN'
@@ -473,6 +474,19 @@ class Action extends HTMLOutputter // lawsuit
         return $messages;
     }
 
+    protected function showScriptVariables()
+    {
+        $vars = array();
+
+        if (Event::handle('StartScriptVariables', array($this, &$vars))) {
+            $vars['urlNewNotice'] = common_local_url('newnotice');
+        }
+        if (!empty($vars)) {
+            $this->inlineScript('SN.V = ' . json_encode($vars));
+        }
+        return $vars;
+    }
+
     /**
      * If the action will need localizable text strings, export them here like so:
      *
@@ -1357,7 +1371,7 @@ class Action extends HTMLOutputter // lawsuit
      * Upstream bug is::
      * https://pear.php.net/bugs/bug.php?id=20291
      */
-    function booleanintstring($key, $def)
+    function booleanintstring($key, $def=false)
     {
         return $this->boolean($key, $def) ? '1' : '0';
     }
index 22f794e14bc1fea23360e76c2f7d13c4c736bb42..529749cc1ddc46b32373a6f12b65744db19657c2 100644 (file)
@@ -160,10 +160,11 @@ abstract class ActivityHandlerPlugin extends Plugin
     * @fixme are there any standard options?
     *
     * @param Activity $activity
-    * @param Profile $actor
+    * @param Notice   $stored       The notice in our database for this certain object
     * @param array $options=array()
     *
-    * @return Notice the resulting notice
+    * @return object    If the verb handling plugin creates an object, it can be returned here (otherwise true)
+    * @throws exception On any error.
     */
     protected function saveObjectFromActivity(Activity $activity, Notice $stored, array $options=array())
     {
@@ -174,7 +175,7 @@ abstract class ActivityHandlerPlugin extends Plugin
      * This usually gets called from Notice::saveActivity after a Notice object has been created,
      * so it contains a proper id and a uri for the object to be saved.
      */
-    public function onStoreActivityObject(Activity $act, Notice $stored, array $options=array(), &$object) {
+    public function onStoreActivityObject(Activity $act, Notice $stored, array $options, &$object) {
         // $this->oldSaveNew is there during a migration period of plugins, to start using
         // Notice::saveActivity instead of Notice::saveNew
         if (!$this->isMyActivity($act) || isset($this->oldSaveNew)) {
@@ -182,7 +183,13 @@ abstract class ActivityHandlerPlugin extends Plugin
         }
         $object = $this->saveObjectFromActivity($act, $stored, $options);
         try {
-            $act->context->attention = array_merge($act->context->attention, $object->getAttentionArray());
+            // In the future we probably want to use something like ActivityVerb_DataObject for the kind
+            // of objects which are returned from saveObjectFromActivity.
+            if ($object instanceof Managed_DataObject) {
+                // If the verb handling plugin figured out some more attention URIs, add them here to the
+                // original activity. This is only done if a separate object is actually needed to be saved.
+                $act->context->attention = array_merge($act->context->attention, $object->getAttentionArray());
+            }
         } catch (Exception $e) {
             common_debug('WARNING: Could not get attention list from object '.get_class($object).'!');
         }
@@ -595,7 +602,6 @@ abstract class ActivityHandlerPlugin extends Plugin
         $nli->showNoticeSource();
         $nli->showNoticeLocation();
         $nli->showPermalink();
-        $nli->showRepeat();
 
         $nli->showNoticeOptions();
     }
index 513605b620a84356e7c82e2e3ad7c4bf05ff35de..dc6f9c93f4719b50b733c021ff1b83ed08613084 100644 (file)
@@ -48,6 +48,7 @@ class ActivityVerb
     const SHARE    = 'http://activitystrea.ms/schema/1.0/share';
     const SAVE     = 'http://activitystrea.ms/schema/1.0/save';
     const FAVORITE = 'http://activitystrea.ms/schema/1.0/favorite';
+    const LIKE     = 'http://activitystrea.ms/schema/1.0/like'; // This is a synonym of favorite
     const PLAY     = 'http://activitystrea.ms/schema/1.0/play';
     const FOLLOW   = 'http://activitystrea.ms/schema/1.0/follow';
     const FRIEND   = 'http://activitystrea.ms/schema/1.0/make-friend';
@@ -56,7 +57,8 @@ class ActivityVerb
 
     // Custom OStatus verbs for the flipside until they're standardized
     const DELETE     = 'http://ostatus.org/schema/1.0/unfollow';
-    const UNFAVORITE = 'http://ostatus.org/schema/1.0/unfavorite';
+    const UNFAVORITE = 'http://activitystrea.ms/schema/1.0/unfavorite';
+    const UNLIKE     = 'http://activitystrea.ms/schema/1.0/unlike'; // This is a synonym of unfavorite
     const UNFOLLOW   = 'http://ostatus.org/schema/1.0/unfollow';
     const LEAVE      = 'http://ostatus.org/schema/1.0/leave';
     const UNTAG      = 'http://ostatus.org/schema/1.0/untag';
index 062e6d07746fcf2cfa34b72cfac95f372b8d5575..0eea08bed63f147a4c05b1edaec6fbfc68793cd2 100755 (executable)
@@ -303,6 +303,7 @@ class ApiAction extends Action
     {
         $base = $this->twitterSimpleStatusArray($notice, $include_user);
 
+        // FIXME: MOVE TO SHARE PLUGIN
         if (!empty($notice->repeat_of)) {
             $original = Notice::getKV('id', $notice->repeat_of);
             if ($original instanceof Notice) {
@@ -374,12 +375,6 @@ class ApiAction extends Action
             $twitter_status['geo'] = null;
         }
 
-        if (!is_null($this->scoped)) {
-            $twitter_status['repeated']  = $this->scoped->hasRepeated($notice);
-        } else {
-            $twitter_status['repeated'] = false;
-        }
-
         // Enclosures
         $attachments = $notice->attachments();
 
@@ -646,6 +641,7 @@ class ApiAction extends Action
                 $this->showGeoXML($value);
                 break;
             case 'retweeted_status':
+                // FIXME: MOVE TO SHARE PLUGIN
                 $this->showTwitterXmlStatus($value, 'retweeted_status');
                 break;
             default:
index 8ab6a82a12680aa367bb723812924f2a7b7a0167..ab156bee92e5b6adbd08f3000bd46502e6aaaace 100644 (file)
@@ -108,6 +108,12 @@ class AttachmentListItem extends Widget
         if (Event::handle('StartShowAttachmentRepresentation', array($this->out, $this->attachment))) {
             if (!empty($this->attachment->mimetype)) {
                 $mediatype = common_get_mime_media($this->attachment->mimetype);
+
+                // FIXME: Get proper mime recognition of Ogg files! If system has 'mediainfo', this should do it:
+                // $ mediainfo --inform='General;%InternetMediaType%'
+                if ($this->attachment->mimetype === 'application/ogg') {
+                    $mediatype = 'video';   // because this element can handle Ogg/Vorbis etc. on its own
+                }
                 switch ($mediatype) {
                 // Anything we understand as an image, if we need special treatment, do it in StartShowAttachmentRepresentation
                 case 'image':
@@ -144,18 +150,6 @@ class AttachmentListItem extends Widget
 
                 default:
                     switch ($this->attachment->mimetype) {
-                    // Ogg media that we're not really sure what it is...
-                    case 'application/ogg':
-                        $arr  = array('type' => $this->attachment->mimetype,
-                            'data' => $this->attachment->getUrl(),
-                            'width' => 320,
-                            'height' => 240
-                        );
-                        $this->out->elementStart('object', $arr);
-                        $this->out->element('param', array('name' => 'src', 'value' => $this->attachment->getUrl()));
-                        $this->out->element('param', array('name' => 'autoStart', 'value' => 1));
-                        $this->out->elementEnd('object');
-                        break;
                     case 'text/html':
                         if (!empty($this->attachment->filename)
                                 && (GNUsocial::isAjax() || common_config('attachments', 'show_html'))) {
index efbcf91bfe881f8c50bcc62116d32218164024c0..830b97ee239e9d8ba693efb3aa7f3ebf1b5cde4a 100644 (file)
@@ -17,7 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
+if (!defined('GNUSOCIAL')) { exit(1); }
 
 require_once(INSTALLDIR.'/lib/channel.php');
 
@@ -523,32 +523,6 @@ class WhoisCommand extends Command
     }
 }
 
-class RepeatCommand extends Command
-{
-    var $other = null;
-    function __construct($user, $other)
-    {
-        parent::__construct($user);
-        $this->other = $other;
-    }
-
-    function handle($channel)
-    {
-        $notice = $this->getNotice($this->other);
-
-        try {
-            $repeat = $notice->repeat($this->scoped->id, $channel->source());
-            $recipient = $notice->getProfile();
-
-            // TRANS: Message given having repeated a notice from another user.
-            // TRANS: %s is the name of the user for which the notice was repeated.
-            $channel->output($this->user, sprintf(_('Notice from %s repeated.'), $recipient->nickname));
-        } catch (Exception $e) {
-            $channel->error($this->user, $e->getMessage());
-        }
-    }
-}
-
 class ReplyCommand extends Command
 {
     var $other = null;
@@ -912,10 +886,6 @@ class HelpCommand extends Command
                           "whois <nickname>" => _m('COMMANDHELP', "get profile info on user"),
                           // TRANS: Help message for IM/SMS command "lose <nickname>".
                           "lose <nickname>" => _m('COMMANDHELP', "force user to stop following you"),
-                          // TRANS: Help message for IM/SMS command "repeat #<notice_id>".
-                          "repeat #<notice_id>" => _m('COMMANDHELP', "repeat a notice with a given id"),
-                          // TRANS: Help message for IM/SMS command "repeat <nickname>".
-                          "repeat <nickname>" => _m('COMMANDHELP', "repeat the last notice from user"),
                           // TRANS: Help message for IM/SMS command "reply #<notice_id>".
                           "reply #<notice_id>" => _m('COMMANDHELP', "reply to notice with a given id"),
                           // TRANS: Help message for IM/SMS command "reply <nickname>".
@@ -962,7 +932,7 @@ class HelpCommand extends Command
         // Give plugins a chance to add or override...
         Event::handle('HelpCommandMessages', array($this, &$commands));
 
-        sort($commands);
+        ksort($commands);
         foreach ($commands as $command => $help) {
             $out[] = "$command - $help";
         }
index e7d98da0255048b9786acc8506f72620f1a3aba2..d2b744e93d79c41554dc2788277efcc7f3f6883c 100644 (file)
@@ -192,21 +192,6 @@ class CommandInterpreter
                     $result = new ReplyCommand($user, $other, $extra);
                 }
                 break;
-            case 'repeat':
-            case 'rp':
-            case 'rt':
-            case 'rd':
-                if (!$arg) {
-                    $result = null;
-                } else {
-                    list($other, $extra) = self::split_arg($arg);
-                    if ($extra) {
-                        $result = null;
-                    } else {
-                        $result = new RepeatCommand($user, $other);
-                    }
-                }
-                break;
             case 'whois':
                 if (!$arg) {
                     $result = null;
index f843d6d9e617286b0b251b55ead91256d3e86fbd..45c4b694d2e4848237bcae4563461fbb79ab55be 100644 (file)
@@ -73,54 +73,57 @@ class DBQueueManager extends QueueManager
     {
         //$this->_log(LOG_DEBUG, 'Checking for notices...');
         $qi = Queue_item::top($this->activeQueues());
-        if (empty($qi)) {
+        if (!$qi instanceof Queue_item) {
             //$this->_log(LOG_DEBUG, 'No notices waiting; idling.');
             return false;
         }
 
-        $queue = $qi->transport;
         try {
             $item = $this->decode($qi->frame);
         } catch (Exception $e) {
-            $this->_log(LOG_INFO, "[$queue] Discarding: ".$e->getMessage());
+            $this->_log(LOG_INFO, "[{$qi->transport}] Discarding: ".$e->getMessage());
             $this->_done($qi);
             return true;
         }
 
         $rep = $this->logrep($item);
-        $this->_log(LOG_DEBUG, "Got $rep for transport $queue");
+        $this->_log(LOG_DEBUG, "Got {$rep} for transport {$qi->transport}");
         
-        $handler = $this->getHandler($queue);
+        $handler = $this->getHandler($qi->transport);
         if ($handler) {
             if ($handler->handle($item)) {
-                $this->_log(LOG_INFO, "[$queue:$rep] Successfully handled item");
+                $this->_log(LOG_INFO, "[{$qi->transport}:$rep] Successfully handled item");
                 $this->_done($qi);
             } else {
-                $this->_log(LOG_INFO, "[$queue:$rep] Failed to handle item");
+                $this->_log(LOG_INFO, "[{$qi->transport}:$rep] Failed to handle item");
                 $this->_fail($qi);
             }
         } else {
-            $this->_log(LOG_INFO, "[$queue:$rep] No handler for queue $queue; discarding.");
-            $this->_done($qi);
+            $this->noHandlerFound($qi, $rep);
         }
         return true;
     }
 
+    // What to do if no handler was found. For example, the OpportunisticQM
+    // should avoid deleting items just because it can't reach XMPP queues etc.
+    protected function noHandlerFound(Queue_item $qi, $rep=null) {
+        $this->_log(LOG_INFO, "[{$qi->transport}:{$rep}] No handler for queue {$qi->transport}; discarding.");
+        $this->_done($qi);
+    }
+
     /**
      * Delete our claimed item from the queue after successful processing.
      *
      * @param QueueItem $qi
      */
-    protected function _done($qi)
+    protected function _done(Queue_item $qi)
     {
-        $queue = $qi->transport;
-
         if (empty($qi->claimed)) {
-            $this->_log(LOG_WARNING, "Reluctantly releasing unclaimed queue item $qi->id from $qi->queue");
+            $this->_log(LOG_WARNING, "Reluctantly releasing unclaimed queue item {$qi->id} from {$qi->transport}");
         }
         $qi->delete();
 
-        $this->stats('handled', $queue);
+        $this->stats('handled', $qi->transport);
     }
 
     /**
@@ -129,16 +132,16 @@ class DBQueueManager extends QueueManager
      *
      * @param QueueItem $qi
      */
-    protected function _fail($qi)
+    protected function _fail(Queue_item $qi, $releaseOnly=false)
     {
-        $queue = $qi->transport;
-
         if (empty($qi->claimed)) {
-            $this->_log(LOG_WARNING, "[$queue:item $qi->id] Ignoring failure for unclaimed queue item");
+            $this->_log(LOG_WARNING, "[{$qi->transport}:item {$qi->id}] Ignoring failure for unclaimed queue item");
         } else {
             $qi->releaseClaim();
         }
 
-        $this->stats('error', $queue);
+        if (!$releaseOnly) {
+            $this->stats('error', $qi->transport);
+        }
     }
 }
index dfa3626927884a23b87da853c3311d6e903cb7ea..6369fbddc6004f12d6f978168e29142a9f1be0e3 100644 (file)
@@ -181,7 +181,7 @@ $default =
         array('dropoff' => 864000.0,   # controls weighting based on age
               'cutoff' => 86400 * 90), # only look at notices favorited in last 90 days
         'daemon' =>
-        array('piddir' => '/var/run',
+        array('piddir' => sys_get_temp_dir(),
               'user' => false,
               'group' => false),
         'emailpost' =>
@@ -285,6 +285,11 @@ $default =
         array('handle' => false,   // whether to handle sessions ourselves
               'debug' => false,    // debugging output for sessions
               'gc_limit' => 1000), // max sessions to expire at a time
+        'htmlfilter' => array(  // purify HTML through htmLawed
+            'img' => true,
+            'video' => true,
+            'audio' => true,
+        ),
         'notice' =>
         array('contentlimit' => null,
               'defaultscope' => null, // null means 1 if site/private, 0 otherwise
@@ -298,9 +303,11 @@ $default =
         array('disabled' => true),
         'plugins' =>
         array('core' => array(
+                            'ActivityVerb' => array(),
                             'AuthCrypt' => array(),
                             'Cronish' => array(),
                             'Favorite' => array(),
+                            'Share' => array(),
                             'LRDD' => array(),
                             'StrictTransportSecurity' => array(),
                         ),
index 0cc455e9681df7462a7d804ef819f7094e068d5b..5cf05d4a324fede134e48eb9f0024a979f80b6f8 100644 (file)
@@ -92,7 +92,7 @@ class FormAction extends ManagedAction
     /**
      * @return string with instructions to pass into common_markup_to_html()
      */
-    public function getInstructions()
+    protected function getInstructions()
     {
         return null;
     }
index b71f365c244cd0e3320142c87be199dc219f1550..4ec8b083e02955de5c08275824dd2729bbe5bcab 100644 (file)
@@ -144,7 +144,7 @@ require_once INSTALLDIR.'/lib/action.php';
 require_once INSTALLDIR.'/lib/mail.php';
 
 //set PEAR error handling to use regular PHP exceptions
-function PEAR_ErrorToPEAR_Exception($err)
+function PEAR_ErrorToPEAR_Exception(PEAR_Error $err)
 {
     //DB_DataObject throws error when an empty set would be returned
     //That behavior is weird, and not how the rest of StatusNet works.
index 3409ff8f9252bd4473dca6db39b7cd10ca141e7f..5e400379529b1c72e5b8c0631528d9ae78e3c3de 100644 (file)
  * @link      http://status.net/
  */
 
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once INSTALLDIR.'/lib/xmloutputter.php';
+if (!defined('GNUSOCIAL')) { exit(1); }
 
 // Can include XHTML options but these are too fragile in practice.
 define('PAGE_TYPE_PREFS', 'text/html');
@@ -58,6 +54,9 @@ define('PAGE_TYPE_PREFS', 'text/html');
 
 class HTMLOutputter extends XMLOutputter
 {
+    protected $DTD = array('doctype' => 'html',
+                           'spec'    => '-//W3C//DTD XHTML 1.0 Strict//EN',
+                           'uri'     => 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
     /**
      * Constructor
      *
@@ -120,9 +119,8 @@ class HTMLOutputter extends XMLOutputter
             // Required for XML documents
             $this->startXML();
         }
-        $this->xw->writeDTD('html',
-                            '-//W3C//DTD XHTML 1.0 Strict//EN',
-                            'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
+
+        $this->writeDTD();
 
         $language = $this->getLanguage();
 
@@ -138,6 +136,18 @@ class HTMLOutputter extends XMLOutputter
         }
     }
 
+    public function setDTD($doctype, $spec, $uri)
+    {
+        $this->DTD = array('doctype' => $doctype, 'spec' => $spec, 'uri' => $uri);
+    }
+
+    protected function writeDTD()
+    {
+        $this->xw->writeDTD($this->DTD['doctype'],
+                            $this->DTD['spec'],
+                            $this->DTD['uri']);
+    }
+
     function getLanguage()
     {
         // FIXME: correct language for interface
index 7a545ad09302ce6117bef7f73c5ac4063da2da4f..2d1a3af02efba2aa68f50ce796c9fa6d4078ae78 100644 (file)
@@ -206,31 +206,6 @@ class ImageFile
         return new ImageFile(null, $_FILES[$param]['tmp_name']);
     }
 
-    /**
-     * Compat interface for old code generating avatar thumbnails...
-     * Saves the scaled file directly into the avatar area.
-     *
-     * @param int $size target width & height -- must be square
-     * @param int $x (default 0) upper-left corner to crop from
-     * @param int $y (default 0) upper-left corner to crop from
-     * @param int $w (default full) width of image area to crop
-     * @param int $h (default full) height of image area to crop
-     * @return string filename
-     */
-    function resize($size, $x = 0, $y = 0, $w = null, $h = null)
-    {
-        $targetType = $this->preferredType();
-        $outname = Avatar::filename($this->id,
-                                    image_type_to_extension($targetType),
-                                    $size,
-                                    common_timestamp());
-        $outpath = Avatar::path($outname);
-        $this->resizeTo($outpath, array('width'=>$size, 'height'=>$size,
-                                        'x'=>$x,        'y'=>$y,
-                                        'w'=>$w,        'h'=>$h));
-        return $outname;
-    }
-
     /**
      * Copy the image file to the given destination.
      *
index 4ee9d854663e22cd240df05d189da15b28a938b4..5b0f3dbe092fa42dbd73d01dc3e727392151d1bb 100644 (file)
@@ -49,6 +49,8 @@ abstract class ImPlugin extends Plugin
     //list of screennames that should get all public notices
     public $public = array();
 
+    protected $requires_cli = true;
+
     /**
      * normalize a screenname for comparison
      *
@@ -530,9 +532,14 @@ abstract class ImPlugin extends Plugin
      */
     function onEndInitializeQueueManager($manager)
     {
-        $manager->connect($this->transport . '-in', new ImReceiverQueueHandler($this), 'im');
-        $manager->connect($this->transport, new ImQueueHandler($this));
-        $manager->connect($this->transport . '-out', new ImSenderQueueHandler($this), 'im');
+        // If we don't require CLI mode, or if we do and GNUSOCIAL_CLI _is_ set, then connect the transports
+        // This check is made mostly because some IM plugins can't deliver to transports unless they
+        // have continously running daemons (such as XMPP) and we can't have that over HTTP requests.
+        if (!$this->requires_cli || defined('GNUSOCIAL_CLI')) {
+            $manager->connect($this->transport . '-in', new ImReceiverQueueHandler($this), 'im');
+            $manager->connect($this->transport, new ImQueueHandler($this));
+            $manager->connect($this->transport . '-out', new ImSenderQueueHandler($this), 'im');
+        }
         return true;
     }
 
index e69de7a33ac3eab0b215b1d2840182cb873e7bba..9e2936ca82dc5860c10e00827197787fef4c24ac 100644 (file)
@@ -224,7 +224,7 @@ abstract class Installer
         }
         // @fixme hardcoded list; should use Nickname::isValid()
         // if/when it's safe to have loaded the infrastructure here
-        $blacklist = array('main', 'panel', 'twitter', 'settings', 'rsd.xml', 'favorited', 'featured', 'favoritedrss', 'featuredrss', 'rss', 'getfile', 'api', 'groups', 'group', 'peopletag', 'tag', 'user', 'message', 'conversation', 'notice', 'attachment', 'search', 'index.php', 'doc', 'opensearch', 'robots.txt', 'xd_receiver.html', 'facebook');
+        $blacklist = array('main', 'panel', 'twitter', 'settings', 'rsd.xml', 'favorited', 'featured', 'favoritedrss', 'featuredrss', 'rss', 'getfile', 'api', 'groups', 'group', 'peopletag', 'tag', 'user', 'message', 'conversation', 'notice', 'attachment', 'search', 'index.php', 'doc', 'opensearch', 'robots.txt', 'xd_receiver.html', 'facebook', 'activity');
         if (in_array($this->adminNick, $blacklist)) {
             $this->updateStatus('The user nickname "' . htmlspecialchars($this->adminNick) .
                          '" is reserved.', true);
index c9d43c097a20437de1aa5e4dfeedc5fc6aef51bc..727be89bc6d0da56effbe0c05bd9e176d323639d 100644 (file)
@@ -155,15 +155,9 @@ class NoticeList extends Widget
 
             // Prefill attachments
             Notice::fillAttachments($notices);
-            // Prefill repeat data
-            Notice::fillRepeats($notices);
             // Prefill the profiles
             $profiles = Notice::fillProfiles($notices);
 
-            if ($scoped instanceof Profile) {
-                Notice::pivotGet('repeat_of', $notice_ids, array('profile_id' => $scoped->id));
-            }
-
             Event::handle('EndNoticeListPrefill', array(&$notices, &$profiles, $notice_ids, $scoped));
         }
     }
index 282ace3724afdd3985b4bdce030a6eca5df35236..f25613b9a96988972946b96b024cda003c735697 100644 (file)
@@ -63,6 +63,7 @@ class NoticeListItem extends Widget
     protected $id_prefix = null;
     protected $options = true;
     protected $maxchars = 0;   // if <= 0 it means use full posts
+    protected $item_tag = 'li';
 
     /**
      * constructor
@@ -101,7 +102,7 @@ class NoticeListItem extends Widget
             }
         }
         // string preferences
-        foreach(array('id_prefix') as $key) {
+        foreach(array('id_prefix', 'item_tag') as $key) {
             if (array_key_exists($key, $prefs)) {
                 $this->$key = $prefs[$key];
             }
@@ -179,7 +180,6 @@ class NoticeListItem extends Widget
             $this->showNoticeSource();
             $this->showNoticeLocation();
             $this->showPermalink();
-            $this->showRepeat();
             Event::handle('EndShowNoticeInfo', array($this));
         }
     }
@@ -192,7 +192,6 @@ class NoticeListItem extends Widget
                 $this->out->elementStart('div', 'notice-options');
                 if (Event::handle('StartShowNoticeOptionItems', array($this))) {
                     $this->showReplyLink();
-                    $this->showRepeatForm();
                     $this->showDeleteLink();
                     Event::handle('EndShowNoticeOptionItems', array($this));
                 }
@@ -219,7 +218,7 @@ class NoticeListItem extends Widget
                 $class .= ' notice-source-'.$this->notice->source;
             }
             $id_prefix = (strlen($this->id_prefix) ? $this->id_prefix . '-' : '');
-            $this->out->elementStart('li', array('class' => $class,
+            $this->out->elementStart($this->item_tag, array('class' => $class,
                                                  'id' => "${id_prefix}notice-${id}"));
             Event::handle('EndOpenNoticeListItemElement', array($this));
         }
@@ -527,32 +526,6 @@ class NoticeListItem extends Widget
         }
     }
 
-    /**
-     * show a link to the author of repeat
-     *
-     * @return void
-     */
-    function showRepeat()
-    {
-        if (!empty($this->repeat)) {
-
-            $repeater = Profile::getKV('id', $this->repeat->profile_id);
-
-            $attrs = array('href' => $repeater->profileurl,
-                           'class' => 'h-card p-author',
-                           'title' => $repeater->getFancyName());
-
-            $this->out->elementStart('span', 'repeat h-entry');
-
-            // TRANS: Addition in notice list item if notice was repeated. Followed by a span with a nickname.
-            $this->out->raw(_('Repeated by').' ');
-
-            $this->out->element('a', $attrs, $repeater->getNickname());
-
-            $this->out->elementEnd('span');
-        }
-    }
-
     /**
      * show a link to reply to the current notice
      *
@@ -604,34 +577,6 @@ class NoticeListItem extends Widget
         }
     }
 
-    /**
-     * show the form to repeat a notice
-     *
-     * @return void
-     */
-    function showRepeatForm()
-    {
-        if ($this->notice->scope == Notice::PUBLIC_SCOPE ||
-            $this->notice->scope == Notice::SITE_SCOPE) {
-            $user = common_current_user();
-            if (!empty($user) &&
-                $user->id != $this->notice->profile_id) {
-                $this->out->text(' ');
-                $profile = $user->getProfile();
-                if ($profile->hasRepeated($this->notice)) {
-                    $this->out->element('span', array('class' => 'repeated',
-                                                      // TRANS: Title for repeat form status in notice list when a notice has been repeated.
-                                                      'title' => _('Notice repeated.')),
-                                        // TRANS: Repeat form status in notice list when a notice has been repeated.
-                                        _('Repeated'));
-                } else {
-                    $rf = new RepeatForm($this->out, $this->notice);
-                    $rf->show();
-                }
-            }
-        }
-    }
-
     /**
      * finish the notice
      *
index d42e4b4b57e88ac31f7b35ddd6f22e46088ea467..45fe1e4ab43040343989833046206622bfc2f72b 100644 (file)
@@ -233,8 +233,6 @@ abstract class QueueManager extends IoManager
             } else {
                 $this->_log(LOG_ERR, "Nonexistent handler class '$class' for queue '$queue'");
             }
-        } else {
-            $this->_log(LOG_ERR, "Requested handler for unkown queue '$queue'");
         }
         return null;
     }
diff --git a/lib/repeatedbymenoticestream.php b/lib/repeatedbymenoticestream.php
deleted file mode 100644 (file)
index 4e3e341..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-<?php
-/**
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2011, StatusNet, Inc.
- *
- * Stream of notices repeated by me
- * 
- * 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  Stream
- * @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);
-}
-
-/**
- * Stream of notices repeated by me
- *
- * @category  General
- * @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 RepeatedByMeNoticeStream extends ScopingNoticeStream
-{
-    function __construct($user, $profile = -1)
-    {
-        if (is_int($profile) && $profile == -1) {
-            $profile = Profile::current();
-        }
-        parent::__construct(new CachingNoticeStream(new RawRepeatedByMeNoticeStream($user),
-                                                    'user:repeated_by_me:'.$user->id),
-                            $profile);
-    }
-}
-
-/**
- * Raw stream of notices repeated by me
- *
- * @category  General
- * @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 RawRepeatedByMeNoticeStream extends NoticeStream
-{
-    protected $user;
-
-    function __construct($user)
-    {
-        $this->user = $user;
-    }
-
-    function getNoticeIds($offset, $limit, $since_id, $max_id)
-    {
-        $notice = new Notice();
-
-        $notice->selectAdd(); // clears it
-        $notice->selectAdd('id');
-
-        $notice->profile_id = $this->user->id;
-        $notice->whereAdd('repeat_of IS NOT NULL');
-
-        $notice->orderBy('created DESC, id DESC');
-
-        if (!is_null($offset)) {
-            $notice->limit($offset, $limit);
-        }
-
-        Notice::addWhereSinceId($notice, $since_id);
-        Notice::addWhereMaxId($notice, $max_id);
-
-        $ids = array();
-
-        if ($notice->find()) {
-            while ($notice->fetch()) {
-                $ids[] = $notice->id;
-            }
-        }
-
-        $notice->free();
-        $notice = NULL;
-
-        return $ids;
-    }
-}
\ No newline at end of file
diff --git a/lib/repeatform.php b/lib/repeatform.php
deleted file mode 100644 (file)
index f0ce37f..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Form for repeating a notice
- *
- * 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  Form
- * @package   StatusNet
- * @author    Evan Prodromou <evan@status.net>
- * @copyright 2009 StatusNet, Inc.
- * @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);
-}
-
-/**
- * Form for repeating a notice
- *
- * @category Form
- * @package  StatusNet
- * @author   Evan Prodromou <evan@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 RepeatForm extends Form
-{
-    /**
-     * Notice to repeat
-     */
-    var $notice = null;
-
-    /**
-     * Constructor
-     *
-     * @param HTMLOutputter $out    output channel
-     * @param Notice        $notice notice to repeat
-     */
-    function __construct($out=null, $notice=null)
-    {
-        parent::__construct($out);
-
-        $this->notice = $notice;
-    }
-
-    /**
-     * ID of the form
-     *
-     * @return int ID of the form
-     */
-    function id()
-    {
-        return 'repeat-' . $this->notice->id;
-    }
-
-    /**
-     * Action of the form
-     *
-     * @return string URL of the action
-     */
-    function action()
-    {
-        return common_local_url('repeat');
-    }
-
-    /**
-     * Legend of the Form
-     *
-     * @return void
-     */
-    function formLegend()
-    {
-        // TRANS: For legend for notice repeat form.
-        $this->out->element('legend', null, _('Repeat this notice?'));
-    }
-
-    /**
-     * Data elements
-     *
-     * @return void
-     */
-    function formData()
-    {
-        $this->out->hidden('notice-n'.$this->notice->id,
-                           $this->notice->id,
-                           'notice');
-    }
-
-    /**
-     * Action elements
-     *
-     * @return void
-     */
-    function formActions()
-    {
-        $this->out->submit('repeat-submit-' . $this->notice->id,
-                           // TRANS: Button text to repeat a notice on notice repeat form.
-                           _m('BUTTON','Yes'), 'submit', null,
-                           // TRANS: Button title to repeat a notice on notice repeat form.
-                           _('Repeat this notice.'));
-    }
-
-    /**
-     * Class of the form.
-     *
-     * @return string the form's class
-     */
-    function formClass()
-    {
-        return 'form_repeat';
-    }
-}
diff --git a/lib/repeatsofmenoticestream.php b/lib/repeatsofmenoticestream.php
deleted file mode 100644 (file)
index ec80d84..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-<?php
-/**
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2011, StatusNet, Inc.
- *
- * Stream of notices that are repeats of mine
- * 
- * 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  Stream
- * @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);
-}
-
-/**
- * Stream of notices that are repeats of mine
- *
- * @category  Stream
- * @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 RepeatsOfMeNoticeStream extends ScopingNoticeStream
-{
-    function __construct($user, $profile=-1)
-    {
-        if (is_int($profile) && $profile == -1) {
-            $profile = Profile::current();
-        }
-        parent::__construct(new CachingNoticeStream(new RawRepeatsOfMeNoticeStream($user),
-                                                    'user:repeats_of_me:'.$user->id),
-                            $profile);
-    }
-}
-
-/**
- * Raw stream of notices that are repeats of mine
- *
- * @category  Stream
- * @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 RawRepeatsOfMeNoticeStream extends NoticeStream
-{
-    protected $user;
-
-    function __construct($user)
-    {
-        $this->user = $user;
-    }
-
-    function getNoticeIds($offset, $limit, $since_id, $max_id)
-    {
-        $qry =
-          'SELECT DISTINCT original.id AS id ' .
-          'FROM notice original JOIN notice rept ON original.id = rept.repeat_of ' .
-          'WHERE original.profile_id = ' . $this->user->id . ' ';
-
-        $since = Notice::whereSinceId($since_id, 'original.id', 'original.created');
-        if ($since) {
-            $qry .= "AND ($since) ";
-        }
-
-        $max = Notice::whereMaxId($max_id, 'original.id', 'original.created');
-        if ($max) {
-            $qry .= "AND ($max) ";
-        }
-
-        $qry .= 'ORDER BY original.created, original.id DESC ';
-
-        if (!is_null($offset)) {
-            $qry .= "LIMIT $limit OFFSET $offset";
-        }
-
-        $ids = array();
-
-        $notice = new Notice();
-
-        $notice->query($qry);
-
-        while ($notice->fetch()) {
-            $ids[] = $notice->id;
-        }
-
-        $notice->free();
-        $notice = NULL;
-
-        return $ids;
-    }
-}
index 4a3f27819e53f78c7077535d28d9fcc649edc7e5..ca8daf5a906f65744ace2d77d8bbea7c1850e5c3 100644 (file)
@@ -149,7 +149,6 @@ class Router
                           'sandbox', 'unsandbox',
                           'silence', 'unsilence',
                           'grantrole', 'revokerole',
-                          'repeat',
                           'deleteuser',
                           'geocode',
                           'version',
@@ -393,18 +392,6 @@ class Router
                         array('action' => 'ApiTimelineMentions',
                               'format' => '(xml|json|rss|atom|as)'));
 
-            $m->connect('api/statuses/retweeted_by_me.:format',
-                        array('action' => 'ApiTimelineRetweetedByMe',
-                              'format' => '(xml|json|atom|as)'));
-
-            $m->connect('api/statuses/retweeted_to_me.:format',
-                        array('action' => 'ApiTimelineRetweetedToMe',
-                              'format' => '(xml|json|atom|as)'));
-
-            $m->connect('api/statuses/retweets_of_me.:format',
-                        array('action' => 'ApiTimelineRetweetsOfMe',
-                              'format' => '(xml|json|atom|as)'));
-
             $m->connect('api/statuses/friends/:id.:format',
                         array('action' => 'ApiUserFriends',
                               'id' => Nickname::INPUT_FMT,
@@ -445,16 +432,6 @@ class Router
                         array('action' => 'ApiStatusesDestroy',
                               'format' => '(xml|json)'));
 
-            $m->connect('api/statuses/retweet/:id.:format',
-                        array('action' => 'ApiStatusesRetweet',
-                              'id' => '[0-9]+',
-                              'format' => '(xml|json)'));
-
-            $m->connect('api/statuses/retweets/:id.:format',
-                        array('action' => 'ApiStatusesRetweets',
-                              'id' => '[0-9]+',
-                              'format' => '(xml|json)'));
-
             // START qvitter API additions
             
             $m->connect('api/attachment/:id.:format',
index 94cde28f9d4692610a5b00cafd11c693ca61f1b5..0421bcb8103ca0cada0d1e233d2e33fe5e7a743e 100644 (file)
@@ -350,7 +350,7 @@ class Schema
         $res = $this->conn->query("DROP TABLE $name");
 
         if ($_PEAR->isError($res)) {
-            throw new Exception($res->getMessage());
+            PEAR_ErrorToPEAR_Exception($res);
         }
 
         return true;
@@ -389,7 +389,7 @@ class Schema
                                    implode(",", $columnNames).")");
 
         if ($_PEAR->isError($res)) {
-            throw new Exception($res->getMessage());
+            PEAR_ErrorToPEAR_Exception($res);
         }
 
         return true;
@@ -411,7 +411,7 @@ class Schema
         $res = $this->conn->query("ALTER TABLE $table DROP INDEX $name");
 
         if ($_PEAR->isError($res)) {
-            throw new Exception($res->getMessage());
+            PEAR_ErrorToPEAR_Exception($res);
         }
 
         return true;
@@ -436,7 +436,7 @@ class Schema
         $res = $this->conn->query($sql);
 
         if ($_PEAR->isError($res)) {
-            throw new Exception($res->getMessage());
+            PEAR_ErrorToPEAR_Exception($res);
         }
 
         return true;
@@ -463,7 +463,7 @@ class Schema
         $res = $this->conn->query($sql);
 
         if ($_PEAR->isError($res)) {
-            throw new Exception($res->getMessage());
+            PEAR_ErrorToPEAR_Exception($res);
         }
 
         return true;
@@ -489,7 +489,7 @@ class Schema
         $res = $this->conn->query($sql);
 
         if ($_PEAR->isError($res)) {
-            throw new Exception($res->getMessage());
+            PEAR_ErrorToPEAR_Exception($res);
         }
 
         return true;
@@ -535,7 +535,7 @@ class Schema
             $res = $this->conn->query($sql);
 
             if ($_PEAR->isError($res)) {
-                throw new Exception($res->getMessage());
+                PEAR_ErrorToPEAR_Exception($res);
             }
         }
         return $ok;
@@ -1045,7 +1045,7 @@ class Schema
 
         $res = $this->conn->query($sql);
         if ($_PEAR->isError($res)) {
-            throw new Exception($res->getMessage());
+            PEAR_ErrorToPEAR_Exception($res);
         }
 
         $out = array();
index ae746c10b546a4919fde4ab68f63a6943c5194cc..38b5b93865255cc719875385f9e927c999da4abe 100644 (file)
@@ -98,7 +98,6 @@ class SchemaUpdater
     {
         $checksums = array();
 
-        PEAR::pushErrorHandling(PEAR_ERROR_EXCEPTION);
         try {
             $sv = new Schema_version();
             $sv->find();
@@ -111,7 +110,6 @@ class SchemaUpdater
             // no dice!
             common_log(LOG_DEBUG, "Possibly schema_version table doesn't exist yet.");
         }
-        PEAR::popErrorHandling();
 
         return $checksums;
     }
@@ -124,7 +122,6 @@ class SchemaUpdater
      */
     protected function saveChecksum($table, $checksum)
     {
-        PEAR::pushErrorHandling(PEAR_ERROR_EXCEPTION);
         try {
             $sv = new Schema_version();
             $sv->table_name = $table;
@@ -139,7 +136,6 @@ class SchemaUpdater
             // no dice!
             common_log(LOG_DEBUG, "Possibly schema_version table doesn't exist yet.");
         }
-        PEAR::popErrorHandling();
         $this->checksums[$table] = $checksum;
     }
 }
index 9eaf6493c67631a52d125f778db0a70119614971..acb9efb972efd702b9ba5a3c3a82b3809cd39ec3 100644 (file)
@@ -27,7 +27,7 @@
  * @link      http://status.net/
  */
 
-if (!defined('GNUSOCIAL') && !defined('STATUSNET')) { exit(1); }
+if (!defined('GNUSOCIAL')) { exit(1); }
 
 /**
  * widget for displaying a list of notices
@@ -229,13 +229,7 @@ class ThreadedNoticeListItem extends NoticeListItem
                 $this->out->elementStart('ul', 'notices threaded-replies xoxo');
 
                 if (Event::handle('StartShowThreadedNoticeTailItems', array($this, $this->notice, &$threadActive))) {
-
-                    // Show the repeats-button for this notice. If there are repeats,
-                    // the show() function will return true, definitely setting our
-                    // $threadActive flag, which will be used later to show a reply box.
-                    $item = new ThreadedNoticeListRepeatsItem($this->notice, $this->out);
-                    $threadActive = $item->show() || $threadActive;
-
+                    // Repeats and Faves/Likes are handled in plugins.
                     Event::handle('EndShowThreadedNoticeTailItems', array($this, $this->notice, &$threadActive));
                 }
 
@@ -309,8 +303,7 @@ class ThreadedNoticeListSubItem extends NoticeListItem
     {
         $threadActive = null;   // unused here for now, but maybe in the future?
         if (Event::handle('StartShowThreadedNoticeTailItems', array($this, $this->notice, &$threadActive))) {
-            $item = new ThreadedNoticeListInlineRepeatsItem($this->notice, $this->out);
-            $hasRepeats = $item->show();
+            // Repeats and Faves/Likes are handled in plugins.
             Event::handle('EndShowThreadedNoticeTailItems', array($this, $this->notice, &$threadActive));
         }
         parent::showEnd();
@@ -374,80 +367,3 @@ class ThreadedNoticeListMoreItem extends NoticeListItem
         $this->out->element('a', array('href' => $url), $msg);
     }
 }
-
-/**
- * Placeholder for showing repeats...
- */
-class ThreadedNoticeListRepeatsItem extends NoticeListActorsItem
-{
-    function getProfiles()
-    {
-        $repeats = $this->notice->getRepeats();
-
-        $profiles = array();
-
-        foreach ($repeats as $rep) {
-            $profiles[] = $rep->profile_id;
-        }
-
-        return $profiles;
-    }
-
-    function magicList($items)
-    {
-        if (count($items) > 4) {
-            return parent::magicList(array_slice($items, 0, 3));
-        } else {
-            return parent::magicList($items);
-        }
-    }
-
-    function getListMessage($count, $you)
-    {
-        if ($count == 1 && $you) {
-            // darn first person being different from third person!
-            // TRANS: List message for notice repeated by logged in user.
-            return _m('REPEATLIST', 'You repeated this.');
-        } else if ($count > 4) {
-            // TRANS: List message for when more than 4 people repeat something.
-            // TRANS: %%s is a list of users liking a notice, %d is the number over 4 that like the notice.
-            // TRANS: Plural is decided on the total number of users liking the notice (count of %%s + %d).
-            return sprintf(_m('%%s and %d other repeated this.',
-                              '%%s and %d others repeated this.',
-                              $count - 3),
-                           $count - 3);
-        } else {
-            // TRANS: List message for repeated notices.
-            // TRANS: %%s is a list of users who have repeated a notice.
-            // TRANS: Plural is based on the number of of users that have repeated a notice.
-            return sprintf(_m('%%s repeated this.',
-                              '%%s repeated this.',
-                              $count),
-                           $count);
-        }
-    }
-
-    function showStart()
-    {
-        $this->out->elementStart('li', array('class' => 'notice-data notice-repeats'));
-    }
-
-    function showEnd()
-    {
-        $this->out->elementEnd('li');
-    }
-}
-
-// @todo FIXME: needs documentation.
-class ThreadedNoticeListInlineRepeatsItem extends ThreadedNoticeListRepeatsItem
-{
-    function showStart()
-    {
-        $this->out->elementStart('div', array('class' => 'notice-repeats'));
-    }
-
-    function showEnd()
-    {
-        $this->out->elementEnd('div');
-    }
-}
index f29507f8465bbe7e2b9fb85109a00f0a01cc096c..14cfd96ee135bba06c64f279c7c9717c89555355 100644 (file)
@@ -580,9 +580,18 @@ function common_purify($html)
 {
     require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php';
 
-    $config = array('safe' => 1,
+    $config = array('safe' => 1,    // means that elements=* means elements=*-applet-embed-iframe-object-script or so
+                    'elements' => '*',
                     'deny_attribute' => 'id,style,on*');
 
+    // Remove more elements than what the 'safe' filter gives (elements must be '*' before this)
+    // http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/htmLawed_README.htm#s3.6
+    foreach (common_config('htmlfilter') as $tag=>$filter) {
+        if ($filter === true) {
+            $config['elements'] .= "-{$tag}";
+        }
+    }
+
     $html = common_remove_unicode_formatting($html);
 
     return htmLawed($html, $config);
@@ -1929,9 +1938,14 @@ function common_negotiate_type($cprefs, $sprefs)
     return $besttype;
 }
 
-function common_config($main, $sub)
+function common_config($main, $sub=null)
 {
     global $config;
+    if (is_null($sub)) {
+        // Return the config category array
+        return array_key_exists($main, $config) ? $config[$main] : array();
+    }
+    // Return the config value
     return (array_key_exists($main, $config) &&
             array_key_exists($sub, $config[$main])) ? $config[$main][$sub] : false;
 }
index 7adda4f30ff33f280af8ca8aa157b00b08e49d32..a5eb313709496c41741da83f408e1f0a26cf867d 100644 (file)
@@ -24,7 +24,7 @@ msgstr ""
 #: ActivityPlugin.php:75
 #, php-format
 msgid "<a href=\"%1$s\">%2$s</a> started following <a href=\"%3$s\">%4$s</a>."
-msgstr "<a href=\"%1$s\">%2$s</a> började följa <a href=\"%3$s\">%4$s</a."
+msgstr "<a href=\"%1$s\">%2$s</a> började följa <a href=\"%3$s\">%4$s</a>."
 
 #. TRANS: Text for "started following" item in activity plugin.
 #. TRANS: %1$s is a profile name, %2$s is a profile URL,
diff --git a/plugins/ActivityVerb/ActivityVerbPlugin.php b/plugins/ActivityVerb/ActivityVerbPlugin.php
new file mode 100644 (file)
index 0000000..dab4cb4
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+/**
+ * GNU social - a federating social network
+ *
+ * Plugin that handles activity verb interact (like 'favorite' etc.)
+ *
+ * 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  Plugin
+ * @package   GNUsocial
+ * @author    Mikael Nordfeldth <mmn@hethane.se>
+ * @copyright 2014 Free Software Foundation http://fsf.org
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      https://www.gnu.org/software/social/
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class ActivityVerbPlugin extends Plugin
+{
+
+    public function onRouterInitialized(URLMapper $m)
+    {
+        $m->connect('notice/:id/:verb',
+                    array('action' => 'activityverb'),
+                    array('id'     => '[0-9]+',
+                          'verb'   => '[a-z]+'));
+        $m->connect('activity/:id/:verb',
+                    array('action' => 'activityverb'),
+                    array('id'     => '[0-9]+',
+                          'verb'   => '[a-z]+'));
+    }
+
+    public function onPluginVersion(&$versions)
+    {
+        $versions[] = array('name' => 'Activity Verb',
+                            'version' => GNUSOCIAL_VERSION,
+                            'author' => 'Mikael Nordfeldth',
+                            'homepage' => 'https://www.gnu.org/software/social/',
+                            'rawdescription' =>
+                            // TRANS: Plugin description.
+                            _m('Adds more standardized verb handling for activities.'));
+        return true;
+    }
+}
diff --git a/plugins/ActivityVerb/actions/activityverb.php b/plugins/ActivityVerb/actions/activityverb.php
new file mode 100644 (file)
index 0000000..0abfacd
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+/**
+ * GNU social - a federating social network
+ *
+ * Class for deleting a notice
+ *
+ * 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  Plugin
+ * @package   GNUsocial
+ * @author    Mikael Nordfeldth <mmn@hethane.se>
+ * @copyright 2015 Free Software Foundaction, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      https://gnu.io/social
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class ActivityverbAction extends ManagedAction
+{
+    protected $needLogin = true;
+    protected $canPost   = true;
+
+    protected $verb      = null;
+
+    public function title()
+    {
+        $title = null;
+        Event::handle('ActivityVerbTitle', array($this, $this->verb, $this->notice, $this->scoped, &$title));
+        return $title;
+    }
+
+    protected function doPreparation()
+    {
+        $this->verb = $this->trimmed('verb');
+        if (empty($this->verb)) {
+            throw new ServerException('A verb has not been specified.');
+        }
+
+        $this->notice = Notice::getById($this->trimmed('id'));
+
+        if (!$this->notice->inScope($this->scoped)) {
+            // TRANS: %1$s is a user nickname, %2$d is a notice ID (number).
+            throw new ClientException(sprintf(_('%1$s has no access to notice %2$d.'),
+                                        $this->scoped->getNickname(), $this->notice->getID()), 403);
+        }
+
+        Event::handle('ActivityVerbDoPreparation', array($this, $this->verb, $this->notice, $this->scoped));
+    }
+
+    protected function doPost()
+    {
+        if (Event::handle('ActivityVerbDoPost', array($this, $this->verb, $this->notice, $this->scoped))) {
+            // TRANS: Error when a POST method for an activity verb has not been handled by a plugin.
+            throw new ClientException(sprintf(_('Could not handle POST for verb "%1$s".'), $this->verb));
+        }
+    }
+
+    protected function showContent()
+    {
+        if (Event::handle('ActivityVerbShowContent', array($this, $this->verb, $this->notice, $this->scoped))) {
+            // TRANS: Error when a page for an activity verb has not been handled by a plugin.
+            $this->element('div', 'error', sprintf(_('Could not show content for verb "%1$s".'), $this->verb));
+        }
+    }
+}
diff --git a/plugins/ActivityVerb/lib/activityverbhandlerplugin.php b/plugins/ActivityVerb/lib/activityverbhandlerplugin.php
new file mode 100644 (file)
index 0000000..0d06266
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+/*
+ * GNU Social - a federating social network
+ * Copyright (C) 2014, Free Software Foundation, 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('GNUSOCIAL')) { exit(1); }
+
+/**
+ * @package     Activity
+ * @maintainer  Mikael Nordfeldth <mmn@hethane.se>
+ */
+abstract class ActivityVerbHandlerPlugin extends ActivityHandlerPlugin
+{
+    public function onActivityVerbTitle(ManagedAction $action, $verb, Notice $target, Profile $scoped, &$title)
+    {
+        if (!$this->isMyVerb($verb)) {
+            return true;
+        }
+
+        $title = $this->getActionTitle($action, $verb, $target, $scoped);
+        return false;
+    }
+    abstract protected function getActionTitle(ManagedAction $action, $verb, Notice $target, Profile $scoped);
+
+    public function onActivityVerbShowContent(ManagedAction $action, $verb, Notice $target, Profile $scoped)
+    {
+        if (!$this->isMyVerb($verb)) {
+            return true;
+        }
+
+        return $this->showActionContent($action, $verb, $target, $scoped);
+    }
+    protected function showActionContent(ManagedAction $action, $verb, Notice $target, Profile $scoped)
+    {
+        if (!GNUsocial::isAjax()) {
+            $nl = new NoticeListItem($target, $action, array('options'=>false, 'attachments'=>false,
+                                                             'item_tag'=>'div', 'id_prefix'=>'fave'));
+            $nl->show();
+        }
+
+        $form = $this->getActivityForm($action, $verb, $target, $scoped);
+        $form->show();
+
+        return false;
+    }
+
+    public function onActivityVerbDoPreparation(ManagedAction $action, $verb, Notice $target, Profile $scoped)
+    {
+        if (!$this->isMyVerb($verb)) {
+            return true;
+        }
+
+        return $this->doActionPreparation($action, $verb, $target, $scoped);
+    }
+    abstract protected function doActionPreparation(ManagedAction $action, $verb, Notice $target, Profile $scoped);
+
+    public function onActivityVerbDoPost(ManagedAction $action, $verb, Notice $target, Profile $scoped)
+    {
+        if (!$this->isMyVerb($verb)) {
+            return true;
+        }
+
+        return $this->doActionPost($action, $verb, $target, $scoped);
+    }
+    abstract protected function doActionPost(ManagedAction $action, $verb, Notice $target, Profile $scoped);
+
+    abstract protected function getActivityForm(ManagedAction $action, $verb, Notice $target, Profile $scoped);
+}
index 4029154d7208b804409fce2b41d293848ea61bf2..287c25cbe98455c12c698e7c31bbfa471554deea 100644 (file)
@@ -62,11 +62,6 @@ class AnonFavorAction extends RedirectingAction
         $notice = Notice::getKV($id);
         $token  = $this->checkSessionToken();
 
-        if (Fave::existsForProfile($notice, $profile)) {
-            // TRANS: Client error.
-            throw new AlreadyFulfilledException(_m('This notice is already a favorite!'));
-        }
-
         // Throws exception
         $stored = Fave::addNew($profile, $notice);
 
index 9ff21c6e50ac5c9a716c3c31d729cf4c695573c8..40823ecf9db6ea70fece5334de53bfa99faa1538 100644 (file)
@@ -31,10 +31,10 @@ if (!defined('GNUSOCIAL')) { exit(1); }
 
 class DefaultLayoutPlugin extends Plugin
 {
-    public $replyforms = false;
+    public $prerender_replyforms = false;
 
     public function onEndShowThreadedNoticeTail(NoticeListItem $nli, Notice $notice, array $notices) {
-        if ($this->replyforms) {
+        if ($this->prerender_replyforms) {
             $nli->out->elementStart('li', array('class'=>'notice-reply', 'style'=>'display: none;'));
             $replyForm = new NoticeForm($nli->out, array('inreplyto' => $notice->getID()));
             $replyForm->show();
index 53815ad264b1cb5a8815c8193fbfc330c7852425..080c59612cfaa9d29359661a81a5a91e1b711697 100644 (file)
@@ -436,8 +436,14 @@ class FacebookfinishloginAction extends Action
                 } else {
                     // save it as an avatar
 
-                    $file = new ImageFile($user->id, Avatar::path($tmpname));
-                    $filename = $file->resize(180); // size of the biggest img we get from Facebook
+                    $imagefile = new ImageFile(null, Avatar::path($tmpname));
+                    $filename = Avatar::filename($user->id, image_type_to_extension($imagefile->preferredType()),
+                                                 180, common_timestamp());
+                    // Previous docs said 180 is the "biggest img we get from Facebook"
+                    $imagefile->resizeTo(Avatar::path($filename, array('width'=>180, 'height'=>180)));
+
+                    // No need to keep the temporary file around...
+                    @unlink(Avatar::path($tmpname));
 
                     $profile   = $user->getProfile();
 
@@ -457,7 +463,6 @@ class FacebookfinishloginAction extends Action
                         );
 
                         // clean up tmp file
-                        @unlink(Avatar::path($tmpname));
                     }
 
                 }
index 9b2248485144adb9095edec9adb417e1c24fa99e..eb73bbe43c9a4d40137688bea56894868c679bda 100644 (file)
@@ -23,7 +23,7 @@ if (!defined('GNUSOCIAL')) { exit(1); }
  * @package     Activity
  * @maintainer  Mikael Nordfeldth <mmn@hethane.se>
  */
-class FavoritePlugin extends ActivityHandlerPlugin
+class FavoritePlugin extends ActivityVerbHandlerPlugin
 {
     protected $email_notify_fave = 1;
 
@@ -39,9 +39,10 @@ class FavoritePlugin extends ActivityHandlerPlugin
 
     public function verbs()
     {
-        return array(ActivityVerb::FAVORITE);
+        return array(ActivityVerb::FAVORITE, ActivityVerb::LIKE,
+                    ActivityVerb::UNFAVORITE, ActivityVerb::UNLIKE);
     }
-    
+
     public function onCheckSchema()
     {
         $schema = Schema::get();
@@ -78,14 +79,14 @@ class FavoritePlugin extends ActivityHandlerPlugin
             printfnq("DONE.\n");
         }
     }
-    
+
     public function onEndUpgrade()
     {
         printfnq("Ensuring all faves have a URI...");
-    
+
         $fave = new Fave();
         $fave->whereAdd('uri IS NULL');
-    
+
         if ($fave->find()) {
             while ($fave->fetch()) {
                 try {
@@ -104,7 +105,7 @@ class FavoritePlugin extends ActivityHandlerPlugin
                 }
             }
         }
-    
+
         printfnq("DONE.\n");
     }
 
@@ -180,7 +181,7 @@ class FavoritePlugin extends ActivityHandlerPlugin
     }
 
     // FIXME: Set this to abstract public in lib/activityhandlerplugin.php ddwhen all plugins have migrated!
-    protected function saveObjectFromActivity(Activity $act, Notice $stored, array $options=array())  
+    protected function saveObjectFromActivity(Activity $act, Notice $stored, array $options=array())
     {
         assert($this->isMyActivity($act));
 
@@ -193,6 +194,8 @@ class FavoritePlugin extends ActivityHandlerPlugin
         $actobj = $act->objects[0];
 
         $object = Fave::saveActivityObject($actobj, $stored);
+        $stored->object_type = ActivityUtils::resolveUri($object->getObjectType(), true);
+
         return $object;
     }
 
@@ -263,7 +266,7 @@ class FavoritePlugin extends ActivityHandlerPlugin
         }
         return true;
     }
-    
+
     public function onNoticeDeleteRelated(Notice $notice)
     {
         parent::onNoticeDeleteRelated($notice);
@@ -360,7 +363,7 @@ class FavoritePlugin extends ActivityHandlerPlugin
         return true;
     }
 
-    public function onStartShowThreadedNoticeTailItems(NoticeListItem $nli, Notice $notice, &$threadActive)
+    public function onEndShowThreadedNoticeTailItems(NoticeListItem $nli, Notice $notice, &$threadActive)
     {
         if ($nli instanceof ThreadedNoticeListSubItem) {
             // The sub-items are replies to a conversation, thus we use different HTML elements etc.
@@ -503,6 +506,62 @@ class FavoritePlugin extends ActivityHandlerPlugin
         }
     }
 
+    protected function getActionTitle(ManagedAction $action, $verb, Notice $target, Profile $scoped)
+    {
+        return Fave::existsForProfile($target, $scoped)
+                // TRANS: Page/dialog box title when a notice is marked as favorite already
+                ? _m('TITLE', 'Unmark notice as favorite')
+                // TRANS: Page/dialog box title when a notice is not marked as favorite
+                : _m('TITLE', 'Mark notice as favorite');
+    }
+
+    protected function doActionPreparation(ManagedAction $action, $verb, Notice $target, Profile $scoped)
+    {
+        if ($action->isPost()) {
+            // The below tests are only for presenting to the user. POSTs which inflict
+            // duplicate favorite entries are handled with AlreadyFulfilledException. 
+            return false;
+        }
+
+        $exists = Fave::existsForProfile($target, $scoped);
+        $expected_verb = $exists ? ActivityVerb::UNFAVORITE : ActivityVerb::FAVORITE;
+
+        switch (true) {
+        case $exists && ActivityUtils::compareTypes($verb, array(ActivityVerb::FAVORITE, ActivityVerb::LIKE)):
+        case !$exists && ActivityUtils::compareTypes($verb, array(ActivityVerb::UNFAVORITE, ActivityVerb::UNLIKE)):
+            common_redirect(common_local_url('activityverb',
+                                array('id'   => $target->getID(),
+                                      'verb' => ActivityUtils::resolveUri($expected_verb, true))));
+            break;
+        default:
+            // No need to redirect as we are on the correct action already.
+        }
+
+        return false;
+    }
+
+    protected function doActionPost(ManagedAction $action, $verb, Notice $target, Profile $scoped)
+    {
+        switch (true) {
+        case ActivityUtils::compareTypes($verb, array(ActivityVerb::FAVORITE, ActivityVerb::LIKE)):
+            Fave::addNew($scoped, $target);
+            break;
+        case ActivityUtils::compareTypes($verb, array(ActivityVerb::UNFAVORITE, ActivityVerb::UNLIKE)):
+            Fave::removeEntry($scoped, $target);
+            break;
+        default:
+            throw new ServerException('ActivityVerb POST not handled by plugin that was supposed to do it.');
+        }
+        return false;
+    }
+
+    protected function getActivityForm(ManagedAction $action, $verb, Notice $target, Profile $scoped)
+    {
+        return Fave::existsForProfile($target, $scoped)
+                ? new DisfavorForm($action, $target)
+                : new FavorForm($action, $target);
+    }
+
     public function onPluginVersion(array &$versions)
     {
         $versions[] = array('name' => 'Favorite',
index 736bd7b0a19eaa720cf49040bd9255912f9c4afa..7e63b199e2285a9fd77327816a40a2a7dbe0c51d 100644 (file)
@@ -89,20 +89,13 @@ class ApiFavoriteCreateAction extends ApiAuthAction
             );
         }
 
-        // Note: Twitter lets you fave things repeatedly via API.
-
-        if (Fave::existsForProfile($this->notice, $this->scoped)) {
-            $this->clientError(
-                // TRANS: Client error displayed when trying to mark a notice favourite that already is a favourite.
-                _('This status is already a favorite.'),
-                403,
-                $this->format
-            );
+        try {
+            $stored = Fave::addNew($this->scoped, $this->notice);
+        } catch (AlreadyFulfilledException $e) {
+            // Note: Twitter lets you fave things repeatedly via API.
+            $this->clientError($e->getMessage(), 403);
         }
 
-        // throws exception on failure
-        $stored = Fave::addNew($this->scoped, $this->notice);
-
         if ($this->format == 'xml') {
             $this->showSingleXmlStatus($this->notice);
         } elseif ($this->format == 'json') {
diff --git a/plugins/Favorite/actions/disfavor.php b/plugins/Favorite/actions/disfavor.php
deleted file mode 100644 (file)
index 926bbec..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-<?php
-/**
- * Disfavor action.
- *
- * PHP version 5
- *
- * @category Action
- * @package  GNUsocial
- * @author   Evan Prodromou <evan@status.net>
- * @author   Robin Millette <millette@status.net>
- * @author   Mikael Nordfeldth <mmn@hethane.se>
- * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link     http://www.gnu.org/software/social/
- *
- * 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('GNUSOCIAL')) { exit(1); }
-
-/**
- * DisfavorAction class.
- *
- * @category Action
- * @package  GNUsocial
- * @author   Evan Prodromou <evan@status.net>
- * @author   Robin Millette <millette@status.net>
- * @author   Mikael Nordfeldth <mmn@hethane.se>
- * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link     http://www.gnu.org/software/social/
- */
-class DisfavorAction extends FormAction
-{
-    protected $needPost = true;
-
-    protected function doPreparation()
-    {
-        $this->target = Notice::getKV($this->trimmed('notice'));
-        if (!$this->target instanceof Notice) {
-            throw new ServerException(_m('No such notice.'));
-        }
-    }
-
-    protected function doPost()
-    {
-        $fave            = new Fave();
-        $fave->user_id   = $this->scoped->getID();
-        $fave->notice_id = $this->target->getID();
-        if (!$fave->find(true)) {
-            // TRANS: Client error displayed when trying to remove a 'favor' when there is none in the first place.
-            throw new AlreadyFulfilledException(_('This is already not favorited.'));
-        }
-        $result = $fave->delete();
-        if ($result === false) {
-            common_log_db_error($fave, 'DELETE', __FILE__);
-            // TRANS: Server error displayed when removing a favorite from the database fails.
-            throw new ServerException(_('Could not delete favorite.'));
-        }
-        Fave::blowCacheForProfileId($this->scoped->getID());
-
-        // TRANS: Message when a disfavor action has been taken for a notice.
-        return _('Disfavored the notice.');
-    }
-
-    protected function showContent()
-    {
-        // We show the 'Favor' form because right now all calls to Disfavor will directly disfavor a notice.
-        $disfavor = new FavorForm($this, $this->target);
-        $disfavor->show();
-    }
-}
diff --git a/plugins/Favorite/actions/favor.php b/plugins/Favorite/actions/favor.php
deleted file mode 100644 (file)
index f659078..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-<?php
-/**
- * Favor action.
- *
- * PHP version 5
- *
- * @category Action
- * @package  GNUsocial
- * @author   Evan Prodromou <evan@status.net>
- * @author   Robin Millette <millette@status.net>
- * @author   Mikael Nordfeldth <mmn@hethane.se>
- * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link     http://www.gnu.org/software/social/
- *
- * 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('GNUSOCIAL')) { exit(1); }
-
-/**
- * FavorAction class.
- *
- * @category Action
- * @package  GNUsocial
- * @author   Evan Prodromou <evan@status.net>
- * @author   Robin Millette <millette@status.net>
- * @author   Mikael Nordfeldth <mmn@hethane.se>
- * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link     http://www.gnu.org/software/social/
- */
-class FavorAction extends FormAction
-{
-    protected $needPost = true;
-
-    protected function doPreparation()
-    {
-        $this->target = Notice::getKV($this->trimmed('notice'));
-        if (!$this->target instanceof Notice) {
-            throw new ServerException(_m('No such notice.'));
-        }
-        if (!$this->target->inScope($this->scoped)) {
-            // TRANS: Client error displayed when trying to interact with a notice a the target has no access to.
-            // TRANS: %1$s is a user nickname, %2$d is a notice ID (number).
-            throw new ClientException(sprintf(_m('%1$s has no right to interact with notice %2$d.'), $this->scoped->getNickname(), $this->target->getID()), 403);
-        }
-    }
-
-    protected function doPost()
-    {
-        if (Fave::existsForProfile($this->target, $this->scoped)) {
-            // TRANS: Client error displayed when trying to mark a notice as favorite that already is a favorite.
-            throw new AlreadyFulfilledException(_('You have already favorited this!'));
-        }
-
-        // throws exception on failure
-        $stored = Fave::addNew($this->scoped, $this->target);
-
-        // TRANS: Message when a favor action has been taken for a notice.
-        return _('Favorited the notice');
-    }
-
-    protected function showContent()
-    {
-        $disfavor = new DisfavorForm($this, $this->target);
-        $disfavor->show();
-    }
-}
index 094b65feaa94fd19b38f6b5dd12e6af5b48ebd10..48ed3ba93bcb3d4a8accae430e4c054e2a0ef9f2 100644 (file)
@@ -48,6 +48,11 @@ class Fave extends Managed_DataObject
      * @throws Exception on failure
      */
     static function addNew(Profile $actor, Notice $target) {
+        if (self::existsForProfile($target, $actor)) {
+            // TRANS: Client error displayed when trying to mark a notice as favorite that already is a favorite.
+            throw new AlreadyFulfilledException(_('You have already favorited this!'));
+        }
+
         $act = new Activity();
         $act->type    = ActivityObject::ACTIVITY;
         $act->verb    = ActivityVerb::FAVORITE;
@@ -74,6 +79,27 @@ class Fave extends Managed_DataObject
         return $stored;
     }
 
+    public function removeEntry(Profile $actor, Notice $target)
+    {
+        $fave            = new Fave();
+        $fave->user_id   = $actor->getID();
+        $fave->notice_id = $target->getID();
+        if (!$fave->find(true)) {
+            // TRANS: Client error displayed when trying to remove a 'favor' when there is none in the first place.
+            throw new AlreadyFulfilledException(_('This is already not favorited.'));
+        }
+
+        $result = $fave->delete();
+        if ($result === false) {
+            common_log_db_error($fave, 'DELETE', __FILE__);
+            // TRANS: Server error displayed when removing a favorite from the database fails.
+            throw new ServerException(_('Could not delete favorite.'));
+        }
+
+        Fave::blowCacheForProfileId($actor->getID());
+        Fave::blowCacheForNoticeId($target->getID());
+    }
+
     // exception throwing takeover!
     public function insert()
     {
index 51903b6cb2d9d61c0f3e0c2bd5e3e9972602199d..f6702ebf29a3d4e11c1f151cedbe6bb112e701e5 100644 (file)
@@ -81,7 +81,9 @@ class DisfavorForm extends Form
      */
     function action()
     {
-        return common_local_url('disfavor');
+        return common_local_url('activityverb',
+                                array('id'   => $this->notice->getID(),
+                                      'verb' => ActivityUtils::resolveUri(ActivityVerb::UNFAVORITE, true)));
     }
 
     /**
index cd956f67ff27ffde0c8876366c21be5fda0ef73d..e7d05ce09729ebe0736d20556d24091db35b5dc7 100644 (file)
@@ -81,7 +81,9 @@ class FavorForm extends Form
      */
     function action()
     {
-        return common_local_url('favor');
+        return common_local_url('activityverb',
+                                array('id'   => $this->notice->getID(),
+                                      'verb' => ActivityUtils::resolveUri(ActivityVerb::FAVORITE, true)));
     }
 
     /**
index cf5ca79218ed293377555b7213ea2d92c37748a7..6c7d0d1946ae1c09c4418ebcabc228937ad1f268 100644 (file)
@@ -14,17 +14,6 @@ class FavCommand extends Command
     { 
         $notice = $this->getNotice($this->other); 
  
-        $fave            = new Fave(); 
-        $fave->user_id   = $this->user->id; 
-        $fave->notice_id = $notice->id; 
-        $fave->find(); 
-        if ($fave->fetch()) { 
-            // TRANS: Error message text shown when a favorite could not be set because it has already been favorited. 
-            $channel->error($this->user, _('Could not create favorite: Already favorited.')); 
-            return; 
-        } 
         try {
             $fave = Fave::addNew($this->user->getProfile(), $notice); 
         } catch (Exception $e) {
index 6b3c757b1e68845a95a80acea90e61fb1dfb0305..c4628c4ecc844041e931b71b3719782f67c1ff74 100644 (file)
@@ -137,21 +137,4 @@ class GNUsocialProfileExtensionsPlugin extends Plugin
                            array('nickname' => $nav->action->trimmed('nickname'))), _('Bio'), 
                            _('The user\'s extended profile'), $nav->action->trimmed('action') == 'bio', 'nav_bio');
     }
-
-    //Why the heck is this shoved into this plugin!?!?  It deserves its own!
-    function onShowStreamNoticeList($notice, $action, &$pnl)
-    {
-        //TODO: This function is called after the notices in $notice are superfluously retrieved in showstream.php
-        $newnotice = new Notice();
-        $newnotice->profile_id = $action->user->id;
-        $newnotice->orderBy('modified DESC');
-        $newnotice->whereAdd('reply_to IS NULL');
-        $newnotice->limit(($action->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
-        $newnotice->find();
-
-        $pnl = new NoticeTree($newnotice, $action);
-        return false;
-    }
-
 }
-
index 44c5c5e880bc555c5e07421fb8f269ecfdeea9cd..986ba3607594170ab07b4e236d710b44d36ee6a6 100644 (file)
  * @link      http://status.net/
  */
 
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
 
 class InfiniteScrollPlugin extends Plugin
 {
-
     public $on_next_only = false;
 
-    function __construct()
-    {
-        parent::__construct();
-    }
-
-    function onEndShowScripts($action)
+    function onEndShowScripts(Action $action)
     {
         $action->inlineScript('var infinite_scroll_on_next_only = ' . ($this->on_next_only?'true':'false') . ';');
         $action->inlineScript('var ajax_loader_url = "' . ($this->path('ajax-loader.gif')) . '";');
@@ -49,7 +41,7 @@ class InfiniteScrollPlugin extends Plugin
         $action->script($this->path('infinitescroll.js'));
     }
 
-    function onPluginVersion(&$versions)
+    function onPluginVersion(array &$versions)
     {
         $versions[] = array('name' => 'InfiniteScroll',
                             'version' => GNUSOCIAL_VERSION,
index 98fcb472fae2214967f944ce3a2a1722693e300b..2efc0068794a9ae19c3ac34213c6ca2f1cb723cd 100644 (file)
@@ -27,9 +27,7 @@
  * @link      http://status.net/
  */
 
-if (!defined('STATUSNET')) {
-    exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
 
 define('PAGE_TYPE_PREFS_MOBILEPROFILE',
        'application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html;q=0.9');
@@ -59,124 +57,120 @@ class MobileProfilePlugin extends WAP20Plugin
         parent::__construct();
     }
 
-    function onStartShowHTML($action)
+    public function onStartShowHTML(Action $action)
     {
-        // XXX: This should probably graduate to WAP20Plugin
+        // TODO: A lot of this should probably graduate to WAP20Plugin
 
-        // If they are on the mobile site, serve them MP
-        if ((common_config('site', 'mobileserver').'/'.
-             common_config('site', 'path').'/' ==
-            $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'])) {
+        $httpaccept = isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : null;
+
+        $cp = common_accept_to_prefs($httpaccept);
+        $sp = common_accept_to_prefs(PAGE_TYPE_PREFS_MOBILEPROFILE);
 
+        $type = common_negotiate_type($cp, $sp);
+
+        if (!$type) {
+            // TRANS: Client exception thrown when requesting a not supported media type.
+            throw new ClientException(_m('This page is not available in a '.
+                                        'media type you accept.'), 406);
+        }
+
+        // If they are on the mobile site, serve them MP
+        if ((common_config('site', 'mobileserver').'/'.common_config('site', 'path').'/'
+                == $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'])) {
             $this->serveMobile = true;
-        } else if (isset($_COOKIE['MobileOverride'])) {
+        } elseif (isset($_COOKIE['MobileOverride'])) {
             // Cookie override is controlled by link at bottom.
             $this->serveMobile = (bool)$_COOKIE['MobileOverride'];
-        } elseif (array_key_exists('HTTP_USER_AGENT', $_SERVER)) {
+        } elseif (strstr('application/vnd.wap.xhtml+xml', $type) !== false) {
             // If they like the WAP 2.0 mimetype, serve them MP
-            // @fixme $type is undefined, making this if case useless and spewing errors.
-            // What's the intent?
-            //if (strstr('application/vnd.wap.xhtml+xml', $type) !== false) {
-            //    $this->serveMobile = true;
-            //} else {
-                // If they are a mobile device that supports WAP 2.0,
-                // serve them MP
-
-                // XXX: Browser sniffing sucks
-
-                // I really don't like going through this every page,
-                // perhaps use $_SESSION or cookies
-
-                // May be better to group the devices in terms of
-                // low,mid,high-end
-
-                // Or, detect the mobile devices based on their support for
-                // MP 1.0, 1.1, or 1.2 may be ideal. Possible?
-
-                $this->mobiledevices = array(
-                    'alcatel',
-                    'android',
-                    'audiovox',
-                    'au-mic,',
-                    'avantgo',
-                    'blackberry',
-                    'blazer',
-                    'cldc-',
-                    'danger',
-                    'epoc',
-                    'ericsson',
-                    'ericy',
-                    'iphone',
-                    'ipaq',
-                    'ipod',
-                    'j2me',
-                    'lg',
-                    'maemo',
-                    'midp-',
-                    'mobile',
-                    'mot',
-                    'netfront',
-                    'nitro',
-                    'nokia',
-                    'opera mini',
-                    'palm',
-                    'palmsource',
-                    'panasonic',
-                    'philips',
-                    'pocketpc',
-                    'portalmmm',
-                    'rover',
-                    'samsung',
-                    'sanyo',
-                    'series60',
-                    'sharp',
-                    'sie-',
-                    'smartphone',
-                    'sony',
-                    'symbian',
-                    'up.browser',
-                    'up.link',
-                    'up.link',
-                    'vodafone',
-                    'wap1',
-                    'wap2',
-                    'webos',
-                    'windows ce'
-                );
-
-                $blacklist = array(
-                    'ipad', // Larger screen handles the full theme fairly well.
-                );
-
-                $httpuseragent = strtolower($_SERVER['HTTP_USER_AGENT']);
-
-                foreach ($blacklist as $md) {
-                    if (strstr($httpuseragent, $md) !== false) {
-                        $this->serveMobile = false;
-                        return true;
-                    }
-                }
+            $this->serveMobile = true;
+        } elseif (array_key_exists('HTTP_USER_AGENT', $_SERVER)) {
+            // If they are a mobile device that supports WAP 2.0,
+            // serve them MP
+
+            // XXX: Browser sniffing sucks
+
+            // I really don't like going through this every page,
+            // perhaps use $_SESSION or cookies
+
+            // May be better to group the devices in terms of
+            // low,mid,high-end
+
+            // Or, detect the mobile devices based on their support for
+            // MP 1.0, 1.1, or 1.2 may be ideal. Possible?
+
+            $this->mobiledevices = array(
+                'alcatel',
+                'android',
+                'audiovox',
+                'au-mic,',
+                'avantgo',
+                'blackberry',
+                'blazer',
+                'cldc-',
+                'danger',
+                'epoc',
+                'ericsson',
+                'ericy',
+                'iphone',
+                'ipaq',
+                'ipod',
+                'j2me',
+                'lg',
+                'maemo',
+                'midp-',
+                'mobile',
+                'mot',
+                'netfront',
+                'nitro',
+                'nokia',
+                'opera mini',
+                'palm',
+                'palmsource',
+                'panasonic',
+                'philips',
+                'pocketpc',
+                'portalmmm',
+                'rover',
+                'samsung',
+                'sanyo',
+                'series60',
+                'sharp',
+                'sie-',
+                'smartphone',
+                'sony',
+                'symbian',
+                'up.browser',
+                'up.link',
+                'up.link',
+                'vodafone',
+                'wap1',
+                'wap2',
+                'webos',
+                'windows ce'
+            );
+
+            $blacklist = array(
+                'ipad', // Larger screen handles the full theme fairly well.
+            );
 
-                foreach ($this->mobiledevices as $md) {
-                    if (strstr($httpuseragent, $md) !== false) {
-                        $this->setMobileFeatures($httpuseragent);
+            $httpuseragent = strtolower($_SERVER['HTTP_USER_AGENT']);
 
-                        $this->serveMobile = true;
-                        $this->reallyMobile = true;
-                        break;
-                    }
+            foreach ($blacklist as $md) {
+                if (strstr($httpuseragent, $md) !== false) {
+                    $this->serveMobile = false;
+                    return true;
                 }
-            //}
+            }
 
-            // If they are okay with MP, and the site has a mobile server,
-            // redirect there
-            if ($this->serveMobile &&
-                common_config('site', 'mobileserver') !== false &&
-                (common_config('site', 'mobileserver') !=
-                    common_config('site', 'server'))) {
+            foreach ($this->mobiledevices as $md) {
+                if (strstr($httpuseragent, $md) !== false) {
+                    $this->setMobileFeatures($httpuseragent);
 
-                // FIXME: Redirect to equivalent page on mobile site instead
-                common_redirect($this->_common_path(''), 302);
+                    $this->serveMobile = true;
+                    $this->reallyMobile = true;
+                    break;
+                }
             }
         }
 
@@ -184,48 +178,23 @@ class MobileProfilePlugin extends WAP20Plugin
             return true;
         }
 
-        // @fixme $type is undefined, making this if case useless and spewing errors.
-        // What's the intent?
-        //if (!$type) {
-            $httpaccept = isset($_SERVER['HTTP_ACCEPT']) ?
-              $_SERVER['HTTP_ACCEPT'] : null;
-
-            $cp = common_accept_to_prefs($httpaccept);
-            $sp = common_accept_to_prefs(PAGE_TYPE_PREFS_MOBILEPROFILE);
+        // If they are okay with MP, and the site has a mobile server,
+        // redirect there
+        if (common_config('site', 'mobileserver') !== false &&
+                common_config('site', 'mobileserver') != common_config('site', 'server')) {
 
-            $type = common_negotiate_type($cp, $sp);
-
-            if (!$type) {
-                // TRANS: Client exception thrown when requesting a not supported media type.
-                throw new ClientException(_m('This page is not available in a '.
-                                            'media type you accept.'), 406);
-            }
-        //}
+            // FIXME: Redirect to equivalent page on mobile site instead
+            common_redirect($this->_common_path(''), 302);
+        }
 
         header('Content-Type: '.$type);
 
         if ($this->reallyMobile) {
-
-           $action->extraHeaders();
-           if (preg_match("/.*\/.*xml/", $type)) {
-               // Required for XML documents
-               $action->startXML();
-           }
-           $action->xw->writeDTD('html',
-                           '-//WAPFORUM//DTD XHTML Mobile 1.0//EN',
-                           $this->DTD);
-
-            $language = $action->getLanguage();
-
-            $action->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml',
-                                            'xml:lang' => $language));
-
-            return false;
-
-        } else {
-        return true;
+           $action->setDTD('html', '-//WAPFORUM//DTD XHTML Mobile 1.0//EN', $this->DTD);
         }
 
+        // continue
+        return true;
     }
 
     function setMobileFeatures($useragent)
@@ -268,7 +237,7 @@ class MobileProfilePlugin extends WAP20Plugin
         return false;
     }
 
-    function onStartShowUAStyles($action) {
+    public function onStartShowUAStyles(Action $action) {
         if (!$this->serveMobile) {
             return true;
         }
@@ -276,7 +245,7 @@ class MobileProfilePlugin extends WAP20Plugin
         return false;
     }
 
-    function onStartShowHeader($action)
+    public function onStartShowHeader(Action $action)
     {
         if (!$this->serveMobile) {
             return true;
@@ -290,7 +259,7 @@ class MobileProfilePlugin extends WAP20Plugin
         return false;
     }
 
-    function _showLogo($action)
+    protected function _showLogo(Action $action)
     {
         $action->elementStart('address');
         if (common_config('singleuser', 'enabled')) {
@@ -316,23 +285,22 @@ class MobileProfilePlugin extends WAP20Plugin
         $action->elementEnd('address');
     }
 
-    function onStartShowAside($action)
+    public function onStartShowAside(Action $action)
     {
         if ($this->serveMobile) {
             return false;
         }
     }
 
-    function onStartShowLocalNavBlock($action)
+    public function onStartShowLocalNavBlock(Action $action)
     {
         if ($this->serveMobile) {
             // @todo FIXME: "Show Navigation" / "Hide Navigation" needs i18n
             $action->element('a', array('href' => '#', 'id' => 'navtoggle'), 'Show Navigation');
-        return true;
         }
     }
 
-    function onEndShowScripts($action)
+    public function onEndShowScripts(Action $action)
     {
         // @todo FIXME: "Show Navigation" / "Hide Navigation" needs i18n
         $action->inlineScript('
@@ -357,18 +325,12 @@ class MobileProfilePlugin extends WAP20Plugin
         );
 
         if ($this->serveMobile) {
-            $action->inlineScript('
-                $(function() {
-                       $(".checkbox-wrapper").unbind("click");
-                });'
-            );
+            $action->inlineScript('$(function() { $(".checkbox-wrapper").unbind("click"); });');
         }
-
-
     }
 
 
-    function onEndShowInsideFooter($action)
+    public function onEndShowInsideFooter(Action $action)
     {
         if ($this->serveMobile) {
             // TRANS: Link to switch site layout from mobile to desktop mode. Appears at very bottom of page.
index 133646480c6b5fbd763eb480b2a8454b9c521baa..4203672030179b74d7b3bad0e5b8d500cfcabd24 100644 (file)
@@ -39,10 +39,7 @@ class RemoteProfileAction extends ShowstreamAction
             $this->raw(common_markup_to_html($markdown));
         }else{
 
-            $pnl = null;
-            if (Event::handle('ShowStreamNoticeList', array($this->notice, $this, &$pnl))) {
-                $pnl = new ProfileNoticeList($this->notice, $this);
-            }
+            $pnl = new NoticeList($this->notice, $this);
             $cnt = $pnl->show();
             if (0 == $cnt) {
                 $this->showEmptyListMessage();
index 2cf4f1bdd15a54b55a7354e0b27cabaca933153a..07c9d1c182a59e38eec64f93c652838db3fa4497 100644 (file)
@@ -513,9 +513,6 @@ class Ostatus_profile extends Managed_DataObject
                     throw new ClientException(_m('Cannot handle that kind of post.'));
                 }
                 break;
-            case ActivityVerb::SHARE:
-                $notice = $this->processShare($activity, $source);
-                break;
             default:
                 common_log(LOG_INFO, "Ignoring activity with unrecognized verb $activity->verb");
             }
@@ -527,214 +524,6 @@ class Ostatus_profile extends Managed_DataObject
         return $notice;
     }
 
-    public function processShare($activity, $method)
-    {
-        $notice = null;
-
-        try {
-            $profile = ActivityUtils::checkAuthorship($activity, $this->localProfile());
-        } catch (ServerException $e) {
-            return null;
-        }
-
-        // The id URI will be used as a unique identifier for the notice,
-        // protecting against duplicate saves. It isn't required to be a URL;
-        // tag: URIs for instance are found in Google Buzz feeds.
-        $dupe = Notice::getKV('uri', $activity->id);
-        if ($dupe instanceof Notice) {
-            common_log(LOG_INFO, "OStatus: ignoring duplicate post: {$activity->id}");
-            return $dupe;
-        }
-
-        if (count($activity->objects) != 1) {
-            // TRANS: Client exception thrown when trying to share multiple activities at once.
-            throw new ClientException(_m('Can only handle share activities with exactly one object.'));
-        }
-
-        $shared = $activity->objects[0];
-
-        if (!$shared instanceof Activity) {
-            // TRANS: Client exception thrown when trying to share a non-activity object.
-            throw new ClientException(_m('Can only handle shared activities.'));
-        }
-
-        $sharedId = $shared->id;
-        if (!empty($shared->objects[0]->id)) {
-            // Because StatusNet since commit 8cc4660 sets $shared->id to a TagURI which
-            // fucks up federation, because the URI is no longer recognised by the origin.
-            // So we set it to the object ID if it exists, otherwise we trust $shared->id
-            $sharedId = $shared->objects[0]->id;
-        }
-        if (empty($sharedId)) {
-            throw new ClientException(_m('Shared activity does not have an id'));
-        }
-
-        // First check if we have the shared activity. This has to be done first, because
-        // we can't use these functions to "ensureActivityObjectProfile" of a local user,
-        // who might be the creator of the shared activity in question.
-        $sharedNotice = Notice::getKV('uri', $sharedId);
-        if (!$sharedNotice instanceof Notice) {
-            // If no locally stored notice is found, process it!
-            // TODO: Remember to check Deleted_notice!
-            // TODO: If a post is shared that we can't retrieve - what to do?
-            try {
-                $other = self::ensureActivityObjectProfile($shared->actor);
-                $sharedNotice = $other->processActivity($shared, $method);
-                if (!$sharedNotice instanceof Notice) {
-                    // And if we apparently can't get the shared notice, we'll abort the whole thing.
-                    // TRANS: Client exception thrown when saving an activity share fails.
-                    // TRANS: %s is a share ID.
-                    throw new ClientException(sprintf(_m('Failed to save activity %s.'), $sharedId));
-                }
-            } catch (FeedSubException $e) {
-                // Remote feed could not be found or verified, should we
-                // transform this into an "RT @user Blah, blah, blah..."?
-                common_log(LOG_INFO, __METHOD__ . ' got a ' . get_class($e) . ': ' . $e->getMessage());
-                return null;
-            }
-        }
-
-        // We'll want to save a web link to the original notice, if provided.
-
-        $sourceUrl = null;
-        if ($activity->link) {
-            $sourceUrl = $activity->link;
-        } else if ($activity->link) {
-            $sourceUrl = $activity->link;
-        } else if (preg_match('!^https?://!', $activity->id)) {
-            $sourceUrl = $activity->id;
-        }
-
-        // Use summary as fallback for content
-
-        if (!empty($activity->content)) {
-            $sourceContent = $activity->content;
-        } else if (!empty($activity->summary)) {
-            $sourceContent = $activity->summary;
-        } else if (!empty($activity->title)) {
-            $sourceContent = $activity->title;
-        } else {
-            // @todo FIXME: Fetch from $sourceUrl?
-            // TRANS: Client exception. %s is a source URI.
-            throw new ClientException(sprintf(_m('No content for notice %s.'), $activity->id));
-        }
-
-        // Get (safe!) HTML and text versions of the content
-
-        $rendered = common_purify($sourceContent);
-        $content = common_strip_html($rendered);
-
-        $shortened = common_shorten_links($content);
-
-        // If it's too long, try using the summary, and make the
-        // HTML an attachment.
-
-        $attachment = null;
-
-        if (Notice::contentTooLong($shortened)) {
-            $attachment = $this->saveHTMLFile($activity->title, $rendered);
-            $summary = common_strip_html($activity->summary);
-            if (empty($summary)) {
-                $summary = $content;
-            }
-            $shortSummary = common_shorten_links($summary);
-            if (Notice::contentTooLong($shortSummary)) {
-                $url = common_shorten_url($sourceUrl);
-                $shortSummary = substr($shortSummary,
-                                       0,
-                                       Notice::maxContent() - (mb_strlen($url) + 2));
-                $content = $shortSummary . ' ' . $url;
-
-                // We mark up the attachment link specially for the HTML output
-                // so we can fold-out the full version inline.
-
-                // @todo FIXME i18n: This tooltip will be saved with the site's default language
-                // TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
-                // TRANS: this will usually be replaced with localised text from StatusNet core messages.
-                $showMoreText = _m('Show more');
-                $attachUrl = common_local_url('attachment',
-                                              array('attachment' => $attachment->id));
-                $rendered = common_render_text($shortSummary) .
-                            '<a href="' . htmlspecialchars($attachUrl) .'"'.
-                            ' class="attachment more"' .
-                            ' title="'. htmlspecialchars($showMoreText) . '">' .
-                            '&#8230;' .
-                            '</a>';
-            }
-        }
-
-        $options = array('is_local' => Notice::REMOTE,
-                         'url' => $sourceUrl,
-                         'uri' => $activity->id,
-                         'rendered' => $rendered,
-                         'replies' => array(),
-                         'groups' => array(),
-                         'peopletags' => array(),
-                         'tags' => array(),
-                         'urls' => array(),
-                         'repeat_of' => $sharedNotice->id,
-                         'scope' => $sharedNotice->scope);
-
-        // Check for optional attributes...
-
-        if (!empty($activity->time)) {
-            $options['created'] = common_sql_date($activity->time);
-        }
-
-        if ($activity->context) {
-            // TODO: context->attention
-            list($options['groups'], $options['replies'])
-                = self::filterAttention($profile, $activity->context->attention);
-
-            // Maintain direct reply associations
-            // @todo FIXME: What about conversation ID?
-            if (!empty($activity->context->replyToID)) {
-                $orig = Notice::getKV('uri',
-                                          $activity->context->replyToID);
-                if ($orig instanceof Notice) {
-                    $options['reply_to'] = $orig->id;
-                }
-            }
-
-            $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;
-                }
-            }
-        }
-
-        if ($this->isPeopletag()) {
-            $options['peopletags'][] = $this->localPeopletag();
-        }
-
-        // Atom categories <-> hashtags
-        foreach ($activity->categories as $cat) {
-            if ($cat->term) {
-                $term = common_canonical_tag($cat->term);
-                if ($term) {
-                    $options['tags'][] = $term;
-                }
-            }
-        }
-
-        // Atom enclosures -> attachment URLs
-        foreach ($activity->enclosures as $href) {
-            // @todo FIXME: Save these locally or....?
-            $options['urls'][] = $href;
-        }
-
-        $notice = Notice::saveNew($profile->id,
-                                  $content,
-                                  'ostatus',
-                                  $options);
-
-        return $notice;
-    }
-
     /**
      * Process an incoming post activity from this remote feed.
      * @param Activity $activity
@@ -1268,8 +1057,7 @@ class Ostatus_profile extends Managed_DataObject
             } else {
                 $id = $this->profile_id;
             }
-            // @todo FIXME: Should we be using different ids?
-            $imagefile = new ImageFile($id, $temp_filename);
+            $imagefile = new ImageFile(null, $temp_filename);
             $filename = Avatar::filename($id,
                                          image_type_to_extension($imagefile->type),
                                          null,
index 44e5d1a72bad57e2a46e38a2a3f7e72d741861d3..c014b65cfb28148be4535502170a4cb7f673b7f7 100644 (file)
@@ -11,7 +11,7 @@ class OembedPlugin extends Plugin
                                     '^i\d*\.vimeocdn\.com$' => 'Vimeo',
                                     );
     public $append_whitelist = array(); // fill this array as domain_whitelist to add more trusted sources
-    public $check_whitelist  = true;    // security/abuse precaution
+    public $check_whitelist  = false;    // security/abuse precaution
 
     protected $imgData = array();
 
index bf3e71c5056578e3ae86ea0e6ed0627e38e4bd39..ccba4c42a585dfd8a77949598764a9cff09833a0 100644 (file)
@@ -44,138 +44,138 @@ class OembedAction extends Action
         parent::handle();
 
         $url = $this->trimmed('url');
-        if (substr(strtolower($url),0,strlen(common_root_url())) == strtolower(common_root_url())) {
-            $path = substr($url,strlen(common_root_url()));
+        if (substr(strtolower($url),0,strlen(common_root_url())) !== strtolower(common_root_url())) {
+            // TRANS: Error message displaying attachments. %s is the site's base URL.
+            $this->clientError(sprintf(_('oEmbed data will only be provided for %s URLs.'), common_root_url()), 400);
+        }
 
-            $r = Router::get();
+        $path = substr($url,strlen(common_root_url()));
 
-            $proxy_args = $r->map($path);
-            if (!$proxy_args) {
-                // TRANS: Client error displayed in oEmbed action when path not found.
-                // TRANS: %s is a path.
-                $this->clientError(sprintf(_('"%s" not found.'),$path), 404);
+        $r = Router::get();
+
+        $proxy_args = $r->map($path);
+        if (!$proxy_args) {
+            // TRANS: Client error displayed in oEmbed action when path not found.
+            // TRANS: %s is a path.
+            $this->clientError(sprintf(_('"%s" not found.'),$path), 404);
+        }
+
+        $oembed=array();
+        $oembed['version']='1.0';
+        $oembed['provider_name']=common_config('site', 'name');
+        $oembed['provider_url']=common_root_url();
+
+        switch ($proxy_args['action']) {
+        case 'shownotice':
+            $oembed['type']='link';
+            $id = $proxy_args['notice'];
+            $notice = Notice::getKV($id);
+            if(empty($notice)){
+                // TRANS: Client error displayed in oEmbed action when notice not found.
+                // TRANS: %s is a notice.
+                $this->clientError(sprintf(_("Notice %s not found."),$id), 404);
+            }
+            $profile = $notice->getProfile();
+            if (empty($profile)) {
+                // TRANS: Server error displayed in oEmbed action when notice has not profile.
+                $this->serverError(_('Notice has no profile.'), 500);
             }
+            $authorname = $profile->getFancyName();
+            // TRANS: oEmbed title. %1$s is the author name, %2$s is the creation date.
+            $oembed['title'] = sprintf(_('%1$s\'s status on %2$s'),
+                $authorname,
+                common_exact_date($notice->created));
+            $oembed['author_name']=$authorname;
+            $oembed['author_url']=$profile->profileurl;
+            $oembed['url']=$notice->getUrl();
+            $oembed['html']=$notice->rendered;
+            break;
 
-            $oembed=array();
-            $oembed['version']='1.0';
-            $oembed['provider_name']=common_config('site', 'name');
-            $oembed['provider_url']=common_root_url();
-            
-            switch ($proxy_args['action']) {
-            case 'shownotice':
-                $oembed['type']='link';
-                $id = $proxy_args['notice'];
-                $notice = Notice::getKV($id);
-                if(empty($notice)){
-                    // TRANS: Client error displayed in oEmbed action when notice not found.
-                    // TRANS: %s is a notice.
-                    $this->clientError(sprintf(_("Notice %s not found."),$id), 404);
-                }
-                $profile = $notice->getProfile();
-                if (empty($profile)) {
-                    // TRANS: Server error displayed in oEmbed action when notice has not profile.
-                    $this->serverError(_('Notice has no profile.'), 500);
-                }
-                $authorname = $profile->getFancyName();
-                // TRANS: oEmbed title. %1$s is the author name, %2$s is the creation date.
-                $oembed['title'] = sprintf(_('%1$s\'s status on %2$s'),
-                    $authorname,
-                    common_exact_date($notice->created));
-                $oembed['author_name']=$authorname;
-                $oembed['author_url']=$profile->profileurl;
-                $oembed['url']=$notice->getUrl();
-                $oembed['html']=$notice->rendered;
-                break;
-
-            case 'attachment':
-                $id = $proxy_args['attachment'];
-                $attachment = File::getKV($id);
-                if(empty($attachment)){
-                    // TRANS: Client error displayed in oEmbed action when attachment not found.
-                    // TRANS: %d is an attachment ID.
-                    $this->clientError(sprintf(_('Attachment %s not found.'),$id), 404);
-                }
-                if (empty($attachment->filename) && $file_oembed = File_oembed::getKV('file_id', $attachment->id)) {
-                    // Proxy the existing oembed information
-                    $oembed['type']=$file_oembed->type;
-                    $oembed['provider']=$file_oembed->provider;
-                    $oembed['provider_url']=$file_oembed->provider_url;
-                    $oembed['width']=$file_oembed->width;
-                    $oembed['height']=$file_oembed->height;
-                    $oembed['html']=$file_oembed->html;
-                    $oembed['title']=$file_oembed->title;
-                    $oembed['author_name']=$file_oembed->author_name;
-                    $oembed['author_url']=$file_oembed->author_url;
-                    $oembed['url']=$file_oembed->getUrl();
-                } elseif (substr($attachment->mimetype,0,strlen('image/'))==='image/') {
-                    $oembed['type']='photo';
-                    if ($attachment->filename) {
-                        $filepath = File::path($attachment->filename);
-                        $gis = @getimagesize($filepath);
-                        if ($gis) {
-                            $oembed['width'] = $gis[0];
-                            $oembed['height'] = $gis[1];
-                        } else {
-                            // TODO Either throw an error or find a fallback?
-                        }
-                    }
-                    $oembed['url']=$attachment->getUrl();
-                    try {
-                        $thumb = $attachment->getThumbnail();
-                        $oembed['thumbnail_url'] = $thumb->getUrl();
-                        $oembed['thumbnail_width'] = $thumb->width;
-                        $oembed['thumbnail_height'] = $thumb->height;
-                        unset($thumb);
-                    } catch (UnsupportedMediaException $e) {
-                        // No thumbnail data available
+        case 'attachment':
+            $id = $proxy_args['attachment'];
+            $attachment = File::getKV($id);
+            if(empty($attachment)){
+                // TRANS: Client error displayed in oEmbed action when attachment not found.
+                // TRANS: %d is an attachment ID.
+                $this->clientError(sprintf(_('Attachment %s not found.'),$id), 404);
+            }
+            if (empty($attachment->filename) && $file_oembed = File_oembed::getKV('file_id', $attachment->id)) {
+                // Proxy the existing oembed information
+                $oembed['type']=$file_oembed->type;
+                $oembed['provider']=$file_oembed->provider;
+                $oembed['provider_url']=$file_oembed->provider_url;
+                $oembed['width']=$file_oembed->width;
+                $oembed['height']=$file_oembed->height;
+                $oembed['html']=$file_oembed->html;
+                $oembed['title']=$file_oembed->title;
+                $oembed['author_name']=$file_oembed->author_name;
+                $oembed['author_url']=$file_oembed->author_url;
+                $oembed['url']=$file_oembed->getUrl();
+            } elseif (substr($attachment->mimetype,0,strlen('image/'))==='image/') {
+                $oembed['type']='photo';
+                if ($attachment->filename) {
+                    $filepath = File::path($attachment->filename);
+                    $gis = @getimagesize($filepath);
+                    if ($gis) {
+                        $oembed['width'] = $gis[0];
+                        $oembed['height'] = $gis[1];
+                    } else {
+                        // TODO Either throw an error or find a fallback?
                     }
-                } else {
-                    $oembed['type']='link';
-                    $oembed['url']=common_local_url('attachment',
-                        array('attachment' => $attachment->id));
                 }
-                if ($attachment->title) {
-                    $oembed['title']=$attachment->title;
+                $oembed['url']=$attachment->getUrl();
+                try {
+                    $thumb = $attachment->getThumbnail();
+                    $oembed['thumbnail_url'] = $thumb->getUrl();
+                    $oembed['thumbnail_width'] = $thumb->width;
+                    $oembed['thumbnail_height'] = $thumb->height;
+                    unset($thumb);
+                } catch (UnsupportedMediaException $e) {
+                    // No thumbnail data available
                 }
-                break;
-            default:
-                // TRANS: Server error displayed in oEmbed request when a path is not supported.
-                // TRANS: %s is a path.
-                $this->serverError(sprintf(_('"%s" not supported for oembed requests.'),$path), 501);
+            } else {
+                $oembed['type']='link';
+                $oembed['url']=common_local_url('attachment',
+                    array('attachment' => $attachment->id));
             }
-            switch ($this->trimmed('format')) {
-            case 'xml':
-                $this->init_document('xml');
-                $this->elementStart('oembed');
-                foreach(array(
-                            'version', 'type', 'provider_name',
-                            'provider_url', 'title', 'author_name',
-                            'author_url', 'url', 'html', 'width',
-                            'height', 'cache_age', 'thumbnail_url',
-                            'thumbnail_width', 'thumbnail_height',
-                        ) as $key) {
-                    if (isset($oembed[$key]) && $oembed[$key]!='') {
-                        $this->element($key, null, $oembed[$key]);
-                    }
+            if ($attachment->title) {
+                $oembed['title']=$attachment->title;
+            }
+            break;
+        default:
+            // TRANS: Server error displayed in oEmbed request when a path is not supported.
+            // TRANS: %s is a path.
+            $this->serverError(sprintf(_('"%s" not supported for oembed requests.'),$path), 501);
+        }
+
+        switch ($this->trimmed('format')) {
+        case 'xml':
+            $this->init_document('xml');
+            $this->elementStart('oembed');
+            foreach(array(
+                        'version', 'type', 'provider_name',
+                        'provider_url', 'title', 'author_name',
+                        'author_url', 'url', 'html', 'width',
+                        'height', 'cache_age', 'thumbnail_url',
+                        'thumbnail_width', 'thumbnail_height',
+                    ) as $key) {
+                if (isset($oembed[$key]) && $oembed[$key]!='') {
+                    $this->element($key, null, $oembed[$key]);
                 }
-                $this->elementEnd('oembed');
-                $this->end_document('xml');
-                break;
-
-            case 'json':
-            case null:
-                $this->init_document('json');
-                $this->raw(json_encode($oembed));
-                $this->end_document('json');
-                break;
-            default:
-                // TRANS: Error message displaying attachments. %s is a raw MIME type (eg 'image/png')
-                $this->serverError(sprintf(_('Content type %s not supported.'), $apidata['content-type']), 501);
             }
-        } else {
-            // TRANS: Error message displaying attachments. %s is the site's base URL.
-            // FIXME: 404 not found?! (this will automatically become a 500 because it's not a serverError!)
-            $this->serverError(sprintf(_('Only %s URLs over plain HTTP please.'), common_root_url()), 404);
+            $this->elementEnd('oembed');
+            $this->end_document('xml');
+            break;
+
+        case 'json':
+        case null:
+            $this->init_document('json');
+            $this->raw(json_encode($oembed));
+            $this->end_document('json');
+            break;
+        default:
+            // TRANS: Error message displaying attachments. %s is a raw MIME type (eg 'image/png')
+            $this->serverError(sprintf(_('Content type %s not supported.'), $apidata['content-type']), 501);
         }
     }
 
index 7435a3891978ca354da7620d9f851dbc8fa4108b..3454ac7fc76e9791cf95ce7f91611a82c7f2c61f 100644 (file)
@@ -120,7 +120,6 @@ class oEmbedHelper
             case 'service':
                 $api = common_config('oembed', 'endpoint');
                 common_log(LOG_INFO, 'Using service API endpoint ' . $api);
-                break 2;
                 break;
             }
         }
index 6356afcd088e781daa7e6e3341a19daf7bbbfebd..4b2b679b580217846dab25d20b03e3b144c5fb08 100644 (file)
@@ -80,6 +80,13 @@ class OpportunisticQueueManager extends DBQueueManager
         return true;
     }
 
+    // OpportunisticQM shouldn't discard items it can't handle, we're
+    // only here to take care of what we _can_ handle!
+    protected function noHandlerFound(Queue_item $qi, $rep=null) {
+        $this->_log(LOG_WARNING, "[{$qi->transport}:item {$qi->id}] Releasing claim for queue item without a handler");
+        $this->_fail($qi, true);    // true here means "releaseOnly", so no error statistics since it's not an _error_
+    }
+
     /**
      * Takes care of running through the queue items, returning when
      * the limits setup in __construct are met.
index bdd54afd2ea7ca995c394be2a1995017fab0ba37..a476c2af56540cf93405e68c165ac641566d7354 100644 (file)
@@ -340,7 +340,6 @@ class QnAPlugin extends MicroAppPlugin
         $nli->showNoticeSource();
         $nli->showNoticeLocation();
         $nli->showPermalink();
-        $nli->showRepeat();
 
         $nli->showNoticeOptions();
 
diff --git a/plugins/Share/SharePlugin.php b/plugins/Share/SharePlugin.php
new file mode 100644 (file)
index 0000000..b5643c1
--- /dev/null
@@ -0,0 +1,377 @@
+<?php
+/*
+ * GNU Social - a federating social network
+ * Copyright (C) 2014, Free Software Foundation, 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('GNUSOCIAL')) { exit(1); }
+
+/**
+ * @package     Activity
+ * @maintainer  Mikael Nordfeldth <mmn@hethane.se>
+ */
+class SharePlugin extends ActivityVerbHandlerPlugin
+{
+    public function tag()
+    {
+        return 'share';
+    }
+
+    public function types()
+    {
+        return array();
+    }
+
+    public function verbs()
+    {
+        return array(ActivityVerb::SHARE);
+    }
+
+    // Share is a bit special and $act->objects[0] should be an Activity
+    // instead of ActivityObject! Therefore also $act->objects[0]->type is not set.
+    public function isMyActivity(Activity $act) {
+        return (count($act->objects) == 1
+            && ($act->objects[0] instanceof Activity)
+            && $this->isMyVerb($act->verb));
+    }
+
+    public function onRouterInitialized(URLMapper $m)
+    {
+        // Web UI actions
+        $m->connect('main/repeat', array('action' => 'repeat'));
+
+        // Share for Twitter API ("Retweet")
+        $m->connect('api/statuses/retweeted_by_me.:format',
+                    array('action' => 'ApiTimelineRetweetedByMe',
+                          'format' => '(xml|json|atom|as)'));
+
+        $m->connect('api/statuses/retweeted_to_me.:format',
+                    array('action' => 'ApiTimelineRetweetedToMe',
+                          'format' => '(xml|json|atom|as)'));
+
+        $m->connect('api/statuses/retweets_of_me.:format',
+                    array('action' => 'ApiTimelineRetweetsOfMe',
+                          'format' => '(xml|json|atom|as)'));
+
+        $m->connect('api/statuses/retweet/:id.:format',
+                    array('action' => 'ApiStatusesRetweet',
+                          'id' => '[0-9]+',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/statuses/retweets/:id.:format',
+                    array('action' => 'ApiStatusesRetweets',
+                          'id' => '[0-9]+',
+                          'format' => '(xml|json)'));
+    }
+
+    // FIXME: Set this to abstract public in lib/activityhandlerplugin.php when all plugins have migrated!
+    protected function saveObjectFromActivity(Activity $act, Notice $stored, array $options=array())
+    {
+        assert($this->isMyActivity($act));
+
+        // The below algorithm is mainly copied from the previous Ostatus_profile->processShare()
+
+        if (count($act->objects) !== 1) {
+            // TRANS: Client exception thrown when trying to share multiple activities at once.
+            throw new ClientException(_m('Can only handle share activities with exactly one object.'));
+        }
+
+        $shared = $act->objects[0];
+        if (!$shared instanceof Activity) {
+            // TRANS: Client exception thrown when trying to share a non-activity object.
+            throw new ClientException(_m('Can only handle shared activities.'));
+        }
+
+        $sharedUri = $shared->id;
+        if (!empty($shared->objects[0]->id)) {
+            // Because StatusNet since commit 8cc4660 sets $shared->id to a TagURI which
+            // fucks up federation, because the URI is no longer recognised by the origin.
+            // So we set it to the object ID if it exists, otherwise we trust $shared->id
+            $sharedUri = $shared->objects[0]->id;
+        }
+        if (empty($sharedUri)) {
+            throw new ClientException(_m('Shared activity does not have an id'));
+        }
+
+        try {
+            // First check if we have the shared activity. This has to be done first, because
+            // we can't use these functions to "ensureActivityObjectProfile" of a local user,
+            // who might be the creator of the shared activity in question.
+            $sharedNotice = Notice::getByUri($sharedUri);
+        } catch (NoResultException $e) {
+            // If no locally stored notice is found, process it!
+            // TODO: Remember to check Deleted_notice!
+            // TODO: If a post is shared that we can't retrieve - what to do?
+            $other = Ostatus_profile::ensureActivityObjectProfile($shared->actor);
+            $sharedNotice = $other->processActivity($shared, 'push');   // FIXME: push/salmon/what?
+            if (!$sharedNotice instanceof Notice) {
+                // And if we apparently can't get the shared notice, we'll abort the whole thing.
+                // TRANS: Client exception thrown when saving an activity share fails.
+                // TRANS: %s is a share ID.
+                throw new ClientException(sprintf(_m('Failed to save activity %s.'), $sharedUri));
+            }
+        } catch (FeedSubException $e) {
+            // Remote feed could not be found or verified, should we
+            // transform this into an "RT @user Blah, blah, blah..."?
+            common_log(LOG_INFO, __METHOD__ . ' got a ' . get_class($e) . ': ' . $e->getMessage());
+            return false;
+        }
+
+        // Setting this here because when the algorithm gets back to
+        // Notice::saveActivity it will update the Notice object.
+        $stored->repeat_of = $sharedNotice->getID();
+        $stored->conversation = $sharedNotice->conversation;
+        $stored->object_type = ActivityUtils::resolveUri(ActivityObject::ACTIVITY, true);
+
+        // We don't have to save a repeat in a separate table, we can
+        // find repeats by just looking at the notice.repeat_of field.
+
+        // By returning true here instead of something that evaluates
+        // to false, we show that we have processed everything properly.
+        return true;
+    }
+
+    // FIXME: Put this in lib/activityhandlerplugin.php when we're ready
+    //          with the other microapps/activityhandlers as well.
+    //          Also it should be StartNoticeAsActivity (with a prepped Activity, including ->context etc.)
+    public function onEndNoticeAsActivity(Notice $stored, Activity $act, Profile $scoped=null)
+    {
+        if (!$this->isMyNotice($stored)) {
+            return true;
+        }
+
+        common_debug('Extending activity '.$stored->id.' with '.get_called_class());
+        $this->extendActivity($stored, $act, $scoped);
+        return false;
+    }
+
+    public function extendActivity(Notice $stored, Activity $act, Profile $scoped=null)
+    {
+        // TODO: How to handle repeats of deleted notices?
+        $target = Notice::getById($stored->repeat_of);
+        // TRANS: A repeat activity's title. %1$s is repeater's nickname
+        //        and %2$s is the repeated user's nickname.
+        $act->title = sprintf(_('%1$s repeated a notice by %2$s'),
+                              $stored->getProfile()->getNickname(),
+                              $target->getProfile()->getNickname());
+        $act->objects[] = $target->asActivity($scoped);
+    }
+
+    public function activityObjectFromNotice(Notice $notice)
+    {
+        // Repeat is a little bit special. As it's an activity, our
+        // ActivityObject is instead turned into an Activity
+        $object          = new Activity();
+        $object->verb    = ActivityVerb::SHARE;
+        $object->content = $notice->rendered;
+        $this->extendActivity($stored, $act);
+
+        return $object;
+    }
+
+    public function deleteRelated(Notice $notice)
+    {
+        // No action needed as we don't have a separate table for share objects.
+        return true;
+    }
+
+    // Layout stuff
+
+    /**
+     * show a link to the author of repeat
+     *
+     * FIXME: Some repeat stuff still in lib/noticelistitem.php! ($nli->repeat etc.)
+     */
+    public function onEndShowNoticeInfo(NoticeListItem $nli)
+    {
+        if (!empty($nli->repeat)) {
+            $repeater = $nli->repeat->getProfile();
+
+            $attrs = array('href' => $repeater->getUrl(),
+                           'class' => 'h-card p-author',
+                           'title' => $repeater->getFancyName());
+
+            $nli->out->elementStart('span', 'repeat h-entry');
+
+            // TRANS: Addition in notice list item if notice was repeated. Followed by a span with a nickname.
+            $nli->out->raw(_('Repeated by').' ');
+
+            $nli->out->element('a', $attrs, $repeater->getNickname());
+
+            $nli->out->elementEnd('span');
+        }
+    }
+
+    public function onEndShowThreadedNoticeTailItems(NoticeListItem $nli, Notice $notice, &$threadActive)
+    {
+        if ($nli instanceof ThreadedNoticeListSubItem) {
+            // The sub-items are replies to a conversation, thus we use different HTML elements etc.
+            $item = new ThreadedNoticeListInlineRepeatsItem($notice, $nli->out);
+        } else {
+            $item = new ThreadedNoticeListRepeatsItem($notice, $nli->out);
+        }
+        $threadActive = $item->show() || $threadActive;
+        return true;
+    }
+
+    /**
+     * show the "repeat" form in the notice options element
+     * FIXME: Don't let a NoticeListItemAdapter slip in here (or extend that from NoticeListItem)
+     *
+     * @return void
+     */
+    public function onEndShowNoticeOptionItems($nli)
+    {
+        // FIXME: Use bitmasks (but be aware that PUBLIC_SCOPE is 0!)
+        if ($nli->notice->scope == Notice::PUBLIC_SCOPE ||
+                $nli->notice->scope == Notice::SITE_SCOPE) {
+            $scoped = Profile::current();
+            if ($scoped instanceof Profile &&
+                    $scoped->getID() !== $nli->notice->getProfile()->getID()) {
+
+                if ($scoped->hasRepeated($nli->notice)) {
+                    $nli->out->element('span', array('class' => 'repeated',
+                                                      // TRANS: Title for repeat form status in notice list when a notice has been repeated.
+                                                      'title' => _('Notice repeated.')),
+                                        // TRANS: Repeat form status in notice list when a notice has been repeated.
+                                        _('Repeated'));
+                } else {
+                    $repeat = new RepeatForm($nli->out, $nli->notice);
+                    $repeat->show();
+                }
+            }
+        }
+    }
+
+    public function showNoticeListItem(NoticeListItem $nli)
+    {
+        // pass
+    }
+    public function openNoticeListItemElement(NoticeListItem $nli)
+    {
+        // pass
+    }
+    public function closeNoticeListItemElement(NoticeListItem $nli)
+    {
+        // pass
+    }
+
+    // API stuff
+
+    /**
+     * Typically just used to fill out Twitter-compatible API status data.
+     *
+     * FIXME: Make all the calls before this end up with a Notice instead of ArrayWrapper please...
+     */
+    public function onNoticeSimpleStatusArray($notice, array &$status, Profile $scoped=null, array $args=array())
+    {
+        $status['repeated'] = $scoped instanceof Profile
+                            ? $scoped->hasRepeated($notice)
+                            : false;
+
+        if ($status['repeated'] === true) {
+            // Qvitter API wants the "repeated_id" value set too.
+            $repeated = Notice::pkeyGet(array('profile_id' => $scoped->getID(),
+                                              'repeat_of' => $notice->getID()));
+            $status['repeated_id'] = $repeated->getID();
+        }
+    }
+
+    public function onTwitterUserArray(Profile $profile, array &$userdata, Profile $scoped=null, array $args=array())
+    {
+        $userdata['favourites_count'] = Fave::countByProfile($profile);
+    }
+
+    // Command stuff
+
+    /**
+     * EndInterpretCommand for RepeatPlugin will handle the 'repeat' command
+     * using the class RepeatCommand.
+     *
+     * @param string  $cmd     Command being run
+     * @param string  $arg     Rest of the message (including address)
+     * @param User    $user    User sending the message
+     * @param Command &$result The resulting command object to be run.
+     *
+     * @return boolean hook value
+     */
+    public function onStartInterpretCommand($cmd, $arg, $user, &$result)
+    {
+        if ($result === false && in_array($cmd, array('repeat', 'rp', 'rt', 'rd'))) {
+            if (empty($arg)) {
+                $result = null;
+            } else {
+                list($other, $extra) = CommandInterpreter::split_arg($arg);
+                if (!empty($extra)) {
+                    $result = null;
+                } else {
+                    $result = new RepeatCommand($user, $other);
+                }
+            }
+            return false;
+        }
+        return true;
+    }
+
+    public function onHelpCommandMessages(HelpCommand $help, array &$commands)
+    {
+        // TRANS: Help message for IM/SMS command "repeat #<notice_id>".
+        $commands['repeat #<notice_id>'] = _m('COMMANDHELP', "repeat a notice with a given id");
+        // TRANS: Help message for IM/SMS command "repeat <nickname>".
+        $commands['repeat <nickname>']   = _m('COMMANDHELP', "repeat the last notice from user");
+    }
+
+    /**
+     * Are we allowed to perform a certain command over the API?
+     */
+    public function onCommandSupportedAPI(Command $cmd, &$supported)
+    {
+        $supported = $supported || $cmd instanceof RepeatCommand;
+    }
+
+    protected function getActionTitle(ManagedAction $action, $verb, Notice $target, Profile $scoped)
+    {
+        // return page title
+    }
+
+    protected function doActionPreparation(ManagedAction $action, $verb, Notice $target, Profile $scoped)
+    {
+        // prepare Action?
+    }
+
+    protected function doActionPost(ManagedAction $action, $verb, Notice $target, Profile $scoped)
+    {
+        // handle repeat POST
+    }
+
+    protected function getActivityForm(ManagedAction $action, $verb, Notice $target, Profile $scoped)
+    {
+        return new RepeatForm($action, $target);
+    }
+
+    public function onPluginVersion(array &$versions)
+    {
+        $versions[] = array('name' => 'Share verb',
+                            'version' => GNUSOCIAL_VERSION,
+                            'author' => 'Mikael Nordfeldth',
+                            'homepage' => 'https://gnu.io/',
+                            'rawdescription' =>
+                            // TRANS: Plugin description.
+                            _m('Shares (repeats) using ActivityStreams.'));
+
+        return true;
+    }
+}
diff --git a/plugins/Share/actions/apistatusesretweet.php b/plugins/Share/actions/apistatusesretweet.php
new file mode 100644 (file)
index 0000000..922d395
--- /dev/null
@@ -0,0 +1,103 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Repeat a notice through the API
+ *
+ * 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    Evan Prodromou <evan@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @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('GNUSOCIAL')) { exit(1); }
+
+/**
+ * Repeat a notice through the API
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@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 ApiStatusesRetweetAction extends ApiAuthAction
+{
+    protected $needPost = true;
+
+    var $original = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     */
+    protected function prepare(array $args=array())
+    {
+        parent::prepare($args);
+
+        $id = $this->trimmed('id');
+
+        $this->original = Notice::getKV('id', $id);
+
+        if (!$this->original instanceof Notice) {
+            // TRANS: Client error displayed trying to repeat a non-existing notice through the API.
+            $this->clientError(_('No such notice.'), 400, $this->format);
+        }
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Make a new notice for the update, save it, and show it
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+    protected function handle()
+    {
+        parent::handle();
+
+        $repeat = $this->original->repeat($this->scoped, $this->source);
+
+        $this->showNotice($repeat);
+    }
+
+    /**
+     * Show the resulting notice
+     *
+     * @return void
+     */
+    function showNotice($notice)
+    {
+        if (!empty($notice)) {
+            if ($this->format == 'xml') {
+                $this->showSingleXmlStatus($notice);
+            } elseif ($this->format == 'json') {
+                $this->show_single_json_status($notice);
+            }
+        }
+    }
+}
diff --git a/plugins/Share/actions/apistatusesretweets.php b/plugins/Share/actions/apistatusesretweets.php
new file mode 100644 (file)
index 0000000..7af4cd3
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show up to 100 repeats of a notice
+ *
+ * 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    Evan Prodromou <evan@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @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);
+}
+
+/**
+ * Show up to 100 repeats of a notice
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@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 ApiStatusesRetweetsAction extends ApiAuthAction
+{
+    const MAXCOUNT = 100;
+
+    var $original = null;
+    var $cnt      = self::MAXCOUNT;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     */
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $id = $this->trimmed('id');
+
+        $this->original = Notice::getKV('id', $id);
+
+        if (empty($this->original)) {
+            // TRANS: Client error displayed trying to display redents of a non-exiting notice.
+            $this->clientError(_('No such notice.'),
+                               400, $this->format);
+            return false;
+        }
+
+        $cnt = $this->trimmed('count');
+
+        if (empty($cnt) || !is_integer($cnt)) {
+            $cnt = 100;
+        } else {
+            $this->cnt = min((int)$cnt, self::MAXCOUNT);
+        }
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Make a new notice for the update, save it, and show it
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+    function handle($args)
+    {
+        parent::handle($args);
+
+        $strm = $this->original->repeatStream($this->cnt);
+
+        switch ($this->format) {
+        case 'xml':
+            $this->showXmlTimeline($strm);
+            break;
+        case 'json':
+            $this->showJsonTimeline($strm);
+            break;
+        default:
+            // TRANS: Client error displayed when coming across a non-supported API method.
+            $this->clientError(_('API method not found.'), $code = 404);
+            break;
+        }
+    }
+
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+}
diff --git a/plugins/Share/actions/apitimelineretweetedbyme.php b/plugins/Share/actions/apitimelineretweetedbyme.php
new file mode 100644 (file)
index 0000000..01f3fe3
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show authenticating user's most recent repeats
+ *
+ * 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    Evan Prodromou <evan@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @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);
+}
+
+/**
+ * Show authenticating user's most recent repeats
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@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 ApiTimelineRetweetedByMeAction extends ApiAuthAction
+{
+    const DEFAULTCOUNT = 20;
+    const MAXCOUNT     = 200;
+    const MAXNOTICES   = 3200;
+
+    var $repeats  = null;
+    var $cnt      = self::DEFAULTCOUNT;
+    var $page     = 1;
+    var $since_id = null;
+    var $max_id   = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        // TRANS: Server error displayed calling unimplemented API method for 'retweeted by me'.
+        $this->serverError(_('Unimplemented.'), 503);
+
+        return false;
+    }
+
+    /**
+     * Return true if read only.
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+    function isReadOnly($args)
+    {
+        return true;
+    }
+}
diff --git a/plugins/Share/actions/apitimelineretweetedtome.php b/plugins/Share/actions/apitimelineretweetedtome.php
new file mode 100644 (file)
index 0000000..92d4b35
--- /dev/null
@@ -0,0 +1,162 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show most recent notices that are repeats in user's inbox
+ *
+ * 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    Evan Prodromou <evan@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @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('GNUSOCIAL')) { exit(1); }
+
+/**
+ * Show most recent notices that are repeats in user's inbox
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@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 ApiTimelineRetweetedToMeAction extends ApiAuthAction
+{
+    const DEFAULTCOUNT = 20;
+    const MAXCOUNT     = 200;
+    const MAXNOTICES   = 3200;
+
+    var $repeats  = null;
+    var $cnt      = self::DEFAULTCOUNT;
+    var $page     = 1;
+    var $since_id = null;
+    var $max_id   = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     */
+    protected function prepare(array $args=array())
+    {
+        parent::prepare($args);
+
+        $cnt = $this->int('count', self::DEFAULTCOUNT, self::MAXCOUNT, 1);
+
+        $page = $this->int('page', 1, (self::MAXNOTICES/$this->cnt));
+
+        $since_id = $this->int('since_id');
+
+        $max_id = $this->int('max_id');
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * show a timeline of the user's repeated notices
+     *
+     * @return void
+     */
+    protected function handle()
+    {
+        parent::handle();
+
+        $offset = ($this->page-1) * $this->cnt;
+        $limit  = $this->cnt;
+
+        // TRANS: Title for Atom feed "repeated to me". %s is the user nickname.
+        $title      = sprintf(_("Repeated to %s"), $this->scoped->getNickname());
+        $subtitle   = sprintf(
+            // @todo FIXME: $profile is not defined.
+            // TRANS: Subtitle for API action that shows most recent notices that are repeats in user's inbox.
+            // TRANS: %1$s is the sitename, %2$s is a user nickname, %3$s is a user profile name.
+            _('%1$s notices that were to repeated to %2$s / %3$s.'),
+            $sitename, $this->scoped->getNickname(), $profile->getBestName()
+        );
+        $taguribase = TagURI::base();
+        $id         = "tag:$taguribase:RepeatedToMe:" . $this->scoped->id;
+
+        $link = common_local_url(
+            'all',
+             array('nickname' => $this->scoped->getNickname())
+        );
+
+        $strm = $this->scoped->repeatedToMe($offset, $limit, $this->since_id, $this->max_id);
+
+        switch ($this->format) {
+        case 'xml':
+            $this->showXmlTimeline($strm);
+            break;
+        case 'json':
+            $this->showJsonTimeline($strm);
+            break;
+        case 'atom':
+            header('Content-Type: application/atom+xml; charset=utf-8');
+
+            $atom = new AtomNoticeFeed($this->scoped->getUser());
+
+            $atom->setId($id);
+            $atom->setTitle($title);
+            $atom->setSubtitle($subtitle);
+            $atom->setUpdated('now');
+            $atom->addLink($link);
+
+            $id = $this->arg('id');
+
+            $atom->setSelfLink($self);
+            $atom->addEntryFromNotices($strm);
+
+            $this->raw($atom->getString());
+
+            break;
+        case 'as':
+            header('Content-Type: ' . ActivityStreamJSONDocument::CONTENT_TYPE);
+            $doc = new ActivityStreamJSONDocument($this->scoped->getUser());
+            $doc->setTitle($title);
+            $doc->addLink($link, 'alternate', 'text/html');
+            $doc->addItemsFromNotices($strm);
+            $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);
+            break;
+        }
+    }
+
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+    function isReadOnly($args)
+    {
+        return true;
+    }
+}
diff --git a/plugins/Share/actions/apitimelineretweetsofme.php b/plugins/Share/actions/apitimelineretweetsofme.php
new file mode 100644 (file)
index 0000000..fe90213
--- /dev/null
@@ -0,0 +1,172 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show authenticating user's most recent notices that have been repeated
+ *
+ * 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    Evan Prodromou <evan@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @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);
+}
+
+/**
+ * Show authenticating user's most recent notices that have been repeated
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@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 ApiTimelineRetweetsOfMeAction extends ApiAuthAction
+{
+    const DEFAULTCOUNT = 20;
+    const MAXCOUNT     = 200;
+    const MAXNOTICES   = 3200;
+
+    var $repeats  = null;
+    var $cnt      = self::DEFAULTCOUNT;
+    var $page     = 1;
+    var $since_id = null;
+    var $max_id   = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     */
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $cnt = $this->int('count', self::DEFAULTCOUNT, self::MAXCOUNT, 1);
+
+        $page = $this->int('page', 1, (self::MAXNOTICES/$this->cnt));
+
+        $since_id = $this->int('since_id');
+
+        $max_id = $this->int('max_id');
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * show a timeline of the user's repeated notices
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+    function handle($args)
+    {
+        parent::handle($args);
+
+        $offset = ($this->page-1) * $this->cnt;
+        $limit  = $this->cnt;
+
+        // TRANS: Title of list of repeated notices of the logged in user.
+        // TRANS: %s is the nickname of the logged in user.
+        $title      = sprintf(_("Repeats of %s"), $this->auth_user->nickname);
+        $sitename   = common_config('site', 'name');
+
+        $profile = $this->auth_user->getProfile();
+
+        $subtitle   = sprintf(
+            // TRANS: Subtitle of API time with retweets of me.
+            // TRANS: %1$s is the StatusNet sitename, %2$s is the user nickname, %3$s is the user profile name.
+            _('%1$s notices that %2$s / %3$s has repeated.'),
+            $sitename, $this->auth_user->nickname, $profile->getBestName()
+        );
+
+        $taguribase = TagURI::base();
+        $id         = "tag:$taguribase:RepeatsOfMe:" . $this->auth_user->id;
+
+        $link = common_local_url(
+            'all',
+             array('nickname' => $this->auth_user->nickname)
+        );
+
+        // This is a really bad query for some reason
+
+        if (!common_config('performance', 'high')) {
+            $strm = $this->auth_user->repeatsOfMe($offset, $limit, $this->since_id, $this->max_id);
+        } else {
+            $strm = new Notice();
+            $strm->whereAdd('0 = 1');
+            $strm->find();
+        }
+
+        switch ($this->format) {
+        case 'xml':
+            $this->showXmlTimeline($strm);
+            break;
+        case 'json':
+            $this->showJsonTimeline($strm);
+            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->setUpdated('now');
+            $atom->addLink($link);
+            $atom->setSelfLink($this->getSelfUri());
+            $atom->addEntryFromNotices($strm);
+            $this->raw($atom->getString());
+            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($strm);
+            $this->raw($doc->asString());
+            break;
+        default:
+            // TRANS: Client error displayed when coming across a non-supported API method.
+            $this->clientError(_('API method not found.'), 404);
+            break;
+        }
+    }
+
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+    function isReadOnly($args)
+    {
+        return true;
+    }
+}
diff --git a/plugins/Share/actions/repeat.php b/plugins/Share/actions/repeat.php
new file mode 100644 (file)
index 0000000..49132e9
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+/**
+ * Repeat action.
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @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) 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('GNUSOCIAL')) { exit(1); }
+
+/**
+ * Repeat action
+ *
+ * @category Action
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://status.net/
+ */
+class RepeatAction extends FormAction
+{
+    protected $notice = null;   // Notice that is being repeated.
+    protected $repeat = null;   // The resulting repeat object/notice.
+
+    function title()
+    {
+        return _m('TITLE', 'Repeat notice');
+    }
+
+    protected function doPreparation()
+    {
+        $id = $this->trimmed('notice');
+
+        if (empty($id)) {
+            // TRANS: Client error displayed when trying to repeat a notice while not providing a notice ID.
+            $this->clientError(_('No notice specified.'));
+        }
+
+        $this->notice = Notice::getKV('id', $id);
+
+        if (!$this->notice instanceof Notice) {
+            // TRANS: Client error displayed when trying to repeat a non-existing notice.
+            $this->clientError(_('Notice not found.'));
+        }
+
+        $this->repeat = $this->notice->repeat($this->scoped, 'web');
+        if (!$this->repeat instanceof Notice) {
+            // TRANS: Error when unable to repeat a notice for unknown reason.
+            $this->clientError(_('Could not repeat notice for unknown reason. Please contact the webmaster!'));
+        }
+
+        return true;
+    }
+
+    /**
+     * Class handler.
+     *
+     * @param array $args query arguments
+     *
+     * @return void
+     */
+    protected function showContent()
+    {
+        $this->element('p', array('id' => 'repeat_response',
+                                  'class' => 'repeated'),
+                            // TRANS: Confirmation text after repeating a notice.
+                            _('Repeated!'));
+    }
+}
diff --git a/plugins/Share/forms/repeat.php b/plugins/Share/forms/repeat.php
new file mode 100644 (file)
index 0000000..f0ce37f
--- /dev/null
@@ -0,0 +1,129 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Form for repeating a notice
+ *
+ * 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  Form
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @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);
+}
+
+/**
+ * Form for repeating a notice
+ *
+ * @category Form
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@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 RepeatForm extends Form
+{
+    /**
+     * Notice to repeat
+     */
+    var $notice = null;
+
+    /**
+     * Constructor
+     *
+     * @param HTMLOutputter $out    output channel
+     * @param Notice        $notice notice to repeat
+     */
+    function __construct($out=null, $notice=null)
+    {
+        parent::__construct($out);
+
+        $this->notice = $notice;
+    }
+
+    /**
+     * ID of the form
+     *
+     * @return int ID of the form
+     */
+    function id()
+    {
+        return 'repeat-' . $this->notice->id;
+    }
+
+    /**
+     * Action of the form
+     *
+     * @return string URL of the action
+     */
+    function action()
+    {
+        return common_local_url('repeat');
+    }
+
+    /**
+     * Legend of the Form
+     *
+     * @return void
+     */
+    function formLegend()
+    {
+        // TRANS: For legend for notice repeat form.
+        $this->out->element('legend', null, _('Repeat this notice?'));
+    }
+
+    /**
+     * Data elements
+     *
+     * @return void
+     */
+    function formData()
+    {
+        $this->out->hidden('notice-n'.$this->notice->id,
+                           $this->notice->id,
+                           'notice');
+    }
+
+    /**
+     * Action elements
+     *
+     * @return void
+     */
+    function formActions()
+    {
+        $this->out->submit('repeat-submit-' . $this->notice->id,
+                           // TRANS: Button text to repeat a notice on notice repeat form.
+                           _m('BUTTON','Yes'), 'submit', null,
+                           // TRANS: Button title to repeat a notice on notice repeat form.
+                           _('Repeat this notice.'));
+    }
+
+    /**
+     * Class of the form.
+     *
+     * @return string the form's class
+     */
+    function formClass()
+    {
+        return 'form_repeat';
+    }
+}
diff --git a/plugins/Share/lib/repeatcommand.php b/plugins/Share/lib/repeatcommand.php
new file mode 100644 (file)
index 0000000..6d2dfb1
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class RepeatCommand extends Command 
+{ 
+    var $other = null; 
+    function __construct($user, $other) 
+    { 
+        parent::__construct($user); 
+        $this->other = $other; 
+    } 
+    function handle($channel) 
+    { 
+        $notice = $this->getNotice($this->other); 
+        try {
+            $repeat = $notice->repeat($this->scoped->id, $channel->source());
+            $recipient = $notice->getProfile();
+
+            // TRANS: Message given having repeated a notice from another user.
+            // TRANS: %s is the name of the user for which the notice was repeated.
+            $channel->output($this->user, sprintf(_('Notice from %s repeated.'), $recipient->nickname));
+        } catch (Exception $e) {
+            $channel->error($this->user, $e->getMessage());
+        }
+    } 
+}
diff --git a/plugins/Share/lib/repeatedbymenoticestream.php b/plugins/Share/lib/repeatedbymenoticestream.php
new file mode 100644 (file)
index 0000000..4e3e341
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Stream of notices repeated by me
+ * 
+ * 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  Stream
+ * @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);
+}
+
+/**
+ * Stream of notices repeated by me
+ *
+ * @category  General
+ * @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 RepeatedByMeNoticeStream extends ScopingNoticeStream
+{
+    function __construct($user, $profile = -1)
+    {
+        if (is_int($profile) && $profile == -1) {
+            $profile = Profile::current();
+        }
+        parent::__construct(new CachingNoticeStream(new RawRepeatedByMeNoticeStream($user),
+                                                    'user:repeated_by_me:'.$user->id),
+                            $profile);
+    }
+}
+
+/**
+ * Raw stream of notices repeated by me
+ *
+ * @category  General
+ * @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 RawRepeatedByMeNoticeStream extends NoticeStream
+{
+    protected $user;
+
+    function __construct($user)
+    {
+        $this->user = $user;
+    }
+
+    function getNoticeIds($offset, $limit, $since_id, $max_id)
+    {
+        $notice = new Notice();
+
+        $notice->selectAdd(); // clears it
+        $notice->selectAdd('id');
+
+        $notice->profile_id = $this->user->id;
+        $notice->whereAdd('repeat_of IS NOT NULL');
+
+        $notice->orderBy('created DESC, id DESC');
+
+        if (!is_null($offset)) {
+            $notice->limit($offset, $limit);
+        }
+
+        Notice::addWhereSinceId($notice, $since_id);
+        Notice::addWhereMaxId($notice, $max_id);
+
+        $ids = array();
+
+        if ($notice->find()) {
+            while ($notice->fetch()) {
+                $ids[] = $notice->id;
+            }
+        }
+
+        $notice->free();
+        $notice = NULL;
+
+        return $ids;
+    }
+}
\ No newline at end of file
diff --git a/plugins/Share/lib/repeatsofmenoticestream.php b/plugins/Share/lib/repeatsofmenoticestream.php
new file mode 100644 (file)
index 0000000..ec80d84
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Stream of notices that are repeats of mine
+ * 
+ * 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  Stream
+ * @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);
+}
+
+/**
+ * Stream of notices that are repeats of mine
+ *
+ * @category  Stream
+ * @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 RepeatsOfMeNoticeStream extends ScopingNoticeStream
+{
+    function __construct($user, $profile=-1)
+    {
+        if (is_int($profile) && $profile == -1) {
+            $profile = Profile::current();
+        }
+        parent::__construct(new CachingNoticeStream(new RawRepeatsOfMeNoticeStream($user),
+                                                    'user:repeats_of_me:'.$user->id),
+                            $profile);
+    }
+}
+
+/**
+ * Raw stream of notices that are repeats of mine
+ *
+ * @category  Stream
+ * @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 RawRepeatsOfMeNoticeStream extends NoticeStream
+{
+    protected $user;
+
+    function __construct($user)
+    {
+        $this->user = $user;
+    }
+
+    function getNoticeIds($offset, $limit, $since_id, $max_id)
+    {
+        $qry =
+          'SELECT DISTINCT original.id AS id ' .
+          'FROM notice original JOIN notice rept ON original.id = rept.repeat_of ' .
+          'WHERE original.profile_id = ' . $this->user->id . ' ';
+
+        $since = Notice::whereSinceId($since_id, 'original.id', 'original.created');
+        if ($since) {
+            $qry .= "AND ($since) ";
+        }
+
+        $max = Notice::whereMaxId($max_id, 'original.id', 'original.created');
+        if ($max) {
+            $qry .= "AND ($max) ";
+        }
+
+        $qry .= 'ORDER BY original.created, original.id DESC ';
+
+        if (!is_null($offset)) {
+            $qry .= "LIMIT $limit OFFSET $offset";
+        }
+
+        $ids = array();
+
+        $notice = new Notice();
+
+        $notice->query($qry);
+
+        while ($notice->fetch()) {
+            $ids[] = $notice->id;
+        }
+
+        $notice->free();
+        $notice = NULL;
+
+        return $ids;
+    }
+}
diff --git a/plugins/Share/lib/threadednoticelistinlinerepeatsitem.php b/plugins/Share/lib/threadednoticelistinlinerepeatsitem.php
new file mode 100644 (file)
index 0000000..a4bcc66
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class ThreadedNoticeListInlineRepeatsItem extends ThreadedNoticeListRepeatsItem
+{
+    function showStart()
+    {
+        $this->out->elementStart('div', array('class' => 'notice-repeats'));
+    }
+
+    function showEnd()
+    {
+        $this->out->elementEnd('div');
+    }
+}
diff --git a/plugins/Share/lib/threadednoticelistrepeatsitem.php b/plugins/Share/lib/threadednoticelistrepeatsitem.php
new file mode 100644 (file)
index 0000000..9117a7a
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+/**
+ * Placeholder for showing repeats...
+ */
+class ThreadedNoticeListRepeatsItem extends NoticeListActorsItem
+{
+    function getProfiles()
+    {
+        $repeats = Notice::listGet('repeat_of', array($this->notice->getID()));
+
+        $profiles = array();
+        foreach ($repeats[$this->notice->getID()] as $rep) {
+            $profiles[] = $rep->profile_id;
+        }
+
+        return $profiles;
+    }
+
+    function magicList($items)
+    {
+        if (count($items) > 4) {
+            return parent::magicList(array_slice($items, 0, 3));
+        } else {
+            return parent::magicList($items);
+        }
+    }
+
+    function getListMessage($count, $you)
+    {
+        if ($count == 1 && $you) {
+            // darn first person being different from third person!
+            // TRANS: List message for notice repeated by logged in user.
+            return _m('REPEATLIST', 'You repeated this.');
+        } else if ($count > 4) {
+            // TRANS: List message for when more than 4 people repeat something.
+            // TRANS: %%s is a list of users liking a notice, %d is the number over 4 that like the notice.
+            // TRANS: Plural is decided on the total number of users liking the notice (count of %%s + %d).
+            return sprintf(_m('%%s and %d other repeated this.',
+                              '%%s and %d others repeated this.',
+                              $count - 3),
+                           $count - 3);
+        } else {
+            // TRANS: List message for repeated notices.
+            // TRANS: %%s is a list of users who have repeated a notice.
+            // TRANS: Plural is based on the number of of users that have repeated a notice.
+            return sprintf(_m('%%s repeated this.',
+                              '%%s repeated this.',
+                              $count),
+                           $count);
+        }
+    }
+
+    function showStart()
+    {
+        $this->out->elementStart('li', array('class' => 'notice-data notice-repeats'));
+    }
+
+    function showEnd()
+    {
+        $this->out->elementEnd('li');
+    }
+}
index 37c37edaf27d5e61e059ce88b04ea42549c5c9ce..b713ec7c6f815ae20f95a51619120c33285074c0 100644 (file)
@@ -60,6 +60,11 @@ class SiteNoticeSection extends Section
         return _('Site notice');
     }
 
+    function divId()
+    {
+        return 'site_notice';
+    }
+
     function showContent()
     {
         $this->out->raw($this->text);
index 6f45da6a707cbbbe8efbafad2736ed94f311c0ab..b058cc3b6530886f50a070c1ef07a36bbe66daaf 100644 (file)
@@ -188,9 +188,7 @@ class WikiHowProfilePlugin extends Plugin
 
             $profile = $user->getProfile();
             $id = $profile->id;
-            // @fixme should we be using different ids?
-
-            $imagefile = new ImageFile($id, $temp_filename);
+            $imagefile = new ImageFile(null, $temp_filename);
             $filename = Avatar::filename($id,
                                          image_type_to_extension($imagefile->type),
                                          null,
index 89b61a34aba87ef83eca47a028dcc012337183c1..392166a98088dae0ca2f563b0a1709b68ccc7e9b 100644 (file)
@@ -29,6 +29,8 @@ if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
 define('GNUSOCIAL', true);
 define('STATUSNET', true); //compatibility
 
+define('GNUSOCIAL_CLI', true);  // to know we're in a CLI environment
+
 // Set various flags so we don't time out on long-running processes
 
 ini_set("max_execution_time", "0");
diff --git a/scripts/deleteprofile.php b/scripts/deleteprofile.php
new file mode 100755 (executable)
index 0000000..8eed6ff
--- /dev/null
@@ -0,0 +1,78 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - a 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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+
+$shortoptions = 'i::n::u::y';
+$longoptions = array('id=', 'nickname=', 'uri=', 'yes');
+
+$helptext = <<<END_OF_DELETEUSER_HELP
+deleteprofile.php [options]
+deletes a profile from the database
+
+  -i --id       ID of the profile
+  -n --nickname nickname of a local user
+  -u --uri      OStatus profile URI (only remote users, requires OStatus plugin)
+  -y --yes      do not wait for confirmation
+
+END_OF_DELETEUSER_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+if (have_option('i', 'id')) {
+    $id = get_option_value('i', 'id');
+    $profile = Profile::getKV('id', $id);
+    if (!$profile instanceof Profile) {
+        print "Can't find profile with ID $id\n";
+        exit(1);
+    }
+} else if (have_option('n', 'nickname')) {
+    $nickname = get_option_value('n', 'nickname');
+    $user = User::getKV('nickname', $nickname);
+    if (!$user instanceof User) {
+        print "Can't find user with nickname '$nickname'\n";
+        exit(1);
+    }
+    $profile = $user->getProfile();
+} else if (have_option('u', 'uri')) {
+    $uri = get_option_value('u', 'uri');
+    $oprofile = Ostatus_profile::getKV('uri', $uri);
+    if (!$oprofile instanceof Ostatus_profile) {
+        print "Can't find profile with URI '$uri'\n";
+        exit(1);
+    }
+    $profile = $oprofile->localProfile();
+} else {
+    print "You must provide either an ID, a URI or a nickname.\n";
+    exit(1);
+}
+
+if (!have_option('y', 'yes')) {
+    print "About to PERMANENTLY delete profile '".$profile->getNickname()."' ({$profile->id}). Are you sure? [y/N] ";
+    $response = fgets(STDIN);
+    if (strtolower(trim($response)) != 'y') {
+        print "Aborting.\n";
+        exit(0);
+    }
+}
+
+print "Deleting...";
+$profile->delete();
+print "DONE.\n";
diff --git a/scripts/deleteuser.php b/scripts/deleteuser.php
deleted file mode 100755 (executable)
index 25af1c5..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/usr/bin/env php
-<?php
-/*
- * StatusNet - a 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/>.
- */
-
-define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
-
-$shortoptions = 'i::n::y';
-$longoptions = array('id=', 'nickname=', 'yes');
-
-$helptext = <<<END_OF_DELETEUSER_HELP
-deleteuser.php [options]
-deletes a user from the database
-
-  -i --id       ID of the user
-  -n --nickname nickname of the user
-  -y --yes      do not wait for confirmation
-
-END_OF_DELETEUSER_HELP;
-
-require_once INSTALLDIR.'/scripts/commandline.inc';
-
-if (have_option('i', 'id')) {
-    $id = get_option_value('i', 'id');
-    $user = User::getKV('id', $id);
-    if (empty($user)) {
-        print "Can't find user with ID $id\n";
-        exit(1);
-    }
-} else if (have_option('n', 'nickname')) {
-    $nickname = get_option_value('n', 'nickname');
-    $user = User::getKV('nickname', $nickname);
-    if (empty($user)) {
-        print "Can't find user with nickname '$nickname'\n";
-        exit(1);
-    }
-} else {
-    print "You must provide either an ID or a nickname.\n";
-    exit(1);
-}
-
-if (!have_option('y', 'yes')) {
-    print "About to PERMANENTLY delete user '{$user->nickname}' ({$user->id}). Are you sure? [y/N] ";
-    $response = fgets(STDIN);
-    if (strtolower(trim($response)) != 'y') {
-        print "Aborting.\n";
-        exit(0);
-    }
-}
-
-print "Deleting...";
-$user->delete();
-print "DONE.\n";
diff --git a/scripts/remove_duplicate_file_urls.php b/scripts/remove_duplicate_file_urls.php
new file mode 100755 (executable)
index 0000000..a4a7cd7
--- /dev/null
@@ -0,0 +1,86 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - a 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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+
+$shortoptions = 'y';
+$longoptions = array('yes');
+
+$helptext = <<<END_OF_HELP
+remove_duplicate_file_urls.php [options]
+Remove duplicate URL entries in the file and file_redirection tables because they for some reason were not unique.
+
+  -y --yes      do not wait for confirmation
+
+END_OF_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+if (!have_option('y', 'yes')) {
+    print "About to remove duplicate URL entries in file and file_redirection tables. Are you sure? [y/N] ";
+    $response = fgets(STDIN);
+    if (strtolower(trim($response)) != 'y') {
+        print "Aborting.\n";
+        exit(0);
+    }
+}
+
+$file = new File();
+$file->query('SELECT id, url, COUNT(*) AS c FROM file GROUP BY url HAVING c > 1');
+print "\nFound {$file->N} URLs with duplicate entries in file table";
+while ($file->fetch()) {
+    // We've got a URL that is duplicated in the file table
+    $dupfile = new File();
+    $dupfile->url = $file->url;
+    if ($dupfile->find(true)) {
+        print "\nDeleting duplicate entries in file table for URL: {$file->url} [";
+        // Leave one of the URLs in the database by using ->find(true)
+        // and only deleting starting with this fetch.
+        while($dupfile->fetch()) {
+            print ".";
+            $dupfile->delete();
+        }
+        print "]\n";
+    } else {
+        print "\nWarning! URL suddenly disappeared from database: {$file->url}\n";
+    }
+}
+
+$file = new File_redirection();
+$file->query('SELECT file_id, url, COUNT(*) AS c FROM file_redirection GROUP BY url HAVING c > 1');
+print "\nFound {$file->N} URLs with duplicate entries in file_redirection table";
+while ($file->fetch()) {
+    // We've got a URL that is duplicated in the file_redirection table
+    $dupfile = new File_redirection();
+    $dupfile->url = $file->url;
+    if ($dupfile->find(true)) {
+        print "\nDeleting duplicate entries in file table for URL: {$file->url} [";
+        // Leave one of the URLs in the database by using ->find(true)
+        // and only deleting starting with this fetch.
+        while($dupfile->fetch()) {
+            print ".";
+            $dupfile->delete();
+        }
+        print "]\n";
+    } else {
+        print "\nWarning! URL suddenly disappeared from database: {$file->url}\n";
+    }
+}
+print "\nDONE.\n";
index 692eaac17a40b8d083b4b1ff8e4be6ecd6932780..126ef290360b27a7d8b0abfcf3963816555b18ef 100644 (file)
@@ -520,7 +520,7 @@ function setFilehashOnLocalFiles()
                 $file->filehash = hash_file(File::FILEHASH_ALG, $file->getPath());
                 $file->update($orig);
             } catch (FileNotFoundException $e) {
-                echo "\n    WARNING: file ID {$file->id} does not exist on path '{$e->path}'. Clean up the file table?";
+                echo "\n    WARNING: file ID {$file->id} does not exist on path '{$e->path}'. If there is no file system error, run: php scripts/clean_file_table.php";
             }
         }
     }
index 7aee99a31b118c718a28124a4a26aad295fc4789..7c0e01c96d2a0ca1b8147043dfecac0bd50d35fa 100644 (file)
@@ -630,7 +630,14 @@ address .poweredby {
     line-height: 1.36em;
 }
 
-.notice, .profile, .application, #content .peopletag {
+.profile, .application, #content .peopletag {
+    position:relative;
+    clear:both;
+    float:left;
+    width:100%;
+}
+
+.application, #content .peopletag {
     position:relative;
     clear:both;
     float:left;
@@ -723,10 +730,8 @@ font-style:italic;
 .notice-options {
     margin-bottom: 7px;
     margin-top: 12px;
-}
-
-.notice-options {
     float: right;    
+    position: relative;
 }
 
 .notice-options fieldset {
@@ -1172,6 +1177,7 @@ cursor:pointer;
 
 .dialogbox {
 position:absolute;
+    width: 200px;
     top: 0px;
     right: 0px;
 z-index:9;
@@ -1212,6 +1218,11 @@ padding-left:4px;
 margin-bottom:0;
 }
 
+.notice-options .form_repeat.dialogbox input.submit_dialogbox {
+    float: right;
+    min-width: 80px;
+}
+
 #pagination {
     background-color: #f2f2f2;
     clear: left;
index ea5309b3265ed17a320f51926222d99030f680b5..39cce89e482edb178fd01b8aeca1c89de754c53a 100644 (file)
@@ -477,10 +477,6 @@ div.entry-content a.response:after {
     padding-top: 10px;
 }
 
-.notice-options .form_repeat.dialogbox input.submit_dialogbox {
-    min-width: 80px;
-}
-
 #content .threaded-replies .notice .author .photo {
     box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
     -moz-box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
index bdaea98e52a7f8c7fc77d314b429cdc99b3f5b52..10ac168666e5bb4ea402ac38fdd9ac36730c8a3a 100644 (file)
@@ -1291,10 +1291,6 @@ div.entry-content a.response:after {
     padding-top: 10px;
 }
 
-.notice-options .form_repeat.dialogbox input.submit_dialogbox {
-    min-width: 80px;
-}
-
 #content .threaded-replies .notice .author .photo {
     box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
     -moz-box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
index 69d9eded49dcf4f2389abe84a5d4d063b57d81ae..601845164b5d532d3465492fb116c63de102bac9 100644 (file)
@@ -441,10 +441,6 @@ abbr {border-bottom: none;}
     padding-top: 10px;
 }
 
-.notice-options .form_repeat.dialogbox input.submit_dialogbox {
-    min-width: 80px;
-}
-
 .user_in.realtime-popup .notice div.e-content {
     max-width: 320px;
 }