Fixes that make quitagram work better amongst other things.
+++ /dev/null
-<?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);
- }
- }
- }
-}
+++ /dev/null
-<?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;
- }
-}
+++ /dev/null
-<?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;
- }
-}
+++ /dev/null
-<?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;
- }
-}
+++ /dev/null
-<?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;
- }
-}
common_redirect($e->file->getUrl());
}
- common_redirect($thumbnail->getUrl());
+ common_redirect(File_thumbnail::url($thumbnail->filename));
}
}
* @link http://status.net/
*/
-if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
-}
-
-
-
-define('MAX_ORIGINAL', 480);
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Upload an avatar
$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']);
* @link http://status.net/
*/
-if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
-}
-
-
-
-define('MAX_ORIGINAL', 480);
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Upload an avatar
$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';
*
* @return void
*/
- function getInstructions()
+ protected function getInstructions()
{
if (common_logged_in() && !common_is_real_login() &&
common_get_returnto()) {
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.');
+++ /dev/null
-<?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!'));
- }
-}
* @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
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();
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');
- }
- }
-}
// 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();
$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
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
'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'),
$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
'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'),
),
);
function delete($useWhere=false)
{
$f = File::getKV('id', $this->file_id);
- if (!empty($f)) {
+ if ($f instanceof File) {
$f->blowCache();
}
return parent::delete($useWhere);
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.
*/
$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)) {
$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)) {
$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();
}
$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();
}
}
$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);
- }
- }
}
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
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;
/**
* 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);
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);
}
+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
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).
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().
-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: Ради-Не-Ради)
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
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.
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
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:
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
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).
<?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');
$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;
}
/**
/**
* 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;
}
* 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;
}
* 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);
}
// 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);
+ }
+}
+
+?>
<?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
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;
/* 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
*/
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)
*/
$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() {
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 */
}
}
}
-
+
/**
* Returns a string from the "originals" table
- *
+ *
* @access private
* @param int num Offset number of original string
* @return string Requested string if found, otherwise ''
$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 ''
$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)
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)
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))
}
}
+ /**
+ * 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) {
} 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
$string = str_replace('nplurals',"\$total",$string);
$string = str_replace("n",$n,$string);
$string = str_replace('plural',"\$plural",$string);
-
+
$total = 0;
$plural = 0;
/**
* Plural version of gettext
- *
+ *
* @access public
* @param string single
* @param string plural
}
// 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;
}
}
+ 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;
+ }
+
+ }
}
?>
<?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;
return strlen($this->_str);
}
-}
+};
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
$bytes -= strlen($chunk);
}
$this->_pos = ftell($this->_fd);
-
+
return $data;
} else return '';
}
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) {
$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);
return false;
}
}
-}
+};
-?>
\ No newline at end of file
+?>
}
},
+ V: { // Variables
+ },
+
/**
* Map of localized message strings exported to script from the PHP
* side via Action::getScriptMessages().
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.
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();
},
/**
return false;
}
SN.U.NoticeLocationAttach(form);
+ SN.U.FormNoticeUniqueID(form);
SN.U.FormNoticeXHR(form);
SN.U.FormNoticeEnhancements(form);
SN.U.NoticeDataAttach(form);
$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'
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:
*
* 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';
}
* @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())
{
* 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)) {
}
$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).'!');
}
$nli->showNoticeSource();
$nli->showNoticeLocation();
$nli->showPermalink();
- $nli->showRepeat();
$nli->showNoticeOptions();
}
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';
// 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';
{
$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) {
$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();
$this->showGeoXML($value);
break;
case 'retweeted_status':
+ // FIXME: MOVE TO SHARE PLUGIN
$this->showTwitterXmlStatus($value, 'retweeted_status');
break;
default:
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':
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'))) {
* 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');
}
}
-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;
"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>".
// 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";
}
$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;
{
//$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);
}
/**
*
* @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);
+ }
}
}
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' =>
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
array('disabled' => true),
'plugins' =>
array('core' => array(
+ 'ActivityVerb' => array(),
'AuthCrypt' => array(),
'Cronish' => array(),
'Favorite' => array(),
+ 'Share' => array(),
'LRDD' => array(),
'StrictTransportSecurity' => array(),
),
/**
* @return string with instructions to pass into common_markup_to_html()
*/
- public function getInstructions()
+ protected function getInstructions()
{
return null;
}
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.
* @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');
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
*
// 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();
}
}
+ 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
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.
*
//list of screennames that should get all public notices
public $public = array();
+ protected $requires_cli = true;
+
/**
* normalize a screenname for comparison
*
*/
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;
}
}
// @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);
// 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));
}
}
protected $id_prefix = null;
protected $options = true;
protected $maxchars = 0; // if <= 0 it means use full posts
+ protected $item_tag = 'li';
/**
* constructor
}
}
// 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];
}
$this->showNoticeSource();
$this->showNoticeLocation();
$this->showPermalink();
- $this->showRepeat();
Event::handle('EndShowNoticeInfo', array($this));
}
}
$this->out->elementStart('div', 'notice-options');
if (Event::handle('StartShowNoticeOptionItems', array($this))) {
$this->showReplyLink();
- $this->showRepeatForm();
$this->showDeleteLink();
Event::handle('EndShowNoticeOptionItems', array($this));
}
$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));
}
}
}
- /**
- * 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
*
}
}
- /**
- * 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
*
} 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;
}
+++ /dev/null
-<?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
+++ /dev/null
-<?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';
- }
-}
+++ /dev/null
-<?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;
- }
-}
'sandbox', 'unsandbox',
'silence', 'unsilence',
'grantrole', 'revokerole',
- 'repeat',
'deleteuser',
'geocode',
'version',
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,
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',
$res = $this->conn->query("DROP TABLE $name");
if ($_PEAR->isError($res)) {
- throw new Exception($res->getMessage());
+ PEAR_ErrorToPEAR_Exception($res);
}
return true;
implode(",", $columnNames).")");
if ($_PEAR->isError($res)) {
- throw new Exception($res->getMessage());
+ PEAR_ErrorToPEAR_Exception($res);
}
return true;
$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;
$res = $this->conn->query($sql);
if ($_PEAR->isError($res)) {
- throw new Exception($res->getMessage());
+ PEAR_ErrorToPEAR_Exception($res);
}
return true;
$res = $this->conn->query($sql);
if ($_PEAR->isError($res)) {
- throw new Exception($res->getMessage());
+ PEAR_ErrorToPEAR_Exception($res);
}
return true;
$res = $this->conn->query($sql);
if ($_PEAR->isError($res)) {
- throw new Exception($res->getMessage());
+ PEAR_ErrorToPEAR_Exception($res);
}
return true;
$res = $this->conn->query($sql);
if ($_PEAR->isError($res)) {
- throw new Exception($res->getMessage());
+ PEAR_ErrorToPEAR_Exception($res);
}
}
return $ok;
$res = $this->conn->query($sql);
if ($_PEAR->isError($res)) {
- throw new Exception($res->getMessage());
+ PEAR_ErrorToPEAR_Exception($res);
}
$out = array();
{
$checksums = array();
- PEAR::pushErrorHandling(PEAR_ERROR_EXCEPTION);
try {
$sv = new Schema_version();
$sv->find();
// no dice!
common_log(LOG_DEBUG, "Possibly schema_version table doesn't exist yet.");
}
- PEAR::popErrorHandling();
return $checksums;
}
*/
protected function saveChecksum($table, $checksum)
{
- PEAR::pushErrorHandling(PEAR_ERROR_EXCEPTION);
try {
$sv = new Schema_version();
$sv->table_name = $table;
// no dice!
common_log(LOG_DEBUG, "Possibly schema_version table doesn't exist yet.");
}
- PEAR::popErrorHandling();
$this->checksums[$table] = $checksum;
}
}
* @link http://status.net/
*/
-if (!defined('GNUSOCIAL') && !defined('STATUSNET')) { exit(1); }
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* widget for displaying a list of notices
$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));
}
{
$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();
$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');
- }
-}
{
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);
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;
}
#: 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,
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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));
+ }
+ }
+}
--- /dev/null
+<?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);
+}
$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);
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();
} 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();
);
// clean up tmp file
- @unlink(Avatar::path($tmpname));
}
}
* @package Activity
* @maintainer Mikael Nordfeldth <mmn@hethane.se>
*/
-class FavoritePlugin extends ActivityHandlerPlugin
+class FavoritePlugin extends ActivityVerbHandlerPlugin
{
protected $email_notify_fave = 1;
public function verbs()
{
- return array(ActivityVerb::FAVORITE);
+ return array(ActivityVerb::FAVORITE, ActivityVerb::LIKE,
+ ActivityVerb::UNFAVORITE, ActivityVerb::UNLIKE);
}
-
+
public function onCheckSchema()
{
$schema = Schema::get();
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 {
}
}
}
-
+
printfnq("DONE.\n");
}
}
// 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));
$actobj = $act->objects[0];
$object = Fave::saveActivityObject($actobj, $stored);
+ $stored->object_type = ActivityUtils::resolveUri($object->getObjectType(), true);
+
return $object;
}
}
return true;
}
-
+
public function onNoticeDeleteRelated(Notice $notice)
{
parent::onNoticeDeleteRelated($notice);
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.
}
}
+ 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',
);
}
- // 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') {
+++ /dev/null
-<?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();
- }
-}
+++ /dev/null
-<?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();
- }
-}
* @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;
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()
{
*/
function action()
{
- return common_local_url('disfavor');
+ return common_local_url('activityverb',
+ array('id' => $this->notice->getID(),
+ 'verb' => ActivityUtils::resolveUri(ActivityVerb::UNFAVORITE, true)));
}
/**
*/
function action()
{
- return common_local_url('favor');
+ return common_local_url('activityverb',
+ array('id' => $this->notice->getID(),
+ 'verb' => ActivityUtils::resolveUri(ActivityVerb::FAVORITE, true)));
}
/**
{
$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) {
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;
- }
-
}
-
* @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')) . '";');
$action->script($this->path('infinitescroll.js'));
}
- function onPluginVersion(&$versions)
+ function onPluginVersion(array &$versions)
{
$versions[] = array('name' => 'InfiniteScroll',
'version' => GNUSOCIAL_VERSION,
* @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');
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;
+ }
}
}
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)
return false;
}
- function onStartShowUAStyles($action) {
+ public function onStartShowUAStyles(Action $action) {
if (!$this->serveMobile) {
return true;
}
return false;
}
- function onStartShowHeader($action)
+ public function onStartShowHeader(Action $action)
{
if (!$this->serveMobile) {
return true;
return false;
}
- function _showLogo($action)
+ protected function _showLogo(Action $action)
{
$action->elementStart('address');
if (common_config('singleuser', 'enabled')) {
$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('
);
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.
$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();
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");
}
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) . '">' .
- '…' .
- '</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
} 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,
'^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();
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);
}
}
case 'service':
$api = common_config('oembed', 'endpoint');
common_log(LOG_INFO, 'Using service API endpoint ' . $api);
- break 2;
break;
}
}
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.
$nli->showNoticeSource();
$nli->showNoticeLocation();
$nli->showPermalink();
- $nli->showRepeat();
$nli->showNoticeOptions();
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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);
+ }
+ }
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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!'));
+ }
+}
--- /dev/null
+<?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';
+ }
+}
--- /dev/null
+<?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());
+ }
+ }
+}
--- /dev/null
+<?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
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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');
+ }
+}
--- /dev/null
+<?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');
+ }
+}
return _('Site notice');
}
+ function divId()
+ {
+ return 'site_notice';
+ }
+
function showContent()
{
$this->out->raw($this->text);
$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,
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");
--- /dev/null
+#!/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";
+++ /dev/null
-#!/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";
--- /dev/null
+#!/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";
$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";
}
}
}
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;
.notice-options {
margin-bottom: 7px;
margin-top: 12px;
-}
-
-.notice-options {
float: right;
+ position: relative;
}
.notice-options fieldset {
.dialogbox {
position:absolute;
+ width: 200px;
top: 0px;
right: 0px;
z-index:9;
margin-bottom:0;
}
+.notice-options .form_repeat.dialogbox input.submit_dialogbox {
+ float: right;
+ min-width: 80px;
+}
+
#pagination {
background-color: #f2f2f2;
clear: left;
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);
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);
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;
}