Merge branch 'testing'

This commit is contained in:
Evan Prodromou 2009-12-22 16:44:19 -08:00
commit f6bf952980
234 changed files with 51807 additions and 16565 deletions

View File

@ -290,6 +290,18 @@ StartRegistrationTry: before validating and saving a new user
EndRegistrationTry: after saving a new user (note: no profile or user object!) EndRegistrationTry: after saving a new user (note: no profile or user object!)
- $action: action object being shown - $action: action object being shown
StartAvatarFormData: before displaying avatar form
- $action: action object being shown
EndAvatarFormData: after displaying avatar form
- $action: action object being shown
StartAvatarSaveForm: before saving the avatar
- $action: action object being shown
EndAvatarSaveForm: after saving the avatar
- $action: action object being shown
StartNewQueueManager: before trying to start a new queue manager; good for plugins implementing new queue manager classes StartNewQueueManager: before trying to start a new queue manager; good for plugins implementing new queue manager classes
- $qm: empty queue manager to set - $qm: empty queue manager to set
@ -574,3 +586,57 @@ EndShortenUrl: After a URL has been shortened
- $shortenerName: name of the requested shortener - $shortenerName: name of the requested shortener
- $shortenedUrl: short version of the url - $shortenedUrl: short version of the url
StartCssLinkElement: Before a <link rel="stylesheet"..> element is written
- $action
- &$src
- &$theme
- &$media
EndCssLinkElement: After a <link rel="stylesheet"..> element is written
- $action
- $src
- $theme
- $media
StartStyleElement: Before a <style...> element is written
- $action
- &$code
- &$type
- &$media
EndStyleElement: After a <style...> element is written
- $action
- $code
- $type
- $media
StartScriptElement: Before a <script...> element is written
- $action
- &$src
- &$type
EndScriptElement: After a <script...> element is written
- $action
- $src
- $type
StartInlineScriptElement: Before a <script...> element is written
- $action
- &$code
- &$type
EndInlineScriptElement: After a <script...> element is written
- $action
- $code
- $type
StartLog: Before writing to the logs
- &$priority
- &$msg
- &$filename
EndLog: After writing to the logs
- $priority
- $msg
- $filename

18
Makefile Normal file
View File

@ -0,0 +1,18 @@
# Warning: do not transform tabs to spaces in this file.
all : translations
core_mo = $(patsubst %.po,%.mo,$(wildcard locale/*/LC_MESSAGES/statusnet.po))
plugin_mo = $(patsubst %.po,%.mo,$(wildcard plugins/*/locale/*/LC_MESSAGES/*.po))
translations : $(core_mo) $(plugin_mo)
clean :
rm -f $(core_mo) $(plugin_mo)
updatepo :
php scripts/update_po_templates.php --all
%.mo : %.po
msgfmt -o $@ $<

205
README
View File

@ -2,8 +2,8 @@
README README
------ ------
StatusNet 0.8.2 ("Life and How to Live It") StatusNet 0.9.0 ("Stand") Release Candidate 2
1 Nov 2009 22 Dec 2009
This is the README file for StatusNet (formerly Laconica), the Open This is the README file for StatusNet (formerly Laconica), the Open
Source microblogging platform. It includes installation instructions, Source microblogging platform. It includes installation instructions,
@ -16,10 +16,10 @@ About
StatusNet (formerly Laconica) is a Free and Open Source microblogging StatusNet (formerly Laconica) is a Free and Open Source microblogging
platform. It helps people in a community, company or group to exchange platform. It helps people in a community, company or group to exchange
short (140 character) messages over the Web. Users can choose which short (140 characters, by default) messages over the Web. Users can
people to "follow" and receive only their friends' or colleagues' choose which people to "follow" and receive only their friends' or
status messages. It provides a similar service to sites like Twitter, colleagues' status messages. It provides a similar service to sites
Jaiku, Yammer, and Plurk. like Twitter, Jaiku, Yammer, and Plurk.
With a little work, status messages can be sent to mobile phones, With a little work, status messages can be sent to mobile phones,
instant messenger programs (GTalk/Jabber), and specially-designed instant messenger programs (GTalk/Jabber), and specially-designed
@ -77,81 +77,96 @@ for additional terms.
New this version New this version
================ ================
This is a minor feature and bugfix release since version 0.8.1, This is a major feature release since version 0.8.2, released Nov 1 2009.
released Aug 26 2009. Notable changes this version: Notable changes this version:
- New script for deleting user accounts. Not particularly safe or - Records of deleted notices are stored without the notice content.
community-friendly. Better for deleting abusive accounts than for - Much of the optional core featureset has been moved to plugins.
users who are 'retiring'. - OpenID support moved from core to a plugin. Helps test the strength of
- Improved detection of URLs in notices, specifically for punctuation our plugin architecture and makes it easy to disable this
chars like ~, :, $, _, -, +, !, @, and %. functionality for e.g. intranet sites.
- Removed some extra <dl> semantic HTML code. - Many additional hook events (see EVENTS.txt for details).
- Correct error in status-network database ini file (having multiple - OMB 0.1 support re-implemented using libomb.
statusnet sites with a single codebase) - Re-structure database so notices, messages, bios and group
- Fixed error output for Twitter posting failures. descriptions can be over 140 characters. Limit defined by
- Fixed bug in Twitter queue handler that requeued inapplicable site administrator as configuration option; can be unlimited.
notices ad infinitum. - Configuration data now optionally stored in the database, which
- Improve FOAF output for remote users. overrides any settings in config files.
- new commands to join and leave groups. - Twitter integration re-implemented as a plugin.
- Fixed bug in which you cannot turn off importing friends timelines - Facebook integration re-implemented as a plugin.
flag. - Role-based authorization framework. Users can have named roles, and
- Better error handling in Twitter posting. roles can have rights (e.g., to delete notices, change configuration
- Show oEmbed data for XHTML files as well as plain HTML. data, or ban uncooperative users). Default roles 'admin' (for
- Updated bug database link in README. configuration) and 'moderator' (for community management) added.
- add support for HTTP Basic Auth in PHP CGI or FastCGI (e.g. GoDaddy). - Plugin for PubSubHubBub (PuSH) support.
- autofocus input to selected entry elements depending on page. - Considerable code style cleanup to meet PEAR code standards.
- updated layout for filter-by-tag form. - Made a common library for HTTP-client access which uses available
- better layout for inbox and outbox pages. HTTP libraries where possible.
- fix highlighting search terms in attributes of notice list elements. - Added statuses/home_timeline method to API.
- Correctly handle errors in linkback plugin. - Hooks for plugins to handle notices offline, either by defining
- Updated biz theme. their own queue handler scripts or to use a default plugin queue
- Updated cloudy theme. handler script.
- Don't match '::' as an IPv6 address. - Plugins can now modify the database schema, adding their own tables
- Use the same decision logic for deciding whether to mark an or modifying existing ones.
attachment as an enclosure in RSS or as a paperclip item in Web - Groups API.
output. - Twitter API supports Web caching for some methods.
- Fixed a bug in the Piwik plugin that hard-coded the site ID. - Twitter API refactored into one-action-per-method.
- Add a param, inreplyto, to notice/new to allow an explicit response - Realtime plugin supports a tear-off window.
to another notice. - FOAF for groups.
- Show username in subject of emails. - Moved all JavaScript tags to just before </body> by default,
- Check if avatar exists before trying to delete it. significantly speeding up apparent page load time.
- Correctly add omb_version to response for request token in OMB. - Added a Realtime plugin for Orbited server.
- Add a few more SMS carriers. - Added a mobile plugin to give a more mobile-phone-friendly layout
- Add a few more notice sources. when a mobile browser is detected.
- Vary: header. - Use CSS sprites for most common icons.
- Improvements to the AutoCompletePlugin. - Fixes for images and buttons on Web output.
- Check for 'dl' before using it. - New plugin requires that users validate their email before posting.
- Make it impossible to delete self-subscriptions via the API. - New plugin UserFlag lets users flag other profiles for review.
- Fix pagination of tagged user pages. - Considerably better i18n support. Use TranslateWiki to update
- Make PiwikAnalyticsPlugin work with addPlugin(). translations.
- Removed trailing single space in user nicknames in notice lists. - Notices and profiles now store location information.
- Show context link if a notice starts a conversation. - New plugin, Geonames, for turning location names and lat/long pairs
- blacklist all files and directories in install dir. into structured IDs and vice versa. Architecture reusable for other
- handle GoDaddy-style PATH_INFO, including script name. systems.
- add home_timeline synonym for friends_timeline. - Better check of license compatibility between site licenses.
- Add a popup window for the realtime plugin. - Some improvements in XMPP output.
- Add some more streams for the realtime plugin. - Media upload in the API.
- Fix a bug that overwrote group creation timestamp on every edit. - Replies appear in the user's inbox.
- Moved HTTP error code strings to a class variable. - Improved the UI on the bookmarklet.
- The Twitter API now returns server errors in the correct format. - StatusNet identities can be used as OpenID identities.
- Reset the doctype for HTML output. - Script to register a user.
- Fixed a number of notices. - Script to make someone a group admin.
- Don't show search suggestions for private sites. - Script to make someone a site admin or moderator.
- Some corrections to FBConnect nav overrides. - 'login' command.
- Slightly less database-intensive session management. - Pluggable authentication.
- Updated name of software in installer script. - LDAP authentication plugin.
- Include long-form attachment URLs if url-shortener is disabled. - Script for console interaction with the site (!).
- Include updated localisations for Polish, Greek, Hebrew, Icelandic, - Users don't see group posts from people they've blocked.
Norwegian, and Chinese. - Admin panel interface for changing site configuration.
- Include upstream fixes to gettext.php. - Users can be sandboxed (limited contributions) or silenced
- Correct for regression in Facebook API for updates. (no contributions) by moderators.
- Ignore "Sent from my iPhone" (and similar) in mail updates. - Many changes to make language usage more consistent.
- Use the NICKNAME_FMT constant for detecting nicknames. - Sphinx search moved to a plugin.
- Check for site servername config'd. - GeoURL plugin.
- Compatibility fix for empty status updates with Twitter API. - Profile and group lists support hAtom.
- Option to show files privately (EXPERIMENTAL! Use with caution.) - Massive refactoring of util.js.
- a script to register a new user. - Mapstraction plugin to show maps on inbox and profile pages.
- a script to make a user admin of a group. - Play/pause buttons for realtime notices.
- Support for geo microformat.
- Partial support for feed subscriptions, RSSCloud, PubSubHubBub.
- Support for geolocation in browser (Chrome, Firefox).
- Quit trying to negotiate HTML format. Always use text/html.
We lose, and so do Web standards. Boo.
- Better logging of request info.
- Better output for errors in Web interface.
- No longer store .mo files; these need to be generated.
- Minify plugin.
- Events to allow pluginizing logger.
- New framework for plugin localization.
- Gravatar plugin.
- Add support for "repeats" (similar to Twitter's "retweets").
- Support for repeats in Twitter API.
- Better notification of direct messages.
Prerequisites Prerequisites
============= =============
@ -358,7 +373,7 @@ It's possible to configure the software so it looks like this instead:
These "fancy URLs" are more readable and memorable for users. To use These "fancy URLs" are more readable and memorable for users. To use
fancy URLs, you must either have Apache 2.x with .htaccess enabled and fancy URLs, you must either have Apache 2.x with .htaccess enabled and
mod_redirect enabled, -OR- know how to configure "url redirection" in mod_rewrite enabled, -OR- know how to configure "url redirection" in
your server. your server.
1. Copy the htaccess.sample file to .htaccess in your StatusNet 1. Copy the htaccess.sample file to .htaccess in your StatusNet
@ -384,6 +399,18 @@ like:
If you changed your HTTP server configuration, you may need to restart If you changed your HTTP server configuration, you may need to restart
the server first. the server first.
If it doesn't work, double-check that AllowOverride for the StatusNet
directory is 'All' in your Apache configuration file. This is usually
/etc/httpd.conf, /etc/apache/httpd.conf, or (on Debian and Ubuntu)
/etc/apache2/sites-available/default. See the Apache documentation for
.htaccess files for more details:
http://httpd.apache.org/docs/2.2/howto/htaccess.html
Also, check that mod_rewrite is installed and enabled:
http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html
Sphinx Sphinx
------ ------
@ -1407,6 +1434,21 @@ contentlimit: max length of the plain-text content of a message.
Default is null, meaning to use the site-wide text limit. Default is null, meaning to use the site-wide text limit.
0 means no limit. 0 means no limit.
logincommand
------------
Configuration options for the login command.
disabled: whether to enable this command. If enabled, users who send
the text 'login' to the site through any channel will
receive a link to login to the site automatically in return.
Possibly useful for users who primarily use an XMPP or SMS
interface and can't be bothered to remember their site
password. Note that the security implications of this are
pretty serious and have not been thoroughly tested. You
should enable it only after you've convinced yourself that
it is safe. Default is 'false'.
Plugins Plugins
======= =======
@ -1573,6 +1615,7 @@ if anyone's been overlooked in error.
* Federico Marani * Federico Marani
* Craig Andrews * Craig Andrews
* mEDI * mEDI
* Brett Taylor
Thanks also to the developers of our upstream library code and to the Thanks also to the developers of our upstream library code and to the
thousands of people who have tried out Identi.ca, installed StatusNet, thousands of people who have tried out Identi.ca, installed StatusNet,

View File

@ -160,12 +160,11 @@ class AllAction extends ProfileAction
function showPageTitle() function showPageTitle()
{ {
$user =& common_current_user(); $user = common_current_user();
if ($user && ($user->id == $this->user->id)) { if ($user && ($user->id == $this->user->id)) {
$this->element('h1', null, _("You and friends")); $this->element('h1', null, _("You and friends"));
} else { } else {
$this->element('h1', null, sprintf(_('%s and friends'), $this->user->nickname)); $this->element('h1', null, sprintf(_('%s and friends'), $this->user->nickname));
} }
} }
} }

View File

@ -98,6 +98,17 @@ class ApiBlockCreateAction extends ApiAuthAction
return; return;
} }
// Don't allow blocking yourself!
if ($this->user->id == $this->other->id) {
$this->clientError(
_("You cannot block yourself!"),
403,
$this->format
);
return;
}
if ($this->user->hasBlocked($this->other) if ($this->user->hasBlocked($this->other)
|| $this->user->block($this->other) || $this->user->block($this->other)
) { ) {

View File

@ -175,7 +175,7 @@ class ApiDirectMessageNewAction extends ApiAuthAction
return; return;
} }
mail_notify_message($message, $this->user, $this->other); $message->notify();
if ($this->format == 'xml') { if ($this->format == 'xml') {
$this->showSingleXmlDirectMessage($message); $this->showSingleXmlDirectMessage($message);

View File

@ -0,0 +1,136 @@
<?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('STATUSNET')) {
exit(1);
}
require_once INSTALLDIR . '/lib/apiauth.php';
require_once INSTALLDIR . '/lib/mediafile.php';
/**
* 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
{
var $original = null;
/**
* Take arguments for running
*
* @param array $args $_REQUEST args
*
* @return boolean success flag
*
*/
function prepare($args)
{
parent::prepare($args);
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
$this->clientError(_('This method requires a POST.'),
400, $this->format);
return false;
}
$id = $this->trimmed('id');
$this->original = Notice::staticGet('id', $id);
if (empty($this->original)) {
$this->clientError(_('No such notice.'),
400, $this->format);
return false;
}
$this->user = $this->auth_user;
if ($this->user->id == $notice->profile_id) {
$this->clientError(_('Cannot repeat your own notice.'),
400, $this->format);
return false;
}
$profile = $this->user->getProfile();
if ($profile->hasRepeated($id)) {
$this->clientError(_('Already repeated that notice.'),
400, $this->format);
return false;
}
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);
$repeat = $this->original->repeat($this->user->id, $this->source);
common_broadcast_notice($repeat);
$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);
}
}
}
}

View File

@ -0,0 +1,116 @@
<?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);
}
require_once INSTALLDIR . '/lib/apiauth.php';
require_once INSTALLDIR . '/lib/mediafile.php';
/**
* 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::staticGet('id', $id);
if (empty($this->original)) {
$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:
$this->clientError(_('API method not found!'), $code = 404);
break;
}
}
}

View File

@ -231,19 +231,22 @@ class ApiStatusesUpdateAction extends ApiAuthAction
} }
} }
$this->notice = Notice::saveNew( $content = html_entity_decode($status_shortened, ENT_NOQUOTES, 'UTF-8');
$this->user->id,
html_entity_decode($status_shortened, ENT_NOQUOTES, 'UTF-8'), $options = array('reply_to' => $reply_to);
$this->source,
1, if (!empty($location)) {
$reply_to, $options['lat'] = $location->lat;
null, $options['lon'] = $location->lon;
null, $options['location_id'] = $location->location_id;
empty($location) ? null : $location->lat, $options['location_ns'] = $location->location_ns;
empty($location) ? null : $location->lon, }
empty($location) ? null : $location->location_id,
empty($location) ? null : $location->location_ns $this->notice =
); Notice::saveNew($this->user->id,
$content,
$this->source,
$options);
if (isset($upload)) { if (isset($upload)) {
$upload->attachToNotice($this->notice); $upload->attachToNotice($this->notice);

View File

@ -101,6 +101,7 @@ class ApiTimelineFavoritesAction extends ApiBareAuthAction
function showTimeline() function showTimeline()
{ {
$profile = $this->user->getProfile(); $profile = $this->user->getProfile();
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
$sitename = common_config('site', 'name'); $sitename = common_config('site', 'name');
$title = sprintf( $title = sprintf(
@ -121,20 +122,21 @@ class ApiTimelineFavoritesAction extends ApiBareAuthAction
$profile->getBestName(), $profile->getBestName(),
$this->user->nickname $this->user->nickname
); );
$logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE);
switch($this->format) { switch($this->format) {
case 'xml': case 'xml':
$this->showXmlTimeline($this->notices); $this->showXmlTimeline($this->notices);
break; break;
case 'rss': case 'rss':
$this->showRssTimeline($this->notices, $title, $link, $subtitle); $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo);
break; break;
case 'atom': case 'atom':
$selfuri = common_root_url() . $selfuri = common_root_url() .
ltrim($_SERVER['QUERY_STRING'], 'p='); ltrim($_SERVER['QUERY_STRING'], 'p=');
$this->showAtomTimeline( $this->showAtomTimeline(
$this->notices, $title, $id, $link, $subtitle, $this->notices, $title, $id, $link, $subtitle,
null, $selfuri null, $selfuri, $logo
); );
break; break;
case 'json': case 'json':

View File

@ -110,24 +110,26 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
function showTimeline() function showTimeline()
{ {
$profile = $this->user->getProfile(); $profile = $this->user->getProfile();
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
$sitename = common_config('site', 'name'); $sitename = common_config('site', 'name');
$title = sprintf(_("%s and friends"), $this->user->nickname); $title = sprintf(_("%s and friends"), $this->user->nickname);
$taguribase = common_config('integration', 'taguri'); $taguribase = common_config('integration', 'taguri');
$id = "tag:$taguribase:FriendsTimeline:" . $this->user->id; $id = "tag:$taguribase:FriendsTimeline:" . $this->user->id;
$link = common_local_url( $link = common_local_url(
'all', array('nickname' => $this->user->nickname) 'all', array('nickname' => $this->user->nickname)
); );
$subtitle = sprintf( $subtitle = sprintf(
_('Updates from %1$s and friends on %2$s!'), _('Updates from %1$s and friends on %2$s!'),
$this->user->nickname, $sitename $this->user->nickname, $sitename
); );
$logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE);
switch($this->format) { switch($this->format) {
case 'xml': case 'xml':
$this->showXmlTimeline($this->notices); $this->showXmlTimeline($this->notices);
break; break;
case 'rss': case 'rss':
$this->showRssTimeline($this->notices, $title, $link, $subtitle); $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo);
break; break;
case 'atom': case 'atom':
@ -135,17 +137,17 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
if (isset($target_id)) { if (isset($target_id)) {
$selfuri = common_root_url() . $selfuri = common_root_url() .
'api/statuses/friends_timeline/' . 'api/statuses/friends_timeline/' .
$target_id . '.atom'; $target_id . '.atom';
} else { } else {
$selfuri = common_root_url() . $selfuri = common_root_url() .
'api/statuses/friends_timeline.atom'; 'api/statuses/friends_timeline.atom';
} }
$this->showAtomTimeline( $this->showAtomTimeline(
$this->notices, $title, $id, $link, $this->notices, $title, $id, $link,
$subtitle, null, $selfuri $subtitle, null, $selfuri, $logo
); );
break; break;
case 'json': case 'json':
$this->showJsonTimeline($this->notices); $this->showJsonTimeline($this->notices);
@ -167,17 +169,13 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
$notices = array(); $notices = array();
if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) { if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) {
$notice = $this->user->noticeInbox( $notice = $this->user->ownFriendsTimeline(($this->page-1) * $this->count,
($this->page-1) * $this->count, $this->count, $this->since_id,
$this->count, $this->since_id, $this->max_id, $this->since);
$this->max_id, $this->since
);
} else { } else {
$notice = $this->user->noticesWithFriends( $notice = $this->user->friendsTimeline(($this->page-1) * $this->count,
($this->page-1) * $this->count, $this->count, $this->since_id,
$this->count, $this->since_id, $this->max_id, $this->since);
$this->max_id, $this->since
);
} }
while ($notice->fetch()) { while ($notice->fetch()) {
@ -231,14 +229,14 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
$last = count($this->notices) - 1; $last = count($this->notices) - 1;
return '"' . implode( return '"' . implode(
':', ':',
array($this->arg('action'), array($this->arg('action'),
common_language(), common_language(),
$this->user->id, $this->user->id,
strtotime($this->notices[0]->created), strtotime($this->notices[0]->created),
strtotime($this->notices[$last]->created)) strtotime($this->notices[$last]->created))
) )
. '"'; . '"';
} }
return null; return null;

View File

@ -105,6 +105,7 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
function showTimeline() function showTimeline()
{ {
$sitename = common_config('site', 'name'); $sitename = common_config('site', 'name');
$avatar = $this->group->homepage_logo;
$title = sprintf(_("%s timeline"), $this->group->nickname); $title = sprintf(_("%s timeline"), $this->group->nickname);
$taguribase = common_config('integration', 'taguri'); $taguribase = common_config('integration', 'taguri');
$id = "tag:$taguribase:GroupTimeline:" . $this->group->id; $id = "tag:$taguribase:GroupTimeline:" . $this->group->id;
@ -117,13 +118,14 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
$this->group->nickname, $this->group->nickname,
$sitename $sitename
); );
$logo = ($avatar) ? $avatar : User_group::defaultLogo(AVATAR_PROFILE_SIZE);
switch($this->format) { switch($this->format) {
case 'xml': case 'xml':
$this->showXmlTimeline($this->notices); $this->showXmlTimeline($this->notices);
break; break;
case 'rss': case 'rss':
$this->showRssTimeline($this->notices, $title, $link, $subtitle); $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo);
break; break;
case 'atom': case 'atom':
$selfuri = common_root_url() . $selfuri = common_root_url() .
@ -136,7 +138,8 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
$link, $link,
$subtitle, $subtitle,
null, null,
$selfuri $selfuri,
$logo
); );
break; break;
case 'json': case 'json':

249
actions/apitimelinehome.php Normal file
View File

@ -0,0 +1,249 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Show the home timeline
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category API
* @package StatusNet
* @author Craig Andrews <candrews@integralblue.com>
* @author Evan Prodromou <evan@status.net>
* @author Jeffery To <jeffery.to@gmail.com>
* @author mac65 <mac65@mac65.com>
* @author Mike Cochrane <mikec@mikenz.geek.nz>
* @author Robin Millette <robin@millette.info>
* @author Zach Copley <zach@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);
}
require_once INSTALLDIR . '/lib/apibareauth.php';
/**
* Returns the most recent notices (default 20) posted by the target user.
* This is the equivalent of 'You and friends' page accessed via Web.
*
* @category API
* @package StatusNet
* @author Craig Andrews <candrews@integralblue.com>
* @author Evan Prodromou <evan@status.net>
* @author Jeffery To <jeffery.to@gmail.com>
* @author mac65 <mac65@mac65.com>
* @author Mike Cochrane <mikec@mikenz.geek.nz>
* @author Robin Millette <robin@millette.info>
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class ApiTimelineHomeAction extends ApiBareAuthAction
{
var $notices = null;
/**
* Take arguments for running
*
* @param array $args $_REQUEST args
*
* @return boolean success flag
*
*/
function prepare($args)
{
parent::prepare($args);
common_debug("api home_timeline");
$this->user = $this->getTargetUser($this->arg('id'));
if (empty($this->user)) {
$this->clientError(_('No such user.'), 404, $this->format);
return;
}
$this->notices = $this->getNotices();
return true;
}
/**
* Handle the request
*
* Just show the notices
*
* @param array $args $_REQUEST data (unused)
*
* @return void
*/
function handle($args)
{
parent::handle($args);
$this->showTimeline();
}
/**
* Show the timeline of notices
*
* @return void
*/
function showTimeline()
{
$profile = $this->user->getProfile();
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
$sitename = common_config('site', 'name');
$title = sprintf(_("%s and friends"), $this->user->nickname);
$taguribase = common_config('integration', 'taguri');
$id = "tag:$taguribase:HomeTimeline:" . $this->user->id;
$link = common_local_url(
'all', array('nickname' => $this->user->nickname)
);
$subtitle = sprintf(
_('Updates from %1$s and friends on %2$s!'),
$this->user->nickname, $sitename
);
$logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE);
switch($this->format) {
case 'xml':
$this->showXmlTimeline($this->notices);
break;
case 'rss':
$this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo);
break;
case 'atom':
$target_id = $this->arg('id');
if (isset($target_id)) {
$selfuri = common_root_url() .
'api/statuses/home_timeline/' .
$target_id . '.atom';
} else {
$selfuri = common_root_url() .
'api/statuses/home_timeline.atom';
}
$this->showAtomTimeline(
$this->notices, $title, $id, $link,
$subtitle, null, $selfuri, $logo
);
break;
case 'json':
$this->showJsonTimeline($this->notices);
break;
default:
$this->clientError(_('API method not found!'), $code = 404);
break;
}
}
/**
* Get notices
*
* @return array notices
*/
function getNotices()
{
$notices = array();
if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) {
$notice = $this->user->noticeInbox(
($this->page-1) * $this->count,
$this->count, $this->since_id,
$this->max_id, $this->since
);
} else {
$notice = $this->user->noticesWithFriends(
($this->page-1) * $this->count,
$this->count, $this->since_id,
$this->max_id, $this->since
);
}
while ($notice->fetch()) {
$notices[] = clone($notice);
}
return $notices;
}
/**
* Is this action read only?
*
* @param array $args other arguments
*
* @return boolean true
*/
function isReadOnly($args)
{
return true;
}
/**
* When was this feed last modified?
*
* @return string datestamp of the latest notice in the stream
*/
function lastModified()
{
if (!empty($this->notices) && (count($this->notices) > 0)) {
return strtotime($this->notices[0]->created);
}
return null;
}
/**
* An entity tag for this stream
*
* Returns an Etag based on the action name, language, user ID, and
* timestamps of the first and last notice in the timeline
*
* @return string etag
*/
function etag()
{
if (!empty($this->notices) && (count($this->notices) > 0)) {
$last = count($this->notices) - 1;
return '"' . implode(
':',
array($this->arg('action'),
common_language(),
$this->user->id,
strtotime($this->notices[0]->created),
strtotime($this->notices[$last]->created))
)
. '"';
}
return null;
}
}

View File

@ -110,6 +110,7 @@ class ApiTimelineMentionsAction extends ApiBareAuthAction
function showTimeline() function showTimeline()
{ {
$profile = $this->user->getProfile(); $profile = $this->user->getProfile();
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
$sitename = common_config('site', 'name'); $sitename = common_config('site', 'name');
$title = sprintf( $title = sprintf(
@ -126,20 +127,21 @@ class ApiTimelineMentionsAction extends ApiBareAuthAction
_('%1$s updates that reply to updates from %2$s / %3$s.'), _('%1$s updates that reply to updates from %2$s / %3$s.'),
$sitename, $this->user->nickname, $profile->getBestName() $sitename, $this->user->nickname, $profile->getBestName()
); );
$logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE);
switch($this->format) { switch($this->format) {
case 'xml': case 'xml':
$this->showXmlTimeline($this->notices); $this->showXmlTimeline($this->notices);
break; break;
case 'rss': case 'rss':
$this->showRssTimeline($this->notices, $title, $link, $subtitle); $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo);
break; break;
case 'atom': case 'atom':
$selfuri = common_root_url() . $selfuri = common_root_url() .
ltrim($_SERVER['QUERY_STRING'], 'p='); ltrim($_SERVER['QUERY_STRING'], 'p=');
$this->showAtomTimeline( $this->showAtomTimeline(
$this->notices, $title, $id, $link, $subtitle, $this->notices, $title, $id, $link, $subtitle,
null, $selfuri null, $selfuri, $logo
); );
break; break;
case 'json': case 'json':

View File

@ -103,6 +103,7 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction
function showTimeline() function showTimeline()
{ {
$sitename = common_config('site', 'name'); $sitename = common_config('site', 'name');
$sitelogo = (common_config('site', 'logo')) ? common_config('site', 'logo') : Theme::path('logo.png');
$title = sprintf(_("%s public timeline"), $sitename); $title = sprintf(_("%s public timeline"), $sitename);
$taguribase = common_config('integration', 'taguri'); $taguribase = common_config('integration', 'taguri');
$id = "tag:$taguribase:PublicTimeline"; $id = "tag:$taguribase:PublicTimeline";
@ -114,13 +115,13 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction
$this->showXmlTimeline($this->notices); $this->showXmlTimeline($this->notices);
break; break;
case 'rss': case 'rss':
$this->showRssTimeline($this->notices, $title, $link, $subtitle); $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $sitelogo);
break; break;
case 'atom': case 'atom':
$selfuri = common_root_url() . 'api/statuses/public_timeline.atom'; $selfuri = common_root_url() . 'api/statuses/public_timeline.atom';
$this->showAtomTimeline( $this->showAtomTimeline(
$this->notices, $title, $id, $link, $this->notices, $title, $id, $link,
$subtitle, null, $selfuri $subtitle, null, $selfuri, $sitelogo
); );
break; break;
case 'json': case 'json':

View File

@ -0,0 +1,126 @@
<?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);
}
require_once INSTALLDIR . '/lib/apiauth.php';
require_once INSTALLDIR . '/lib/mediafile.php';
/**
* 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);
$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;
$strm = $this->auth_user->repeatedByMe($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':
$profile = $this->auth_user->getProfile();
$title = sprintf(_("Repeated by %s"), $this->auth_user->nickname);
$taguribase = common_config('integration', 'taguri');
$id = "tag:$taguribase:RepeatedByMe:" . $this->auth_user->id;
$link = common_local_url('showstream',
array('nickname' => $this->auth_user->nickname));
$this->showAtomTimeline($strm, $title, $id, $link);
break;
default:
$this->clientError(_('API method not found!'), $code = 404);
break;
}
}
}

View File

@ -0,0 +1,125 @@
<?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('STATUSNET')) {
exit(1);
}
require_once INSTALLDIR . '/lib/apiauth.php';
/**
* 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
*
*/
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;
$strm = $this->auth_user->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':
$profile = $this->auth_user->getProfile();
$title = sprintf(_("Repeated to %s"), $this->auth_user->nickname);
$taguribase = common_config('integration', 'taguri');
$id = "tag:$taguribase:RepeatedToMe:" . $this->auth_user->id;
$link = common_local_url('all',
array('nickname' => $this->auth_user->nickname));
$this->showAtomTimeline($strm, $title, $id, $link);
break;
default:
$this->clientError(_('API method not found!'), $code = 404);
break;
}
}
}

View File

@ -0,0 +1,126 @@
<?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);
}
require_once INSTALLDIR . '/lib/apiauth.php';
require_once INSTALLDIR . '/lib/mediafile.php';
/**
* 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;
$strm = $this->auth_user->repeatsOfMe($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':
$profile = $this->auth_user->getProfile();
$title = sprintf(_("Repeats of %s"), $this->auth_user->nickname);
$taguribase = common_config('integration', 'taguri');
$id = "tag:$taguribase:RepeatsOfMe:" . $this->auth_user->id;
$link = common_local_url('showstream',
array('nickname' => $this->auth_user->nickname));
$this->showAtomTimeline($strm, $title, $id, $link);
break;
default:
$this->clientError(_('API method not found!'), $code = 404);
break;
}
}
}

View File

@ -98,6 +98,7 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction
function showTimeline() function showTimeline()
{ {
$sitename = common_config('site', 'name'); $sitename = common_config('site', 'name');
$sitelogo = (common_config('site', 'logo')) ? common_config('site', 'logo') : Theme::path('logo.png');
$title = sprintf(_("Notices tagged with %s"), $this->tag); $title = sprintf(_("Notices tagged with %s"), $this->tag);
$link = common_local_url( $link = common_local_url(
'tag', 'tag',
@ -116,7 +117,7 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction
$this->showXmlTimeline($this->notices); $this->showXmlTimeline($this->notices);
break; break;
case 'rss': case 'rss':
$this->showRssTimeline($this->notices, $title, $link, $subtitle); $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $sitelogo);
break; break;
case 'atom': case 'atom':
$selfuri = common_root_url() . $selfuri = common_root_url() .
@ -129,7 +130,8 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction
$link, $link,
$subtitle, $subtitle,
null, null,
$selfuri $selfuri,
$sitelogo
); );
break; break;
case 'json': case 'json':

View File

@ -112,6 +112,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
function showTimeline() function showTimeline()
{ {
$profile = $this->user->getProfile(); $profile = $this->user->getProfile();
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
$sitename = common_config('site', 'name'); $sitename = common_config('site', 'name');
$title = sprintf(_("%s timeline"), $this->user->nickname); $title = sprintf(_("%s timeline"), $this->user->nickname);
@ -125,6 +126,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
_('Updates from %1$s on %2$s!'), _('Updates from %1$s on %2$s!'),
$this->user->nickname, $sitename $this->user->nickname, $sitename
); );
$logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE);
// FriendFeed's SUP protocol // FriendFeed's SUP protocol
// Also added RSS and Atom feeds // Also added RSS and Atom feeds
@ -139,7 +141,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
case 'rss': case 'rss':
$this->showRssTimeline( $this->showRssTimeline(
$this->notices, $title, $link, $this->notices, $title, $link,
$subtitle, $suplink $subtitle, $suplink, $logo
); );
break; break;
case 'atom': case 'atom':
@ -153,7 +155,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
} }
$this->showAtomTimeline( $this->showAtomTimeline(
$this->notices, $title, $id, $link, $this->notices, $title, $id, $link,
$subtitle, $suplink, $selfuri $subtitle, $suplink, $selfuri, $logo
); );
break; break;
case 'json': case 'json':

View File

@ -118,53 +118,56 @@ class AvatarsettingsAction extends AccountSettingsAction
$this->elementStart('fieldset'); $this->elementStart('fieldset');
$this->element('legend', null, _('Avatar settings')); $this->element('legend', null, _('Avatar settings'));
$this->hidden('token', common_session_token()); $this->hidden('token', common_session_token());
if (Event::handle('StartAvatarFormData', array($this))) {
$this->elementStart('ul', 'form_data');
if ($original) {
$this->elementStart('li', array('id' => 'avatar_original',
'class' => 'avatar_view'));
$this->element('h2', null, _("Original"));
$this->elementStart('div', array('id'=>'avatar_original_view'));
$this->element('img', array('src' => $original->url,
'width' => $original->width,
'height' => $original->height,
'alt' => $user->nickname));
$this->elementEnd('div');
$this->elementEnd('li');
}
$this->elementStart('ul', 'form_data'); $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
if ($original) {
$this->elementStart('li', array('id' => 'avatar_original', if ($avatar) {
'class' => 'avatar_view')); $this->elementStart('li', array('id' => 'avatar_preview',
$this->element('h2', null, _("Original")); 'class' => 'avatar_view'));
$this->elementStart('div', array('id'=>'avatar_original_view')); $this->element('h2', null, _("Preview"));
$this->element('img', array('src' => $original->url, $this->elementStart('div', array('id'=>'avatar_preview_view'));
'width' => $original->width, $this->element('img', array('src' => $original->url,
'height' => $original->height, 'width' => AVATAR_PROFILE_SIZE,
'alt' => $user->nickname)); 'height' => AVATAR_PROFILE_SIZE,
$this->elementEnd('div'); 'alt' => $user->nickname));
$this->elementEnd('div');
$this->submit('delete', _('Delete'));
$this->elementEnd('li');
}
$this->elementStart('li', array ('id' => 'settings_attach'));
$this->element('input', array('name' => 'avatarfile',
'type' => 'file',
'id' => 'avatarfile'));
$this->element('input', array('name' => 'MAX_FILE_SIZE',
'type' => 'hidden',
'id' => 'MAX_FILE_SIZE',
'value' => ImageFile::maxFileSizeInt()));
$this->elementEnd('li'); $this->elementEnd('li');
} $this->elementEnd('ul');
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); $this->elementStart('ul', 'form_actions');
$this->elementStart('li');
if ($avatar) { $this->submit('upload', _('Upload'));
$this->elementStart('li', array('id' => 'avatar_preview',
'class' => 'avatar_view'));
$this->element('h2', null, _("Preview"));
$this->elementStart('div', array('id'=>'avatar_preview_view'));
$this->element('img', array('src' => $original->url,
'width' => AVATAR_PROFILE_SIZE,
'height' => AVATAR_PROFILE_SIZE,
'alt' => $user->nickname));
$this->elementEnd('div');
$this->submit('delete', _('Delete'));
$this->elementEnd('li'); $this->elementEnd('li');
$this->elementEnd('ul');
} }
Event::handle('EndAvatarFormData', array($this));
$this->elementStart('li', array ('id' => 'settings_attach'));
$this->element('input', array('name' => 'avatarfile',
'type' => 'file',
'id' => 'avatarfile'));
$this->element('input', array('name' => 'MAX_FILE_SIZE',
'type' => 'hidden',
'id' => 'MAX_FILE_SIZE',
'value' => ImageFile::maxFileSizeInt()));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->elementStart('ul', 'form_actions');
$this->elementStart('li');
$this->submit('upload', _('Upload'));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->elementEnd('fieldset'); $this->elementEnd('fieldset');
$this->elementEnd('form'); $this->elementEnd('form');
@ -266,15 +269,18 @@ class AvatarsettingsAction extends AccountSettingsAction
'Try again, please.')); 'Try again, please.'));
return; return;
} }
if ($this->arg('upload')) { if (Event::handle('StartAvatarSaveForm', array($this))) {
$this->uploadAvatar(); if ($this->arg('upload')) {
} else if ($this->arg('crop')) { $this->uploadAvatar();
$this->cropAvatar(); } else if ($this->arg('crop')) {
} else if ($this->arg('delete')) { $this->cropAvatar();
$this->deleteAvatar(); } else if ($this->arg('delete')) {
} else { $this->deleteAvatar();
$this->showForm(_('Unexpected form submission.')); } else {
$this->showForm(_('Unexpected form submission.'));
}
Event::handle('EndAvatarSaveForm', array($this));
} }
} }

View File

@ -31,15 +31,15 @@ class FileAction extends Action
parent::prepare($args); parent::prepare($args);
$this->id = $this->trimmed('notice'); $this->id = $this->trimmed('notice');
if (empty($this->id)) { if (empty($this->id)) {
$this->clientError(_('No notice id')); $this->clientError(_('No notice ID.'));
} }
$notice = Notice::staticGet('id', $this->id); $notice = Notice::staticGet('id', $this->id);
if (empty($notice)) { if (empty($notice)) {
$this->clientError(_('No notice')); $this->clientError(_('No notice.'));
} }
$atts = $notice->attachments(); $atts = $notice->attachments();
if (empty($atts)) { if (empty($atts)) {
$this->clientError(_('No attachments')); $this->clientError(_('No attachments.'));
} }
foreach ($atts as $att) { foreach ($atts as $att) {
if (!empty($att->filename)) { if (!empty($att->filename)) {
@ -48,7 +48,7 @@ class FileAction extends Action
} }
} }
if (empty($this->filerec)) { if (empty($this->filerec)) {
$this->clientError(_('No uploaded attachments')); $this->clientError(_('No uploaded attachments.'));
} }
return true; return true;
} }

View File

@ -88,14 +88,14 @@ class groupRssAction extends Rss10Action
} }
if (!$nickname) { if (!$nickname) {
$this->clientError(_('No nickname'), 404); $this->clientError(_('No nickname.'), 404);
return false; return false;
} }
$this->group = User_group::staticGet('nickname', $nickname); $this->group = User_group::staticGet('nickname', $nickname);
if (!$this->group) { if (!$this->group) {
$this->clientError(_('No such group'), 404); $this->clientError(_('No such group.'), 404);
return false; return false;
} }

View File

@ -75,10 +75,16 @@ class LoginAction extends Action
function handle($args) function handle($args)
{ {
parent::handle($args); parent::handle($args);
$disabled = common_config('logincommand','disabled');
$disabled = isset($disabled) && $disabled;
if (common_is_real_login()) { if (common_is_real_login()) {
$this->clientError(_('Already logged in.')); $this->clientError(_('Already logged in.'));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') { } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->checkLogin(); $this->checkLogin();
} else if (!$disabled && isset($args['user_id']) && isset($args['token'])){
$this->checkLogin($args['user_id'],$args['token']);
} else { } else {
common_ensure_session(); common_ensure_session();
$this->showForm(); $this->showForm();
@ -95,7 +101,7 @@ class LoginAction extends Action
* @return void * @return void
*/ */
function checkLogin() function checkLogin($user_id=null, $token=null)
{ {
if(isset($token) && isset($user_id)){ if(isset($token) && isset($user_id)){
//Token based login (from the LoginCommand) //Token based login (from the LoginCommand)
@ -137,11 +143,6 @@ class LoginAction extends Action
$user = common_check_user($nickname, $password); $user = common_check_user($nickname, $password);
} }
$nickname = common_canonical_nickname($this->trimmed('nickname'));
$password = $this->arg('password');
$user = common_check_user($nickname, $password);
if (!$user) { if (!$user) {
$this->showForm(_('Incorrect username or password.')); $this->showForm(_('Incorrect username or password.'));
return; return;

View File

@ -173,7 +173,7 @@ class NewmessageAction extends Action
return; return;
} }
$this->notify($user, $this->other, $message); $message->notify();
if ($this->boolean('ajax')) { if ($this->boolean('ajax')) {
$this->startHTML('text/xml;charset=utf-8'); $this->startHTML('text/xml;charset=utf-8');
@ -247,12 +247,6 @@ class NewmessageAction extends Action
} }
} }
function notify($from, $to, $message)
{
mail_notify_message($message, $from, $to);
// XXX: Jabber, SMS notifications... probably queued
}
// Do nothing (override) // Do nothing (override)
function showNoticeForm() function showNoticeForm()

View File

@ -187,10 +187,12 @@ class NewnoticeAction extends Action
} }
} }
$notice = Notice::saveNew($user->id, $content_shortened, 'web', 1, $notice = Notice::saveNew($user->id, $content_shortened, 'web',
($replyto == 'false') ? null : $replyto, array('reply_to' => ($replyto == 'false') ? null : $replyto,
null, null, 'lat' => $lat,
$lat, $lon, $location_id, $location_ns); 'lon' => $lon,
'location_id' => $location_id,
'location_ns' => $location_ns));
if (isset($upload)) { if (isset($upload)) {
$upload->attachToNotice($notice); $upload->attachToNotice($notice);

126
actions/repeat.php Normal file
View File

@ -0,0 +1,126 @@
<?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('STATUSNET')) {
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 Action
{
var $user = null;
var $notice = null;
function prepare($args)
{
parent::prepare($args);
$this->user = common_current_user();
if (empty($this->user)) {
$this->clientError(_("Only logged-in users can repeat notices."));
return false;
}
$id = $this->trimmed('notice');
if (empty($id)) {
$this->clientError(_("No notice specified."));
return false;
}
$this->notice = Notice::staticGet('id', $id);
if (empty($this->notice)) {
$this->clientError(_("No notice specified."));
return false;
}
if ($this->user->id == $this->notice->profile_id) {
$this->clientError(_("You can't repeat your own notice."));
return false;
}
$token = $this->trimmed('token-'.$id);
if (empty($token) || $token != common_session_token()) {
$this->clientError(_("There was a problem with your session token. Try again, please."));
return false;
}
$profile = $this->user->getProfile();
if ($profile->hasRepeated($id)) {
$this->clientError(_("You already repeated that notice."));
return false;
}
return true;
}
/**
* Class handler.
*
* @param array $args query arguments
*
* @return void
*/
function handle($args)
{
$repeat = $this->notice->repeat($this->user->id, 'web');
common_broadcast_notice($repeat);
if ($this->boolean('ajax')) {
$this->startHTML('text/xml;charset=utf-8');
$this->elementStart('head');
$this->element('title', null, _('Repeated'));
$this->elementEnd('head');
$this->elementStart('body');
$this->element('p', array('id' => 'repeat_response',
'class' => 'repeated'),
_('Repeated!'));
$this->elementEnd('body');
$this->elementEnd('html');
} else {
// FIXME!
}
}
}

View File

@ -269,4 +269,50 @@ class ProfileNoticeListItem extends NoticeListItem
{ {
return; return;
} }
/**
* 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('href' => $this->profile->profileurl,
'class' => 'url');
if (!empty($this->profile->fullname)) {
$attrs['title'] = $this->profile->fullname . ' (' . $this->profile->nickname . ')';
}
$this->out->elementStart('span', 'repeat');
$this->out->elementStart('a', $attrs);
$avatar = $this->profile->getAvatar(AVATAR_MINI_SIZE);
$this->out->element('img', array('src' => ($avatar) ?
$avatar->displayUrl() :
Avatar::defaultImage(AVATAR_MINI_SIZE),
'class' => 'avatar photo',
'width' => AVATAR_MINI_SIZE,
'height' => AVATAR_MINI_SIZE,
'alt' =>
($this->profile->fullname) ?
$this->profile->fullname :
$this->profile->nickname));
$this->out->elementEnd('a');
$text_link = XMLStringer::estring('a', $attrs, $this->profile->nickname);
$this->out->raw(sprintf(_('Repeat of %s'), $text_link));
$this->out->elementEnd('span');
}
}
} }

View File

@ -57,7 +57,7 @@ class SubscribersAction extends GalleryAction
function showPageNotice() function showPageNotice()
{ {
$user =& common_current_user(); $user = common_current_user();
if ($user && ($user->id == $this->profile->id)) { if ($user && ($user->id == $this->profile->id)) {
$this->element('p', null, $this->element('p', null,
_('These are the people who listen to '. _('These are the people who listen to '.

View File

@ -59,7 +59,7 @@ class SubscriptionsAction extends GalleryAction
function showPageNotice() function showPageNotice()
{ {
$user =& common_current_user(); $user = common_current_user();
if ($user && ($user->id == $this->profile->id)) { if ($user && ($user->id == $this->profile->id)) {
$this->element('p', null, $this->element('p', null,
_('These are the people whose notices '. _('These are the people whose notices '.

View File

@ -30,13 +30,13 @@ class TagotherAction extends Action
{ {
parent::prepare($args); parent::prepare($args);
if (!common_logged_in()) { if (!common_logged_in()) {
$this->clientError(_('Not logged in'), 403); $this->clientError(_('Not logged in.'), 403);
return false; return false;
} }
$id = $this->trimmed('id'); $id = $this->trimmed('id');
if (!$id) { if (!$id) {
$this->clientError(_('No id argument.')); $this->clientError(_('No ID argument.'));
return false; return false;
} }

View File

@ -71,7 +71,7 @@ class TwitapisearchatomAction extends ApiAction
* @see Action::__construct * @see Action::__construct
*/ */
function __construct($output='php://output', $indent=true) function __construct($output='php://output', $indent=null)
{ {
parent::__construct($output, $indent); parent::__construct($output, $indent);
} }

View File

@ -47,17 +47,17 @@ class UserbyidAction extends Action
{ {
/** /**
* Is read only? * Is read only?
* *
* @return boolean true * @return boolean true
*/ */
function isReadOnly($args) function isReadOnly($args)
{ {
return true; return true;
} }
/** /**
* Class handler. * Class handler.
* *
* @param array $args array of arguments * @param array $args array of arguments
* *
* @return nothing * @return nothing
@ -67,9 +67,9 @@ class UserbyidAction extends Action
parent::handle($args); parent::handle($args);
$id = $this->trimmed('id'); $id = $this->trimmed('id');
if (!$id) { if (!$id) {
$this->clientError(_('No id.')); $this->clientError(_('No ID.'));
} }
$user =& User::staticGet($id); $user = User::staticGet($id);
if (!$user) { if (!$user) {
$this->clientError(_('No such user.')); $this->clientError(_('No such user.'));
} }
@ -88,4 +88,3 @@ class UserbyidAction extends Action
common_redirect($url, 303); common_redirect($url, 303);
} }
} }

View File

@ -101,7 +101,7 @@ class Design extends Memcached_DataObject
} }
if (0 != mb_strlen($css)) { if (0 != mb_strlen($css)) {
$out->element('style', array('type' => 'text/css'), $css); $out->style($css);
} }
} }

55
classes/Login_token.php Normal file
View File

@ -0,0 +1,55 @@
<?php
/**
* Table Definition for login_token
*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2009, StatusNet, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Login_token extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'login_token'; // table name
public $user_id; // int(4) primary_key not_null
public $token; // char(32) not_null
public $created; // datetime() not_null
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
/* Static get */
function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('Login_token',$k,$v); }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
/*
DB_DataObject calculates the sequence key(s) by taking the first key returned by the keys() function.
In this case, the keys() function returns user_id as the first key. user_id is not a sequence, but
DB_DataObject's sequenceKey() will incorrectly think it is. Then, since the sequenceKey() is a numeric
type, but is not set to autoincrement in the database, DB_DataObject will create a _seq table and
manage the sequence itself. This is not the correct behavior for the user_id in this class.
So we override that incorrect behavior, and simply say there is no sequence key.
*/
function sequenceKey()
{
return array(false,false);
}
}

View File

@ -23,6 +23,20 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Memcached_DataObject extends DB_DataObject class Memcached_DataObject extends DB_DataObject
{ {
/**
* Destructor to free global memory resources associated with
* this data object when it's unset or goes out of scope.
* DB_DataObject doesn't do this yet by itself.
*/
function __destruct()
{
$this->free();
if (method_exists('DB_DataObject', '__destruct')) {
parent::__destruct();
}
}
function &staticGet($cls, $k, $v=null) function &staticGet($cls, $k, $v=null)
{ {
if (is_null($v)) { if (is_null($v)) {

View File

@ -89,4 +89,12 @@ class Message extends Memcached_DataObject
$contentlimit = self::maxContent(); $contentlimit = self::maxContent();
return ($contentlimit > 0 && !empty($content) && (mb_strlen($content) > $contentlimit)); return ($contentlimit > 0 && !empty($content) && (mb_strlen($content) > $contentlimit));
} }
function notify()
{
$from = User::staticGet('id', $this->from_profile);
$to = User::staticGet('id', $this->to_profile);
mail_notify_message($this, $from, $to);
}
} }

View File

@ -55,13 +55,13 @@ class Notice extends Memcached_DataObject
public $__table = 'notice'; // table name public $__table = 'notice'; // table name
public $id; // int(4) primary_key not_null public $id; // int(4) primary_key not_null
public $profile_id; // int(4) not_null public $profile_id; // int(4) multiple_key not_null
public $uri; // varchar(255) unique_key public $uri; // varchar(255) unique_key
public $content; // text() public $content; // text
public $rendered; // text() public $rendered; // text
public $url; // varchar(255) public $url; // varchar(255)
public $created; // datetime() not_null public $created; // datetime multiple_key not_null default_0000-00-00%2000%3A00%3A00
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
public $reply_to; // int(4) public $reply_to; // int(4)
public $is_local; // tinyint(1) public $is_local; // tinyint(1)
public $source; // varchar(32) public $source; // varchar(32)
@ -70,9 +70,11 @@ class Notice extends Memcached_DataObject
public $lon; // decimal(10,7) public $lon; // decimal(10,7)
public $location_id; // int(4) public $location_id; // int(4)
public $location_ns; // int(4) public $location_ns; // int(4)
public $repeat_of; // int(4)
/* Static get */ /* Static get */
function staticGet($k,$v=NULL) { function staticGet($k,$v=NULL)
{
return Memcached_DataObject::staticGet('Notice',$k,$v); return Memcached_DataObject::staticGet('Notice',$k,$v);
} }
@ -113,6 +115,12 @@ class Notice extends Memcached_DataObject
//Null any notices that are replies to this notice //Null any notices that are replies to this notice
$this->query(sprintf("UPDATE notice set reply_to = null WHERE reply_to = %d", $this->id)); $this->query(sprintf("UPDATE notice set reply_to = null WHERE reply_to = %d", $this->id));
//Null any notices that are repeats of this notice
//XXX: probably need to uncache these, too
$this->query(sprintf("UPDATE notice set repeat_of = null WHERE repeat_of = %d", $this->id));
$related = array('Reply', $related = array('Reply',
'Fave', 'Fave',
'Notice_tag', 'Notice_tag',
@ -167,9 +175,48 @@ class Notice extends Memcached_DataObject
} }
} }
static function saveNew($profile_id, $content, $source=null, /**
$is_local=Notice::LOCAL_PUBLIC, $reply_to=null, $uri=null, $created=null, * Save a new notice and push it out to subscribers' inboxes.
$lat=null, $lon=null, $location_id=null, $location_ns=null) { * Poster's permissions are checked before sending.
*
* @param int $profile_id Profile ID of the poster
* @param string $content source message text; links may be shortened
* per current user's preference
* @param string $source source key ('web', 'api', etc)
* @param array $options Associative array of optional properties:
* string 'created' timestamp of notice; defaults to now
* int 'is_local' source/gateway ID, one of:
* Notice::LOCAL_PUBLIC - Local, ok to appear in public timeline
* Notice::REMOTE_OMB - Sent from a remote OMB service;
* hide from public timeline but show in
* local "and friends" timelines
* Notice::LOCAL_NONPUBLIC - Local, but hide from public timeline
* Notice::GATEWAY - From another non-OMB service;
* will not appear in public views
* float 'lat' decimal latitude for geolocation
* float 'lon' decimal longitude for geolocation
* int 'location_id' geoname identifier
* int 'location_ns' geoname namespace to interpret location_id
* int 'reply_to'; notice ID this is a reply to
* int 'repeat_of'; notice ID this is a repeat of
* string 'uri' permalink to notice; defaults to local notice URL
*
* @return Notice
* @throws ClientException
*/
static function saveNew($profile_id, $content, $source, $options=null) {
$defaults = array('uri' => null,
'reply_to' => null,
'repeat_of' => null);
if (!empty($options)) {
$options = $options + $defaults;
extract($options);
}
if (empty($is_local)) {
$is_local = Notice::LOCAL_PUBLIC;
}
$profile = Profile::staticGet($profile_id); $profile = Profile::staticGet($profile_id);
@ -225,7 +272,14 @@ class Notice extends Memcached_DataObject
$notice->source = $source; $notice->source = $source;
$notice->uri = $uri; $notice->uri = $uri;
$notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final); // Handle repeat case
if (isset($repeat_of)) {
$notice->repeat_of = $repeat_of;
$notice->reply_to = $repeat_of;
} else {
$notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final);
}
if (!empty($notice->reply_to)) { if (!empty($notice->reply_to)) {
$reply = Notice::staticGet('id', $notice->reply_to); $reply = Notice::staticGet('id', $notice->reply_to);
@ -423,10 +477,60 @@ class Notice extends Memcached_DataObject
$this->blowTagCache($blowLast); $this->blowTagCache($blowLast);
$this->blowGroupCache($blowLast); $this->blowGroupCache($blowLast);
$this->blowConversationCache($blowLast); $this->blowConversationCache($blowLast);
$this->blowRepeatCache();
$profile = Profile::staticGet($this->profile_id); $profile = Profile::staticGet($this->profile_id);
$profile->blowNoticeCount(); $profile->blowNoticeCount();
} }
function blowRepeatCache()
{
if (!empty($this->repeat_of)) {
$cache = common_memcache();
if (!empty($cache)) {
// XXX: only blow if <100 in cache
$ck = common_cache_key('notice:repeats:'.$this->repeat_of);
$result = $cache->delete($ck);
$user = User::staticGet('id', $this->profile_id);
if (!empty($user)) {
$uk = common_cache_key('user:repeated_by_me:'.$user->id);
$cache->delete($uk);
$user->free();
unset($user);
}
$original = Notice::staticGet('id', $this->repeat_of);
if (!empty($original)) {
$originalUser = User::staticGet('id', $original->profile_id);
if (!empty($originalUser)) {
$ouk = common_cache_key('user:repeats_of_me:'.$originalUser->id);
$cache->delete($ouk);
$originalUser->free();
unset($originalUser);
}
$original->free();
unset($original);
}
$ni = new Notice_inbox();
$ni->notice_id = $this->id;
if ($ni->find()) {
while ($ni->fetch()) {
$tmk = common_cache_key('user:repeated_to_me:'.$ni->user_id);
$cache->delete($tmk);
}
}
$ni->free();
unset($ni);
}
}
}
function blowConversationCache($blowLast=false) function blowConversationCache($blowLast=false)
{ {
$cache = common_memcache(); $cache = common_memcache();
@ -456,8 +560,18 @@ class Notice extends Memcached_DataObject
if ($member->find()) { if ($member->find()) {
while ($member->fetch()) { while ($member->fetch()) {
$cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id)); $cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id));
$cache->delete(common_cache_key('notice_inbox:by_user_own:' . $member->profile_id));
if (empty($this->repeat_of)) {
$cache->delete(common_cache_key('user:friends_timeline:' . $member->profile_id));
$cache->delete(common_cache_key('user:friends_timeline_own:' . $member->profile_id));
}
if ($blowLast) { if ($blowLast) {
$cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id . ';last')); $cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id . ';last'));
$cache->delete(common_cache_key('notice_inbox:by_user_own:' . $member->profile_id . ';last'));
if (empty($this->repeat_of)) {
$cache->delete(common_cache_key('user:friends_timeline:' . $member->profile_id . ';last'));
$cache->delete(common_cache_key('user:friends_timeline_own:' . $member->profile_id . ';last'));
}
} }
} }
} }
@ -505,9 +619,17 @@ class Notice extends Memcached_DataObject
while ($user->fetch()) { while ($user->fetch()) {
$cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id)); $cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id));
$cache->delete(common_cache_key('notice_inbox:by_user_own:'.$user->id)); $cache->delete(common_cache_key('notice_inbox:by_user_own:'.$user->id));
if (empty($this->repeat_of)) {
$cache->delete(common_cache_key('user:friends_timeline:'.$user->id));
$cache->delete(common_cache_key('user:friends_timeline_own:'.$user->id));
}
if ($blowLast) { if ($blowLast) {
$cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id.';last')); $cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id.';last'));
$cache->delete(common_cache_key('notice_inbox:by_user_own:'.$user->id.';last')); $cache->delete(common_cache_key('notice_inbox:by_user_own:'.$user->id.';last'));
if (empty($this->repeat_of)) {
$cache->delete(common_cache_key('user:friends_timeline:'.$user->id.';last'));
$cache->delete(common_cache_key('user:friends_timeline_own:'.$user->id.';last'));
}
} }
} }
$user->free(); $user->free();
@ -581,193 +703,6 @@ class Notice extends Memcached_DataObject
} }
} }
# XXX: too many args; we need to move to named params or even a separate
# class for notice streams
static function getStream($qry, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0, $order=null, $since=null) {
if (common_config('memcached', 'enabled')) {
# Skip the cache if this is a since, since_id or max_id qry
if ($since_id > 0 || $max_id > 0 || $since) {
return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since);
} else {
return Notice::getCachedStream($qry, $cachekey, $offset, $limit, $order);
}
}
return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since);
}
static function getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since) {
$needAnd = false;
$needWhere = true;
if (preg_match('/\bWHERE\b/i', $qry)) {
$needWhere = false;
$needAnd = true;
}
if ($since_id > 0) {
if ($needWhere) {
$qry .= ' WHERE ';
$needWhere = false;
} else {
$qry .= ' AND ';
}
$qry .= ' notice.id > ' . $since_id;
}
if ($max_id > 0) {
if ($needWhere) {
$qry .= ' WHERE ';
$needWhere = false;
} else {
$qry .= ' AND ';
}
$qry .= ' notice.id <= ' . $max_id;
}
if ($since) {
if ($needWhere) {
$qry .= ' WHERE ';
$needWhere = false;
} else {
$qry .= ' AND ';
}
$qry .= ' notice.created > \'' . date('Y-m-d H:i:s', $since) . '\'';
}
# Allow ORDER override
if ($order) {
$qry .= $order;
} else {
$qry .= ' ORDER BY notice.created DESC, notice.id DESC ';
}
if (common_config('db','type') == 'pgsql') {
$qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
} else {
$qry .= ' LIMIT ' . $offset . ', ' . $limit;
}
$notice = new Notice();
$notice->query($qry);
return $notice;
}
# XXX: this is pretty long and should probably be broken up into
# some helper functions
static function getCachedStream($qry, $cachekey, $offset, $limit, $order) {
# If outside our cache window, just go to the DB
if ($offset + $limit > NOTICE_CACHE_WINDOW) {
return Notice::getStreamDirect($qry, $offset, $limit, null, null, $order, null);
}
# Get the cache; if we can't, just go to the DB
$cache = common_memcache();
if (empty($cache)) {
return Notice::getStreamDirect($qry, $offset, $limit, null, null, $order, null);
}
# Get the notices out of the cache
$notices = $cache->get(common_cache_key($cachekey));
# On a cache hit, return a DB-object-like wrapper
if ($notices !== false) {
$wrapper = new ArrayWrapper(array_slice($notices, $offset, $limit));
return $wrapper;
}
# If the cache was invalidated because of new data being
# added, we can try and just get the new stuff. We keep an additional
# copy of the data at the key + ';last'
# No cache hit. Try to get the *last* cached version
$last_notices = $cache->get(common_cache_key($cachekey) . ';last');
if ($last_notices) {
# Reverse-chron order, so last ID is last.
$last_id = $last_notices[0]->id;
# XXX: this assumes monotonically increasing IDs; a fair
# bet with our DB.
$new_notice = Notice::getStreamDirect($qry, 0, NOTICE_CACHE_WINDOW,
$last_id, null, $order, null);
if ($new_notice) {
$new_notices = array();
while ($new_notice->fetch()) {
$new_notices[] = clone($new_notice);
}
$new_notice->free();
$notices = array_slice(array_merge($new_notices, $last_notices),
0, NOTICE_CACHE_WINDOW);
# Store the array in the cache for next time
$result = $cache->set(common_cache_key($cachekey), $notices);
$result = $cache->set(common_cache_key($cachekey) . ';last', $notices);
# return a wrapper of the array for use now
return new ArrayWrapper(array_slice($notices, $offset, $limit));
}
}
# Otherwise, get the full cache window out of the DB
$notice = Notice::getStreamDirect($qry, 0, NOTICE_CACHE_WINDOW, null, null, $order, null);
# If there are no hits, just return the value
if (empty($notice)) {
return $notice;
}
# Pack results into an array
$notices = array();
while ($notice->fetch()) {
$notices[] = clone($notice);
}
$notice->free();
# Store the array in the cache for next time
$result = $cache->set(common_cache_key($cachekey), $notices);
$result = $cache->set(common_cache_key($cachekey) . ';last', $notices);
# return a wrapper of the array for use now
$wrapper = new ArrayWrapper(array_slice($notices, $offset, $limit));
return $wrapper;
}
function getStreamByIds($ids) function getStreamByIds($ids)
{ {
$cache = common_memcache(); $cache = common_memcache();
@ -788,10 +723,24 @@ class Notice extends Memcached_DataObject
return $notice; return $notice;
} }
$notice->whereAdd('id in (' . implode(', ', $ids) . ')'); $notice->whereAdd('id in (' . implode(', ', $ids) . ')');
$notice->orderBy('id DESC');
$notice->find(); $notice->find();
return $notice;
$temp = array();
while ($notice->fetch()) {
$temp[$notice->id] = clone($notice);
}
$wrapped = array();
foreach ($ids as $id) {
if (array_key_exists($id, $temp)) {
$wrapped[] = $temp[$id];
}
}
return new ArrayWrapper($wrapped);
} }
} }
@ -948,39 +897,7 @@ class Notice extends Memcached_DataObject
} }
} }
$cnt = 0; Notice_inbox::bulkInsert($this->id, $this->created, $ni);
$qryhdr = 'INSERT INTO notice_inbox (user_id, notice_id, source, created) VALUES ';
$qry = $qryhdr;
foreach ($ni as $id => $source) {
if ($cnt > 0) {
$qry .= ', ';
}
$qry .= '('.$id.', '.$this->id.', '.$source.", '".$this->created. "') ";
$cnt++;
if (rand() % NOTICE_INBOX_SOFT_LIMIT == 0) {
// FIXME: Causes lag in replicated servers
// Notice_inbox::gc($id);
}
if ($cnt >= MAX_BOXCARS) {
$inbox = new Notice_inbox();
$result = $inbox->query($qry);
if (PEAR::isError($result)) {
common_log_db_error($inbox, $qry);
}
$qry = $qryhdr;
$cnt = 0;
}
}
if ($cnt > 0) {
$inbox = new Notice_inbox();
$result = $inbox->query($qry);
if (PEAR::isError($result)) {
common_log_db_error($inbox, $qry);
}
}
return; return;
} }
@ -1079,6 +996,9 @@ class Notice extends Memcached_DataObject
return true; return true;
} }
/**
* @return array of integer profile IDs
*/
function saveReplies() function saveReplies()
{ {
// Alternative reply format // Alternative reply format
@ -1157,8 +1077,8 @@ class Notice extends Memcached_DataObject
$recipientIds = array_keys($replied); $recipientIds = array_keys($replied);
foreach ($recipientIds as $recipient) { foreach ($recipientIds as $recipientId) {
$user = User::staticGet('id', $recipient); $user = User::staticGet('id', $recipientId);
if ($user) { if ($user) {
mail_notify_attn($user, $this); mail_notify_attn($user, $this);
} }
@ -1441,4 +1361,72 @@ class Notice extends Memcached_DataObject
return $location; return $location;
} }
function repeat($repeater_id, $source)
{
$author = Profile::staticGet('id', $this->profile_id);
// FIXME: truncate on long repeats...?
$content = sprintf(_('RT @%1$s %2$s'),
$author->nickname,
$this->content);
return self::saveNew($repeater_id, $content, $source,
array('repeat_of' => $this->id));
}
// These are supposed to be in chron order!
function repeatStream($limit=100)
{
$cache = common_memcache();
if (empty($cache)) {
$ids = $this->_repeatStreamDirect($limit);
} else {
$idstr = $cache->get(common_cache_key('notice:repeats:'.$this->id));
if (!empty($idstr)) {
$ids = explode(',', $idstr);
} else {
$ids = $this->_repeatStreamDirect(100);
$cache->set(common_cache_key('notice:repeats:'.$this->id), implode(',', $ids));
}
if ($limit < 100) {
// We do a max of 100, so slice down to limit
$ids = array_slice($ids, 0, $limit);
}
}
return Notice::getStreamByIds($ids);
}
function _repeatStreamDirect($limit)
{
$notice = new Notice();
$notice->selectAdd(); // clears it
$notice->selectAdd('id');
$notice->repeat_of = $this->id;
$notice->orderBy('created'); // NB: asc!
if (!is_null($offset)) {
$notice->limit($offset, $limit);
}
$ids = array();
if ($notice->find()) {
while ($notice->fetch()) {
$ids[] = $notice->id;
}
}
$notice->free();
$notice = NULL;
return $ids;
}
} }

View File

@ -32,6 +32,7 @@ define('NOTICE_INBOX_SOFT_LIMIT', 1000);
define('NOTICE_INBOX_SOURCE_SUB', 1); define('NOTICE_INBOX_SOURCE_SUB', 1);
define('NOTICE_INBOX_SOURCE_GROUP', 2); define('NOTICE_INBOX_SOURCE_GROUP', 2);
define('NOTICE_INBOX_SOURCE_REPLY', 3); define('NOTICE_INBOX_SOURCE_REPLY', 3);
define('NOTICE_INBOX_SOURCE_FORWARD', 4);
define('NOTICE_INBOX_SOURCE_GATEWAY', -1); define('NOTICE_INBOX_SOURCE_GATEWAY', -1);
class Notice_inbox extends Memcached_DataObject class Notice_inbox extends Memcached_DataObject
@ -83,7 +84,7 @@ class Notice_inbox extends Memcached_DataObject
$inbox->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\''); $inbox->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\'');
} }
$inbox->orderBy('notice_id DESC'); $inbox->orderBy('created DESC');
if (!is_null($offset)) { if (!is_null($offset)) {
$inbox->limit($offset, $limit); $inbox->limit($offset, $limit);
@ -141,4 +142,43 @@ class Notice_inbox extends Memcached_DataObject
'WHERE user_id = ' . $user_id . ' ' . 'WHERE user_id = ' . $user_id . ' ' .
'AND notice_id in ('.implode(',', $notices).')'); 'AND notice_id in ('.implode(',', $notices).')');
} }
static function bulkInsert($notice_id, $created, $ni)
{
$cnt = 0;
$qryhdr = 'INSERT INTO notice_inbox (user_id, notice_id, source, created) VALUES ';
$qry = $qryhdr;
foreach ($ni as $id => $source) {
if ($cnt > 0) {
$qry .= ', ';
}
$qry .= '('.$id.', '.$notice_id.', '.$source.", '".$created. "') ";
$cnt++;
if (rand() % NOTICE_INBOX_SOFT_LIMIT == 0) {
// FIXME: Causes lag in replicated servers
// Notice_inbox::gc($id);
}
if ($cnt >= MAX_BOXCARS) {
$inbox = new Notice_inbox();
$result = $inbox->query($qry);
if (PEAR::isError($result)) {
common_log_db_error($inbox, $qry);
}
$qry = $qryhdr;
$cnt = 0;
}
}
if ($cnt > 0) {
$inbox = new Notice_inbox();
$result = $inbox->query($qry);
if (PEAR::isError($result)) {
common_log_db_error($inbox, $qry);
}
}
return;
}
} }

View File

@ -716,4 +716,15 @@ class Profile extends Memcached_DataObject
} }
return $result; return $result;
} }
function hasRepeated($notice_id)
{
// XXX: not really a pkey, but should work
$notice = Memcached_DataObject::pkeyGet('Notice',
array('profile_id' => $this->id,
'repeat_of' => $notice_id));
return !empty($notice);
}
} }

View File

@ -180,6 +180,27 @@ class User extends Memcached_DataObject
return $result; return $result;
} }
/**
* Register a new user account and profile and set up default subscriptions.
* If a new-user welcome message is configured, this will be sent.
*
* @param array $fields associative array of optional properties
* string 'bio'
* string 'email'
* bool 'email_confirmed' pass true to mark email as pre-confirmed
* string 'fullname'
* string 'homepage'
* string 'location' informal string description of geolocation
* float 'lat' decimal latitude for geolocation
* float 'lon' decimal longitude for geolocation
* int 'location_id' geoname identifier
* int 'location_ns' geoname namespace to interpret location_id
* string 'nickname' REQUIRED
* string 'password' (may be missing for eg OpenID registrations)
* string 'code' invite code
* ?string 'uri' permalink to notice; defaults to local notice URL
* @return mixed User object or false on failure
*/
static function register($fields) { static function register($fields) {
// MAGICALLY put fields into current scope // MAGICALLY put fields into current scope
@ -329,7 +350,7 @@ class User extends Memcached_DataObject
$profile->query('COMMIT'); $profile->query('COMMIT');
if ($email && !$user->email) { if (!empty($email) && !$user->email) {
mail_confirm_address($user, $confirm->code, $profile->nickname, $email); mail_confirm_address($user, $confirm->code, $profile->nickname, $email);
} }
@ -473,6 +494,77 @@ class User extends Memcached_DataObject
return Notice::getStreamByIds($ids); return Notice::getStreamByIds($ids);
} }
function friendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
{
$ids = Notice::stream(array($this, '_friendsTimelineDirect'),
array(false),
'user:friends_timeline:'.$this->id,
$offset, $limit, $since_id, $before_id, $since);
return Notice::getStreamByIds($ids);
}
function ownFriendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
{
$ids = Notice::stream(array($this, '_friendsTimelineDirect'),
array(true),
'user:friends_timeline_own:'.$this->id,
$offset, $limit, $since_id, $before_id, $since);
return Notice::getStreamByIds($ids);
}
function _friendsTimelineDirect($own, $offset, $limit, $since_id, $max_id, $since)
{
$qry =
'SELECT notice.id AS id ' .
'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' .
'WHERE notice_inbox.user_id = ' . $this->id . ' ' .
'AND notice.repeat_of IS NULL ';
if (!$own) {
// XXX: autoload notice inbox for constant
$inbox = new Notice_inbox();
$qry .= 'AND notice_inbox.source != ' . NOTICE_INBOX_SOURCE_GATEWAY . ' ';
}
if ($since_id != 0) {
$qry .= 'AND notice.id > ' . $since_id . ' ';
}
if ($max_id != 0) {
$qry .= 'AND notice.id <= ' . $max_id . ' ';
}
if (!is_null($since)) {
$qry .= 'AND notice.modified > \'' . date('Y-m-d H:i:s', $since) . '\' ';
}
// NOTE: we sort by fave time, not by notice time!
$qry .= 'ORDER BY notice.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;
}
function blowFavesCache() function blowFavesCache()
{ {
$cache = common_memcache(); $cache = common_memcache();
@ -502,6 +594,19 @@ class User extends Memcached_DataObject
{ {
// Add a new block record // Add a new block record
// no blocking (and thus unsubbing from) yourself
if ($this->id == $other->id) {
common_log(LOG_WARNING,
sprintf(
"Profile ID %d (%s) tried to block his or herself.",
$profile->id,
$profile->nickname
)
);
return false;
}
$block = new Profile_block(); $block = new Profile_block();
// Begin a transaction // Begin a transaction
@ -520,16 +625,7 @@ class User extends Memcached_DataObject
// Cancel their subscription, if it exists // Cancel their subscription, if it exists
$sub = Subscription::pkeyGet(array('subscriber' => $other->id, subs_unsubscribe_to($other->getUser(),$this->getProfile());
'subscribed' => $this->id));
if ($sub) {
$result = $sub->delete();
if (!$result) {
common_log_db_error($sub, 'DELETE', __FILE__);
return false;
}
}
$block->query('COMMIT'); $block->query('COMMIT');
@ -737,4 +833,163 @@ class User extends Memcached_DataObject
$profile = $this->getProfile(); $profile = $this->getProfile();
return $profile->isSilenced(); return $profile->isSilenced();
} }
function repeatedByMe($offset=0, $limit=20, $since_id=null, $max_id=null)
{
$ids = Notice::stream(array($this, '_repeatedByMeDirect'),
array(),
'user:repeated_by_me:'.$this->id,
$offset, $limit, $since_id, $max_id, null);
return Notice::getStreamByIds($ids);
}
function _repeatedByMeDirect($offset, $limit, $since_id, $max_id, $since)
{
$notice = new Notice();
$notice->selectAdd(); // clears it
$notice->selectAdd('id');
$notice->profile_id = $this->id;
$notice->whereAdd('repeat_of IS NOT NULL');
$notice->orderBy('id DESC');
if (!is_null($offset)) {
$notice->limit($offset, $limit);
}
if ($since_id != 0) {
$notice->whereAdd('id > ' . $since_id);
}
if ($max_id != 0) {
$notice->whereAdd('id <= ' . $max_id);
}
if (!is_null($since)) {
$notice->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\'');
}
$ids = array();
if ($notice->find()) {
while ($notice->fetch()) {
$ids[] = $notice->id;
}
}
$notice->free();
$notice = NULL;
return $ids;
}
function repeatsOfMe($offset=0, $limit=20, $since_id=null, $max_id=null)
{
$ids = Notice::stream(array($this, '_repeatsOfMeDirect'),
array(),
'user:repeats_of_me:'.$this->id,
$offset, $limit, $since_id, $max_id, null);
return Notice::getStreamByIds($ids);
}
function _repeatsOfMeDirect($offset, $limit, $since_id, $max_id, $since)
{
$qry =
'SELECT DISTINCT original.id AS id ' .
'FROM notice original JOIN notice rept ON original.id = rept.repeat_of ' .
'WHERE original.profile_id = ' . $this->id . ' ';
if ($since_id != 0) {
$qry .= 'AND original.id > ' . $since_id . ' ';
}
if ($max_id != 0) {
$qry .= 'AND original.id <= ' . $max_id . ' ';
}
if (!is_null($since)) {
$qry .= 'AND original.modified > \'' . date('Y-m-d H:i:s', $since) . '\' ';
}
// NOTE: we sort by fave time, not by notice time!
$qry .= 'ORDER BY 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;
}
function repeatedToMe($offset=0, $limit=20, $since_id=null, $max_id=null)
{
$ids = Notice::stream(array($this, '_repeatedToMeDirect'),
array(),
'user:repeated_to_me:'.$this->id,
$offset, $limit, $since_id, $max_id, null);
return Notice::getStreamByIds($ids);
}
function _repeatedToMeDirect($offset, $limit, $since_id, $max_id, $since)
{
$qry =
'SELECT notice.id AS id ' .
'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' .
'WHERE notice_inbox.user_id = ' . $this->id . ' ' .
'AND notice.repeat_of IS NOT NULL ';
if ($since_id != 0) {
$qry .= 'AND notice.id > ' . $since_id . ' ';
}
if ($max_id != 0) {
$qry .= 'AND notice.id <= ' . $max_id . ' ';
}
if (!is_null($since)) {
$qry .= 'AND notice.modified > \'' . date('Y-m-d H:i:s', $since) . '\' ';
}
// NOTE: we sort by fave time, not by notice time!
$qry .= 'ORDER BY notice.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;
}
} }

View File

@ -1,3 +1,4 @@
[avatar] [avatar]
profile_id = 129 profile_id = 129
original = 17 original = 17
@ -306,6 +307,7 @@ lat = 1
lon = 1 lon = 1
location_id = 1 location_id = 1
location_ns = 1 location_ns = 1
repeat_of = 1
[notice__keys] [notice__keys]
id = N id = N

View File

@ -154,7 +154,7 @@ $config['sphinx']['port'] = 3312;
// $config['queue']['subsystem'] = 'stomp'; // $config['queue']['subsystem'] = 'stomp';
// $config['queue']['stomp_server'] = 'tcp://localhost:61613'; // $config['queue']['stomp_server'] = 'tcp://localhost:61613';
// use different queue_basename for each statusnet instance managed by the server // use different queue_basename for each statusnet instance managed by the server
// $config['queue']['queue_basename'] = 'statusnet'; // $config['queue']['queue_basename'] = '/queue/statusnet/';
// The following customise the behaviour of the various daemons: // The following customise the behaviour of the various daemons:
// $config['daemon']['piddir'] = '/var/run'; // $config['daemon']['piddir'] = '/var/run';
@ -236,6 +236,11 @@ $config['sphinx']['port'] = 3312;
// Use a different hostname for SSL-encrypted pages // Use a different hostname for SSL-encrypted pages
// $config['site']['sslserver'] = 'secure.example.org'; // $config['site']['sslserver'] = 'secure.example.org';
// Indent HTML and XML
// Enable (default) for easier to read markup for developers,
// disable to save some bandwidth.
// $config['site']['indent'] = true;
// If you have a lot of status networks on the same server, you can // If you have a lot of status networks on the same server, you can
// store the site data in a database and switch as follows // store the site data in a database and switch as follows
// Status_network::setupDB('localhost', 'statusnet', 'statuspass', 'statusnet'); // Status_network::setupDB('localhost', 'statusnet', 'statuspass', 'statusnet');

View File

@ -4,8 +4,10 @@ alter table notice
add column lon decimal(10,7) comment 'longitude', add column lon decimal(10,7) comment 'longitude',
add column location_id integer comment 'location id if possible', add column location_id integer comment 'location id if possible',
add column location_ns integer comment 'namespace for location', add column location_ns integer comment 'namespace for location',
add column repeat_of integer comment 'notice this is a repeat of' references notice (id),
drop index notice_profile_id_idx, drop index notice_profile_id_idx,
add index notice_profile_id_idx (profile_id,created,id); add index notice_profile_id_idx (profile_id,created,id),
add index notice_repeatof_idx (repeat_of);
alter table message alter table message
modify column content text comment 'message content'; modify column content text comment 'message content';
@ -72,4 +74,13 @@ create table location_namespace (
created datetime not null comment 'date the record was created', created datetime not null comment 'date the record was created',
modified timestamp comment 'date this record was modified' modified timestamp comment 'date this record was modified'
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table login_token (
user_id integer not null comment 'user owning this token' references user (id),
token char(32) not null comment 'token useable for logging in',
created datetime not null comment 'date this record was created',
modified timestamp comment 'date this record was modified',
constraint primary key (user_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;

View File

@ -39,6 +39,25 @@ create table profile_role (
); );
create table location_namespace (
id integer /*comment 'identity for this namespace'*/,
description text /* comment 'description of the namespace'*/ ,
created integer not null /*comment 'date the record was created*/ ,
/* modified timestamp comment 'date this record was modified',*/
primary key (id)
);
create table login_token (
user_id integer not null /* comment 'user owning this token'*/ references "user" (id),
token char(32) not null /* comment 'token useable for logging in'*/,
created timestamp not null DEFAULT CURRENT_TIMESTAMP /* comment 'date this record was created'*/,
modified timestamp /* comment 'date this record was modified'*/,
primary key (user_id)
);
DROP index fave_user_id_idx; DROP index fave_user_id_idx;
CREATE index fave_user_id_idx on fave (user_id,modified); CREATE index fave_user_id_idx on fave (user_id,modified);
@ -55,9 +74,10 @@ ALTER TABLE notice ADD COLUMN lat decimal(10, 7) /* comment 'latitude'*/;
ALTER TABLE notice ADD COLUMN lon decimal(10,7) /* comment 'longitude'*/; ALTER TABLE notice ADD COLUMN lon decimal(10,7) /* comment 'longitude'*/;
ALTER TABLE notice ADD COLUMN location_id integer /* comment 'location id if possible'*/ ; ALTER TABLE notice ADD COLUMN location_id integer /* comment 'location id if possible'*/ ;
ALTER TABLE notice ADD COLUMN location_ns integer /* comment 'namespace for location'*/; ALTER TABLE notice ADD COLUMN location_ns integer /* comment 'namespace for location'*/;
ALTER TABLE notice ADD COLUMN repeat_of integer /* comment 'notice this is a repeat of' */ references notice (id);
ALTER TABLE profile ADD COLUMN lat decimal(10,7) /*comment 'latitude'*/ ; ALTER TABLE profile ADD COLUMN lat decimal(10,7) /*comment 'latitude'*/ ;
ALTER TABLE profile ADD COLUMN lon decimal(10,7) /*comment 'longitude'*/; ALTER TABLE profile ADD COLUMN lon decimal(10,7) /*comment 'longitude'*/;
ALTER TABLE profile ADD COLUMN location_id integer /* comment 'location id if possible'*/; ALTER TABLE profile ADD COLUMN location_id integer /* comment 'location id if possible'*/;
ALTER TABLE profile ADD COLUMN location_ns integer /* comment 'namespace for location'*/; ALTER TABLE profile ADD COLUMN location_ns integer /* comment 'namespace for location'*/;

View File

@ -129,11 +129,13 @@ create table notice (
lon decimal(10,7) comment 'longitude', lon decimal(10,7) comment 'longitude',
location_id integer comment 'location id if possible', location_id integer comment 'location id if possible',
location_ns integer comment 'namespace for location', location_ns integer comment 'namespace for location',
repeat_of integer comment 'notice this is a repeat of' references notice (id),
index notice_profile_id_idx (profile_id,created,id), index notice_profile_id_idx (profile_id,created,id),
index notice_conversation_idx (conversation), index notice_conversation_idx (conversation),
index notice_created_idx (created), index notice_created_idx (created),
index notice_replyto_idx (reply_to), index notice_replyto_idx (reply_to),
index notice_repeatof_idx (repeat_of),
FULLTEXT(content) FULLTEXT(content)
) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci; ) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
@ -575,3 +577,13 @@ create table location_namespace (
modified timestamp comment 'date this record was modified' modified timestamp comment 'date this record was modified'
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table login_token (
user_id integer not null comment 'user owning this token' references user (id),
token char(32) not null comment 'token useable for logging in',
created datetime not null comment 'date this record was created',
modified timestamp comment 'date this record was modified',
constraint primary key (user_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;

View File

@ -64,7 +64,7 @@ create table "user" (
emailnotifyfav integer default 1 /* comment 'Notify by email of favorites' */, emailnotifyfav integer default 1 /* comment 'Notify by email of favorites' */,
emailnotifynudge integer default 1 /* comment 'Notify by email of nudges' */, emailnotifynudge integer default 1 /* comment 'Notify by email of nudges' */,
emailnotifymsg integer default 1 /* comment 'Notify by email of direct messages' */, emailnotifymsg integer default 1 /* comment 'Notify by email of direct messages' */,
emailnotifyattn integer default 1 /* command 'Notify by email of @-replies' */, emailnotifyattn integer default 1 /* command 'Notify by email of @-replies' */,
emailmicroid integer default 1 /* comment 'whether to publish email microid' */, emailmicroid integer default 1 /* comment 'whether to publish email microid' */,
language varchar(50) /* comment 'preferred language' */, language varchar(50) /* comment 'preferred language' */,
timezone varchar(50) /* comment 'timezone' */, timezone varchar(50) /* comment 'timezone' */,
@ -82,7 +82,7 @@ create table "user" (
uri varchar(255) unique /* comment 'universally unique identifier, usually a tag URI' */, uri varchar(255) unique /* comment 'universally unique identifier, usually a tag URI' */,
autosubscribe integer default 0 /* comment 'automatically subscribe to users who subscribe to us' */, autosubscribe integer default 0 /* comment 'automatically subscribe to users who subscribe to us' */,
urlshorteningservice varchar(50) default 'ur1.ca' /* comment 'service to use for auto-shortening URLs' */, urlshorteningservice varchar(50) default 'ur1.ca' /* comment 'service to use for auto-shortening URLs' */,
inboxed integer default 0 /* comment 'has an inbox been created for this user?' */, inboxed integer default 0 /* comment 'has an inbox been created for this user?' */,
design_id integer /* comment 'id of a design' */references design(id), design_id integer /* comment 'id of a design' */references design(id),
viewdesigns integer default 1 /* comment 'whether to view user-provided designs'*/, viewdesigns integer default 1 /* comment 'whether to view user-provided designs'*/,
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */,
@ -135,7 +135,9 @@ create table notice (
lat decimal(10,7) /* comment 'latitude'*/ , lat decimal(10,7) /* comment 'latitude'*/ ,
lon decimal(10,7) /* comment 'longitude'*/ , lon decimal(10,7) /* comment 'longitude'*/ ,
location_id integer /* comment 'location id if possible'*/ , location_id integer /* comment 'location id if possible'*/ ,
location_ns integer /* comment 'namespace for location'*/ location_ns integer /* comment 'namespace for location'*/ ,
repeat_of integer /* comment 'notice this is a repeat of' */ references notice (id) ,
/* FULLTEXT(content) */ /* FULLTEXT(content) */
); );
@ -298,7 +300,7 @@ create table foreign_user (
nickname varchar(255) /* comment 'nickname on foreign service' */, nickname varchar(255) /* comment 'nickname on foreign service' */,
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */,
modified timestamp /* comment 'date this record was modified' */, modified timestamp /* comment 'date this record was modified' */,
primary key (id, service) primary key (id, service)
); );
@ -308,7 +310,7 @@ create table foreign_link (
service int not null /* comment 'foreign key to service' */ references foreign_service (id), service int not null /* comment 'foreign key to service' */ references foreign_service (id),
credentials varchar(255) /* comment 'authc credentials, typically a password' */, credentials varchar(255) /* comment 'authc credentials, typically a password' */,
noticesync int not null default 1 /* comment 'notice synchronisation, bit 1 = sync outgoing, bit 2 = sync incoming, bit 3 = filter local replies' */, noticesync int not null default 1 /* comment 'notice synchronisation, bit 1 = sync outgoing, bit 2 = sync incoming, bit 3 = filter local replies' */,
friendsync int not null default 2 /* comment 'friend synchronisation, bit 1 = sync outgoing, bit 2 = sync incoming */, friendsync int not null default 2 /* comment 'friend synchronisation, bit 1 = sync outgoing, bit 2 = sync incoming */,
profilesync int not null default 1 /* comment 'profile synchronization, bit 1 = sync outgoing, bit 2 = sync incoming' */, profilesync int not null default 1 /* comment 'profile synchronization, bit 1 = sync outgoing, bit 2 = sync incoming' */,
last_noticesync timestamp default null /* comment 'last time notices were imported' */, last_noticesync timestamp default null /* comment 'last time notices were imported' */,
last_friendsync timestamp default null /* comment 'last time friends were imported' */, last_friendsync timestamp default null /* comment 'last time friends were imported' */,
@ -324,7 +326,7 @@ create table foreign_subscription (
subscriber int not null /* comment 'subscriber on foreign service' */ , subscriber int not null /* comment 'subscriber on foreign service' */ ,
subscribed int not null /* comment 'subscribed user' */ , subscribed int not null /* comment 'subscribed user' */ ,
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */,
primary key (service, subscriber, subscribed) primary key (service, subscriber, subscribed)
); );
create index foreign_subscription_subscriber_idx on foreign_subscription using btree(subscriber); create index foreign_subscription_subscriber_idx on foreign_subscription using btree(subscriber);
@ -354,7 +356,7 @@ create table message (
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */,
modified timestamp /* comment 'date this record was modified' */, modified timestamp /* comment 'date this record was modified' */,
source varchar(32) /* comment 'source of comment, like "web", "im", or "clientname"' */ source varchar(32) /* comment 'source of comment, like "web", "im", or "clientname"' */
); );
create index message_from_idx on message using btree(from_profile); create index message_from_idx on message using btree(from_profile);
create index message_to_idx on message using btree(to_profile); create index message_to_idx on message using btree(to_profile);
@ -409,7 +411,6 @@ create table user_group (
mini_logo varchar(255) /* comment 'mini logo' */, mini_logo varchar(255) /* comment 'mini logo' */,
design_id integer /*comment 'id of a design' */ references design(id), design_id integer /*comment 'id of a design' */ references design(id),
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */,
modified timestamp /* comment 'date this record was modified' */ modified timestamp /* comment 'date this record was modified' */
@ -447,16 +448,15 @@ create table group_inbox (
); );
create index group_inbox_created_idx on group_inbox using btree(created); create index group_inbox_created_idx on group_inbox using btree(created);
/*attachments and URLs stuff */ /*attachments and URLs stuff */
create sequence file_seq; create sequence file_seq;
create table file ( create table file (
id bigint default nextval('file_seq') primary key /* comment 'unique identifier' */, id bigint default nextval('file_seq') primary key /* comment 'unique identifier' */,
url varchar(255) unique, url varchar(255) unique,
mimetype varchar(50), mimetype varchar(50),
size integer, size integer,
title varchar(255), title varchar(255),
date integer, date integer,
protected integer, protected integer,
filename text /* comment 'if a local file, name of the file' */, filename text /* comment 'if a local file, name of the file' */,
modified timestamp default CURRENT_TIMESTAMP /* comment 'date this record was modified'*/ modified timestamp default CURRENT_TIMESTAMP /* comment 'date this record was modified'*/
@ -467,38 +467,38 @@ create table file_oembed (
file_id bigint default nextval('file_oembed_seq') primary key /* comment 'unique identifier' */, file_id bigint default nextval('file_oembed_seq') primary key /* comment 'unique identifier' */,
version varchar(20), version varchar(20),
type varchar(20), type varchar(20),
mimetype varchar(50), mimetype varchar(50),
provider varchar(50), provider varchar(50),
provider_url varchar(255), provider_url varchar(255),
width integer, width integer,
height integer, height integer,
html text, html text,
title varchar(255), title varchar(255),
author_name varchar(50), author_name varchar(50),
author_url varchar(255), author_url varchar(255),
url varchar(255) url varchar(255)
); );
create sequence file_redirection_seq; create sequence file_redirection_seq;
create table file_redirection ( create table file_redirection (
url varchar(255) primary key, url varchar(255) primary key,
file_id bigint, file_id bigint,
redirections integer, redirections integer,
httpcode integer httpcode integer
); );
create sequence file_thumbnail_seq; create sequence file_thumbnail_seq;
create table file_thumbnail ( create table file_thumbnail (
file_id bigint primary key, file_id bigint primary key,
url varchar(255) unique, url varchar(255) unique,
width integer, width integer,
height integer height integer
); );
create sequence file_to_post_seq; create sequence file_to_post_seq;
create table file_to_post ( create table file_to_post (
file_id bigint, file_id bigint,
post_id bigint, post_id bigint,
primary key (file_id, post_id) primary key (file_id, post_id)
); );
@ -527,7 +527,7 @@ create table session (
id varchar(32) primary key /* comment 'session ID'*/, id varchar(32) primary key /* comment 'session ID'*/,
session_data text /* comment 'session data'*/, session_data text /* comment 'session data'*/,
created timestamp not null DEFAULT CURRENT_TIMESTAMP /* comment 'date this record was created'*/, created timestamp not null DEFAULT CURRENT_TIMESTAMP /* comment 'date this record was created'*/,
modified integer DEFAULT extract(epoch from CURRENT_TIMESTAMP) /* comment 'date this record was modified'*/ modified integer DEFAULT extract(epoch from CURRENT_TIMESTAMP) /* comment 'date this record was modified'*/
); );
create index session_modified_idx on session (modified); create index session_modified_idx on session (modified);
@ -543,7 +543,6 @@ create table deleted_notice (
CREATE index deleted_notice_profile_id_idx on deleted_notice (profile_id); CREATE index deleted_notice_profile_id_idx on deleted_notice (profile_id);
/* Textsearch stuff */ /* Textsearch stuff */
create index textsearch_idx on profile using gist(textsearch); create index textsearch_idx on profile using gist(textsearch);
@ -551,7 +550,6 @@ create index noticecontent_idx on notice using gist(to_tsvector('english',conten
create trigger textsearchupdate before insert or update on profile for each row create trigger textsearchupdate before insert or update on profile for each row
execute procedure tsvector_update_trigger(textsearch, 'pg_catalog.english', nickname, fullname, location, bio, homepage); execute procedure tsvector_update_trigger(textsearch, 'pg_catalog.english', nickname, fullname, location, bio, homepage);
create table config ( create table config (
section varchar(32) /* comment 'configuration section'*/, section varchar(32) /* comment 'configuration section'*/,
@ -570,4 +568,24 @@ create table profile_role (
primary key (profile_id, role) primary key (profile_id, role)
); );
create table location_namespace (
id integer /*comment 'identity for this namespace'*/,
description text /* comment 'description of the namespace'*/ ,
created integer not null /*comment 'date the record was created*/ ,
/* modified timestamp comment 'date this record was modified',*/
primary key (id)
);
create table login_token (
user_id integer not null /* comment 'user owning this token'*/ references "user" (id),
token char(32) not null /* comment 'token useable for logging in'*/,
created timestamp not null DEFAULT CURRENT_TIMESTAMP /* comment 'date this record was created'*/,
modified timestamp /* comment 'date this record was modified'*/,
primary key (user_id)
);

View File

@ -15,7 +15,7 @@
* @author Alan Knowles <alan@akbkhome.com> * @author Alan Knowles <alan@akbkhome.com>
* @copyright 1997-2006 The PHP Group * @copyright 1997-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01 * @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: DataObject.php 284150 2009-07-15 23:27:59Z alan_k $ * @version CVS: $Id: DataObject.php 291349 2009-11-27 09:15:18Z alan_k $
* @link http://pear.php.net/package/DB_DataObject * @link http://pear.php.net/package/DB_DataObject
*/ */
@ -235,7 +235,7 @@ class DB_DataObject extends DB_DataObject_Overload
* @access private * @access private
* @var string * @var string
*/ */
var $_DB_DataObject_version = "1.8.12"; var $_DB_DataObject_version = "1.9.0";
/** /**
* The Database table (used by table extends) * The Database table (used by table extends)
@ -309,7 +309,8 @@ class DB_DataObject extends DB_DataObject_Overload
/** /**
* An autoloading, caching static get method using key, value (based on get) * An autoloading, caching static get method using key, value (based on get)
* * (depreciated - use ::get / and your own caching method)
*
* Usage: * Usage:
* $object = DB_DataObject::staticGet("DbTable_mytable",12); * $object = DB_DataObject::staticGet("DbTable_mytable",12);
* or * or
@ -942,9 +943,13 @@ class DB_DataObject extends DB_DataObject_Overload
} }
$this->$key = $keyvalue; $this->$key = $keyvalue;
} }
// if we haven't set disable_null_strings to "full"
$ignore_null = !isset($options['disable_null_strings'])
|| !is_string($options['disable_null_strings'])
|| strtolower($options['disable_null_strings']) !== 'full' ;
foreach($items as $k => $v) { foreach($items as $k => $v) {
// if we are using autoincrement - skip the column... // if we are using autoincrement - skip the column...
@ -953,7 +958,10 @@ class DB_DataObject extends DB_DataObject_Overload
} }
if (!isset($this->$k)) {
// Ignore variables which aren't set to a value
if ( !isset($this->$k) && $ignore_null) {
continue; continue;
} }
// dont insert data into mysql timestamps // dont insert data into mysql timestamps
@ -980,8 +988,7 @@ class DB_DataObject extends DB_DataObject_Overload
} }
if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this,$k)) {
if (!isset($options['disable_null_strings']) && is_string($this->$k) && (strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
$rightq .= " NULL "; $rightq .= " NULL ";
continue; continue;
} }
@ -1194,8 +1201,14 @@ class DB_DataObject extends DB_DataObject_Overload
$options = $_DB_DATAOBJECT['CONFIG']; $options = $_DB_DATAOBJECT['CONFIG'];
$ignore_null = !isset($options['disable_null_strings'])
|| !is_string($options['disable_null_strings'])
|| strtolower($options['disable_null_strings']) !== 'full' ;
foreach($items as $k => $v) { foreach($items as $k => $v) {
if (!isset($this->$k)) {
if (!isset($this->$k) && $ignore_null) {
continue; continue;
} }
// ignore stuff thats // ignore stuff thats
@ -1234,7 +1247,7 @@ class DB_DataObject extends DB_DataObject_Overload
} }
// special values ... at least null is handled... // special values ... at least null is handled...
if (!isset($options['disable_null_strings']) && (strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) { if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this,$k)) {
$settings .= "$kSql = NULL "; $settings .= "$kSql = NULL ";
continue; continue;
} }
@ -1796,10 +1809,15 @@ class DB_DataObject extends DB_DataObject_Overload
} }
$_DB_DATAOBJECT['INI'][$this->_database] = array();
foreach ($schemas as $ini) { foreach ($schemas as $ini) {
if (file_exists($ini) && is_file($ini)) { if (file_exists($ini) && is_file($ini)) {
$_DB_DATAOBJECT['INI'][$this->_database] = parse_ini_file($ini, true);
$_DB_DATAOBJECT['INI'][$this->_database] = array_merge(
$_DB_DATAOBJECT['INI'][$this->_database],
parse_ini_file($ini, true)
);
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
if (!is_readable ($ini)) { if (!is_readable ($ini)) {
$this->debug("ini file is not readable: $ini","databaseStructure",1); $this->debug("ini file is not readable: $ini","databaseStructure",1);
@ -2478,7 +2496,8 @@ class DB_DataObject extends DB_DataObject_Overload
$x = new DB_DataObject; $x = new DB_DataObject;
$this->_query= $x->_query; $this->_query= $x->_query;
} }
foreach($keys as $k => $v) { foreach($keys as $k => $v) {
// index keys is an indexed array // index keys is an indexed array
/* these filter checks are a bit suspicious.. /* these filter checks are a bit suspicious..
@ -2519,7 +2538,7 @@ class DB_DataObject extends DB_DataObject_Overload
continue; continue;
} }
if (!isset($options['disable_null_strings']) && (strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) { if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this,$k)) {
$this->whereAdd(" $kSql IS NULL"); $this->whereAdd(" $kSql IS NULL");
continue; continue;
} }
@ -2624,15 +2643,31 @@ class DB_DataObject extends DB_DataObject_Overload
} }
// does this need multi db support?? // does this need multi db support??
$p = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ? $cp = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
$_DB_DATAOBJECT['CONFIG']['class_prefix'] : ''; explode(PATH_SEPARATOR, $_DB_DATAOBJECT['CONFIG']['class_prefix']) : '';
$class = $p . preg_replace('/[^A-Z0-9]/i','_',ucfirst($table));
$ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class);
$class = $ce ? $class : DB_DataObject::_autoloadClass($class); // multiprefix support.
$tbl = preg_replace('/[^A-Z0-9]/i','_',ucfirst($table));
if (is_array($cp)) {
$class = array();
foreach($cp as $cpr) {
$ce = substr(phpversion(),0,1) > 4 ? class_exists($cpr . $tbl,false) : class_exists($cpr . $tbl);
if ($ce) {
$class = $cpr . $tbl;
break;
}
$class[] = $cpr . $tbl;
}
} else {
$class = $tbl;
$ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class);
}
$rclass = $ce ? $class : DB_DataObject::_autoloadClass($class, $table);
// proxy = full|light // proxy = full|light
if (!$class && isset($_DB_DATAOBJECT['CONFIG']['proxy'])) { if (!$rclass && isset($_DB_DATAOBJECT['CONFIG']['proxy'])) {
DB_DataObject::debug("FAILED TO Autoload $database.$table - using proxy.","FACTORY",1); DB_DataObject::debug("FAILED TO Autoload $database.$table - using proxy.","FACTORY",1);
@ -2653,12 +2688,14 @@ class DB_DataObject extends DB_DataObject_Overload
return $x->$proxyMethod( $d->_database, $table); return $x->$proxyMethod( $d->_database, $table);
} }
if (!$class) { if (!$rclass) {
return DB_DataObject::raiseError( return DB_DataObject::raiseError(
"factory could not find class $class from $table", "factory could not find class " .
(is_array($class) ? implode(PATH_SEPARATOR, $class) : $class ).
"from $table",
DB_DATAOBJECT_ERROR_INVALIDCONFIG); DB_DATAOBJECT_ERROR_INVALIDCONFIG);
} }
$ret = new $class; $ret = new $rclass;
if (!empty($database)) { if (!empty($database)) {
DB_DataObject::debug("Setting database to $database","FACTORY",1); DB_DataObject::debug("Setting database to $database","FACTORY",1);
$ret->database($database); $ret->database($database);
@ -2668,11 +2705,12 @@ class DB_DataObject extends DB_DataObject_Overload
/** /**
* autoload Class * autoload Class
* *
* @param string $class Class * @param string|array $class Class
* @param string $table Table trying to load.
* @access private * @access private
* @return string classname on Success * @return string classname on Success
*/ */
function _autoloadClass($class) function _autoloadClass($class, $table=false)
{ {
global $_DB_DATAOBJECT; global $_DB_DATAOBJECT;
@ -2682,32 +2720,62 @@ class DB_DataObject extends DB_DataObject_Overload
$class_prefix = empty($_DB_DATAOBJECT['CONFIG']['class_prefix']) ? $class_prefix = empty($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
'' : $_DB_DATAOBJECT['CONFIG']['class_prefix']; '' : $_DB_DATAOBJECT['CONFIG']['class_prefix'];
$table = substr($class,strlen($class_prefix)); $table = $table ? $table : substr($class,strlen($class_prefix));
// only include the file if it exists - and barf badly if it has parse errors :) // only include the file if it exists - and barf badly if it has parse errors :)
if (!empty($_DB_DATAOBJECT['CONFIG']['proxy']) || empty($_DB_DATAOBJECT['CONFIG']['class_location'])) { if (!empty($_DB_DATAOBJECT['CONFIG']['proxy']) || empty($_DB_DATAOBJECT['CONFIG']['class_location'])) {
return false; return false;
} }
// support for:
// class_location = mydir/ => maps to mydir/Tablename.php
// class_location = mydir/myfile_%s.php => maps to mydir/myfile_Tablename
// with directory sepr
// class_location = mydir/:mydir2/: => tries all of thes locations.
$cl = $_DB_DATAOBJECT['CONFIG']['class_location'];
if (strpos($_DB_DATAOBJECT['CONFIG']['class_location'],'%s') !== false) { switch (true) {
$file = sprintf($_DB_DATAOBJECT['CONFIG']['class_location'], preg_replace('/[^A-Z0-9]/i','_',ucfirst($table))); case (strpos($cl ,'%s') !== false):
} else { $file = sprintf($cl , preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)));
$file = $_DB_DATAOBJECT['CONFIG']['class_location'].'/'.preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)).".php"; break;
case (strpos($cl , PATH_SEPARATOR) !== false):
$file = array();
foreach(explode(PATH_SEPARATOR, $cl ) as $p) {
$file[] = $p .'/'.preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)).".php";
}
break;
default:
$file = $cl .'/'.preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)).".php";
break;
} }
if (!file_exists($file)) { $cls = is_array($class) ? $class : array($class);
if (is_array($file) || !file_exists($file)) {
$found = false; $found = false;
foreach(explode(PATH_SEPARATOR, ini_get('include_path')) as $p) {
if (file_exists("$p/$file")) { $file = is_array($file) ? $file : array($file);
$file = "$p/$file"; $search = implode(PATH_SEPARATOR, $file);
$found = true; foreach($file as $f) {
foreach(explode(PATH_SEPARATOR, '' . PATH_SEPARATOR . ini_get('include_path')) as $p) {
$ff = empty($p) ? $f : "$p/$f";
if (file_exists($ff)) {
$file = $ff;
$found = true;
break;
}
}
if ($found) {
break; break;
} }
} }
if (!$found) { if (!$found) {
DB_DataObject::raiseError( DB_DataObject::raiseError(
"autoload:Could not find class {$class} using class_location value", "autoload:Could not find class " . implode(',', $cls) .
" using class_location value :" . $search .
" using include_path value :" . ini_get('include_path'),
DB_DATAOBJECT_ERROR_INVALIDCONFIG); DB_DATAOBJECT_ERROR_INVALIDCONFIG);
return false; return false;
} }
@ -2715,12 +2783,18 @@ class DB_DataObject extends DB_DataObject_Overload
include_once $file; include_once $file;
$ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class); $ce = false;
foreach($cls as $c) {
$ce = substr(phpversion(),0,1) > 4 ? class_exists($c,false) : class_exists($c);
if ($ce) {
$class = $c;
break;
}
}
if (!$ce) { if (!$ce) {
DB_DataObject::raiseError( DB_DataObject::raiseError(
"autoload:Could not autoload {$class}", "autoload:Could not autoload " . implode(',', $cls) ,
DB_DATAOBJECT_ERROR_INVALIDCONFIG); DB_DATAOBJECT_ERROR_INVALIDCONFIG);
return false; return false;
} }
@ -2786,7 +2860,7 @@ class DB_DataObject extends DB_DataObject_Overload
} }
$_DB_DATAOBJECT['LINKS'][$this->_database] = array();
foreach ($schemas as $ini) { foreach ($schemas as $ini) {
$links = $links =
@ -2794,9 +2868,13 @@ class DB_DataObject extends DB_DataObject_Overload
$_DB_DATAOBJECT['CONFIG']["links_{$this->_database}"] : $_DB_DATAOBJECT['CONFIG']["links_{$this->_database}"] :
str_replace('.ini','.links.ini',$ini); str_replace('.ini','.links.ini',$ini);
if (empty($_DB_DATAOBJECT['LINKS'][$this->_database]) && file_exists($links) && is_file($links)) { if (file_exists($links) && is_file($links)) {
/* not sure why $links = ... here - TODO check if that works */ /* not sure why $links = ... here - TODO check if that works */
$_DB_DATAOBJECT['LINKS'][$this->_database] = parse_ini_file($links, true); $_DB_DATAOBJECT['LINKS'][$this->_database] = array_merge(
$_DB_DATAOBJECT['LINKS'][$this->_database],
parse_ini_file($links, true)
);
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("Loaded links.ini file: $links","links",1); $this->debug("Loaded links.ini file: $links","links",1);
} }
@ -2977,14 +3055,12 @@ class DB_DataObject extends DB_DataObject_Overload
} }
if ($link) { if ($link) {
if ($obj->get($link, $this->$row)) { if ($obj->get($link, $this->$row)) {
$obj->free();
return $obj; return $obj;
} }
return false; return false;
} }
if ($obj->get($this->$row)) { if ($obj->get($this->$row)) {
$obj->free();
return $obj; return $obj;
} }
return false; return false;
@ -3315,14 +3391,23 @@ class DB_DataObject extends DB_DataObject_Overload
DB_DATAOBJECT_ERROR_INVALIDCONFIG); DB_DATAOBJECT_ERROR_INVALIDCONFIG);
return false; return false;
} }
$ignore_null = !isset($options['disable_null_strings'])
|| !is_string($options['disable_null_strings'])
|| strtolower($options['disable_null_strings']) !== 'full' ;
foreach($items as $k => $v) { foreach($items as $k => $v) {
if (!isset($obj->$k)) { if (!isset($obj->$k) && $ignore_null) {
continue; continue;
} }
$kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k); $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k);
if (DB_DataObject::_is_null($obj,$k)) {
$obj->whereAdd("{$joinAs}.{$kSql} IS NULL");
continue;
}
if ($v & DB_DATAOBJECT_STR) { if ($v & DB_DATAOBJECT_STR) {
$obj->whereAdd("{$joinAs}.{$kSql} = " . $this->_quote((string) ( $obj->whereAdd("{$joinAs}.{$kSql} = " . $this->_quote((string) (
@ -3344,14 +3429,9 @@ class DB_DataObject extends DB_DataObject_Overload
if (PEAR::isError($value)) { if (PEAR::isError($value)) {
$this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG); $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
return false; return false;
} }
if (!isset($options['disable_null_strings']) && strtolower($value) === 'null') { $obj->whereAdd("{$joinAs}.{$kSql} = $value");
$obj->whereAdd("{$joinAs}.{$kSql} IS NULL"); continue;
continue;
} else {
$obj->whereAdd("{$joinAs}.{$kSql} = $value");
continue;
}
} }
@ -3514,7 +3594,7 @@ class DB_DataObject extends DB_DataObject_Overload
continue; continue;
} }
if (!isset($from[sprintf($format,$k)])) { if (!isset($from[sprintf($format,$k)]) && !DB_DataObject::_is_null($from, sprintf($format,$k))) {
continue; continue;
} }
@ -3643,7 +3723,7 @@ class DB_DataObject extends DB_DataObject_Overload
// if not null - and it's not set....... // if not null - and it's not set.......
if (!isset($this->$key) && ($val & DB_DATAOBJECT_NOTNULL)) { if ($val & DB_DATAOBJECT_NOTNULL && DB_DataObject::_is_null($this, $key)) {
// dont check empty sequence key values.. // dont check empty sequence key values..
if (($key == $seq[0]) && ($seq[1] == true)) { if (($key == $seq[0]) && ($seq[1] == true)) {
continue; continue;
@ -3653,7 +3733,7 @@ class DB_DataObject extends DB_DataObject_Overload
} }
if (!isset($options['disable_null_strings']) && is_string($this->$key) && (strtolower($this->$key) == 'null')) { if (DB_DataObject::_is_null($this, $key)) {
if ($val & DB_DATAOBJECT_NOTNULL) { if ($val & DB_DATAOBJECT_NOTNULL) {
$this->debug("'null' field used for '$key', but it is defined as NOT NULL", 'VALIDATION', 4); $this->debug("'null' field used for '$key', but it is defined as NOT NULL", 'VALIDATION', 4);
$ret[$key] = false; $ret[$key] = false;
@ -3868,13 +3948,14 @@ class DB_DataObject extends DB_DataObject_Overload
//echo "FROM VALUE $col, {$cols[$col]}, $value\n"; //echo "FROM VALUE $col, {$cols[$col]}, $value\n";
switch (true) { switch (true) {
// set to null and column is can be null... // set to null and column is can be null...
case (!isset($options['disable_null_strings']) && (strtolower($value) == 'null') && (!($cols[$col] & DB_DATAOBJECT_NOTNULL))): case ((!($cols[$col] & DB_DATAOBJECT_NOTNULL)) && DB_DataObject::_is_null($value, false)):
case (is_object($value) && is_a($value,'DB_DataObject_Cast')): case (is_object($value) && is_a($value,'DB_DataObject_Cast')):
$this->$col = $value; $this->$col = $value;
return true; return true;
// fail on setting null on a not null field.. // fail on setting null on a not null field..
case (!isset($options['disable_null_strings']) && (strtolower($value) == 'null') && ($cols[$col] & DB_DATAOBJECT_NOTNULL)): case (($cols[$col] & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($value,false)):
return false; return false;
case (($cols[$col] & DB_DATAOBJECT_DATE) && ($cols[$col] & DB_DATAOBJECT_TIME)): case (($cols[$col] & DB_DATAOBJECT_DATE) && ($cols[$col] & DB_DATAOBJECT_TIME)):
@ -4189,9 +4270,65 @@ class DB_DataObject extends DB_DataObject_Overload
if (isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { if (isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->num_rows = array(); $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->num_rows = array();
} }
if (is_array($this->_link_loaded)) {
foreach ($this->_link_loaded as $do) {
$do->free();
}
}
} }
/**
* Evaluate whether or not a value is set to null, taking the 'disable_null_strings' option into account.
* If the value is a string set to "null" and the "disable_null_strings" option is not set to
* true, then the value is considered to be null.
* If the value is actually a PHP NULL value, and "disable_null_strings" has been set to
* the value "full", then it will also be considered null. - this can not differenticate between not set
*
*
* @param object|array $obj_or_ar
* @param string|false $prop prperty
* @access private
* @return bool object
*/
function _is_null($obj_or_ar , $prop)
{
global $_DB_DATAOBJECT;
$isset = $prop === false ? isset($obj_or_ar) :
(is_array($obj_or_ar) ? isset($obj_or_ar[$prop]) : isset($obj_or_ar->$prop));
$value = $isset ?
($prop === false ? $obj_or_ar :
(is_array($obj_or_ar) ? $obj_or_ar[$prop] : $obj_or_ar->$prop))
: null;
$options = $_DB_DATAOBJECT['CONFIG'];
$null_strings = !isset($options['disable_null_strings'])
|| $options['disable_null_strings'] === false;
$crazy_null = isset($options['disable_null_strings'])
&& is_string($options['disable_null_strings'])
&& strtolower($options['disable_null_strings'] === 'full');
if ( $null_strings && $isset && is_string($value) && (strtolower($value) === 'null') ) {
return true;
}
if ( $crazy_null && !$isset ) {
return true;
}
return false;
}
/* ---- LEGACY BC METHODS - NOT DOCUMENTED - See Documentation on New Methods. ---*/ /* ---- LEGACY BC METHODS - NOT DOCUMENTED - See Documentation on New Methods. ---*/
@ -4214,3 +4351,4 @@ if (!defined('DB_DATAOBJECT_NO_OVERLOAD')) {
} }
} }

View File

@ -15,7 +15,7 @@
* @author Alan Knowles <alan@akbkhome.com> * @author Alan Knowles <alan@akbkhome.com>
* @copyright 1997-2006 The PHP Group * @copyright 1997-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01 * @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: Generator.php 284150 2009-07-15 23:27:59Z alan_k $ * @version CVS: $Id: Generator.php 289384 2009-10-09 00:17:26Z alan_k $
* @link http://pear.php.net/package/DB_DataObject * @link http://pear.php.net/package/DB_DataObject
*/ */
@ -33,7 +33,7 @@
/** /**
* *
* Config _$ptions * Config _$ptions
* [DB_DataObject_Generator] * [DB_DataObject]
* ; optional default = DB/DataObject.php * ; optional default = DB/DataObject.php
* extends_location = * extends_location =
* ; optional default = DB_DataObject * ; optional default = DB_DataObject
@ -775,11 +775,9 @@ class DB_DataObject_Generator extends DB_DataObject
//echo "Generating Class files: \n"; //echo "Generating Class files: \n";
$options = &PEAR::getStaticProperty('DB_DataObject','options'); $options = &PEAR::getStaticProperty('DB_DataObject','options');
$this->_extends = empty($options['extends']) ? $this->_extends : $options['extends'];
if ($extends = @$options['extends']) { $this->_extendsFile = empty($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
$this->_extends = $extends;
$this->_extendsFile = $options['extends_location'];
}
foreach($this->tables as $this->table) { foreach($this->tables as $this->table) {
$this->table = trim($this->table); $this->table = trim($this->table);
@ -814,7 +812,7 @@ class DB_DataObject_Generator extends DB_DataObject
} }
/** /**
* class being extended (can be overridden by [DB_DataObject_Generator] extends=xxxx * class being extended (can be overridden by [DB_DataObject] extends=xxxx
* *
* @var string * @var string
* @access private * @access private
@ -1142,10 +1140,9 @@ class DB_DataObject_Generator extends DB_DataObject
$options = &PEAR::getStaticProperty('DB_DataObject','options'); $options = &PEAR::getStaticProperty('DB_DataObject','options');
$class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix']; $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix'];
if ($extends = @$options['extends']) { $this->_extends = empty($options['extends']) ? $this->_extends : $options['extends'];
$this->_extends = $extends; $this->_extendsFile = empty($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
$this->_extendsFile = $options['extends_location'];
}
$classname = $this->classname = $this->getClassNameFromTableName($this->table); $classname = $this->classname = $this->getClassNameFromTableName($this->table);
$out = $this->_generateClassTable(); $out = $this->_generateClassTable();

View File

@ -57,21 +57,31 @@ var SN = { // StatusNet
U: { // Utils U: { // Utils
FormNoticeEnhancements: function(form) { FormNoticeEnhancements: function(form) {
form_id = form.attr('id'); form_id = form.attr('id');
$('#'+form_id+' #'+SN.C.S.NoticeDataText).unbind('keyup');
$('#'+form_id+' #'+SN.C.S.NoticeDataText).unbind('keydown'); if (jQuery.data(form[0], 'ElementData') === undefined) {
if (maxLength > 0) { MaxLength = $('#'+form_id+' #'+SN.C.S.NoticeTextCount).text();
$('#'+form_id+' #'+SN.C.S.NoticeDataText).bind('keyup', function(e) { if (typeof(MaxLength) == 'undefined') {
MaxLength = SN.C.I.MaxLength;
}
jQuery.data(form[0], 'ElementData', {MaxLength:MaxLength});
SN.U.Counter(form);
NDT = $('#'+form_id+' #'+SN.C.S.NoticeDataText);
NDT.bind('keyup', function(e) {
SN.U.Counter(form); SN.U.Counter(form);
}); });
// run once in case there's something in there
SN.U.Counter(form); NDT.bind('keydown', function(e) {
SN.U.SubmitOnReturn(e, form);
});
}
else {
$('#'+form_id+' #'+SN.C.S.NoticeTextCount).text(jQuery.data(form[0], 'ElementData').MaxLength);
} }
$('#'+form_id+' #'+SN.C.S.NoticeDataText).bind('keydown', function(e) { if ($('body')[0].id != 'conversation') {
SN.U.SubmitOnReturn(e, form);
});
if($('body')[0].id != 'conversation') {
$('#'+form_id+' textarea').focus(); $('#'+form_id+' textarea').focus();
} }
}, },
@ -91,15 +101,14 @@ var SN = { // StatusNet
Counter: function(form) { Counter: function(form) {
SN.C.I.FormNoticeCurrent = form; SN.C.I.FormNoticeCurrent = form;
form_id = form.attr('id'); form_id = form.attr('id');
if (typeof(maxLength) == "undefined") {
maxLength = SN.C.I.MaxLength;
}
if (maxLength <= 0) { var MaxLength = jQuery.data(form[0], 'ElementData').MaxLength;
if (MaxLength <= 0) {
return; return;
} }
var remaining = maxLength - $('#'+form_id+' #'+SN.C.S.NoticeDataText).val().length; var remaining = MaxLength - $('#'+form_id+' #'+SN.C.S.NoticeDataText).val().length;
var counter = $('#'+form_id+' #'+SN.C.S.NoticeTextCount); var counter = $('#'+form_id+' #'+SN.C.S.NoticeTextCount);
if (remaining.toString() != counter.text()) { if (remaining.toString() != counter.text()) {
@ -184,30 +193,33 @@ var SN = { // StatusNet
form.removeClass(SN.C.S.Processing); form.removeClass(SN.C.S.Processing);
$('#'+form_id+' #'+SN.C.S.NoticeActionSubmit).removeClass(SN.C.S.Disabled); $('#'+form_id+' #'+SN.C.S.NoticeActionSubmit).removeClass(SN.C.S.Disabled);
$('#'+form_id+' #'+SN.C.S.NoticeActionSubmit).removeAttr(SN.C.S.Disabled, SN.C.S.Disabled); $('#'+form_id+' #'+SN.C.S.NoticeActionSubmit).removeAttr(SN.C.S.Disabled, SN.C.S.Disabled);
$('#'+form_id+' .form_response').remove();
if (textStatus == 'timeout') { if (textStatus == 'timeout') {
form.append('<p class="error>Sorry! We had trouble sending your notice. The servers are overloaded. Please try again, and contact the site administrator if this problem persists.</p>'); form.append('<p class="form_response error">Sorry! We had trouble sending your notice. The servers are overloaded. Please try again, and contact the site administrator if this problem persists.</p>');
} }
else { else {
if ($('.'+SN.C.S.Error, xhr.responseXML).length > 0) { if ($('.'+SN.C.S.Error, xhr.responseXML).length > 0) {
form.append(document._importNode($('.'+SN.C.S.Error, xhr.responseXML)[0], true)); form.append(document._importNode($('.'+SN.C.S.Error, xhr.responseXML)[0], true));
} }
else { else {
if(jQuery.inArray(parseInt(xhr.status), SN.C.I.HTTP20x30x) < 0) { if (parseInt(xhr.status) === 0 || jQuery.inArray(parseInt(xhr.status), SN.C.I.HTTP20x30x) >= 0) {
form.append('<p class="error>(Sorry! We had trouble sending your notice ('+xhr.status+' '+xhr.statusText+'). Please report the problem to the site administrator if this happens again.</p>'); $('#'+form_id).resetForm();
$('#'+form_id+' #'+SN.C.S.NoticeDataAttachSelected).remove();
SN.U.FormNoticeEnhancements($('#'+form_id));
} }
else { else {
$('#'+form_id+' #'+SN.C.S.NoticeDataText).val(''); form.append('<p class="form_response error">(Sorry! We had trouble sending your notice ('+xhr.status+' '+xhr.statusText+'). Please report the problem to the site administrator if this happens again.</p>');
SN.U.FormNoticeEnhancements($('#'+form_id));
} }
} }
} }
}, },
success: function(data, textStatus) { success: function(data, textStatus) {
$('#'+form_id+' .form_response').remove();
var result; var result;
if ($('#'+SN.C.S.Error, data).length > 0) { if ($('#'+SN.C.S.Error, data).length > 0) {
result = document._importNode($('p', data)[0], true); result = document._importNode($('p', data)[0], true);
result = result.textContent || result.innerHTML; result = result.textContent || result.innerHTML;
form.append('<p class="error">'+result+'</p>'); form.append('<p class="form_response error">'+result+'</p>');
} }
else { else {
if($('body')[0].id == 'bookmarklet') { if($('body')[0].id == 'bookmarklet') {
@ -217,7 +229,7 @@ var SN = { // StatusNet
if ($('#'+SN.C.S.CommandResult, data).length > 0) { if ($('#'+SN.C.S.CommandResult, data).length > 0) {
result = document._importNode($('p', data)[0], true); result = document._importNode($('p', data)[0], true);
result = result.textContent || result.innerHTML; result = result.textContent || result.innerHTML;
form.append('<p class="success">'+result+'</p>'); form.append('<p class="form_response success">'+result+'</p>');
} }
else { else {
var notices = $('#notices_primary .notices'); var notices = $('#notices_primary .notices');
@ -245,12 +257,10 @@ var SN = { // StatusNet
else { else {
result = document._importNode($('title', data)[0], true); result = document._importNode($('title', data)[0], true);
result_title = result.textContent || result.innerHTML; result_title = result.textContent || result.innerHTML;
form.append('<p class="success">'+result_title+'</p>'); form.append('<p class="form_response success">'+result_title+'</p>');
} }
} }
$('#'+form_id+' #'+SN.C.S.NoticeDataText).val(''); $('#'+form_id).resetForm();
$('#'+form_id+' #'+SN.C.S.NoticeDataAttach).val('');
$('#'+form_id+' #'+SN.C.S.NoticeInReplyTo).val('');
$('#'+form_id+' #'+SN.C.S.NoticeDataAttachSelected).remove(); $('#'+form_id+' #'+SN.C.S.NoticeDataAttachSelected).remove();
SN.U.FormNoticeEnhancements($('#'+form_id)); SN.U.FormNoticeEnhancements($('#'+form_id));
} }
@ -305,8 +315,12 @@ var SN = { // StatusNet
$('.form_disfavor').each(function() { SN.U.FormXHR($(this)); }); $('.form_disfavor').each(function() { SN.U.FormXHR($(this)); });
}, },
NoticeRepeat: function() {
$('.form_repeat').each(function() { SN.U.FormXHR($(this)); });
},
NoticeAttachments: function() { NoticeAttachments: function() {
$('.notice a.attachment').each(function() { $('.notice a.attachment').each(function() {
SN.U.NoticeWithAttachment($(this).closest('.notice')); SN.U.NoticeWithAttachment($(this).closest('.notice'));
}); });
}, },
@ -438,7 +452,7 @@ var SN = { // StatusNet
Notices: function() { Notices: function() {
if ($('body.user_in').length > 0) { if ($('body.user_in').length > 0) {
SN.U.NoticeFavor(); SN.U.NoticeFavor();
SN.U.NoticeRepeat();
SN.U.NoticeReply(); SN.U.NoticeReply();
} }
@ -452,6 +466,8 @@ var SN = { // StatusNet
$('.form_group_join').each(function() { SN.U.FormXHR($(this)); }); $('.form_group_join').each(function() { SN.U.FormXHR($(this)); });
$('.form_group_leave').each(function() { SN.U.FormXHR($(this)); }); $('.form_group_leave').each(function() { SN.U.FormXHR($(this)); });
$('.form_user_nudge').each(function() { SN.U.FormXHR($(this)); }); $('.form_user_nudge').each(function() { SN.U.FormXHR($(this)); });
SN.U.NewDirectMessage();
} }
} }
} }

View File

@ -68,7 +68,7 @@ class Action extends HTMLOutputter // lawsuit
* @see XMLOutputter::__construct * @see XMLOutputter::__construct
* @see HTMLOutputter::__construct * @see HTMLOutputter::__construct
*/ */
function __construct($output='php://output', $indent=true) function __construct($output='php://output', $indent=null)
{ {
parent::__construct($output, $indent); parent::__construct($output, $indent);
} }
@ -951,6 +951,36 @@ class Action extends HTMLOutputter // lawsuit
} }
} }
/**
* Integer value of an argument
*
* @param string $key query key we're interested in
* @param string $defValue optional default value (default null)
* @param string $maxValue optional max value (default null)
* @param string $minValue optional min value (default null)
*
* @return integer integer value
*/
function int($key, $defValue=null, $maxValue=null, $minValue=null)
{
$arg = strtolower($this->trimmed($key));
if (is_null($arg) || !is_integer($arg)) {
return $defValue;
}
if (!is_null($maxValue)) {
$arg = min($arg, $maxValue);
}
if (!is_null($minValue)) {
$arg = max($arg, $minValue);
}
return $arg;
}
/** /**
* Server error * Server error
* *

View File

@ -53,13 +53,14 @@ if (!defined('STATUSNET')) {
class ApiAction extends Action class ApiAction extends Action
{ {
var $format = null; var $format = null;
var $user = null; var $user = null;
var $page = null; var $auth_user = null;
var $count = null; var $page = null;
var $max_id = null; var $count = null;
var $since_id = null; var $max_id = null;
var $since = null; var $since_id = null;
var $since = null;
/** /**
* Initialization. * Initialization.
@ -190,13 +191,14 @@ class ApiAction extends Action
$twitter_user['following'] = false; $twitter_user['following'] = false;
$twitter_user['notifications'] = false; $twitter_user['notifications'] = false;
if (isset($apidata['user'])) { if (isset($this->auth_user)) {
$twitter_user['following'] = $apidata['user']->isSubscribed($profile); $twitter_user['following'] = $this->auth_user->isSubscribed($profile);
// Notifications on? // Notifications on?
$sub = Subscription::pkeyGet(array('subscriber' => $sub = Subscription::pkeyGet(array('subscriber' =>
$apidata['user']->id, 'subscribed' => $profile->id)); $this->auth_user->id,
'subscribed' => $profile->id));
if ($sub) { if ($sub) {
$twitter_user['notifications'] = ($sub->jabber || $sub->sms); $twitter_user['notifications'] = ($sub->jabber || $sub->sms);
@ -215,6 +217,21 @@ class ApiAction extends Action
} }
function twitterStatusArray($notice, $include_user=true) function twitterStatusArray($notice, $include_user=true)
{
$base = $this->twitterSimpleStatusArray($notice, $include_user);
if (!empty($notice->repeat_of)) {
$original = Notice::staticGet('id', $notice->repeat_of);
if (!empty($original)) {
$original_array = $this->twitterSimpleStatusArray($original, $include_user);
$base['retweeted_status'] = $original_array;
}
}
return $base;
}
function twitterSimpleStatusArray($notice, $include_user=true)
{ {
$profile = $notice->getProfile(); $profile = $notice->getProfile();
@ -448,9 +465,9 @@ class ApiAction extends Action
} }
} }
function showTwitterXmlStatus($twitter_status) function showTwitterXmlStatus($twitter_status, $tag='status')
{ {
$this->elementStart('status'); $this->elementStart($tag);
foreach($twitter_status as $element => $value) { foreach($twitter_status as $element => $value) {
switch ($element) { switch ($element) {
case 'user': case 'user':
@ -465,11 +482,14 @@ class ApiAction extends Action
case 'geo': case 'geo':
$this->showGeoRSS($value); $this->showGeoRSS($value);
break; break;
case 'retweeted_status':
$this->showTwitterXmlStatus($value, 'retweeted_status');
break;
default: default:
$this->element($element, null, $value); $this->element($element, null, $value);
} }
} }
$this->elementEnd('status'); $this->elementEnd($tag);
} }
function showTwitterXmlGroup($twitter_group) function showTwitterXmlGroup($twitter_group)
@ -588,7 +608,7 @@ class ApiAction extends Action
$this->endDocument('xml'); $this->endDocument('xml');
} }
function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null) function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null, $logo=null)
{ {
$this->initDocument('rss'); $this->initDocument('rss');
@ -602,6 +622,15 @@ class ApiAction extends Action
'href' => $suplink, 'href' => $suplink,
'type' => 'application/json')); 'type' => 'application/json'));
} }
if (!is_null($logo)) {
$this->elementStart('image');
$this->element('link', null, $link);
$this->element('title', null, $title);
$this->element('url', null, $logo);
$this->elementEnd('image');
}
$this->element('description', null, $subtitle); $this->element('description', null, $subtitle);
$this->element('language', null, 'en-us'); $this->element('language', null, 'en-us');
$this->element('ttl', null, '40'); $this->element('ttl', null, '40');
@ -621,7 +650,7 @@ class ApiAction extends Action
$this->endTwitterRss(); $this->endTwitterRss();
} }
function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null) function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null, $logo=null)
{ {
$this->initDocument('atom'); $this->initDocument('atom');
@ -630,6 +659,10 @@ class ApiAction extends Action
$this->element('id', null, $id); $this->element('id', null, $id);
$this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null); $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
if (!is_null($logo)) {
$this->element('logo',null,$logo);
}
if (!is_null($suplink)) { if (!is_null($suplink)) {
# For FriendFeed's SUP protocol # For FriendFeed's SUP protocol
$this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup', $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',

View File

@ -53,8 +53,6 @@ require_once INSTALLDIR . '/lib/api.php';
class ApiAuthAction extends ApiAction class ApiAuthAction extends ApiAction
{ {
var $auth_user = null;
/** /**
* Take arguments for running, and output basic auth header if needed * Take arguments for running, and output basic auth header if needed
* *

View File

@ -372,6 +372,7 @@ class MessageCommand extends Command
} }
$message = Message::saveNew($this->user->id, $other->id, $this->text, $channel->source()); $message = Message::saveNew($this->user->id, $other->id, $this->text, $channel->source());
if ($message) { if ($message) {
$message->notify();
$channel->output($this->user, sprintf(_('Direct message to %s sent'), $this->other)); $channel->output($this->user, sprintf(_('Direct message to %s sent'), $this->other));
} else { } else {
$channel->error($this->user, _('Error sending direct message.')); $channel->error($this->user, _('Error sending direct message.'));
@ -379,6 +380,65 @@ class MessageCommand extends Command
} }
} }
class RepeatCommand extends Command
{
var $other = null;
function __construct($user, $other)
{
parent::__construct($user);
$this->other = $other;
}
function execute($channel)
{
if(substr($this->other,0,1)=='#'){
//repeating a specific notice_id
$notice = Notice::staticGet(substr($this->other,1));
if (!$notice) {
$channel->error($this->user, _('Notice with that id does not exist'));
return;
}
$recipient = $notice->getProfile();
}else{
//repeating a given user's last notice
$recipient =
common_relative_profile($this->user, common_canonical_nickname($this->other));
if (!$recipient) {
$channel->error($this->user, _('No such user.'));
return;
}
$notice = $recipient->getCurrentNotice();
if (!$notice) {
$channel->error($this->user, _('User has no last notice'));
return;
}
}
if($this->user->id == $notice->profile_id)
{
$channel->error($this->user, _('Cannot repeat your own notice'));
return;
}
if ($recipient->hasRepeated($notice->id)) {
$channel->error($this->user, _('Already repeated that notice'));
return;
}
$repeat = $notice->repeat($this->user->id, $channel->source);
if ($repeat) {
common_broadcast_notice($repeat);
$channel->output($this->user, sprintf(_('Notice from %s repeated'), $recipient->nickname));
} else {
$channel->error($this->user, _('Error repeating notice.'));
}
}
}
class ReplyCommand extends Command class ReplyCommand extends Command
{ {
var $other = null; var $other = null;
@ -433,8 +493,9 @@ class ReplyCommand extends Command
return; return;
} }
$notice = Notice::saveNew($this->user->id, $this->text, $channel->source(), 1, $notice = Notice::saveNew($this->user->id, $this->text, $channel->source(),
$notice->id); array('reply_to' => $notice->id));
if ($notice) { if ($notice) {
$channel->output($this->user, sprintf(_('Reply to %s sent'), $recipient->nickname)); $channel->output($this->user, sprintf(_('Reply to %s sent'), $recipient->nickname));
} else { } else {
@ -579,6 +640,38 @@ class OnCommand extends Command
} }
} }
class LoginCommand extends Command
{
function execute($channel)
{
$disabled = common_config('logincommand','disabled');
$disabled = isset($disabled) && $disabled;
if($disabled) {
$channel->error($this->user, _('Login command is disabled'));
return;
}
$login_token = Login_token::staticGet('user_id',$this->user->id);
if($login_token){
$login_token->delete();
}
$login_token = new Login_token();
$login_token->user_id = $this->user->id;
$login_token->token = common_good_rand(16);
$login_token->created = common_sql_now();
$result = $login_token->insert();
if (!$result) {
common_log_db_error($login_token, 'INSERT', __FILE__);
$channel->error($this->user, sprintf(_('Could not create login token for %s'),
$this->user->nickname));
return;
}
$channel->output($this->user,
sprintf(_('This link is useable only once, and is good for only 2 minutes: %s'),
common_local_url('login',
array('user_id'=>$login_token->user_id, 'token'=>$login_token->token))));
}
}
class SubscriptionsCommand extends Command class SubscriptionsCommand extends Command
{ {
function execute($channel) function execute($channel)
@ -663,9 +756,12 @@ class HelpCommand extends Command
"whois <nickname> - get profile info on user\n". "whois <nickname> - get profile info on user\n".
"fav <nickname> - add user's last notice as a 'fave'\n". "fav <nickname> - add user's last notice as a 'fave'\n".
"fav #<notice_id> - add notice with the given id as a 'fave'\n". "fav #<notice_id> - add notice with the given id as a 'fave'\n".
"repeat #<notice_id> - repeat a notice with a given id\n".
"repeat <nickname> - repeat the last notice from user\n".
"reply #<notice_id> - reply to notice with a given id\n". "reply #<notice_id> - reply to notice with a given id\n".
"reply <nickname> - reply to the last notice from user\n". "reply <nickname> - reply to the last notice from user\n".
"join <group> - join group\n". "join <group> - join group\n".
"login - Get a link to login to the web interface\n".
"drop <group> - leave group\n". "drop <group> - leave group\n".
"stats - get your stats\n". "stats - get your stats\n".
"stop - same as 'off'\n". "stop - same as 'off'\n".

View File

@ -41,6 +41,12 @@ class CommandInterpreter
return null; return null;
} }
return new HelpCommand($user); return new HelpCommand($user);
case 'login':
if ($arg) {
return null;
} else {
return new LoginCommand($user);
}
case 'subscribers': case 'subscribers':
if ($arg) { if ($arg) {
return null; return null;
@ -163,6 +169,19 @@ class CommandInterpreter
} else { } else {
return new ReplyCommand($user, $other, $extra); return new ReplyCommand($user, $other, $extra);
} }
case 'repeat':
case 'rp':
case 'rt':
case 'rd':
if (!$arg) {
return null;
}
list($other, $extra) = $this->split_arg($arg);
if ($extra) {
return null;
} else {
return new RepeatCommand($user, $other);
}
case 'whois': case 'whois':
if (!$arg) { if (!$arg) {
return null; return null;

View File

@ -20,9 +20,9 @@
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
//exit with 200 response, if this is checking fancy from the installer //exit with 200 response, if this is checking fancy from the installer
if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; } if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; }
define('STATUSNET_VERSION', '0.9.0rc1'); define('STATUSNET_VERSION', '0.9.0rc2');
define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility
define('STATUSNET_CODENAME', 'Stand'); define('STATUSNET_CODENAME', 'Stand');

36
lib/curry.php Normal file
View File

@ -0,0 +1,36 @@
<?php
/*
* 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/>.
*/
/**
* PHP 5.3 implementation of function currying, using native closures.
* On 5.2 and lower we use the fallback implementation in util.php
*
* @param callback $fn
* @param ... any remaining arguments will be appended to call-time params
* @return callback
*/
function curry($fn) {
$extra_args = func_get_args();
array_shift($extra_args);
return function() use ($fn, $extra_args) {
$args = func_get_args();
return call_user_func_array($fn,
array_merge($args, $extra_args));
};
}

View File

@ -53,6 +53,7 @@ $default =
'shorturllength' => 30, 'shorturllength' => 30,
'dupelimit' => 60, # default for same person saying the same thing 'dupelimit' => 60, # default for same person saying the same thing
'textlimit' => 140, 'textlimit' => 140,
'indent' => true,
), ),
'db' => 'db' =>
array('database' => 'YOU HAVE TO SET THIS IN config.php', array('database' => 'YOU HAVE TO SET THIS IN config.php',
@ -74,7 +75,7 @@ $default =
array('enabled' => false, array('enabled' => false,
'subsystem' => 'db', # default to database, or 'stomp' 'subsystem' => 'db', # default to database, or 'stomp'
'stomp_server' => null, 'stomp_server' => null,
'queue_basename' => 'statusnet', 'queue_basename' => '/queue/statusnet/',
'stomp_username' => null, 'stomp_username' => null,
'stomp_password' => null, 'stomp_password' => null,
), ),
@ -228,4 +229,6 @@ $default =
array('namespace' => 1), // 1 = geonames, 2 = Yahoo Where on Earth array('namespace' => 1), // 1 = geonames, 2 = Yahoo Where on Earth
'omb' => 'omb' =>
array('timeout' => 5), // HTTP request timeout in seconds when contacting remote hosts for OMB updates array('timeout' => 5), // HTTP request timeout in seconds when contacting remote hosts for OMB updates
'logincommand' =>
array('disabled' => true),
); );

View File

@ -50,7 +50,7 @@ class ErrorAction extends Action
var $message = null; var $message = null;
var $default = null; var $default = null;
function __construct($message, $code, $output='php://output', $indent=true) function __construct($message, $code, $output='php://output', $indent=null)
{ {
parent::__construct($output, $indent); parent::__construct($output, $indent);

View File

@ -67,7 +67,7 @@ class HTMLOutputter extends XMLOutputter
* @param boolean $indent Whether to indent output, default true * @param boolean $indent Whether to indent output, default true
*/ */
function __construct($output='php://output', $indent=true) function __construct($output='php://output', $indent=null)
{ {
parent::__construct($output, $indent); parent::__construct($output, $indent);
} }
@ -350,14 +350,43 @@ class HTMLOutputter extends XMLOutputter
*/ */
function script($src, $type='text/javascript') function script($src, $type='text/javascript')
{ {
$url = parse_url($src); if(Event::handle('StartScriptElement', array($this,&$src,&$type))) {
if( empty($url->scheme) && empty($url->host) && empty($url->query) && empty($url->fragment)) $url = parse_url($src);
{ if( empty($url->scheme) && empty($url->host) && empty($url->query) && empty($url->fragment))
$src = common_path($src) . '?version=' . STATUSNET_VERSION; {
$src = common_path($src) . '?version=' . STATUSNET_VERSION;
}
$this->element('script', array('type' => $type,
'src' => $src),
' ');
Event::handle('EndScriptElement', array($this,$src,$type));
}
}
/**
* output a script (almost always javascript) tag with inline
* code.
*
* @param string $code code to put in the script tag
* @param string $type 'type' attribute value of the tag
*
* @return void
*/
function inlineScript($code, $type='text/javascript')
{
if(Event::handle('StartInlineScriptElement', array($this,&$code,&$type))) {
$this->elementStart('script', array('type' => $type));
if($type == 'text/javascript') {
$this->raw('/*<![CDATA[*/ '); // XHTML compat
}
$this->raw($code);
if($type == 'text/javascript') {
$this->raw(' /*]]>*/'); // XHTML compat
}
$this->elementEnd('script');
Event::handle('EndInlineScriptElement', array($this,$code,$type));
} }
$this->element('script', array('type' => $type,
'src' => $src),
' ');
} }
/** /**
@ -371,19 +400,44 @@ class HTMLOutputter extends XMLOutputter
*/ */
function cssLink($src,$theme=null,$media=null) function cssLink($src,$theme=null,$media=null)
{ {
$url = parse_url($src); if(Event::handle('StartCssLinkElement', array($this,&$src,&$theme,&$media))) {
if( empty($url->scheme) && empty($url->host) && empty($url->query) && empty($url->fragment)) $url = parse_url($src);
{ if( empty($url->scheme) && empty($url->host) && empty($url->query) && empty($url->fragment))
if(file_exists(Theme::file($src,$theme))){ {
$src = Theme::path($src, $theme) . '?version=' . STATUSNET_VERSION; if(file_exists(Theme::file($src,$theme))){
}else{ $src = Theme::path($src, $theme);
$src = common_path($src); }else{
$src = common_path($src);
}
$src.= '?version=' . STATUSNET_VERSION;
} }
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => $src,
'media' => $media));
Event::handle('EndCssLinkElement', array($this,$src,$theme,$media));
}
}
/**
* output a style (almost always css) tag with inline
* code.
*
* @param string $code code to put in the style tag
* @param string $type 'type' attribute value of the tag
* @param string $media 'media' attribute value of the tag
*
* @return void
*/
function style($code, $type = 'text/css', $media = null)
{
if(Event::handle('StartStyleElement', array($this,&$code,&$type,&$media))) {
$this->elementStart('style', array('type' => $type, 'media' => $media));
$this->raw($code);
$this->elementEnd('style');
Event::handle('EndStyleElement', array($this,$code,$type,$media));
} }
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => $src,
'media' => $media));
} }
/** /**
@ -414,7 +468,6 @@ class HTMLOutputter extends XMLOutputter
} }
} }
/** /**
* Internal script to autofocus the given element on page onload. * Internal script to autofocus the given element on page onload.
* *
@ -425,13 +478,10 @@ class HTMLOutputter extends XMLOutputter
*/ */
function autofocus($id) function autofocus($id)
{ {
$this->elementStart('script', array('type' => 'text/javascript')); $this->inlineScript(
$this->raw('/*<![CDATA[*/'.
' $(document).ready(function() {'. ' $(document).ready(function() {'.
' var el = $("#' . $id . '");'. ' var el = $("#' . $id . '");'.
' if (el.length) { el.focus(); }'. ' if (el.length) { el.focus(); }'.
' });'. ' });');
' /*]]>*/');
$this->elementEnd('script');
} }
} }

View File

@ -36,6 +36,33 @@ if (!function_exists('gettext')) {
require_once("php-gettext/gettext.inc"); require_once("php-gettext/gettext.inc");
} }
if (!function_exists('dpgettext')) {
/**
* Context-aware dgettext wrapper; use when messages in different contexts
* won't be distinguished from the English source but need different translations.
* The context string will appear as msgctxt in the .po files.
*
* Not currently exposed in PHP's gettext module; implemented to be compat
* with gettext.h's macros.
*
* @param string $domain domain identifier, or null for default domain
* @param string $context context identifier, should be some key like "menu|file"
* @param string $msgid English source text
* @return string original or translated message
*/
function dpgettext($domain, $context, $msg)
{
$msgid = $context . "\004" . $msg;
$out = dcgettext($domain, $msgid, LC_MESSAGES);
if ($out == $msgid) {
return $msg;
} else {
return $out;
}
}
}
if (!function_exists('pgettext')) { if (!function_exists('pgettext')) {
/** /**
* Context-aware gettext wrapper; use when messages in different contexts * Context-aware gettext wrapper; use when messages in different contexts
@ -50,9 +77,31 @@ if (!function_exists('pgettext')) {
* @return string original or translated message * @return string original or translated message
*/ */
function pgettext($context, $msg) function pgettext($context, $msg)
{
return dpgettext(textdomain(NULL), $context, $msg);
}
}
if (!function_exists('dnpgettext')) {
/**
* Context-aware dngettext wrapper; use when messages in different contexts
* won't be distinguished from the English source but need different translations.
* The context string will appear as msgctxt in the .po files.
*
* Not currently exposed in PHP's gettext module; implemented to be compat
* with gettext.h's macros.
*
* @param string $domain domain identifier, or null for default domain
* @param string $context context identifier, should be some key like "menu|file"
* @param string $msg singular English source text
* @param string $plural plural English source text
* @param int $n number of items to control plural selection
* @return string original or translated message
*/
function dnpgettext($domain, $context, $msg, $plural, $n)
{ {
$msgid = $context . "\004" . $msg; $msgid = $context . "\004" . $msg;
$out = dcgettext(textdomain(NULL), $msgid, LC_MESSAGES); $out = dcngettext($domain, $msgid, $plural, $n, LC_MESSAGES);
if ($out == $msgid) { if ($out == $msgid) {
return $msg; return $msg;
} else { } else {
@ -78,14 +127,78 @@ if (!function_exists('npgettext')) {
*/ */
function npgettext($context, $msg, $plural, $n) function npgettext($context, $msg, $plural, $n)
{ {
$msgid = $context . "\004" . $msg; return dnpgettext(textdomain(NULL), $msgid, $plural, $n, LC_MESSAGES);
$out = dcngettext(textdomain(NULL), $msgid, $plural, $n, LC_MESSAGES); }
if ($out == $msgid) { }
return $msg;
/**
* Shortcut for *gettext functions with smart domain detection.
*
* If calling from a plugin, this function checks which plugin was
* being called from and uses that as text domain, which will have
* been set up during plugin initialization.
*
* Also handles plurals and contexts depending on what parameters
* are passed to it:
*
* gettext -> _m($msg)
* ngettext -> _m($msg1, $msg2, $n)
* pgettext -> _m($ctx, $msg)
* npgettext -> _m($ctx, $msg1, $msg2, $n)
*
* @fixme may not work properly in eval'd code
*
* @param string $msg
* @return string
*/
function _m($msg/*, ...*/)
{
$domain = _mdomain(debug_backtrace());
$args = func_get_args();
switch(count($args)) {
case 1: return dgettext($domain, $msg);
case 2: return dpgettext($domain, $args[0], $args[1]);
case 3: return dngettext($domain, $args[0], $args[1], $args[2]);
case 4: return dnpgettext($domain, $args[0], $args[1], $args[2], $args[3]);
default: throw new Exception("Bad parameter count to _m()");
}
}
/**
* Looks for which plugin we've been called from to set the gettext domain.
*
* @param array $backtrace debug_backtrace() output
* @return string
* @private
* @fixme could explode if SN is under a 'plugins' folder or share name.
*/
function _mdomain($backtrace)
{
/*
0 =>
array
'file' => string '/var/www/mublog/plugins/FeedSub/FeedSubPlugin.php' (length=49)
'line' => int 77
'function' => string '_m' (length=2)
'args' =>
array
0 => &string 'Feeds' (length=5)
*/
static $cached;
$path = $backtrace[0]['file'];
if (!isset($cached[$path])) {
if (DIRECTORY_SEPARATOR !== '/') {
$path = strtr($path, DIRECTORY_SEPARATOR, '/');
}
$cut = strpos($path, '/plugins/') + 9;
$cut2 = strpos($path, '/', $cut);
if ($cut && $cut2) {
$cached[$path] = substr($path, $cut, $cut2 - $cut);
} else { } else {
return $out; return null;
} }
} }
return $cached[$path];
} }
@ -159,6 +272,7 @@ function get_nice_language_list()
function get_all_languages() { function get_all_languages() {
return array( return array(
'ar' => array('q' => 0.8, 'lang' => 'ar', 'name' => 'Arabic', 'direction' => 'rtl'), 'ar' => array('q' => 0.8, 'lang' => 'ar', 'name' => 'Arabic', 'direction' => 'rtl'),
'arz' => array('q' => 0.8, 'lang' => 'arz', 'name' => 'Egyptian Spoken Arabic', 'direction' => 'rtl'),
'bg' => array('q' => 0.8, 'lang' => 'bg', 'name' => 'Bulgarian', 'direction' => 'ltr'), 'bg' => array('q' => 0.8, 'lang' => 'bg', 'name' => 'Bulgarian', 'direction' => 'ltr'),
'ca' => array('q' => 0.5, 'lang' => 'ca', 'name' => 'Catalan', 'direction' => 'ltr'), 'ca' => array('q' => 0.5, 'lang' => 'ca', 'name' => 'Catalan', 'direction' => 'ltr'),
'cs' => array('q' => 0.5, 'lang' => 'cs', 'name' => 'Czech', 'direction' => 'ltr'), 'cs' => array('q' => 0.5, 'lang' => 'cs', 'name' => 'Czech', 'direction' => 'ltr'),
@ -173,6 +287,7 @@ function get_all_languages() {
'ga' => array('q' => 0.5, 'lang' => 'ga', 'name' => 'Galician', 'direction' => 'ltr'), 'ga' => array('q' => 0.5, 'lang' => 'ga', 'name' => 'Galician', 'direction' => 'ltr'),
'he' => array('q' => 0.5, 'lang' => 'he', 'name' => 'Hebrew', 'direction' => 'rtl'), 'he' => array('q' => 0.5, 'lang' => 'he', 'name' => 'Hebrew', 'direction' => 'rtl'),
'hsb' => array('q' => 0.8, 'lang' => 'hsb', 'name' => 'Upper Sorbian', 'direction' => 'ltr'), 'hsb' => array('q' => 0.8, 'lang' => 'hsb', 'name' => 'Upper Sorbian', 'direction' => 'ltr'),
'ia' => array('q' => 0.8, 'lang' => 'ia', 'name' => 'Interlingua', 'direction' => 'ltr'),
'is' => array('q' => 0.1, 'lang' => 'is', 'name' => 'Icelandic', 'direction' => 'ltr'), 'is' => array('q' => 0.1, 'lang' => 'is', 'name' => 'Icelandic', 'direction' => 'ltr'),
'it' => array('q' => 1, 'lang' => 'it', 'name' => 'Italian', 'direction' => 'ltr'), 'it' => array('q' => 1, 'lang' => 'it', 'name' => 'Italian', 'direction' => 'ltr'),
'jp' => array('q' => 0.5, 'lang' => 'ja', 'name' => 'Japanese', 'direction' => 'ltr'), 'jp' => array('q' => 0.5, 'lang' => 'ja', 'name' => 'Japanese', 'direction' => 'ltr'),

View File

@ -154,9 +154,6 @@ class MessageForm extends Form
$contentLimit = Message::maxContent(); $contentLimit = Message::maxContent();
$this->out->element('script', array('type' => 'text/javascript'),
'maxLength = ' . $contentLimit . ';');
if ($contentLimit > 0) { if ($contentLimit > 0) {
$this->out->elementStart('dl', 'form_note'); $this->out->elementStart('dl', 'form_note');
$this->out->element('dt', null, _('Available characters')); $this->out->element('dt', null, _('Available characters'));

View File

@ -178,9 +178,6 @@ class NoticeForm extends Form
$contentLimit = Notice::maxContent(); $contentLimit = Notice::maxContent();
$this->out->element('script', array('type' => 'text/javascript'),
'maxLength = ' . $contentLimit . ';');
if ($contentLimit > 0) { if ($contentLimit > 0) {
$this->out->elementStart('dl', 'form_note'); $this->out->elementStart('dl', 'form_note');
$this->out->element('dt', null, _('Available characters')); $this->out->element('dt', null, _('Available characters'));

View File

@ -147,6 +147,10 @@ class NoticeListItem extends Widget
var $notice = null; var $notice = null;
/** The notice that was repeated. */
var $repeat = null;
/** The profile of the author of the notice, extracted once for convenience. */ /** The profile of the author of the notice, extracted once for convenience. */
var $profile = null; var $profile = null;
@ -162,8 +166,18 @@ class NoticeListItem extends Widget
function __construct($notice, $out=null) function __construct($notice, $out=null)
{ {
parent::__construct($out); parent::__construct($out);
$this->notice = $notice; if (!empty($notice->repeat_of)) {
$this->profile = $notice->getProfile(); $original = Notice::staticGet('id', $notice->repeat_of);
if (empty($original)) { // could have been deleted
$this->notice = $notice;
} else {
$this->notice = $original;
$this->repeat = $notice;
}
} else {
$this->notice = $notice;
}
$this->profile = $this->notice->getProfile();
} }
/** /**
@ -202,6 +216,7 @@ class NoticeListItem extends Widget
$this->showNoticeSource(); $this->showNoticeSource();
$this->showNoticeLocation(); $this->showNoticeLocation();
$this->showContext(); $this->showContext();
$this->showRepeat();
$this->out->elementEnd('div'); $this->out->elementEnd('div');
} }
@ -212,6 +227,7 @@ class NoticeListItem extends Widget
$this->out->elementStart('div', 'notice-options'); $this->out->elementStart('div', 'notice-options');
$this->showFaveForm(); $this->showFaveForm();
$this->showReplyLink(); $this->showReplyLink();
$this->showRepeatForm();
$this->showDeleteLink(); $this->showDeleteLink();
$this->out->elementEnd('div'); $this->out->elementEnd('div');
} }
@ -227,8 +243,9 @@ class NoticeListItem extends Widget
{ {
// XXX: RDFa // XXX: RDFa
// TODO: add notice_type class e.g., notice_video, notice_image // TODO: add notice_type class e.g., notice_video, notice_image
$id = (empty($this->repeat)) ? $this->notice->id : $this->repeat->id;
$this->out->elementStart('li', array('class' => 'hentry notice', $this->out->elementStart('li', array('class' => 'hentry notice',
'id' => 'notice-' . $this->notice->id)); 'id' => 'notice-' . $id));
} }
/** /**
@ -507,6 +524,40 @@ class NoticeListItem extends Widget
} }
} }
/**
* show a link to the author of repeat
*
* @return void
*/
function showRepeat()
{
if (!empty($this->repeat)) {
$repeater = Profile::staticGet('id', $this->repeat->profile_id);
$attrs = array('href' => $repeater->profileurl,
'class' => 'url');
if (!empty($repeater->fullname)) {
$attrs['title'] = $repeater->fullname . ' (' . $repeater->nickname . ')';
}
$this->out->elementStart('span', 'repeat vcard');
$this->out->raw(_('Repeated by'));
$avatar = $repeater->getAvatar(AVATAR_MINI_SIZE);
$this->out->elementStart('a', $attrs);
$this->out->element('span', 'nickname', $repeater->nickname);
$this->out->elementEnd('a');
$this->out->elementEnd('span');
}
}
/** /**
* show a link to reply to the current notice * show a link to reply to the current notice
* *
@ -540,17 +591,41 @@ class NoticeListItem extends Widget
{ {
$user = common_current_user(); $user = common_current_user();
$todel = (empty($this->repeat)) ? $this->notice : $this->repeat;
if (!empty($user) && if (!empty($user) &&
($this->notice->profile_id == $user->id || $user->hasRight(Right::DELETEOTHERSNOTICE))) { ($todel->profile_id == $user->id || $user->hasRight(Right::DELETEOTHERSNOTICE))) {
$deleteurl = common_local_url('deletenotice', $deleteurl = common_local_url('deletenotice',
array('notice' => $this->notice->id)); array('notice' => $todel->id));
$this->out->element('a', array('href' => $deleteurl, $this->out->element('a', array('href' => $deleteurl,
'class' => 'notice_delete', 'class' => 'notice_delete',
'title' => _('Delete this notice')), _('Delete')); 'title' => _('Delete this notice')), _('Delete'));
} }
} }
/**
* show the form to repeat a notice
*
* @return void
*/
function showRepeatForm()
{
$user = common_current_user();
if ($user && $user->id != $this->notice->profile_id) {
$profile = $user->getProfile();
if ($profile->hasRepeated($this->notice->id)) {
$this->out->element('span', array('class' => 'repeated',
'title' => _('Notice repeated')),
_('Repeated'));
} else {
$rf = new RepeatForm($this->out, $this->notice);
$rf->show();
}
}
}
/** /**
* finish the notice * finish the notice
* *

View File

@ -359,9 +359,8 @@ class StatusNetOAuthDataStore extends OAuthDataStore
$notice = Notice::saveNew($author->id, $notice = Notice::saveNew($author->id,
$omb_notice->getContent(), $omb_notice->getContent(),
'omb', 'omb',
false, array('is_local' => Notice::REMOTE_OMB,
null, 'uri' => $omb_notice->getIdentifierURI()));
$omb_notice->getIdentifierURI());
common_broadcast_notice($notice, true); common_broadcast_notice($notice, true);
} }

View File

@ -65,6 +65,8 @@ class Plugin
Event::addHandler(mb_substr($method, 2), array($this, $method)); Event::addHandler(mb_substr($method, 2), array($this, $method));
} }
} }
$this->setupGettext();
} }
function initialize() function initialize()
@ -76,4 +78,31 @@ class Plugin
{ {
return true; return true;
} }
/**
* Checks if this plugin has localization that needs to be set up.
* Gettext localizations can be called via the _m() helper function.
*/
protected function setupGettext()
{
$class = get_class($this);
if (substr($class, -6) == 'Plugin') {
$name = substr($class, 0, -6);
$path = INSTALLDIR . "/plugins/$name/locale";
if (file_exists($path) && is_dir($path)) {
bindtextdomain($name, $path);
}
}
}
protected function log($level, $msg)
{
common_log($level, get_class($this) . ': '.$msg);
}
protected function debug($msg)
{
$this->log(LOG_DEBUG, $msg);
}
} }

View File

@ -120,7 +120,7 @@ class ProfileFormAction extends Action
if ($action) { if ($action) {
common_redirect(common_local_url($action, $args), 303); common_redirect(common_local_url($action, $args), 303);
} else { } else {
$this->clientError(_("No return-to arguments")); $this->clientError(_("No return-to arguments."));
} }
} }
@ -134,6 +134,6 @@ class ProfileFormAction extends Action
function handlePost() function handlePost()
{ {
$this->serverError(_("unimplemented method")); $this->serverError(_("Unimplemented method."));
} }
} }

145
lib/repeatform.php Normal file
View File

@ -0,0 +1,145 @@
<?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');
}
/**
* Include a session token for CSRF protection
*
* @return void
*/
function sessionToken()
{
$this->out->hidden('token-' . $this->notice->id,
common_session_token());
}
/**
* Legend of the Form
*
* @return void
*/
function formLegend()
{
$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,
_('Repeat'), 'submit', null, _('Repeat this notice'));
}
/**
* Class of the form.
*
* @return string the form's class
*/
function formClass()
{
return 'form_repeat';
}
}

View File

@ -88,6 +88,8 @@ class Router
$m->connect('doc/:title', array('action' => 'doc')); $m->connect('doc/:title', array('action' => 'doc'));
$m->connect('main/login?user_id=:user_id&token=:token', array('action'=>'login'), array('user_id'=> '[0-9]+', 'token'=>'.+'));
// main stuff is repetitive // main stuff is repetitive
$main = array('login', 'logout', 'register', 'subscribe', $main = array('login', 'logout', 'register', 'subscribe',
@ -97,6 +99,7 @@ class Router
'groupblock', 'groupunblock', 'groupblock', 'groupunblock',
'sandbox', 'unsandbox', 'sandbox', 'unsandbox',
'silence', 'unsilence', 'silence', 'unsilence',
'repeat',
'deleteuser'); 'deleteuser');
foreach ($main as $a) { foreach ($main as $a) {
@ -280,12 +283,13 @@ class Router
array('action' => 'ApiTimelineFriends', array('action' => 'ApiTimelineFriends',
'id' => '[a-zA-Z0-9]+', 'id' => '[a-zA-Z0-9]+',
'format' => '(xml|json|rss|atom)')); 'format' => '(xml|json|rss|atom)'));
$m->connect('api/statuses/home_timeline.:format', $m->connect('api/statuses/home_timeline.:format',
array('action' => 'ApiTimelineFriends', array('action' => 'ApiTimelineHome',
'format' => '(xml|json|rss|atom)')); 'format' => '(xml|json|rss|atom)'));
$m->connect('api/statuses/home_timeline/:id.:format', $m->connect('api/statuses/home_timeline/:id.:format',
array('action' => 'ApiTimelineFriends', array('action' => 'ApiTimelineHome',
'id' => '[a-zA-Z0-9]+', 'id' => '[a-zA-Z0-9]+',
'format' => '(xml|json|rss|atom)')); 'format' => '(xml|json|rss|atom)'));
@ -316,6 +320,18 @@ class Router
'id' => '[a-zA-Z0-9]+', 'id' => '[a-zA-Z0-9]+',
'format' => '(xml|json|rss|atom)')); 'format' => '(xml|json|rss|atom)'));
$m->connect('api/statuses/retweeted_by_me.:format',
array('action' => 'ApiTimelineRetweetedByMe',
'format' => '(xml|json|atom)'));
$m->connect('api/statuses/retweeted_to_me.:format',
array('action' => 'ApiTimelineRetweetedToMe',
'format' => '(xml|json|atom)'));
$m->connect('api/statuses/retweets_of_me.:format',
array('action' => 'ApiTimelineRetweetsOfMe',
'format' => '(xml|json|atom)'));
$m->connect('api/statuses/friends.:format', $m->connect('api/statuses/friends.:format',
array('action' => 'ApiUserFriends', array('action' => 'ApiUserFriends',
'format' => '(xml|json)')); 'format' => '(xml|json)'));
@ -356,6 +372,16 @@ class Router
'id' => '[0-9]+', 'id' => '[0-9]+',
'format' => '(xml|json)')); '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)'));
// users // users
$m->connect('api/users/show.:format', $m->connect('api/users/show.:format',

View File

@ -52,7 +52,7 @@ class Rss10Action extends Action
* @see Action::__construct * @see Action::__construct
*/ */
function __construct($output='php://output', $indent=true) function __construct($output='php://output', $indent=null)
{ {
parent::__construct($output, $indent); parent::__construct($output, $indent);
} }

View File

@ -94,7 +94,7 @@ class Schema
public function getTableDef($name) public function getTableDef($name)
{ {
$res =& $this->conn->query('DESCRIBE ' . $name); $res = $this->conn->query('DESCRIBE ' . $name);
if (PEAR::isError($res)) { if (PEAR::isError($res)) {
throw new Exception($res->getMessage()); throw new Exception($res->getMessage());
@ -213,7 +213,7 @@ class Schema
$sql .= "); "; $sql .= "); ";
$res =& $this->conn->query($sql); $res = $this->conn->query($sql);
if (PEAR::isError($res)) { if (PEAR::isError($res)) {
throw new Exception($res->getMessage()); throw new Exception($res->getMessage());
@ -234,7 +234,7 @@ class Schema
public function dropTable($name) public function dropTable($name)
{ {
$res =& $this->conn->query("DROP TABLE $name"); $res = $this->conn->query("DROP TABLE $name");
if (PEAR::isError($res)) { if (PEAR::isError($res)) {
throw new Exception($res->getMessage()); throw new Exception($res->getMessage());
@ -269,7 +269,7 @@ class Schema
$name = "$table_".implode("_", $columnNames)."_idx"; $name = "$table_".implode("_", $columnNames)."_idx";
} }
$res =& $this->conn->query("ALTER TABLE $table ". $res = $this->conn->query("ALTER TABLE $table ".
"ADD INDEX $name (". "ADD INDEX $name (".
implode(",", $columnNames).")"); implode(",", $columnNames).")");
@ -291,7 +291,7 @@ class Schema
public function dropIndex($table, $name) public function dropIndex($table, $name)
{ {
$res =& $this->conn->query("ALTER TABLE $table DROP INDEX $name"); $res = $this->conn->query("ALTER TABLE $table DROP INDEX $name");
if (PEAR::isError($res)) { if (PEAR::isError($res)) {
throw new Exception($res->getMessage()); throw new Exception($res->getMessage());
@ -314,7 +314,7 @@ class Schema
{ {
$sql = "ALTER TABLE $table ADD COLUMN " . $this->_columnSql($columndef); $sql = "ALTER TABLE $table ADD COLUMN " . $this->_columnSql($columndef);
$res =& $this->conn->query($sql); $res = $this->conn->query($sql);
if (PEAR::isError($res)) { if (PEAR::isError($res)) {
throw new Exception($res->getMessage()); throw new Exception($res->getMessage());
@ -339,7 +339,7 @@ class Schema
$sql = "ALTER TABLE $table MODIFY COLUMN " . $sql = "ALTER TABLE $table MODIFY COLUMN " .
$this->_columnSql($columndef); $this->_columnSql($columndef);
$res =& $this->conn->query($sql); $res = $this->conn->query($sql);
if (PEAR::isError($res)) { if (PEAR::isError($res)) {
throw new Exception($res->getMessage()); throw new Exception($res->getMessage());
@ -363,7 +363,7 @@ class Schema
{ {
$sql = "ALTER TABLE $table DROP COLUMN $columnName"; $sql = "ALTER TABLE $table DROP COLUMN $columnName";
$res =& $this->conn->query($sql); $res = $this->conn->query($sql);
if (PEAR::isError($res)) { if (PEAR::isError($res)) {
throw new Exception($res->getMessage()); throw new Exception($res->getMessage());
@ -446,7 +446,7 @@ class Schema
$sql = 'ALTER TABLE ' . $tableName . ' ' . implode(', ', $phrase); $sql = 'ALTER TABLE ' . $tableName . ' ' . implode(', ', $phrase);
$res =& $this->conn->query($sql); $res = $this->conn->query($sql);
if (PEAR::isError($res)) { if (PEAR::isError($res)) {
throw new Exception($res->getMessage()); throw new Exception($res->getMessage());

View File

@ -127,6 +127,12 @@ function subs_unsubscribe_to($user, $other)
if (!$user->isSubscribed($other)) if (!$user->isSubscribed($other))
return _('Not subscribed!'); return _('Not subscribed!');
// Don't allow deleting self subs
if ($user->id == $other->id) {
return _('Couldn\'t delete self-subscription.');
}
$sub = DB_DataObject::factory('subscription'); $sub = DB_DataObject::factory('subscription');
$sub->subscriber = $user->id; $sub->subscriber = $user->id;

View File

@ -135,7 +135,7 @@ function common_check_user($nickname, $password)
if (0 == strcmp(common_munge_password($password, $user->id), if (0 == strcmp(common_munge_password($password, $user->id),
$user->password)) { $user->password)) {
//internal checking passed //internal checking passed
$authenticatedUser =& $user; $authenticatedUser = $user;
} }
} }
} }
@ -531,19 +531,23 @@ function callback_helper($matches, $callback, $notice_id) {
return substr($matches[0],0,$left) . $result . substr($matches[0],$right); return substr($matches[0],0,$left) . $result . substr($matches[0],$right);
} }
function curry($fn) { if (version_compare(PHP_VERSION, '5.3.0', 'ge')) {
//TODO switch to a PHP 5.3 function closure based approach if PHP 5.3 is used // lambda implementation in a separate file; PHP 5.2 won't parse it.
$args = func_get_args(); require_once INSTALLDIR . "/lib/curry.php";
array_shift($args); } else {
$id = uniqid('_partial'); function curry($fn) {
$GLOBALS[$id] = array($fn, $args); $args = func_get_args();
return create_function('', array_shift($args);
'$args = func_get_args(); '. $id = uniqid('_partial');
'return call_user_func_array('. $GLOBALS[$id] = array($fn, $args);
'$GLOBALS["'.$id.'"][0],'. return create_function('',
'array_merge('. '$args = func_get_args(); '.
'$args,'. 'return call_user_func_array('.
'$GLOBALS["'.$id.'"][1]));'); '$GLOBALS["'.$id.'"][0],'.
'array_merge('.
'$args,'.
'$GLOBALS["'.$id.'"][1]));');
}
} }
function common_linkify($url) { function common_linkify($url) {
@ -1078,18 +1082,21 @@ function common_request_id()
function common_log($priority, $msg, $filename=null) function common_log($priority, $msg, $filename=null)
{ {
$msg = '[' . common_request_id() . '] ' . $msg; if(Event::handle('StartLog', array(&$priority, &$msg, &$filename))){
$logfile = common_config('site', 'logfile'); $msg = '[' . common_request_id() . '] ' . $msg;
if ($logfile) { $logfile = common_config('site', 'logfile');
$log = fopen($logfile, "a"); if ($logfile) {
if ($log) { $log = fopen($logfile, "a");
$output = common_log_line($priority, $msg); if ($log) {
fwrite($log, $output); $output = common_log_line($priority, $msg);
fclose($log); fwrite($log, $output);
fclose($log);
}
} else {
common_ensure_syslog();
syslog($priority, $msg);
} }
} else { Event::handle('EndLog', array($priority, $msg, $filename));
common_ensure_syslog();
syslog($priority, $msg);
} }
} }
@ -1245,8 +1252,12 @@ function common_copy_args($from)
return $to; return $to;
} }
// Neutralise the evil effects of magic_quotes_gpc in the current request. /**
// This is used before handing a request off to OAuthRequest::from_request. * Neutralise the evil effects of magic_quotes_gpc in the current request.
* This is used before handing a request off to OAuthRequest::from_request.
* @fixme Doesn't consider vars other than _POST and _GET?
* @fixme Can't be undone and could corrupt data if run twice.
*/
function common_remove_magic_from_request() function common_remove_magic_from_request()
{ {
if(get_magic_quotes_gpc()) { if(get_magic_quotes_gpc()) {
@ -1448,6 +1459,17 @@ function common_database_tablename($tablename)
return $tablename; return $tablename;
} }
/**
* Shorten a URL with the current user's configured shortening service,
* or ur1.ca if configured, or not at all if no shortening is set up.
* Length is not considered.
*
* @param string $long_url
* @return string may return the original URL if shortening failed
*
* @fixme provide a way to specify a particular shortener
* @fixme provide a way to specify to use a given user's shortening preferences
*/
function common_shorten_url($long_url) function common_shorten_url($long_url)
{ {
$user = common_current_user(); $user = common_current_user();
@ -1468,6 +1490,16 @@ function common_shorten_url($long_url)
} }
} }
/**
* @return mixed array($proxy, $ip) for web requests; proxy may be null
* null if not a web request
*
* @fixme X-Forwarded-For can be chained by multiple proxies;
we should parse the list and provide a cleaner array
* @fixme X-Forwarded-For can be forged by clients; only use them if trusted
* @fixme X_Forwarded_For headers will override X-Forwarded-For read through $_SERVER;
* use function to get exact request headers from Apache if possible.
*/
function common_client_ip() function common_client_ip()
{ {
if (!isset($_SERVER) || !array_key_exists('REQUEST_METHOD', $_SERVER)) { if (!isset($_SERVER) || !array_key_exists('REQUEST_METHOD', $_SERVER)) {

View File

@ -67,10 +67,13 @@ class XMLOutputter
* @param boolean $indent Whether to indent output, default true * @param boolean $indent Whether to indent output, default true
*/ */
function __construct($output='php://output', $indent=true) function __construct($output='php://output', $indent=null)
{ {
$this->xw = new XMLWriter(); $this->xw = new XMLWriter();
$this->xw->openURI($output); $this->xw->openURI($output);
if(is_null($indent)) {
$indent = common_config('site', 'indent');
}
$this->xw->setIndent($indent); $this->xw->setIndent($indent);
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More