Merge branch '1.0.x' into shortcontrol10x

This commit is contained in:
Evan Prodromou 2010-09-10 22:20:52 -04:00
commit 12eee30586
931 changed files with 139590 additions and 63596 deletions

View File

@ -551,6 +551,12 @@ EndPublicXRDS: End XRDS output (right before the closing XRDS tag)
- $action: the current action
- &$xrdsoutputter - XRDSOutputter object to write to
StartHostMetaLinks: Start /.well-known/host-meta links
- &links: array containing the links elements to be written
EndHostMetaLinks: End /.well-known/host-meta links
- &links: array containing the links elements to be written
StartCheckPassword: Check a username/password
- $nickname: The nickname to check
- $password: The password to check
@ -836,3 +842,271 @@ EndDeleteUser: handling the post for deleting a user
- $action: action being shown
- $user: user being deleted
StartActivityStart: starting the output for a notice activity <event>
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$attrs: <entry> attributes (mostly namespace declarations, if any)
EndActivityStart: end the opening tag for an activity <event>
- &$notice: notice being output
- &$xs: XMLStringer for output
- $attrs: <entry> attributes (mostly namespace declarations, if any)
StartActivitySource: before outputting the <source> element for a notice activity
- &$notice: notice being output
- &$xs: XMLStringer for output
EndActivitySource: after outputting the <source> element for a notice activity
- &$notice: notice being output
- &$xs: XMLStringer for output
StartActivityTitle: before outputting notice activity title
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$title: title of the notice, mutable
EndActivityTitle: after outputting notice activity title
- $notice: notice being output
- &$xs: XMLStringer for output
- $title: title of the notice
StartActivityAuthor: before outputting atom author
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$atomAuthor: string for XML representing atom author
EndActivityAuthor: after outputting atom author
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$atomAuthor: string for XML representing atom author
StartActivityActor: before outputting activity actor element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$actor: string for XML representing activity actor
EndActivityActor: after outputting activity actor element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$actor: string for XML representing activity actor
StartActivityLink: before outputting activity HTML link element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$url: URL for activity HTML link element for a notice activity entry
EndActivityLink: before outputting activity HTML link element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- $url: URL for activity HTML link element for a notice activity entry
StartActivityId: before outputting atom:id element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$id: atom:id (notice URI by default)
EndActivityId: after outputting atom:id element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- $id: atom:id (notice URI by default)
StartActivityPublished: before outputting atom:published element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$published: atom:published value (notice created by default)
EndActivityPublished: before outputting atom:published element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- $published: atom:published value (notice created by default)
StartActivityUpdated: before outputting atom:updated element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$updated: atom:updated value (same as atom:published by default)
EndActivityUpdated: after outputting atom:updated element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- $updated: atom:updated value (same as atom:published by default)
StartActivityContent: before outputting atom:content element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$content: atom:content value (notice rendered HTML by default)
EndActivityContent: after outputting atom:content element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- $content: atom:content value (notice rendered HTML by default)
StartActivityVerb: before outputting activity:verb element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$verb: activity:verb URI ('http://activitystrea.ms/schema/1.0/post' by default)
EndActivityVerb: after outputting activity:verb element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- $verb: activity:verb URI ('http://activitystrea.ms/schema/1.0/post' by default)
StartActivityDefaultObjectType: before outputting activity:object-type element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$type: activity:object-type URI for default object ('http://activitystrea.ms/schema/1.0/note' by default)
EndActivityDefaultObjectType: after outputting activity:verb element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- $type: activity:object-type URI for default object ('http://activitystrea.ms/schema/1.0/note' by default)
StartActivityObjects: before outputting activity:object elements for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$objects: array of ActivityObject objects to output (empty by default)
EndActivityObjects: after outputting activity:object elements for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- $objects: array of ActivityObject objects to output (empty by default)
StartActivityNoticeInfo: before outputting statusnet:notice-info element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$noticeInfoAttr: array of attributes for notice info element
EndActivityNoticeInfo: after outputting statusnet:notice-info element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- $noticeInfoAttr: array of attributes for notice info element
StartActivityInReplyTo: before outputting thr:in-reply-to element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$replyNotice: Notice object the main notice is in-reply-to
EndActivityInReplyTo: after outputting thr:in-reply-to element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- $replyNotice: Notice object the main notice is in-reply-to
StartActivityConversation: before outputting ostatus:conversation link element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$conv: Conversation object
EndActivityConversation: after outputting ostatus:conversation link element for a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- $conv: Conversation object
StartActivityAttentionProfiles: before outputting ostatus:attention link element for people in a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$replyProfiles: array of profiles of people being replied to
EndActivityAttentionProfiles: after outputting ostatus:attention link element for people in a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- $replyProfiles: array of Profile object of people being replied to
StartActivityAttentionGroups: before outputting ostatus:attention link element for groups in a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$groups: array of Group objects of groups being addressed
EndActivityAttentionGroups: after outputting ostatus:attention link element for groups in a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- $groups: array of Group objects of groups being addressed
StartActivityForward: before outputting ostatus:forward link element in a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$repeat: Notice that was repeated
EndActivityForward: after outputting ostatus:forward link element in a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- $repeat: Notice that was repeated
StartActivityCategories: before outputting atom:category elements in a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$tags: array of strings for tags on the notice (used for categories)
EndActivityCategories: after outputting atom:category elements in a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- $tags: array of strings for tags on the notice (used for categories)
StartActivityEnclosures: before outputting enclosure link elements in a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$enclosures: array of enclosure objects (see File::getEnclosure() for details)
EndActivityEnclosures: after outputting enclosure link elements in a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- $enclosures: array of enclosure objects (see File::getEnclosure() for details)
StartActivityGeo: before outputting geo:rss element in a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- &$lat: latitude
- &$lon: longitude
EndActivityGeo: after outputting geo:rss element in a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
- $lat: latitude
- $lon: longitude
StartActivityEnd: before the closing </entry> in a notice activity entry (last chance for data!)
- &$notice: notice being output
- &$xs: XMLStringer for output
EndActivityEnd: after the closing </entry> in a notice activity entry
- &$notice: notice being output
- &$xs: XMLStringer for output
StartNoticeSaveWeb: before saving a notice through the Web interface
- $action: action being executed (instance of NewNoticeAction)
- &$authorId: integer ID of the author
- &$text: text of the notice
- &$options: additional options (location, replies, etc.)
EndNoticeSaveWeb: after saving a notice through the Web interface
- $action: action being executed (instance of NewNoticeAction)
- $notice: notice that was saved
StartRssEntryArray: at the start of copying a notice to an array
- $notice: the notice being copied
- &$entry: the entry (empty at beginning)
EndRssEntryArray: at the end of copying a notice to an array
- $notice: the notice being copied
- &$entry: the entry, with all the fields filled up
NoticeDeleteRelated: at the beginning of deleting related fields to a notice
- $notice: notice being deleted
StartShowHeadTitle: when beginning to show the <title> element
- $action: action being shown
EndShowHeadTitle: when done showing the <title>
- $action: action being shown
StartShowPageTitle: when beginning to show the page title <h1>
- $action: action being shown
EndShowPageTitle: when done showing the page title <h1>
- $action: action being shown
StartDeleteOwnNotice: when a user starts to delete their own notice
- $user: the user doing the delete
- $notice: the notice being deleted
EndDeleteOwnNotice: when a user has deleted their own notice
- $user: the user doing the delete
- $notice: the notice being deleted

89
README
View File

@ -2,8 +2,8 @@
README
------
StatusNet 0.9.1 ("Everybody Hurts")
28 Mar 2010
StatusNet 0.9.5 "What's The Frequency, Kenneth?"
10 September 2010
This is the README file for StatusNet, the Open Source microblogging
platform. It includes installation instructions, descriptions of
@ -38,11 +38,16 @@ more, please see the Open Software Service Definition 1.1:
http://www.opendefinition.org/ossd
StatusNet, Inc. <http://status.net/> also offers this software as a
Web service, requiring no installation on your part. The software run
Web service, requiring no installation on your part. See
<http://status.net/signup> for details. The software run
on status.net is identical to the software available for download, so
you can move back and forth between a hosted version or a version
installed on your own servers.
A commercial software subscription is available from StatusNet Inc. It
includes 24-hour technical support and developer support. More
information at http://status.net/contact or email sales@status.net.
License
=======
@ -68,6 +73,20 @@ License along with this program, in the file "COPYING". If not, see
of using the software, and if you do not wish to share your
modifications, *YOU MAY NOT INSTALL STATUSNET*.
Documentation in the /doc-src/ directory is available under the
Creative Commons Attribution 3.0 Unported license, with attribution to
"StatusNet". See http://creativecommons.org/licenses/by/3.0/ for details.
CSS and images in the /theme/ directory are available under the
Creative Commons Attribution 3.0 Unported license, with attribution to
"StatusNet". See http://creativecommons.org/licenses/by/3.0/ for details.
Our understanding and intention is that if you add your own theme that
uses only CSS and images, those files are not subject to the copyleft
requirements of the Affero General Public License 3.0. See
http://wordpress.org/news/2009/07/themes-are-gpl-too/ . This is not
legal advice; consult your lawyer.
Additional library software has been made available in the 'extlib'
directory. All of it is Free Software and can be distributed under
liberal terms, but those terms may differ in detail from the AGPL's
@ -77,34 +96,33 @@ for additional terms.
New this version
================
This is a minor bug and feature release since version 0.9.0 released 4
March 2010.
This is a security, bug and feature release since version 0.9.4 released on
16 August 2010.
Because of fixes to OStatus bugs, it is highly recommended that all
public sites upgrade to the new version immediately.
For best compatibility with client software and site federation, and a lot of
bug fixes, it is highly recommended that all public sites upgrade to the new
version.
Notable changes this version:
- Twitter bridge truncates and links back to original for long
notices.
- Changed "Home" link in main menu to "Personal".
- A new memcached plugin (using pecl/memcached versus pecl/memcache)
- Opt-in subscription to update@status.net
- Script to run commands on behalf of a user.
- Better Web UI for long notices.
- A plugin to open external links in their own window or tab
- Fixes to Salmon protocol for compatibility with other systems.
- Updates to latest ActivityStreams definition.
- Twitpic-compatible API for image upload.
- Background deletion of user accounts.
- Better support for HTTP basic authentication with CGI/FastCGI
- Better discovery on OStatus
- Support for PuSH-enabled RSS 2.0 feeds
- OpenID-only mode
- OpenID blacklist/whitelist
- OStatus unit tests
- Change of license for default themes and documentation from
AGPLv3 to CC-By 3.0 Unported.
- An experimental TinyMCE plugin to do in-browser rich editing of
status updates. Does not support StatusNet syntax like @-replies or
#hashtags very well.
- An experimental plugin to add titles to notices.
- A plugin to support the Echo <http://aboutecho.com/> commenting
system.
- A plugin to support the Disqus <http://disqus.com/> commenting system.
- Changes to OStatus support to make StatusNet work for the Social Web
Acid Test Level 0 <http://federatedsocialweb.net/wiki/SWAT0>.
- Themes now support a theme.ini file for theme configuration, including
defining a "base" theme.
- Improved two-way Twitter integration, including support for
repeats and retweets, replies, and faves going both ways across the
bridge, as well as better parsing of Twitter statuses.
A full changelog is available at http://status.net/wiki/StatusNet_0.9.1.
A full changelog is available at http://status.net/wiki/StatusNet_0.9.5.
Prerequisites
=============
@ -115,8 +133,8 @@ run correctly.
- PHP 5.2.3+. It may be possible to run this software on earlier
versions of PHP, but many of the functions used are only available
in PHP 5.2 or above. 5.2.6 or later is needed for XMPP background
daemons on 64-bit platforms. PHP 5.3.x should work but is known
to cause some failures for OpenID.
daemons on 64-bit platforms. PHP 5.3.x should work correctly in this
release, but problems with some plugins are possible.
- MySQL 5.x. The StatusNet database is stored, by default, in a MySQL
server. It has been primarily tested on 5.x servers, although it may
be possible to install on earlier (or later!) versions. The server
@ -132,7 +150,6 @@ Your PHP installation must include the following PHP extensions:
- MySQL. For accessing the database.
- GD. For scaling down avatar images.
- mbstring. For handling Unicode (UTF-8) encoded strings.
- gettext. For multiple languages. Default on many PHP installs.
For some functionality, you will also need the following extensions:
@ -147,6 +164,8 @@ For some functionality, you will also need the following extensions:
Sphinx server to serve the search queries.
- bcmath or gmp. For Salmon signatures (part of OStatus). Needed
if you have OStatus configured.
- gettext. For multiple languages. Default on many PHP installs;
will be emulated if not present.
You will almost definitely get 2-3 times better performance from your
site if you install a PHP bytecode cache/accelerator. Some well-known
@ -216,9 +235,9 @@ especially if you've previously installed PHP/MySQL packages.
1. Unpack the tarball you downloaded on your Web server. Usually a
command like this will work:
tar zxf statusnet-0.9.1.tar.gz
tar zxf statusnet-0.9.5.tar.gz
...which will make a statusnet-0.9.1 subdirectory in your current
...which will make a statusnet-0.9.5 subdirectory in your current
directory. (If you don't have shell access on your Web server, you
may have to unpack the tarball on your local computer and FTP the
files to the server.)
@ -226,7 +245,7 @@ especially if you've previously installed PHP/MySQL packages.
2. Move the tarball to a directory of your choosing in your Web root
directory. Usually something like this will work:
mv statusnet-0.9.1 /var/www/statusnet
mv statusnet-0.9.5 /var/www/statusnet
This will make your StatusNet instance available in the statusnet path of
your server, like "http://example.net/statusnet". "microblog" or
@ -641,7 +660,7 @@ with this situation.
If you've been using StatusNet 0.7, 0.6, 0.5 or lower, or if you've
been tracking the "git" version of the software, you will probably
want to upgrade and keep your existing data. There is no automated
upgrade procedure in StatusNet 0.9.1. Try these step-by-step
upgrade procedure in StatusNet 0.9.5. Try these step-by-step
instructions; read to the end first before trying them.
0. Download StatusNet and set up all the prerequisites as if you were
@ -662,7 +681,7 @@ instructions; read to the end first before trying them.
5. Once all writing processes to your site are turned off, make a
final backup of the Web directory and database.
6. Move your StatusNet directory to a backup spot, like "statusnet.bak".
7. Unpack your StatusNet 0.9.1 tarball and move it to "statusnet" or
7. Unpack your StatusNet 0.9.5 tarball and move it to "statusnet" or
wherever your code used to be.
8. Copy the config.php file and avatar directory from your old
directory to your new directory.
@ -1539,7 +1558,7 @@ repository (see below), and you get a compilation error ("unexpected
T_STRING") in the browser, check to see that you don't have any
conflicts in your code.
If you upgraded to StatusNet 0.9.1 without reading the "Notice
If you upgraded to StatusNet 0.9.5 without reading the "Notice
inboxes" section above, and all your users' 'Personal' tabs are empty,
read the "Notice inboxes" section above.

View File

@ -18,15 +18,19 @@
*
* @category Actions
* @package Actions
* @author Evan Prodromou <evan@status.net>
* @author Mike Cochrane <mikec@mikenz.geek.nz>
* @author Robin Millette <millette@controlyourself.ca>
* @author Adrian Lang <mail@adrianlang.de>
* @author Meitar Moscovitz <meitarm@gmail.com>
* @author Sarven Capadisli <csarven@status.net>
* @author Brenda Wallace <shiny@cpan.org>
* @author Brion Vibber <brion@pobox.com>
* @author Craig Andrews <candrews@integralblue.com>
* @author Evan Prodromou <evan@status.net>
* @author Jeffery To <jeffery.to@gmail.com>
* @author Zach Copley <zach@controlyourself.ca>
* @author Meitar Moscovitz <meitarm@gmail.com>
* @author Mike Cochrane <mikec@mikenz.geek.nz>
* @author Robin Millette <millette@status.net>
* @author Sarven Capadisli <csarven@status.net>
* @author Siebrand Mazeland <s.mazeland@xs4all.nl>
* @author Zach Copley <zach@status.net>
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license GNU Affero General Public License http://www.gnu.org/licenses/
* @link http://status.net
*/
@ -139,10 +143,10 @@ class AllAction extends ProfileAction
$message .= _('Try subscribing to more people, [join a group](%%action.groups%%) or post something yourself.');
} else {
// TRANS: %1$s is user nickname, %2$s is user nickname, %2$s is user nickname prefixed with "@"
$message .= sprintf(_('You can try to [nudge %1$s](../%2$s) from his profile or [post something to his or her attention](%%%%action.newnotice%%%%?status_textarea=%3$s).'), $this->user->nickname, $this->user->nickname, '@' . $this->user->nickname);
$message .= sprintf(_('You can try to [nudge %1$s](../%2$s) from their profile or [post something to them](%%%%action.newnotice%%%%?status_textarea=%3$s).'), $this->user->nickname, $this->user->nickname, '@' . $this->user->nickname);
}
} else {
$message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname);
$message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to them.'), $this->user->nickname);
}
$this->elementStart('div', 'guide');

View File

@ -21,8 +21,10 @@
*
* @category API
* @package StatusNet
* @author Brion Vibber <brion@pobox.com>
* @author Evan Prodromou <evan@status.net>
* @author Robin Millette <robin@millette.info>
* @author Siebrand Mazeland <s.mazeland@xs4all.nl>
* @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

View File

@ -21,6 +21,7 @@
*
* @category API
* @package StatusNet
* @author Siebrand Mazeland <s.mazeland@xs4all.nl>
* @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

View File

@ -22,7 +22,7 @@
* @category API
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009-2010 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/
*/
@ -131,7 +131,7 @@ class ApiAccountUpdateProfileColorsAction extends ApiAuthAction
try {
$this->setColors($design);
} catch (WebColorException $e) {
$this->clientError($e->getMessage());
$this->clientError($e->getMessage(), 400, $this->format);
return false;
}
@ -153,7 +153,7 @@ class ApiAccountUpdateProfileColorsAction extends ApiAuthAction
try {
$this->setColors($design);
} catch (WebColorException $e) {
$this->clientError($e->getMessage());
$this->clientError($e->getMessage(), 400, $this->format);
return false;
}

View File

@ -75,7 +75,7 @@ class ApiAccountVerifyCredentialsAction extends ApiAuthAction
if ($this->format == 'xml') {
$this->initDocument('xml');
$this->showTwitterXmlUser($twitter_user);
$this->showTwitterXmlUser($twitter_user, 'user', true);
$this->endDocument('xml');
} elseif ($this->format == 'json') {
$this->initDocument('json');

View File

@ -23,7 +23,7 @@
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009-2010 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/
*/
@ -65,7 +65,7 @@ class ApiBlockCreateAction extends ApiAuthAction
parent::prepare($args);
$this->user = $this->auth_user;
$this->other = $this->getTargetUser($this->arg('id'));
$this->other = $this->getTargetProfile($this->arg('id'));
return true;
}

View File

@ -23,7 +23,7 @@
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009-2010 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/
*/
@ -64,7 +64,7 @@ class ApiBlockDestroyAction extends ApiAuthAction
parent::prepare($args);
$this->user = $this->auth_user;
$this->other = $this->getTargetUser($this->arg('id'));
$this->other = $this->getTargetProfile($this->arg('id'));
return true;
}

View File

@ -232,7 +232,8 @@ class ApiDirectMessageAction extends ApiAuthAction
function showXmlDirectMessages()
{
$this->initDocument('xml');
$this->elementStart('direct-messages', array('type' => 'array'));
$this->elementStart('direct-messages', array('type' => 'array',
'xmlns:statusnet' => 'http://status.net/schema/api/1/'));
foreach ($this->messages as $m) {
$dm_array = $this->directMessageArray($m);

View File

@ -52,7 +52,6 @@ require_once INSTALLDIR . '/lib/apiauth.php';
class ApiDirectMessageNewAction extends ApiAuthAction
{
var $source = null;
var $other = null;
var $content = null;
@ -76,13 +75,6 @@ class ApiDirectMessageNewAction extends ApiAuthAction
return;
}
$this->source = $this->trimmed('source'); // Not supported by Twitter.
$reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api');
if (empty($this->source) || in_array($this->source, $reserved_sources)) {
$source = 'api';
}
$this->content = $this->trimmed('text');
$this->user = $this->auth_user;

View File

@ -25,6 +25,7 @@
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/

View File

@ -25,6 +25,7 @@
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/

View File

@ -24,7 +24,7 @@
* @author Dan Moore <dan@moore.cx>
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009-2010 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/
*/
@ -67,7 +67,7 @@ class ApiFriendshipsCreateAction extends ApiAuthAction
parent::prepare($args);
$this->user = $this->auth_user;
$this->other = $this->getTargetUser($id);
$this->other = $this->getTargetProfile($this->arg('id'));
return true;
}
@ -106,7 +106,7 @@ class ApiFriendshipsCreateAction extends ApiAuthAction
if (empty($this->other)) {
$this->clientError(
_('Could not follow user: User not found.'),
_('Could not follow user: profile not found.'),
403,
$this->format
);

View File

@ -24,7 +24,7 @@
* @author Dan Moore <dan@moore.cx>
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009-2010 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/
*/
@ -67,7 +67,7 @@ class ApiFriendshipsDestroyAction extends ApiAuthAction
parent::prepare($args);
$this->user = $this->auth_user;
$this->other = $this->getTargetUser($id);
$this->other = $this->getTargetProfile($this->arg('id'));
return true;
}
@ -125,8 +125,7 @@ class ApiFriendshipsDestroyAction extends ApiAuthAction
}
// throws an exception on error
Subscription::cancel($this->user->getProfile(),
$this->other->getProfile());
Subscription::cancel($this->user->getProfile(), $this->other);
$this->initDocument($this->format);
$this->showProfile($this->other, $this->format);

View File

@ -24,7 +24,7 @@
* @author Dan Moore <dan@moore.cx>
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009-2010 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/
*/
@ -50,8 +50,8 @@ require_once INSTALLDIR . '/lib/apiprivateauth.php';
class ApiFriendshipsExistsAction extends ApiPrivateAuthAction
{
var $user_a = null;
var $user_b = null;
var $profile_a = null;
var $profile_b = null;
/**
* Take arguments for running
@ -66,11 +66,8 @@ class ApiFriendshipsExistsAction extends ApiPrivateAuthAction
{
parent::prepare($args);
$user_a_id = $this->trimmed('user_a');
$user_b_id = $this->trimmed('user_b');
$this->user_a = $this->getTargetUser($user_a_id);
$this->user_b = $this->getTargetUser($user_b_id);
$this->profile_a = $this->getTargetProfile($this->trimmed('user_a'));
$this->profile_b = $this->getTargetProfile($this->trimmed('user_b'));
return true;
}
@ -89,16 +86,16 @@ class ApiFriendshipsExistsAction extends ApiPrivateAuthAction
{
parent::handle($args);
if (empty($this->user_a) || empty($this->user_b)) {
if (empty($this->profile_a) || empty($this->profile_b)) {
$this->clientError(
_('Two user ids or screen_names must be supplied.'),
_('Two valid IDs or screen_names must be supplied.'),
400,
$this->format
);
return;
}
$result = $this->user_a->isSubscribed($this->user_b);
$result = Subscription::exists($this->profile_a, $this->profile_b);
switch ($this->format) {
case 'xml':

View File

@ -26,6 +26,7 @@
* @author Jeffery To <jeffery.to@gmail.com>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/

View File

@ -26,6 +26,7 @@
* @author Jeffery To <jeffery.to@gmail.com>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/

View File

@ -26,6 +26,7 @@
* @author Jeffery To <jeffery.to@gmail.com>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/

View File

@ -26,6 +26,7 @@
* @author Jeffery To <jeffery.to@gmail.com>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/

View File

@ -26,6 +26,7 @@
* @author Jeffery To <jeffery.to@gmail.com>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/

View File

@ -26,6 +26,7 @@
* @author Jeffery To <jeffery.to@gmail.com>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/

View File

@ -26,6 +26,7 @@
* @author Jeffery To <jeffery.to@gmail.com>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/

View File

@ -0,0 +1,367 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Update a group's profile
*
* 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 Zach Copley <zach@status.net>
* @copyright 2010 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';
/**
* API analog to the group edit page
*
* @category API
* @package StatusNet
* @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 ApiGroupProfileUpdateAction extends ApiAuthAction
{
/**
* Take arguments for running
*
* @param array $args $_REQUEST args
*
* @return boolean success flag
*
*/
function prepare($args)
{
parent::prepare($args);
$this->nickname = common_canonical_nickname($this->trimmed('nickname'));
$this->fullname = $this->trimmed('fullname');
$this->homepage = $this->trimmed('homepage');
$this->description = $this->trimmed('description');
$this->location = $this->trimmed('location');
$this->aliasstring = $this->trimmed('aliases');
$this->user = $this->auth_user;
$this->group = $this->getTargetGroup($this->arg('id'));
return true;
}
/**
* Handle the request
*
* See which request params have been set, and update the profile
*
* @param array $args $_REQUEST data (unused)
*
* @return void
*/
function handle($args)
{
parent::handle($args);
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
$this->clientError(
_('This method requires a POST.'),
400, $this->format
);
return;
}
if (!in_array($this->format, array('xml', 'json'))) {
$this->clientError(
_('API method not found.'),
404,
$this->format
);
return;
}
if (empty($this->user)) {
$this->clientError(_('No such user.'), 404, $this->format);
return;
}
if (empty($this->group)) {
$this->clientError(_('Group not found.'), 404, $this->format);
return false;
}
if (!$this->user->isAdmin($this->group)) {
$this->clientError(_('You must be an admin to edit the group.'), 403);
return false;
}
$this->group->query('BEGIN');
$orig = clone($this->group);
try {
if (!empty($this->nickname)) {
if ($this->validateNickname()) {
$this->group->nickname = $this->nickname;
$this->group->mainpage = common_local_url(
'showgroup',
array('nickname' => $this->nickname)
);
}
}
if (!empty($this->fullname)) {
$this->validateFullname();
$this->group->fullname = $this->fullname;
}
if (!empty($this->homepage)) {
$this->validateHomepage();
$this->group->homepage = $this->hompage;
}
if (!empty($this->description)) {
$this->validateDescription();
$this->group->description = $this->decription;
}
if (!empty($this->location)) {
$this->validateLocation();
$this->group->location = $this->location;
}
} catch (ApiValidationException $ave) {
$this->clientError(
$ave->getMessage(),
403,
$this->format
);
return;
}
$result = $this->group->update($orig);
if (!$result) {
common_log_db_error($this->group, 'UPDATE', __FILE__);
$this->serverError(_('Could not update group.'));
}
$aliases = array();
try {
if (!empty($this->aliasstring)) {
$aliases = $this->validateAliases();
}
} catch (ApiValidationException $ave) {
$this->clientError(
$ave->getMessage(),
403,
$this->format
);
return;
}
$result = $this->group->setAliases($aliases);
if (!$result) {
$this->serverError(_('Could not create aliases.'));
}
if (!empty($this->nickname) && ($this->nickname != $orig->nickname)) {
common_log(LOG_INFO, "Saving local group info.");
$local = Local_group::staticGet('group_id', $this->group->id);
$local->setNickname($this->nickname);
}
$this->group->query('COMMIT');
switch($this->format) {
case 'xml':
$this->showSingleXmlGroup($this->group);
break;
case 'json':
$this->showSingleJsonGroup($this->group);
break;
default:
$this->clientError(_('API method not found.'), 404, $this->format);
break;
}
}
function nicknameExists($nickname)
{
$group = Local_group::staticGet('nickname', $nickname);
if (!empty($group) &&
$group->group_id != $this->group->id) {
return true;
}
$alias = Group_alias::staticGet('alias', $nickname);
if (!empty($alias) &&
$alias->group_id != $this->group->id) {
return true;
}
return false;
}
function validateNickname()
{
if (!Validate::string(
$this->nickname, array(
'min_length' => 1,
'max_length' => 64,
'format' => NICKNAME_FMT
)
)
) {
throw new ApiValidationException(
_(
'Nickname must have only lowercase letters ' .
'and numbers and no spaces.'
)
);
} else if ($this->nicknameExists($this->nickname)) {
throw new ApiValidationException(
_('Nickname already in use. Try another one.')
);
} else if (!User_group::allowedNickname($this->nickname)) {
throw new ApiValidationException(
_('Not a valid nickname.')
);
}
return true;
}
function validateHomepage()
{
if (!is_null($this->homepage)
&& (strlen($this->homepage) > 0)
&& !Validate::uri(
$this->homepage,
array('allowed_schemes' => array('http', 'https')
)
)
) {
throw new ApiValidationException(
_('Homepage is not a valid URL.')
);
}
}
function validateFullname()
{
if (!is_null($this->fullname) && mb_strlen($this->fullname) > 255) {
throw new ApiValidationException(
_('Full name is too long (max 255 chars).')
);
}
}
function validateDescription()
{
if (User_group::descriptionTooLong($this->description)) {
throw new ApiValidationException(
sprintf(
_('description is too long (max %d chars).'),
User_group::maxDescription()
)
);
}
}
function validateLocation()
{
if (!is_null($this->location) && mb_strlen($this->location) > 255) {
throw new ApiValidationException(
_('Location is too long (max 255 chars).')
);
}
}
function validateAliases()
{
$aliases = array_map(
'common_canonical_nickname',
array_unique(
preg_split('/[\s,]+/',
$this->aliasstring
)
)
);
if (count($aliases) > common_config('group', 'maxaliases')) {
throw new ApiValidationException(
sprintf(
_('Too many aliases! Maximum %d.'),
common_config('group', 'maxaliases')
)
);
}
foreach ($aliases as $alias) {
if (!Validate::string(
$alias, array(
'min_length' => 1,
'max_length' => 64,
'format' => NICKNAME_FMT)
)
) {
throw new ApiValidationException(
sprintf(
_('Invalid alias: "%s"'),
$alias
)
);
}
if ($this->nicknameExists($alias)) {
throw new ApiValidationException(
sprintf(
_('Alias "%s" already in use. Try another one.'),
$alias)
);
}
// XXX assumes alphanum nicknames
if (strcmp($alias, $this->nickname) == 0) {
throw new ApiValidationException(
_('Alias can\'t be the same as nickname.')
);
}
}
return $aliases;
}
}

View File

@ -26,6 +26,7 @@
* @author Jeffery To <jeffery.to@gmail.com>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/

View File

@ -88,15 +88,15 @@ class ApiMediaUploadAction extends ApiAuthAction
try {
$upload = MediaFile::fromUpload('media', $this->auth_user);
} catch (ClientException $ce) {
$this->clientError($ce->getMessage());
} catch (Exception $e) {
$this->clientError($e->getMessage(), $e->getCode());
return;
}
if (isset($upload)) {
$this->showResponse($upload);
} else {
$this->clientError('Upload failed.');
$this->clientError(_('Upload failed.'));
return;
}
}

View File

@ -22,7 +22,7 @@
* @category Search
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2008-2009 StatusNet, Inc.
* @copyright 2008-2010 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/
*/
@ -31,6 +31,8 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/lib/apiprivateauth.php';
/**
* Action for outputting search results in Twitter compatible Atom
* format.
@ -44,10 +46,10 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*
* @see ApiAction
* @see ApiPrivateAuthAction
*/
class TwitapisearchatomAction extends ApiAction
class ApiSearchAtomAction extends ApiPrivateAuthAction
{
var $cnt;
@ -96,8 +98,11 @@ class TwitapisearchatomAction extends ApiAction
function prepare($args)
{
common_debug("in apisearchatom prepare()");
parent::prepare($args);
$this->query = $this->trimmed('q');
$this->lang = $this->trimmed('lang');
$this->rpp = $this->trimmed('rpp');
@ -138,6 +143,7 @@ class TwitapisearchatomAction extends ApiAction
function handle($args)
{
parent::handle($args);
common_debug("In apisearchatom handle()");
$this->showAtom();
}
@ -342,10 +348,24 @@ class TwitapisearchatomAction extends ApiAction
'rel' => 'related',
'href' => $profile->avatarUrl()));
// TODO: Here is where we'd put in a link to an atom feed for threads
// @todo: Here is where we'd put in a link to an atom feed for threads
$this->element("twitter:source", null,
htmlentities($this->sourceLink($notice->source)));
$source = null;
$ns = $notice->getSource();
if ($ns) {
if (!empty($ns->name) && !empty($ns->url)) {
$source = '<a href="'
. htmlspecialchars($ns->url)
. '" rel="nofollow">'
. htmlspecialchars($ns->name)
. '</a>';
} else {
$source = $ns->code;
}
}
$this->element("twitter:source", null, $source);
$this->elementStart('author');

View File

@ -22,7 +22,7 @@
* @category Search
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2008-2009 StatusNet, Inc.
* @copyright 2008-2010 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/
*/
@ -31,6 +31,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/lib/apiprivateauth.php';
require_once INSTALLDIR.'/lib/jsonsearchresultslist.php';
/**
@ -44,7 +45,7 @@ require_once INSTALLDIR.'/lib/jsonsearchresultslist.php';
* @see ApiAction
*/
class TwitapisearchjsonAction extends ApiAction
class ApiSearchJSONAction extends ApiPrivateAuthAction
{
var $query;
var $lang;
@ -64,6 +65,8 @@ class TwitapisearchjsonAction extends ApiAction
function prepare($args)
{
common_debug("apisearchjson prepare()");
parent::prepare($args);
$this->query = $this->trimmed('q');

View File

@ -29,6 +29,7 @@
* @author Robin Millette <robin@millette.info>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
@ -99,39 +100,43 @@ class ApiStatusesDestroyAction extends ApiAuthAction
parent::handle($args);
if (!in_array($this->format, array('xml', 'json'))) {
$this->clientError(_('API method not found.'), $code = 404);
$this->clientError(
_('API method not found.'),
404
);
return;
}
if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
$this->clientError(_('This method requires a POST or DELETE.'),
400, $this->format);
$this->clientError(
_('This method requires a POST or DELETE.'),
400,
$this->format
);
return;
}
if (empty($this->notice)) {
$this->clientError(_('No status found with that ID.'),
404, $this->format);
$this->clientError(
_('No status found with that ID.'),
404, $this->format
);
return;
}
if ($this->user->id == $this->notice->profile_id) {
$replies = new Reply;
$replies->get('notice_id', $this->notice_id);
$replies->delete();
if (Event::handle('StartDeleteOwnNotice', array($this->user, $this->notice))) {
$this->notice->delete();
if ($this->format == 'xml') {
$this->showSingleXmlStatus($this->notice);
} elseif ($this->format == 'json') {
$this->show_single_json_status($this->notice);
Event::handle('EndDeleteOwnNotice', array($this->user, $this->notice));
}
} else {
$this->clientError(_('You may not delete another user\'s status.'),
403, $this->format);
}
$this->showNotice();
} else {
$this->clientError(
_('You may not delete another user\'s status.'),
403,
$this->format
);
}
}
/**

View File

@ -79,7 +79,7 @@ class ApiStatusesRetweetAction extends ApiAuthAction
$this->user = $this->auth_user;
if ($this->user->id == $notice->profile_id) {
if ($this->user->id == $this->original->profile_id) {
$this->clientError(_('Cannot repeat your own notice.'),
400, $this->format);
return false;

View File

@ -29,6 +29,7 @@
* @author Robin Millette <robin@millette.info>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/

View File

@ -29,10 +29,102 @@
* @author Robin Millette <robin@millette.info>
* @author Zach Copley <zach@status.net>
* @copyright 2009-2010 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
/* External API usage documentation. Please update when you change how this method works. */
/*! @page statusesupdate statuses/update
@section Description
Updates the authenticating user's status. Requires the status parameter specified below.
Request must be a POST.
@par URL pattern
/api/statuses/update.:format
@par Formats (:format)
xml, json
@par HTTP Method(s)
POST
@par Requires Authentication
Yes
@param status (Required) The URL-encoded text of the status update.
@param source (Optional) The source of the status.
@param in_reply_to_status_id (Optional) The ID of an existing status that the update is in reply to.
@param lat (Optional) The latitude the status refers to.
@param long (Optional) The longitude the status refers to.
@param media (Optional) a media upload, such as an image or movie file.
@sa @ref authentication
@sa @ref apiroot
@subsection usagenotes Usage notes
@li The URL pattern is relative to the @ref apiroot.
@li If the @e source parameter is not supplied the source of the status will default to 'api'.
@li The XML response uses <a href="http://georss.org/Main_Page">GeoRSS</a>
to encode the latitude and longitude (see example response below <georss:point>).
@li Data uploaded via the @e media parameter should be multipart/form-data encoded.
@subsection exampleusage Example usage
@verbatim
curl -u username:password http://example.com/api/statuses/update.xml -d status='Howdy!' -d lat='30.468' -d long='-94.743'
@endverbatim
@subsection exampleresponse Example response
@verbatim
<?xml version="1.0" encoding="UTF-8"?>
<status>
<text>Howdy!</text>
<truncated>false</truncated>
<created_at>Tue Mar 30 23:28:05 +0000 2010</created_at>
<in_reply_to_status_id/>
<source>api</source>
<id>26668724</id>
<in_reply_to_user_id/>
<in_reply_to_screen_name/>
<geo xmlns:georss="http://www.georss.org/georss">
<georss:point>30.468 -94.743</georss:point>
</geo>
<favorited>false</favorited>
<user>
<id>25803</id>
<name>Jed Sanders</name>
<screen_name>jedsanders</screen_name>
<location>Hoop and Holler, Texas</location>
<description>I like to think of myself as America's Favorite.</description>
<profile_image_url>http://avatar.example.com/25803-48-20080924200604.png</profile_image_url>
<url>http://jedsanders.net</url>
<protected>false</protected>
<followers_count>5</followers_count>
<profile_background_color/>
<profile_text_color/>
<profile_link_color/>
<profile_sidebar_fill_color/>
<profile_sidebar_border_color/>
<friends_count>2</friends_count>
<created_at>Wed Sep 24 20:04:00 +0000 2008</created_at>
<favourites_count>0</favourites_count>
<utc_offset>0</utc_offset>
<time_zone>UTC</time_zone>
<profile_background_image_url/>
<profile_background_tile>false</profile_background_tile>
<statuses_count>70</statuses_count>
<following>true</following>
<notifications>true</notifications>
</user>
</status>
@endverbatim
*/
if (!defined('STATUSNET')) {
exit(1);
}
@ -64,8 +156,6 @@ class ApiStatusesUpdateAction extends ApiAuthAction
var $lat = null;
var $lon = null;
static $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api');
/**
* Take arguments for running
*
@ -80,19 +170,9 @@ class ApiStatusesUpdateAction extends ApiAuthAction
parent::prepare($args);
$this->status = $this->trimmed('status');
$this->source = $this->trimmed('source');
$this->lat = $this->trimmed('lat');
$this->lon = $this->trimmed('long');
// try to set the source attr from OAuth app
if (empty($this->source)) {
$this->source = $this->oauth_source;
}
if (empty($this->source) || in_array($this->source, self::$reserved_sources)) {
$this->source = 'api';
}
$this->in_reply_to_status_id
= intval($this->trimmed('in_reply_to_status_id'));
@ -116,7 +196,8 @@ class ApiStatusesUpdateAction extends ApiAuthAction
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
$this->clientError(
_('This method requires a POST.'),
400, $this->format
400,
$this->format
);
return;
}
@ -137,7 +218,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction
if (empty($this->status)) {
$this->clientError(
'Client must provide a \'status\' parameter with a value.',
_('Client must provide a \'status\' parameter with a value.'),
400,
$this->format
);
@ -211,8 +292,8 @@ class ApiStatusesUpdateAction extends ApiAuthAction
try {
$upload = MediaFile::fromUpload('media', $this->auth_user);
} catch (ClientException $ce) {
$this->clientError($ce->getMessage());
} catch (Exception $e) {
$this->clientError($e->getMessage(), $e->getCode(), $this->format);
return;
}
@ -225,7 +306,11 @@ class ApiStatusesUpdateAction extends ApiAuthAction
'Max notice size is %d chars, ' .
'including attachment URL.'
);
$this->clientError(sprintf($msg, Notice::maxContent()));
$this->clientError(
sprintf($msg, Notice::maxContent()),
400,
$this->format
);
}
}
@ -252,7 +337,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction
$options
);
} catch (Exception $e) {
$this->clientError($e->getMessage());
$this->clientError($e->getMessage(), $e->getCode(), $this->format);
return;
}

View File

@ -206,7 +206,8 @@ class ApiSubscriptionsAction extends ApiBareAuthAction
{
switch ($this->format) {
case 'xml':
$this->elementStart('users', array('type' => 'array'));
$this->elementStart('users', array('type' => 'array',
'xmlns:statusnet' => 'http://status.net/schema/api/1/'));
foreach ($this->profiles as $profile) {
$this->showProfile(
$profile,

View File

@ -25,6 +25,7 @@
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @copyright 2009-2010 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
@ -150,7 +151,7 @@ class ApiTimelineFavoritesAction extends ApiBareAuthAction
header('Content-Type: application/atom+xml; charset=utf-8');
$atom = new AtomNoticeFeed();
$atom = new AtomNoticeFeed($this->auth_user);
$atom->setId($id);
$atom->setTitle($title);
@ -185,17 +186,23 @@ class ApiTimelineFavoritesAction extends ApiBareAuthAction
{
$notices = array();
common_debug("since id = " . $this->since_id . " max id = " . $this->max_id);
if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) {
$notice = $this->user->favoriteNotices(
true,
($this->page-1) * $this->count,
$this->count,
true
$this->since_id,
$this->max_id
);
} else {
$notice = $this->user->favoriteNotices(
false,
($this->page-1) * $this->count,
$this->count,
false
$this->since_id,
$this->max_id
);
}

View File

@ -28,11 +28,107 @@
* @author Mike Cochrane <mikec@mikenz.geek.nz>
* @author Robin Millette <robin@millette.info>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009-2010 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
/* External API usage documentation. Please update when you change how this method works. */
/*! @page friendstimeline statuses/friends_timeline
@section Description
Returns the 20 most recent statuses posted by the authenticating
user and that user's friends. This is the equivalent of "You and
friends" page in the web interface.
@par URL patterns
@li /api/statuses/friends_timeline.:format
@li /api/statuses/friends_timeline/:id.:format
@par Formats (:format)
xml, json, rss, atom
@par ID (:id)
username, user id
@par HTTP Method(s)
GET
@par Requires Authentication
Sometimes (see: @ref authentication)
@param user_id (Optional) Specifies a user by ID
@param screen_name (Optional) Specifies a user by screename (nickname)
@param since_id (Optional) Returns only statuses with an ID greater
than (that is, more recent than) the specified ID.
@param max_id (Optional) Returns only statuses with an ID less than
(that is, older than) or equal to the specified ID.
@param count (Optional) Specifies the number of statuses to retrieve.
@param page (Optional) Specifies the page of results to retrieve.
@sa @ref authentication
@sa @ref apiroot
@subsection usagenotes Usage notes
@li The URL pattern is relative to the @ref apiroot.
@li The XML response uses <a href="http://georss.org/Main_Page">GeoRSS</a>
to encode the latitude and longitude (see example response below <georss:point>).
@subsection exampleusage Example usage
@verbatim
curl http://identi.ca/api/statuses/friends_timeline/evan.xml?count=1&page=2
@endverbatim
@subsection exampleresponse Example response
@verbatim
<?xml version="1.0"?>
<statuses type="array">
<status>
<text>back from the !yul !drupal meet with Evolving Web folk, @anarcat, @webchick and others, and an interesting refresher on SQL indexing</text>
<truncated>false</truncated>
<created_at>Wed Mar 31 01:33:02 +0000 2010</created_at>
<in_reply_to_status_id/>
<source>&lt;a href="http://code.google.com/p/microblog-purple/"&gt;mbpidgin&lt;/a&gt;</source>
<id>26674201</id>
<in_reply_to_user_id/>
<in_reply_to_screen_name/>
<geo/>
<favorited>false</favorited>
<user>
<id>246</id>
<name>Mark</name>
<screen_name>lambic</screen_name>
<location>Montreal, Canada</location>
<description>Geek</description>
<profile_image_url>http://avatar.identi.ca/246-48-20080702141545.png</profile_image_url>
<url>http://lambic.co.uk</url>
<protected>false</protected>
<followers_count>73</followers_count>
<profile_background_color>#F0F2F5</profile_background_color>
<profile_text_color/>
<profile_link_color>#002E6E</profile_link_color>
<profile_sidebar_fill_color>#CEE1E9</profile_sidebar_fill_color>
<profile_sidebar_border_color/>
<friends_count>58</friends_count>
<created_at>Wed Jul 02 14:12:15 +0000 2008</created_at>
<favourites_count>2</favourites_count>
<utc_offset>-14400</utc_offset>
<time_zone>US/Eastern</time_zone>
<profile_background_image_url/>
<profile_background_tile>false</profile_background_tile>
<statuses_count>933</statuses_count>
<following>false</following>
<notifications>false</notifications>
</user>
</status>
</statuses>
@endverbatim
*/
if (!defined('STATUSNET')) {
exit(1);
}
@ -153,7 +249,7 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
header('Content-Type: application/atom+xml; charset=utf-8');
$atom = new AtomNoticeFeed();
$atom = new AtomNoticeFeed($this->auth_user);
$atom->setId($id);
$atom->setTitle($title);

View File

@ -25,7 +25,8 @@
* @author Evan Prodromou <evan@status.net>
* @author Jeffery To <jeffery.to@gmail.com>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009-2010 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
@ -105,7 +106,7 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
function showTimeline()
{
// We'll pull common formatting out of this for other formats
$atom = new AtomGroupNoticeFeed($this->group);
$atom = new AtomGroupNoticeFeed($this->group, $this->auth_user);
$self = $this->getSelfUri();
@ -137,7 +138,9 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
$this->raw($atom->getString());
} catch (Atom10FeedException $e) {
$this->serverError(
'Could not generate feed for group - ' . $e->getMessage()
'Could not generate feed for group - ' . $e->getMessage(),
400,
$this->format
);
return;
}

View File

@ -29,6 +29,7 @@
* @author Robin Millette <robin@millette.info>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
@ -152,7 +153,7 @@ class ApiTimelineHomeAction extends ApiBareAuthAction
header('Content-Type: application/atom+xml; charset=utf-8');
$atom = new AtomNoticeFeed();
$atom = new AtomNoticeFeed($this->auth_user);
$atom->setId($id);
$atom->setTitle($title);

View File

@ -29,6 +29,7 @@
* @author Robin Millette <robin@millette.info>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
@ -151,7 +152,7 @@ class ApiTimelineMentionsAction extends ApiBareAuthAction
header('Content-Type: application/atom+xml; charset=utf-8');
$atom = new AtomNoticeFeed();
$atom = new AtomNoticeFeed($this->auth_user);
$atom->setId($id);
$atom->setTitle($title);

View File

@ -29,6 +29,7 @@
* @author Robin Millette <robin@millette.info>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
@ -55,6 +56,95 @@ require_once INSTALLDIR . '/lib/apiprivateauth.php';
* @link http://status.net/
*/
/* External API usage documentation. Please update when you change how this method works. */
/*! @page publictimeline statuses/public_timeline
@section Description
Returns the 20 most recent notices from users throughout the system who have
uploaded their own avatars. Depending on configuration, it may or may not
not include notices from automatic posting services.
@par URL patterns
@li /api/statuses/public_timeline.:format
@par Formats (:format)
xml, json, rss, atom
@par HTTP Method(s)
GET
@par Requires Authentication
No
@param since_id (Optional) Returns only statuses with an ID greater
than (that is, more recent than) the specified ID.
@param max_id (Optional) Returns only statuses with an ID less than
(that is, older than) or equal to the specified ID.
@param count (Optional) Specifies the number of statuses to retrieve.
@param page (Optional) Specifies the page of results to retrieve.
@sa @ref apiroot
@subsection usagenotes Usage notes
@li The URL pattern is relative to the @ref apiroot.
@li The XML response uses <a href="http://georss.org/Main_Page">GeoRSS</a>
to encode the latitude and longitude (see example response below <georss:point>).
@subsection exampleusage Example usage
@verbatim
curl http://identi.ca/api/statuses/friends_timeline/evan.xml?count=1&page=2
@endverbatim
@subsection exampleresponse Example response
@verbatim
<?xml version="1.0" encoding="UTF-8"?>
<statuses type="array">
<status>
<text>@skwashd oh, commbank reenabled me super quick both times. but disconcerting when you don't expect it though</text>
<truncated>false</truncated>
<created_at>Sat Apr 17 00:49:12 +0000 2010</created_at>
<in_reply_to_status_id>28838393</in_reply_to_status_id>
<source>xmpp</source>
<id>28838456</id>
<in_reply_to_user_id>39303</in_reply_to_user_id>
<in_reply_to_screen_name>skwashd</in_reply_to_screen_name>
<geo></geo>
<favorited>false</favorited>
<user>
<id>44517</id>
<name>joshua may</name>
<screen_name>notjosh</screen_name>
<location></location>
<description></description>
<profile_image_url>http://avatar.identi.ca/44517-48-20090321004106.jpeg</profile_image_url>
<url></url>
<protected>false</protected>
<followers_count>17</followers_count>
<profile_background_color></profile_background_color>
<profile_text_color></profile_text_color>
<profile_link_color></profile_link_color>
<profile_sidebar_fill_color></profile_sidebar_fill_color>
<profile_sidebar_border_color></profile_sidebar_border_color>
<friends_count>20</friends_count>
<created_at>Sat Mar 21 00:40:25 +0000 2009</created_at>
<favourites_count>0</favourites_count>
<utc_offset>0</utc_offset>
<time_zone>UTC</time_zone>
<profile_background_image_url></profile_background_image_url>
<profile_background_tile>false</profile_background_tile>
<statuses_count>100</statuses_count>
<following>false</following>
<notifications>false</notifications>
</user>
</status>
[....]
</statuses>
@endverbatim
*/
class ApiTimelinePublicAction extends ApiPrivateAuthAction
{
@ -130,7 +220,7 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction
header('Content-Type: application/atom+xml; charset=utf-8');
$atom = new AtomNoticeFeed();
$atom = new AtomNoticeFeed($this->auth_user);
$atom->setId($id);
$atom->setTitle($title);

View File

@ -117,7 +117,7 @@ class ApiTimelineRetweetsOfMeAction extends ApiAuthAction
header('Content-Type: application/atom+xml; charset=utf-8');
$atom = new AtomNoticeFeed();
$atom = new AtomNoticeFeed($this->auth_user);
$atom->setId($id);
$atom->setTitle($title);

View File

@ -26,6 +26,7 @@
* @author Jeffery To <jeffery.to@gmail.com>
* @author Zach Copley <zach@status.net>
* @copyright 2009-2010 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
@ -138,7 +139,7 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction
header('Content-Type: application/atom+xml; charset=utf-8');
$atom = new AtomNoticeFeed();
$atom = new AtomNoticeFeed($this->auth_user);
$atom->setId($id);
$atom->setTitle($title);

View File

@ -29,6 +29,7 @@
* @author Robin Millette <robin@millette.info>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
@ -115,7 +116,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
// We'll use the shared params from the Atom stub
// for other feed types.
$atom = new AtomUserNoticeFeed($this->user);
$atom = new AtomUserNoticeFeed($this->user, $this->auth_user);
$link = common_local_url(
'showstream',

View File

@ -22,7 +22,7 @@
* @category Search
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2008-2009 StatusNet, Inc.
* @copyright 2008-2010 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/
*/
@ -31,6 +31,8 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/lib/apiprivateauth.php';
/**
* Returns the top ten queries that are currently trending
*
@ -43,7 +45,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
* @see ApiAction
*/
class TwitapitrendsAction extends ApiAction
class ApiTrendsAction extends ApiPrivateAuthAction
{
var $callback;
@ -82,7 +84,7 @@ class TwitapitrendsAction extends ApiAction
*/
function showTrends()
{
$this->serverError(_('API method under construction.'), $code = 501);
$this->serverError(_('API method under construction.'), 501);
}
}

View File

@ -113,7 +113,7 @@ class ApiUserShowAction extends ApiPrivateAuthAction
if ($this->format == 'xml') {
$this->initDocument('xml');
$this->showTwitterXmlUser($twitter_user);
$this->showTwitterXmlUser($twitter_user, 'user', true);
$this->endDocument('xml');
} elseif ($this->format == 'json') {
$this->initDocument('json');

View File

@ -87,13 +87,15 @@ class BlockAction extends ProfileFormAction
{
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if ($this->arg('no')) {
$this->returnToArgs();
$this->returnToPrevious();
} elseif ($this->arg('yes')) {
$this->handlePost();
$this->returnToArgs();
$this->returnToPrevious();
} else {
$this->showPage();
}
} else {
$this->showPage();
}
}
@ -118,6 +120,12 @@ class BlockAction extends ProfileFormAction
*/
function areYouSureForm()
{
// @fixme if we ajaxify the confirmation form, skip the preview on ajax hits
$profile = new ArrayWrapper(array($this->profile));
$preview = new ProfileList($profile, $this);
$preview->show();
$id = $this->profile->id;
$this->elementStart('form', array('id' => 'block-' . $id,
'method' => 'post',
@ -187,4 +195,38 @@ class BlockAction extends ProfileFormAction
$this->autofocus('form_action-yes');
}
/**
* Override for form session token checks; on our first hit we're just
* requesting confirmation, which doesn't need a token. We need to be
* able to take regular GET requests from email!
*
* @throws ClientException if token is bad on POST request or if we have
* confirmation parameters which could trigger something.
*/
function checkSessionToken()
{
if ($_SERVER['REQUEST_METHOD'] == 'POST' ||
$this->arg('yes') ||
$this->arg('no')) {
return parent::checkSessionToken();
}
}
/**
* If we reached this form without returnto arguments, return to the
* current user's subscription list.
*
* @return string URL
*/
function defaultReturnTo()
{
$user = common_current_user();
if ($user) {
return common_local_url('subscribers',
array('nickname' => $user->nickname));
} else {
return common_local_url('public');
}
}
}

View File

@ -172,7 +172,10 @@ class DeletenoticeAction extends Action
}
if ($this->arg('yes')) {
if (Event::handle('StartDeleteOwnNotice', array($this->user, $this->notice))) {
$this->notice->delete();
Event::handle('EndDeleteOwnNotice', array($this->user, $this->notice));
}
}
$url = common_get_returnto();

View File

@ -92,10 +92,10 @@ class DeleteuserAction extends ProfileFormAction
{
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if ($this->arg('no')) {
$this->returnToArgs();
$this->returnToPrevious();
} elseif ($this->arg('yes')) {
$this->handlePost();
$this->returnToArgs();
$this->returnToPrevious();
} else {
$this->showPage();
}

View File

@ -126,9 +126,19 @@ class DesignadminpanelAction extends AdminPanelAction
return;
}
// check for an image upload
// check for file uploads
$bgimage = $this->saveBackgroundImage();
$customTheme = $this->saveCustomTheme();
$oldtheme = common_config('site', 'theme');
if ($customTheme) {
// This feels pretty hacky :D
$this->args['theme'] = $customTheme;
$themeChanged = true;
} else {
$themeChanged = ($this->trimmed('theme') != $oldtheme);
}
static $settings = array('theme', 'logo');
@ -140,15 +150,13 @@ class DesignadminpanelAction extends AdminPanelAction
$this->validate($values);
$oldtheme = common_config('site', 'theme');
$config = new Config();
$config->query('BEGIN');
// Only update colors if the theme has not changed.
if ($oldtheme == $values['theme']) {
if (!$themeChanged) {
$bgcolor = new WebColor($this->trimmed('design_background'));
$ccolor = new WebColor($this->trimmed('design_content'));
@ -190,6 +198,13 @@ class DesignadminpanelAction extends AdminPanelAction
Config::save('design', 'backgroundimage', $bgimage);
}
if (common_config('custom_css', 'enabled')) {
$css = $this->arg('css');
if ($css != common_config('custom_css', 'css')) {
Config::save('custom_css', 'css', $css);
}
}
$config->query('COMMIT');
}
@ -263,6 +278,33 @@ class DesignadminpanelAction extends AdminPanelAction
}
}
/**
* Save the custom theme if the user uploaded one.
*
* @return mixed custom theme name, if succesful, or null if no theme upload.
* @throws ClientException for invalid theme archives
* @throws ServerException if trouble saving the theme files
*/
function saveCustomTheme()
{
if (common_config('theme_upload', 'enabled') &&
$_FILES['design_upload_theme']['error'] == UPLOAD_ERR_OK) {
$upload = ThemeUploader::fromUpload('design_upload_theme');
$basedir = common_config('local', 'dir');
if (empty($basedir)) {
$basedir = INSTALLDIR . '/local';
}
$name = 'custom'; // @todo allow multiples, custom naming?
$outdir = $basedir . '/theme/' . $name;
$upload->extract($outdir);
return $name;
} else {
return null;
}
}
/**
* Attempt to validate setting values
*
@ -371,7 +413,15 @@ class DesignAdminPanelForm extends AdminForm
function formData()
{
$this->showLogo();
$this->showTheme();
$this->showBackground();
$this->showColors();
$this->showAdvanced();
}
function showLogo()
{
$this->out->elementStart('fieldset', array('id' => 'settings_design_logo'));
$this->out->element('legend', null, _('Change logo'));
@ -384,6 +434,11 @@ class DesignAdminPanelForm extends AdminForm
$this->out->elementEnd('ul');
$this->out->elementEnd('fieldset');
}
function showTheme()
{
$this->out->elementStart('fieldset', array('id' => 'settings_design_theme'));
$this->out->element('legend', null, _('Change theme'));
@ -407,10 +462,23 @@ class DesignAdminPanelForm extends AdminForm
false, $this->value('theme'));
$this->unli();
if (common_config('theme_upload', 'enabled')) {
$this->li();
$this->out->element('label', array('for' => 'design_upload_theme'), _('Custom theme'));
$this->out->element('input', array('id' => 'design_upload_theme',
'name' => 'design_upload_theme',
'type' => 'file'));
$this->out->element('p', 'form_guide', _('You can upload a custom StatusNet theme as a .ZIP archive.'));
$this->unli();
}
$this->out->elementEnd('ul');
$this->out->elementEnd('fieldset');
}
function showBackground()
{
$design = $this->out->design;
$this->out->elementStart('fieldset', array('id' =>
@ -486,6 +554,11 @@ class DesignAdminPanelForm extends AdminForm
$this->out->elementEnd('ul');
$this->out->elementEnd('fieldset');
}
function showColors()
{
$design = $this->out->design;
$this->out->elementStart('fieldset', array('id' => 'settings_design_color'));
$this->out->element('legend', null, _('Change colours'));
@ -493,6 +566,7 @@ class DesignAdminPanelForm extends AdminForm
$this->out->elementStart('ul', 'form_data');
try {
// @fixme avoid loop unrolling in non-performance-critical contexts like this
$bgcolor = new WebColor($design->backgroundcolor);
@ -560,6 +634,7 @@ class DesignAdminPanelForm extends AdminForm
$this->unli();
} catch (WebColorException $e) {
// @fixme normalize them individually!
common_log(LOG_ERR, 'Bad color values in site design: ' .
$e->getMessage());
}
@ -569,6 +644,27 @@ class DesignAdminPanelForm extends AdminForm
$this->out->elementEnd('ul');
}
function showAdvanced()
{
if (common_config('custom_css', 'enabled')) {
$this->out->elementStart('fieldset', array('id' => 'settings_design_advanced'));
$this->out->element('legend', null, _('Advanced'));
$this->out->elementStart('ul', 'form_data');
$this->li();
$this->out->element('label', array('for' => 'css'), _('Custom CSS'));
$this->out->element('textarea', array('name' => 'css',
'id' => 'css',
'cols' => '50',
'rows' => '10'),
strval(common_config('custom_css', 'css')));
$this->unli();
$this->out->elementEnd('fieldset');
$this->out->elementEnd('ul');
}
}
/**
* Action elements
*

View File

@ -104,7 +104,7 @@ class FavorAction extends Action
}
/**
* Notifies a user when his notice is favorited.
* Notifies a user when their notice is favorited.
*
* @param class $notice favorited notice
* @param class $user user declaring a favorite

View File

@ -89,7 +89,7 @@ class FavoritesrssAction extends Rss10Action
function getNotices($limit=0)
{
$user = $this->user;
$notice = $user->favoriteNotices(0, $limit);
$notice = $user->favoriteNotices(false, 0, $limit);
$notices = array();
while ($notice->fetch()) {
$notices[] = clone($notice);

View File

@ -37,7 +37,7 @@ require_once INSTALLDIR.'/lib/omb.php';
* Handler for remote subscription finish callback
*
* When a remote user subscribes a local user, a redirect to this action is
* issued after the remote user authorized his service to subscribe.
* issued after the remote user authorized their service to subscribe.
*
* @category Action
* @package Laconica

View File

@ -95,7 +95,9 @@ class FoafAction extends Action
// Would be nice to tell if they were a Person or not (e.g. a #person usertag?)
$this->elementStart('Agent', array('rdf:about' =>
$this->user->uri));
if ($this->user->email) {
$this->element('mbox_sha1sum', null, sha1('mailto:' . $this->user->email));
}
if ($this->profile->fullname) {
$this->element('name', null, $this->profile->fullname);
}
@ -152,7 +154,9 @@ class FoafAction extends Action
}
$person = $this->showMicrobloggingAccount($this->profile,
common_root_url(), $this->user->uri, false);
common_root_url(), $this->user->uri,
/*$fetchSubscriptions*/true,
/*$isSubscriber*/false);
// Get people who subscribe to user
@ -207,7 +211,8 @@ class FoafAction extends Action
$this->showMicrobloggingAccount($profile,
($local == 'local') ? common_root_url() : null,
$uri,
true);
/*$fetchSubscriptions*/false,
/*$isSubscriber*/($type == LISTENER || $type == BOTH));
if ($foaf_url) {
$this->element('rdfs:seeAlso', array('rdf:resource' => $foaf_url));
}
@ -232,7 +237,21 @@ class FoafAction extends Action
$this->elementEnd('PersonalProfileDocument');
}
function showMicrobloggingAccount($profile, $service=null, $useruri=null, $isSubscriber=false)
/**
* Output FOAF <account> bit for the given profile.
*
* @param Profile $profile
* @param mixed $service Root URL of this StatusNet instance for a local
* user, otherwise null.
* @param mixed $useruri URI string for the referenced profile..
* @param boolean $fetchSubscriptions Should we load and list all their subscriptions?
* @param boolean $isSubscriber if not fetching subs, we can still mark the user as following the current page.
*
* @return array if $fetchSubscribers is set, return a list of info on those
* subscriptions.
*/
function showMicrobloggingAccount($profile, $service=null, $useruri=null, $fetchSubscriptions=false, $isSubscriber=false)
{
$attr = array();
if ($useruri) {
@ -254,9 +273,7 @@ class FoafAction extends Action
$person = array();
if ($isSubscriber) {
$this->element('sioc:follows', array('rdf:resource'=>$this->user->uri . '#acct'));
} else {
if ($fetchSubscriptions) {
// Get people user is subscribed to
$sub = new Subscription();
$sub->subscriber = $profile->id;
@ -281,6 +298,9 @@ class FoafAction extends Action
}
unset($sub);
} else if ($isSubscriber) {
// Just declare that they follow the user whose FOAF we're showing.
$this->element('sioc:follows', array('rdf:resource' => $this->user->uri . '#acct'));
}
$this->elementEnd('OnlineAccount');

View File

@ -37,6 +37,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
* @category Action
* @package StatusNet
* @author Craig Andrews <candrews@integralblue.com>
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*/

View File

@ -129,9 +129,9 @@ class GetfileAction extends Action
return null;
}
$cache = common_memcache();
$cache = Cache::instance();
if($cache) {
$key = common_cache_key('attachments:etag:' . $this->path);
$key = Cache::key('attachments:etag:' . $this->path);
$etag = $cache->get($key);
if($etag === false) {
$etag = crc32(file_get_contents($this->path));

View File

@ -117,7 +117,7 @@ class GroupblockAction extends RedirectingAction
parent::handle($args);
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if ($this->arg('no')) {
$this->returnToArgs();
$this->returnToPrevious();
} elseif ($this->arg('yes')) {
$this->blockProfile();
} elseif ($this->arg('blockto')) {
@ -207,7 +207,7 @@ class GroupblockAction extends RedirectingAction
return false;
}
$this->returnToArgs();
$this->returnToPrevious();
}
/**

View File

@ -18,8 +18,10 @@
*/
/**
* @package OStatusPlugin
* @category Action
* @package StatusNet
* @maintainer James Walker <james@status.net>
* @author Craig Andrews <candrews@integralblue.com>
*/
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
@ -27,22 +29,30 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
class HostMetaAction extends Action
{
/**
* Is read only?
*
* @return boolean true
*/
function isReadOnly()
{
return true;
}
function handle()
{
parent::handle();
$domain = common_config('site', 'server');
$url = common_local_url('userxrd');
$url.= '?uri={uri}';
$xrd = new XRD();
$xrd = new XRD();
$xrd->host = $domain;
$xrd->links[] = array('rel' => Discovery::LRDD_REL,
'template' => $url,
'title' => array('Resource Descriptor'));
if(Event::handle('StartHostMetaLinks', array(&$xrd->links))) {
Event::handle('EndHostMetaLinks', array(&$xrd->links));
}
header('Content-type: application/xrd+xml');
print $xrd->toXML();
}
}

View File

@ -133,8 +133,7 @@ class ImsettingsAction extends ConnectSettingsAction
'message with further instructions. '.
'(Did you add %s to your buddy list?)'),
$transport_info['display'],
$transport_info['daemon_screenname'],
jabber_daemon_address()));
$transport_info['daemonScreenname']));
$this->hidden('screenname', $confirm->address);
// TRANS: Button label to cancel an IM address confirmation procedure.
$this->submit('cancel', _m('BUTTON','Cancel'));
@ -163,12 +162,11 @@ class ImsettingsAction extends ConnectSettingsAction
'action' =>
common_local_url('imsettings')));
$this->elementStart('fieldset', array('id' => 'settings_im_preferences'));
$this->element('legend', null, _('Preferences'));
// TRANS: Header for IM preferences form.
$this->element('legend', null, _('IM Preferences'));
$this->hidden('token', common_session_token());
$this->elementStart('table');
$this->elementStart('tr');
// TRANS: Header for IM preferences form.
$this->element('th', null, _('IM Preferences'));
foreach($user_im_prefs_by_transport as $transport=>$user_im_prefs)
{
$this->element('th', null, $transports[$transport]['display']);
@ -278,19 +276,20 @@ class ImsettingsAction extends ConnectSettingsAction
$user = common_current_user();
$user_im_prefs = new User_im_prefs();
$user_im_prefs->query('BEGIN');
$user_im_prefs->user_id = $user->id;
if($user_im_prefs->find() && $user_im_prefs->fetch())
{
$preferences = array('notify', 'updatefrompresence', 'replies', 'microid');
$user_im_prefs->query('BEGIN');
do
{
$original = clone($user_im_prefs);
$new = clone($user_im_prefs);
foreach($preferences as $preference)
{
$user_im_prefs->$preference = $this->boolean($user_im_prefs->transport . '_' . $preference);
$new->$preference = $this->boolean($new->transport . '_' . $preference);
}
$result = $user_im_prefs->update($original);
$result = $new->update($original);
if ($result === false) {
common_log_db_error($user, 'UPDATE', __FILE__);
@ -299,8 +298,8 @@ class ImsettingsAction extends ConnectSettingsAction
return;
}
}while($user_im_prefs->fetch());
$user_im_prefs->query('COMMIT');
}
$user_im_prefs->query('COMMIT');
// TRANS: Confirmation message for successful IM preferences save.
$this->showForm(_('Preferences saved.'), true);
}

View File

@ -62,6 +62,28 @@ class LoginAction extends Action
return false;
}
/**
* Prepare page to run
*
*
* @param $args
* @return string title
*/
function prepare($args)
{
parent::prepare($args);
// @todo this check should really be in index.php for all sensitive actions
$ssl = common_config('site', 'ssl');
if (empty($_SERVER['HTTPS']) && ($ssl == 'always' || $ssl == 'sometimes')) {
common_redirect(common_local_url('login'));
// exit
}
return true;
}
/**
* Handle input, produce output
*
@ -96,27 +118,10 @@ class LoginAction extends Action
* @return void
*/
function checkLogin($user_id=null, $token=null)
function checkLogin($user_id=null)
{
// XXX: login throttle
// CSRF protection - token set in NoticeForm
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$st = common_session_token();
if (empty($token)) {
common_log(LOG_WARNING, 'No token provided by client.');
} else if (empty($st)) {
common_log(LOG_WARNING, 'No session token stored.');
} else {
common_log(LOG_WARNING, 'Token = ' . $token . ' and session token = ' . $st);
}
$this->clientError(_('There was a problem with your session token. '.
'Try again, please.'));
return;
}
$nickname = $this->trimmed('nickname');
$password = $this->arg('password');
@ -239,7 +244,6 @@ class LoginAction extends Action
$this->elementEnd('li');
$this->elementEnd('ul');
$this->submit('submit', _('Login'));
$this->hidden('token', common_session_token());
$this->elementEnd('fieldset');
$this->elementEnd('form');
$this->elementStart('p');
@ -267,10 +271,14 @@ class LoginAction extends Action
'user name and password ' .
'before changing your settings.');
} else {
return _('Login with your username and password. ' .
'Don\'t have a username yet? ' .
$prompt = _('Login with your username and password.');
if (!common_config('site', 'closed') && !common_config('site', 'inviteonly')) {
$prompt .= ' ';
$prompt .= _('Don\'t have a username yet? ' .
'[Register](%%action.register%%) a new account.');
}
return $prompt;
}
}
/**

View File

@ -131,6 +131,8 @@ class NewnoticeAction extends Action
$user = common_current_user();
assert($user); // XXX: maybe an error instead...
$content = $this->trimmed('status_textarea');
$options = array();
Event::handle('StartSaveNewNoticeWeb', array($this, $user, &$content, &$options));
if (!$content) {
$this->clientError(_('No content!'));
@ -157,11 +159,9 @@ class NewnoticeAction extends Action
Notice::maxContent()));
}
$replyto = $this->trimmed('inreplyto');
#If an ID of 0 is wrongly passed here, it will cause a database error,
#so override it...
if ($replyto == 0) {
$replyto = 'false';
$replyto = intval($this->trimmed('inreplyto'));
if ($replyto) {
$options['reply_to'] = $replyto;
}
$upload = null;
@ -169,7 +169,10 @@ class NewnoticeAction extends Action
if (isset($upload)) {
if (Event::handle('StartSaveNewNoticeAppendAttachment', array($this, $upload, &$content_shortened, &$options))) {
$content_shortened .= ' ' . $upload->shortUrl();
}
Event::handle('EndSaveNewNoticeAppendAttachment', array($this, $upload, &$content_shortened, &$options));
if (Notice::contentTooLong($content_shortened)) {
$upload->delete();
@ -182,8 +185,6 @@ class NewnoticeAction extends Action
}
}
$options = array('reply_to' => ($replyto == 'false') ? null : $replyto);
if ($user->shareLocation()) {
// use browser data if checked; otherwise profile data
if ($this->arg('notice_data-geo')) {
@ -203,12 +204,21 @@ class NewnoticeAction extends Action
$options = array_merge($options, $locOptions);
}
$author_id = $user->id;
$text = $content_shortened;
if (Event::handle('StartNoticeSaveWeb', array($this, &$author_id, &$text, &$options))) {
$notice = Notice::saveNew($user->id, $content_shortened, 'web', $options);
if (isset($upload)) {
$upload->attachToNotice($notice);
}
Event::handle('EndNoticeSaveWeb', array($this, $notice));
}
Event::handle('EndSaveNewNoticeWeb', array($this, $user, &$content_shortened, &$options));
if ($this->boolean('ajax')) {
header('Content-Type: text/xml;charset=utf-8');
$this->xw->startDocument('1.0', 'UTF-8');

View File

@ -82,7 +82,7 @@ class NudgeAction extends Action
}
if (!$other->email || !$other->emailnotifynudge) {
$this->clientError(_('This user doesn\'t allow nudges or hasn\'t confirmed or set his email yet.'));
$this->clientError(_('This user doesn\'t allow nudges or hasn\'t confirmed or set their email yet.'));
return;
}

View File

@ -23,6 +23,7 @@
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2008 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/

View File

@ -8,7 +8,9 @@
* @category Action
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Craig Andrews <candrews@integralblue.com>
* @author Robin Millette <millette@status.net>
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*
@ -44,6 +46,7 @@ require_once INSTALLDIR.'/lib/xrdsoutputter.php';
* @author Evan Prodromou <evan@status.net>
* @author Robin Millette <millette@status.net>
* @author Craig Andrews <candrews@integralblue.com>
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*

View File

@ -74,6 +74,13 @@ class RegisterAction extends Action
parent::prepare($args);
$this->code = $this->trimmed('code');
// @todo this check should really be in index.php for all sensitive actions
$ssl = common_config('site', 'ssl');
if (empty($_SERVER['HTTPS']) && ($ssl == 'always' || $ssl == 'sometimes')) {
common_redirect(common_local_url('register'));
// exit
}
if (empty($this->code)) {
common_ensure_session();
if (array_key_exists('invitecode', $_SESSION)) {
@ -491,6 +498,45 @@ class RegisterAction extends Action
$this->elementStart('li');
$this->element('input', $attrs);
$this->elementStart('label', array('class' => 'checkbox', 'for' => 'license'));
$this->raw($this->licenseCheckbox());
$this->elementEnd('label');
$this->elementEnd('li');
}
$this->elementEnd('ul');
$this->submit('submit', _('Register'));
$this->elementEnd('fieldset');
$this->elementEnd('form');
}
function licenseCheckbox()
{
$out = '';
switch (common_config('license', 'type')) {
case 'private':
// TRANS: Copyright checkbox label in registration dialog, for private sites.
$out .= htmlspecialchars(sprintf(
_('I understand that content and data of %1$s are private and confidential.'),
common_config('site', 'name')));
// fall through
case 'allrightsreserved':
if ($out != '') {
$out .= ' ';
}
if (common_config('license', 'owner')) {
// TRANS: Copyright checkbox label in registration dialog, for all rights reserved with a specified copyright owner.
$out .= htmlspecialchars(sprintf(
_('My text and files are copyright by %1$s.'),
common_config('license', 'owner')));
} else {
// TRANS: Copyright checkbox label in registration dialog, for all rights reserved with ownership left to contributors.
$out .= htmlspecialchars(_('My text and files remain under my own copyright.'));
}
// TRANS: Copyright checkbox label in registration dialog, for all rights reserved.
$out .= ' ' . _('All rights reserved.');
break;
case 'cc': // fall through
default:
// TRANS: Copyright checkbox label in registration dialog, for Creative Commons-style licenses.
$message = _('My text and files are available under %s ' .
'except this private data: password, ' .
'email address, IM address, and phone number.');
@ -499,14 +545,9 @@ class RegisterAction extends Action
'">' .
htmlspecialchars(common_config('license', 'title')) .
'</a>';
$this->raw(sprintf(htmlspecialchars($message), $link));
$this->elementEnd('label');
$this->elementEnd('li');
$out .= sprintf(htmlspecialchars($message), $link);
}
$this->elementEnd('ul');
$this->submit('submit', _('Register'));
$this->elementEnd('fieldset');
$this->elementEnd('form');
return $out;
}
/**

View File

@ -196,18 +196,18 @@ class RepliesAction extends OwnerDesignAction
function showEmptyListMessage()
{
$message = sprintf(_('This is the timeline showing replies to %1$s but %2$s hasn\'t received a notice to his attention yet.'), $this->user->nickname, $this->user->nickname) . ' ';
$message = sprintf(_('This is the timeline showing replies to %1$s but %2$s hasn\'t received a notice to them yet.'), $this->user->nickname, $this->user->nickname) . ' ';
if (common_logged_in()) {
$current_user = common_current_user();
if ($this->user->id === $current_user->id) {
$message .= _('You can engage other users in a conversation, subscribe to more people or [join groups](%%action.groups%%).');
} else {
$message .= sprintf(_('You can try to [nudge %1$s](../%2$s) or [post something to his or her attention](%%%%action.newnotice%%%%?status_textarea=%3$s).'), $this->user->nickname, $this->user->nickname, '@' . $this->user->nickname);
$message .= sprintf(_('You can try to [nudge %1$s](../%2$s) or [post something to them](%%%%action.newnotice%%%%?status_textarea=%3$s).'), $this->user->nickname, $this->user->nickname, '@' . $this->user->nickname);
}
}
else {
$message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname);
$message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to them.'), $this->user->nickname);
}
$this->elementStart('div', 'guide');

View File

@ -119,13 +119,13 @@ class ShowfavoritesAction extends OwnerDesignAction
if (!empty($cur) && $cur->id == $this->user->id) {
// Show imported/gateway notices as well as local if
// the user is looking at his own favorites
// the user is looking at their own favorites
$this->notice = $this->user->favoriteNotices(($this->page-1)*NOTICES_PER_PAGE,
NOTICES_PER_PAGE + 1, true);
$this->notice = $this->user->favoriteNotices(true, ($this->page-1)*NOTICES_PER_PAGE,
NOTICES_PER_PAGE + 1);
} else {
$this->notice = $this->user->favoriteNotices(($this->page-1)*NOTICES_PER_PAGE,
NOTICES_PER_PAGE + 1, false);
$this->notice = $this->user->favoriteNotices(false, ($this->page-1)*NOTICES_PER_PAGE,
NOTICES_PER_PAGE + 1);
}
if (empty($this->notice)) {
@ -205,11 +205,11 @@ class ShowfavoritesAction extends OwnerDesignAction
if ($this->user->id === $current_user->id) {
$message = _('You haven\'t chosen any favorite notices yet. Click the fave button on notices you like to bookmark them for later or shed a spotlight on them.');
} else {
$message = sprintf(_('%s hasn\'t added any notices to his favorites yet. Post something interesting they would add to their favorites :)'), $this->user->nickname);
$message = sprintf(_('%s hasn\'t added any favorite notices yet. Post something interesting they would add to their favorites :)'), $this->user->nickname);
}
}
else {
$message = sprintf(_('%s hasn\'t added any notices to his favorites yet. Why not [register an account](%%%%action.register%%%%) and then post something interesting they would add to their favorites :)'), $this->user->nickname);
$message = sprintf(_('%s hasn\'t added any favorite notices yet. Why not [register an account](%%%%action.register%%%%) and then post something interesting they would add to their favorites :)'), $this->user->nickname);
}
$this->elementStart('div', 'guide');

View File

@ -430,14 +430,6 @@ class ShowgroupAction extends GroupDesignAction
function showStatistics()
{
// XXX: WORM cache this
$members = $this->group->getMembers();
$members_count = 0;
/** $member->count() doesn't work. */
while ($members->fetch()) {
$members_count++;
}
$this->elementStart('div', array('id' => 'entity_statistics',
'class' => 'section'));
@ -451,7 +443,7 @@ class ShowgroupAction extends GroupDesignAction
$this->elementStart('dl', 'entity_members');
$this->element('dt', null, _('Members'));
$this->element('dd', null, (is_int($members_count)) ? $members_count : '0');
$this->element('dd', null, $this->group->getMemberCount());
$this->elementEnd('dl');
$this->elementEnd('div');

View File

@ -198,11 +198,11 @@ class ShowstreamAction extends ProfileAction
if ($this->user->id === $current_user->id) {
$message .= _('Seen anything interesting recently? You haven\'t posted any notices yet, now would be a good time to start :)');
} else {
$message .= sprintf(_('You can try to nudge %1$s or [post something to his or her attention](%%%%action.newnotice%%%%?status_textarea=%2$s).'), $this->user->nickname, '@' . $this->user->nickname);
$message .= sprintf(_('You can try to nudge %1$s or [post something to them](%%%%action.newnotice%%%%?status_textarea=%2$s).'), $this->user->nickname, '@' . $this->user->nickname);
}
}
else {
$message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname);
$message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to them.'), $this->user->nickname);
}
$this->elementStart('div', 'guide');

View File

@ -185,7 +185,9 @@ class SubscriptionsListItem extends SubscriptionListItem
return;
}
if (!common_config('xmpp', 'enabled') && !common_config('sms', 'enabled')) {
$transports = array();
Event::handle('GetImTransports', array(&$transports));
if (!$transports && !common_config('sms', 'enabled')) {
return;
}
@ -195,7 +197,7 @@ class SubscriptionsListItem extends SubscriptionListItem
'action' => common_local_url('subedit')));
$this->out->hidden('token', common_session_token());
$this->out->hidden('profile', $this->profile->id);
if (common_config('xmpp', 'enabled')) {
if ($transports) {
$attrs = array('name' => 'jabber',
'type' => 'checkbox',
'class' => 'checkbox',
@ -205,7 +207,7 @@ class SubscriptionsListItem extends SubscriptionListItem
}
$this->out->element('input', $attrs);
$this->out->element('label', array('for' => 'jabber-'.$this->profile->id), _('Jabber'));
$this->out->element('label', array('for' => 'jabber-'.$this->profile->id), _('IM'));
} else {
$this->out->hidden('jabber', $sub->jabber);
}

View File

@ -41,6 +41,8 @@ if (!defined('STATUSNET')) {
* @category Info
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Craig Andrews <candrews@integralblue.com>
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
* @link http://status.net/
*/

0
avatar/.gitignore vendored Normal file → Executable file
View File

View File

@ -58,7 +58,7 @@ class Config extends Memcached_DataObject
$c = self::memcache();
if (!empty($c)) {
$settings = $c->get(common_cache_key(self::settingsKey));
$settings = $c->get(Cache::key(self::settingsKey));
if ($settings !== false) {
return $settings;
}
@ -77,7 +77,7 @@ class Config extends Memcached_DataObject
$config->free();
if (!empty($c)) {
$c->set(common_cache_key(self::settingsKey), $settings);
$c->set(Cache::key(self::settingsKey), $settings);
}
return $settings;
@ -154,7 +154,7 @@ class Config extends Memcached_DataObject
$c = self::memcache();
if (!empty($c)) {
$c->delete(common_cache_key(self::settingsKey));
$c->delete(Cache::key(self::settingsKey));
}
}
}

View File

@ -75,13 +75,13 @@ class Fave extends Memcached_DataObject
return Memcached_DataObject::pkeyGet('Fave', $kv);
}
function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $own=false)
function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $own=false, $since_id=0, $max_id=0)
{
$ids = Notice::stream(array('Fave', '_streamDirect'),
array($user_id, $own),
($own) ? 'fave:ids_by_user_own:'.$user_id :
'fave:ids_by_user:'.$user_id,
$offset, $limit);
$offset, $limit, $since_id, $max_id);
return $ids;
}

View File

@ -116,7 +116,11 @@ class File extends Memcached_DataObject
return false;
}
function processNew($given_url, $notice_id=null) {
/**
* @fixme refactor this mess, it's gotten pretty scary.
* @param bool $followRedirects
*/
function processNew($given_url, $notice_id=null, $followRedirects=true) {
if (empty($given_url)) return -1; // error, no url to process
$given_url = File_redirection::_canonUrl($given_url);
if (empty($given_url)) return -1; // error, no url to process
@ -124,6 +128,10 @@ class File extends Memcached_DataObject
if (empty($file)) {
$file_redir = File_redirection::staticGet('url', $given_url);
if (empty($file_redir)) {
// @fixme for new URLs this also looks up non-redirect data
// such as target content type, size, etc, which we need
// for File::saveNew(); so we call it even if not following
// new redirects.
$redir_data = File_redirection::where($given_url);
if (is_array($redir_data)) {
$redir_url = $redir_data['url'];
@ -131,14 +139,23 @@ class File extends Memcached_DataObject
$redir_url = $redir_data;
$redir_data = array();
} else {
throw new ServerException("Can't process url '$given_url'");
// TRANS: Server exception thrown when a URL cannot be processed.
throw new ServerException(sprintf(_("Cannot process URL '%s'"), $given_url));
}
// TODO: max field length
if ($redir_url === $given_url || strlen($redir_url) > 255) {
if ($redir_url === $given_url || strlen($redir_url) > 255 || !$followRedirects) {
$x = File::saveNew($redir_data, $given_url);
$file_id = $x->id;
} else {
$x = File::processNew($redir_url, $notice_id);
// This seems kind of messed up... for now skipping this part
// if we're already under a redirect, so we don't go into
// horrible infinite loops if we've been given an unstable
// redirect (where the final destination of the first request
// doesn't match what we get when we ask for it again).
//
// Seen in the wild with clojure.org, which redirects through
// wikispaces for auth and appends session data in the URL params.
$x = File::processNew($redir_url, $notice_id, /*followRedirects*/false);
$file_id = $x->id;
File_redirection::saveNew($redir_data, $file_id, $given_url);
}
@ -153,7 +170,9 @@ class File extends Memcached_DataObject
if (empty($x)) {
$x = File::staticGet($file_id);
if (empty($x)) {
throw new ServerException("Robin thinks something is impossible.");
// FIXME: This could possibly be a clearer message :)
// TRANS: Server exception thrown when... Robin thinks something is impossible!
throw new ServerException(_("Robin thinks something is impossible."));
}
}
@ -166,8 +185,10 @@ class File extends Memcached_DataObject
function isRespectsQuota($user,$fileSize) {
if ($fileSize > common_config('attachments', 'file_quota')) {
return sprintf(_('No file may be larger than %d bytes ' .
'and the file you sent was %d bytes. Try to upload a smaller version.'),
// TRANS: Message given if an upload is larger than the configured maximum.
// TRANS: %1$d is the byte limit for uploads, %2$d is the byte count for the uploaded file.
return sprintf(_('No file may be larger than %1$d bytes ' .
'and the file you sent was %2$d bytes. Try to upload a smaller version.'),
common_config('attachments', 'file_quota'), $fileSize);
}
@ -176,6 +197,8 @@ class File extends Memcached_DataObject
$this->fetch();
$total = $this->total + $fileSize;
if ($total > common_config('attachments', 'user_quota')) {
// TRANS: Message given if an upload would exceed user quota.
// TRANS: %d (number) is the user quota in bytes.
return sprintf(_('A file this large would exceed your user quota of %d bytes.'), common_config('attachments', 'user_quota'));
}
$query .= ' AND EXTRACT(month FROM file.modified) = EXTRACT(month FROM now()) and EXTRACT(year FROM file.modified) = EXTRACT(year FROM now())';
@ -183,6 +206,8 @@ class File extends Memcached_DataObject
$this->fetch();
$total = $this->total + $fileSize;
if ($total > common_config('attachments', 'monthly_quota')) {
// TRANS: Message given id an upload would exceed a user's monthly quota.
// TRANS: $d (number) is the monthly user quota in bytes.
return sprintf(_('A file this large would exceed your monthly quota of %d bytes.'), common_config('attachments', 'monthly_quota'));
}
return true;
@ -219,7 +244,8 @@ class File extends Memcached_DataObject
static function path($filename)
{
if (!self::validFilename($filename)) {
throw new ClientException("Invalid filename");
// TRANS: Client exception thrown if a file upload does not have a valid name.
throw new ClientException(_("Invalid filename."));
}
$dir = common_config('attachments', 'dir');
@ -233,7 +259,8 @@ class File extends Memcached_DataObject
static function url($filename)
{
if (!self::validFilename($filename)) {
throw new ClientException("Invalid filename");
// TRANS: Client exception thrown if a file upload does not have a valid name.
throw new ClientException(_("Invalid filename."));
}
if(common_config('site','private')) {
@ -286,6 +313,7 @@ class File extends Memcached_DataObject
if(! isset($this->filename)){
$notEnclosureMimeTypes = array(null,'text/html','application/xhtml+xml');
$mimetype = $this->mimetype;
if($mimetype != null){
$mimetype = strtolower($this->mimetype);
}
@ -325,4 +353,3 @@ class File extends Memcached_DataObject
return !empty($enclosure);
}
}

View File

@ -240,6 +240,14 @@ class File_redirection extends Memcached_DataObject
} else if (is_string($redir_data)) {
// The file is a known redirect target.
$file = File::staticGet('url', $redir_data);
if (empty($file)) {
// @fixme should we save a new one?
// this case was triggering sometimes for redirects
// with unresolvable targets; found while fixing
// "can't linkify" bugs with shortened links to
// SSL sites with cert issues.
return null;
}
$file_id = $file->id;
}
} else {

View File

@ -39,6 +39,22 @@ class Foreign_user extends Memcached_DataObject
return null;
}
static function getByNickname($nickname, $service)
{
if (empty($nickname) || empty($service)) {
return null;
} else {
$fuser = new Foreign_user();
$fuser->service = $service;
$fuser->nickname = $nickname;
$fuser->limit(1);
$result = $fuser->find(true);
return empty($result) ? null : $fuser;
}
}
function updateKeys(&$orig)
{
$this->_connect();

View File

@ -38,6 +38,7 @@ class Group_member extends Memcached_DataObject
if (!$result) {
common_log_db_error($member, 'INSERT', __FILE__);
// TRANS: Exception thrown when joining a group fails.
throw new Exception(_("Group join failed."));
}
@ -50,6 +51,7 @@ class Group_member extends Memcached_DataObject
'profile_id' => $profile_id));
if (empty($member)) {
// TRANS: Exception thrown when trying to leave a group the user is not a member of.
throw new Exception(_("Not part of group."));
}
@ -57,6 +59,7 @@ class Group_member extends Memcached_DataObject
if (!$result) {
common_log_db_error($member, 'INSERT', __FILE__);
// TRANS: Exception thrown when trying to leave a group fails.
throw new Exception(_("Group leave failed."));
}

View File

@ -115,9 +115,12 @@ class Inbox extends Memcached_DataObject
*/
static function insertNotice($user_id, $notice_id)
{
$inbox = DB_DataObject::staticGet('inbox', 'user_id', $user_id);
if (empty($inbox)) {
// Going straight to the DB rather than trusting our caching
// during an update. Note: not using DB_DataObject::staticGet,
// which is unsafe to use directly (in-process caching causes
// memory leaks, which accumulate in queue processes).
$inbox = new Inbox();
if (!$inbox->get('user_id', $user_id)) {
$inbox = Inbox::initialize($user_id);
}

View File

@ -38,6 +38,7 @@ class Local_group extends Memcached_DataObject
$this->encache();
} else {
common_log_db_error($local, 'UPDATE', __FILE__);
// TRANS: Server exception thrown when updating a local group fails.
throw new ServerException(_('Could not update local group.'));
}

View File

@ -73,6 +73,8 @@ class Login_token extends Memcached_DataObject
if (!$result) {
common_log_db_error($login_token, 'INSERT', __FILE__);
// TRANS: Exception thrown when trying creating a login token failed.
// TRANS: %s is the user nickname for which token creation failed.
throw new Exception(sprintf(_('Could not create login token for %s'),
$user->nickname));
}

View File

@ -124,16 +124,17 @@ class Memcached_DataObject extends Safe_DataObject
}
static function memcache() {
return common_memcache();
return Cache::instance();
}
static function cacheKey($cls, $k, $v) {
if (is_object($cls) || is_object($k) || is_object($v)) {
if (is_object($cls) || is_object($k) || (is_object($v) && !($v instanceof DB_DataObject_Cast))) {
$e = new Exception();
common_log(LOG_ERR, __METHOD__ . ' object in param: ' .
str_replace("\n", " ", $e->getTraceAsString()));
}
return common_cache_key(strtolower($cls).':'.$k.':'.$v);
$vstr = self::valueString($v);
return Cache::key(strtolower($cls).':'.$k.':'.$vstr);
}
static function getcached($cls, $k, $v) {
@ -229,11 +230,12 @@ class Memcached_DataObject extends Safe_DataObject
if (empty($this->$key)) {
continue;
}
$ckeys[] = $this->cacheKey($this->tableName(), $key, $this->$key);
$ckeys[] = $this->cacheKey($this->tableName(), $key, self::valueString($this->$key));
} else if ($type == 'K' || $type == 'N') {
$pkey[] = $key;
$pval[] = $this->$key;
$pval[] = self::valueString($this->$key);
} else {
// Low level exception. No need for i18n as discussed with Brion.
throw new Exception("Unknown key type $key => $type for " . $this->tableName());
}
}
@ -281,6 +283,7 @@ class Memcached_DataObject extends Safe_DataObject
} else if ($type == 'fulltext') {
$search_engine = new MySQLSearch($this, $table);
} else {
// Low level exception. No need for i18n as discussed with Brion.
throw new ServerException('Unknown search type: ' . $type);
}
} else {
@ -299,8 +302,8 @@ class Memcached_DataObject extends Safe_DataObject
$inst->query($qry);
return $inst;
}
$key_part = common_keyize($cls).':'.md5($qry);
$ckey = common_cache_key($key_part);
$key_part = Cache::keyize($cls).':'.md5($qry);
$ckey = Cache::key($key_part);
$stored = $c->get($ckey);
if ($stored !== false) {
@ -526,7 +529,8 @@ class Memcached_DataObject extends Safe_DataObject
}
if (!$dsn) {
throw new Exception("No database name / dsn found anywhere");
// TRANS: Exception thrown when database name or Data Source Name could not be found.
throw new Exception(_("No database name or DSN found anywhere."));
}
return $dsn;
@ -546,7 +550,7 @@ class Memcached_DataObject extends Safe_DataObject
$keyPart = vsprintf($format, $args);
$cacheKey = common_cache_key($keyPart);
$cacheKey = Cache::key($keyPart);
return $c->delete($cacheKey);
}
@ -576,6 +580,7 @@ class Memcached_DataObject extends Safe_DataObject
if ($message instanceof PEAR_Error) {
$message = $message->getMessage();
}
// Low level exception. No need for i18n as discussed with Brion.
throw new ServerException("[$id] DB_DataObject error [$type]: $message");
}
@ -587,12 +592,12 @@ class Memcached_DataObject extends Safe_DataObject
return false;
}
$cacheKey = common_cache_key($keyPart);
$cacheKey = Cache::key($keyPart);
return $c->get($cacheKey);
}
static function cacheSet($keyPart, $value)
static function cacheSet($keyPart, $value, $flag=null, $expiry=null)
{
$c = self::memcache();
@ -600,9 +605,36 @@ class Memcached_DataObject extends Safe_DataObject
return false;
}
$cacheKey = common_cache_key($keyPart);
$cacheKey = Cache::key($keyPart);
return $c->set($cacheKey, $value);
return $c->set($cacheKey, $value, $flag, $expiry);
}
static function valueString($v)
{
$vstr = null;
if (is_object($v) && $v instanceof DB_DataObject_Cast) {
switch ($v->type) {
case 'date':
$vstr = $v->year . '-' . $v->month . '-' . $v->day;
break;
case 'blob':
case 'string':
case 'sql':
case 'datetime':
case 'time':
// Low level exception. No need for i18n as discussed with Brion.
throw new ServerException("Unhandled DB_DataObject_Cast type passed as cacheKey value: '$v->type'");
break;
default:
// Low level exception. No need for i18n as discussed with Brion.
throw new ServerException("Unknown DB_DataObject_Cast type passed as cacheKey value: '$v->type'");
break;
}
} else {
$vstr = strval($v);
}
return $vstr;
}
}

View File

@ -42,6 +42,7 @@ class Message extends Memcached_DataObject
$sender = Profile::staticGet('id', $from);
if (!$sender->hasRight(Right::NEWMESSAGE)) {
// TRANS: Client exception thrown when a user tries to send a direct message while being banned from sending them.
throw new ClientException(_('You are banned from sending direct messages.'));
}
@ -58,6 +59,7 @@ class Message extends Memcached_DataObject
if (!$result) {
common_log_db_error($msg, 'INSERT', __FILE__);
// TRANS: Message given when a message could not be stored on the server.
return _('Could not insert message.');
}
@ -68,6 +70,7 @@ class Message extends Memcached_DataObject
if (!$result) {
common_log_db_error($msg, 'UPDATE', __FILE__);
// TRANS: Message given when a message could not be updated on the server.
return _('Could not update message with new URI.');
}

View File

@ -29,6 +29,7 @@
* @author Robin Millette <millette@controlyourself.ca>
* @author Sarven Capadisli <csarven@controlyourself.ca>
* @author Tom Adams <tom@holizz.com>
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license GNU Affero General Public License http://www.gnu.org/licenses/
*/
@ -41,10 +42,10 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
/* We keep the first three 20-notice pages, plus one for pagination check,
/* We keep 200 notices, the max number of notices available per API request,
* in the memcached cache. */
define('NOTICE_CACHE_WINDOW', 61);
define('NOTICE_CACHE_WINDOW', 200);
define('MAX_BOXCARS', 128);
@ -89,7 +90,15 @@ class Notice extends Memcached_DataObject
function getProfile()
{
return Profile::staticGet('id', $this->profile_id);
$profile = Profile::staticGet('id', $this->profile_id);
if (empty($profile)) {
// TRANS: Server exception thrown when a user profile for a notice cannot be found.
// TRANS: %1$d is a profile ID (number), %2$d is a notice ID (number).
throw new ServerException(sprintf(_('No such profile (%1$d) for notice (%2$d).'), $this->profile_id, $this->id));
}
return $profile;
}
function delete()
@ -97,6 +106,10 @@ class Notice extends Memcached_DataObject
// For auditing purposes, save a record that the notice
// was deleted.
// @fixme we have some cases where things get re-run and so the
// insert fails.
$deleted = Deleted_notice::staticGet('id', $this->id);
if (!$deleted) {
$deleted = new Deleted_notice();
$deleted->id = $this->id;
@ -106,6 +119,9 @@ class Notice extends Memcached_DataObject
$deleted->deleted = common_sql_now();
$deleted->insert();
}
if (Event::handle('NoticeDeleteRelated', array($this))) {
// Clear related records
@ -117,6 +133,7 @@ class Notice extends Memcached_DataObject
// NOTE: we don't clear inboxes
// NOTE: we don't clear queue items
}
$result = parent::delete();
@ -231,6 +248,8 @@ class Notice extends Memcached_DataObject
if (!empty($options)) {
$options = $options + $defaults;
extract($options);
} else {
extract($defaults);
}
if (!isset($is_local)) {
@ -242,28 +261,34 @@ class Notice extends Memcached_DataObject
$final = common_shorten_links($content);
if (Notice::contentTooLong($final)) {
// TRANS: Client exception thrown if a notice contains too many characters.
throw new ClientException(_('Problem saving notice. Too long.'));
}
if (empty($profile)) {
// TRANS: Client exception thrown when trying to save a notice for an unknown user.
throw new ClientException(_('Problem saving notice. Unknown user.'));
}
if (common_config('throttle', 'enabled') && !Notice::checkEditThrottle($profile_id)) {
common_log(LOG_WARNING, 'Excessive posting by profile #' . $profile_id . '; throttled.');
// TRANS: Client exception thrown when a user tries to post too many notices in a given time frame.
throw new ClientException(_('Too many notices too fast; take a breather '.
'and post again in a few minutes.'));
}
if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $final)) {
common_log(LOG_WARNING, 'Dupe posting by profile #' . $profile_id . '; throttled.');
// TRANS: Client exception thrown when a user tries to post too many duplicate notices in a given time frame.
throw new ClientException(_('Too many duplicate messages too quickly;'.
' take a breather and post again in a few minutes.'));
}
if (!$profile->hasRight(Right::NEWNOTICE)) {
common_log(LOG_WARNING, "Attempted post from user disallowed to post: " . $profile->nickname);
throw new ClientException(_('You are banned from posting notices on this site.'));
// TRANS: Client exception thrown when a user tries to post while being banned.
throw new ClientException(_('You are banned from posting notices on this site.'), 403);
}
$notice = new Notice();
@ -329,6 +354,7 @@ class Notice extends Memcached_DataObject
if (!$id) {
common_log_db_error($notice, 'INSERT', __FILE__);
// TRANS: Server exception thrown when a notice cannot be saved.
throw new ServerException(_('Problem saving notice.'));
}
@ -355,6 +381,7 @@ class Notice extends Memcached_DataObject
if ($changed) {
if (!$notice->update($orig)) {
common_log_db_error($notice, 'UPDATE', __FILE__);
// TRANS: Server exception thrown when a notice cannot be updated.
throw new ServerException(_('Problem saving notice.'));
}
}
@ -463,7 +490,7 @@ class Notice extends Memcached_DataObject
function saveKnownUrls($urls)
{
// @fixme validation?
foreach ($urls as $url) {
foreach (array_unique($urls) as $url) {
File::processNew($url, $this->id);
}
}
@ -556,15 +583,17 @@ class Notice extends Memcached_DataObject
if ($f2p->find()) {
while ($f2p->fetch()) {
$f = File::staticGet($f2p->file_id);
if ($f) {
$att[] = clone($f);
}
}
}
return $att;
}
function getStreamByIds($ids)
{
$cache = common_memcache();
$cache = Cache::instance();
if (!empty($cache)) {
$notices = array();
@ -732,7 +761,7 @@ class Notice extends Memcached_DataObject
$c = self::memcache();
if (!empty($c)) {
$ni = $c->get(common_cache_key('notice:who_gets:'.$this->id));
$ni = $c->get(Cache::key('notice:who_gets:'.$this->id));
if ($ni !== false) {
return $ni;
}
@ -784,7 +813,7 @@ class Notice extends Memcached_DataObject
if (!empty($c)) {
// XXX: pack this data better
$c->set(common_cache_key('notice:who_gets:'.$this->id), $ni);
$c->set(Cache::key('notice:who_gets:'.$this->id), $ni);
}
return $ni;
@ -866,11 +895,12 @@ class Notice extends Memcached_DataObject
function saveKnownGroups($group_ids)
{
if (!is_array($group_ids)) {
throw new ServerException("Bad type provided to saveKnownGroups");
// TRANS: Server exception thrown when no array is provided to the method saveKnownGroups().
throw new ServerException(_("Bad type provided to saveKnownGroups"));
}
$groups = array();
foreach ($group_ids as $id) {
foreach (array_unique($group_ids) as $id) {
$group = User_group::staticGet('id', $id);
if ($group) {
common_log(LOG_ERR, "Local delivery to group id $id, $group->nickname");
@ -964,6 +994,7 @@ class Notice extends Memcached_DataObject
if (!$result) {
common_log_db_error($gi, 'INSERT', __FILE__);
// TRANS: Server exception thrown when an update for a group inbox fails.
throw new ServerException(_('Problem saving group inbox.'));
}
@ -981,8 +1012,7 @@ class Notice extends Memcached_DataObject
* messages, we won't deliver to any remote targets as that's the
* source service's responsibility.
*
* @fixme Unlike saveReplies() there's no mail notification here.
* Move that to distrib queue handler?
* Mail notifications etc will be handled later.
*
* @param array of unique identifier URIs for recipients
*/
@ -991,26 +1021,31 @@ class Notice extends Memcached_DataObject
if (empty($uris)) {
return;
}
$sender = Profile::staticGet($this->profile_id);
foreach ($uris as $uri) {
foreach (array_unique($uris) as $uri) {
$user = User::staticGet('uri', $uri);
$profile = Profile::fromURI($uri);
if (!empty($user)) {
if ($user->hasBlocked($sender)) {
if (empty($profile)) {
common_log(LOG_WARNING, "Unable to determine profile for URI '$uri'");
continue;
}
if ($profile->hasBlocked($sender)) {
common_log(LOG_INFO, "Not saving reply to profile {$profile->id} ($uri) from sender {$sender->id} because of a block.");
continue;
}
$reply = new Reply();
$reply->notice_id = $this->id;
$reply->profile_id = $user->id;
$reply->profile_id = $profile->id;
common_log(LOG_INFO, __METHOD__ . ": saving reply: notice $this->id to profile $profile->id");
$id = $reply->insert();
self::blow('reply:stream:%d', $user->id);
}
}
return;
@ -1021,8 +1056,7 @@ class Notice extends Memcached_DataObject
* and save reply records indicating that this message needs to be
* delivered to those users.
*
* Side effect: local recipients get e-mail notifications here.
* @fixme move mail notifications to distrib?
* Mail notifications to local profiles will be sent later.
*
* @return array of integer profile IDs
*/
@ -1073,26 +1107,26 @@ class Notice extends Memcached_DataObject
if (!$id) {
common_log_db_error($reply, 'INSERT', __FILE__);
throw new ServerException("Couldn't save reply for {$this->id}, {$mentioned->id}");
// TRANS: Server exception thrown when a reply cannot be saved.
// TRANS: %1$d is a notice ID, %2$d is the ID of the mentioned user.
throw new ServerException(sprintf(_("Could not save reply for %1$d, %2$d."), $this->id, $mentioned->id));
} else {
$replied[$mentioned->id] = 1;
self::blow('reply:stream:%d', $mentioned->id);
}
}
}
$recipientIds = array_keys($replied);
foreach ($recipientIds as $recipientId) {
$user = User::staticGet('id', $recipientId);
if (!empty($user)) {
self::blow('reply:stream:%d', $reply->profile_id);
mail_notify_attn($user, $this);
}
}
return $recipientIds;
}
/**
* Pull the complete list of @-reply targets for this notice.
*
* @return array of integer profile ids
*/
function getReplies()
{
// XXX: cache me
@ -1115,6 +1149,30 @@ class Notice extends Memcached_DataObject
return $ids;
}
/**
* Send e-mail notifications to local @-reply targets.
*
* Replies must already have been saved; this is expected to be run
* from the distrib queue handler.
*/
function sendReplyNotifications()
{
// Don't send reply notifications for repeats
if (!empty($this->repeat_of)) {
return array();
}
$recipientIds = $this->getReplies();
foreach ($recipientIds as $recipientId) {
$user = User::staticGet('id', $recipientId);
if (!empty($user)) {
mail_notify_attn($user, $this);
}
}
}
/**
* Pull list of groups this notice needs to be delivered to,
* as previously recorded by saveGroups() or saveKnownGroups().
@ -1154,7 +1212,10 @@ class Notice extends Memcached_DataObject
return $groups;
}
function asAtomEntry($namespace=false, $source=false, $author=true)
// This has gotten way too long. Needs to be sliced up into functional bits
// or ideally exported to a utility class.
function asAtomEntry($namespace=false, $source=false, $author=true, $cur=null)
{
$profile = $this->getProfile();
@ -1167,149 +1228,332 @@ class Notice extends Memcached_DataObject
'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/',
'xmlns:media' => 'http://purl.org/syndication/atommedia',
'xmlns:poco' => 'http://portablecontacts.net/spec/1.0',
'xmlns:ostatus' => 'http://ostatus.org/schema/1.0');
'xmlns:ostatus' => 'http://ostatus.org/schema/1.0',
'xmlns:statusnet' => 'http://status.net/schema/api/1/');
} else {
$attrs = array();
}
if (Event::handle('StartActivityStart', array(&$this, &$xs, &$attrs))) {
$xs->elementStart('entry', $attrs);
Event::handle('EndActivityStart', array(&$this, &$xs, &$attrs));
}
if (Event::handle('StartActivitySource', array(&$this, &$xs))) {
if ($source) {
$atom_feed = $profile->getAtomFeed();
if (!empty($atom_feed)) {
$xs->elementStart('source');
$xs->element('id', null, $profile->profileurl);
$xs->element('title', null, $profile->nickname . " - " . common_config('site', 'name'));
$xs->element('link', array('href' => $profile->profileurl));
$user = User::staticGet('id', $profile->id);
if (!empty($user)) {
$atom_feed = common_local_url('ApiTimelineUser',
array('format' => 'atom',
'id' => $profile->nickname));
// XXX: we should store the actual feed ID
$xs->element('id', null, $atom_feed);
// XXX: we should store the actual feed title
$xs->element('title', null, $profile->getBestName());
$xs->element('link', array('rel' => 'alternate',
'type' => 'text/html',
'href' => $profile->profileurl));
$xs->element('link', array('rel' => 'self',
'type' => 'application/atom+xml',
'href' => $profile->profileurl));
'href' => $atom_feed));
$xs->element('icon', null, $profile->avatarUrl(AVATAR_PROFILE_SIZE));
$notice = $profile->getCurrentNotice();
if (!empty($notice)) {
$xs->element('updated', null, self::utcDate($notice->created));
}
$user = User::staticGet('id', $profile->id);
if (!empty($user)) {
$xs->element('link', array('rel' => 'license',
'href' => common_config('license', 'url')));
}
$xs->element('icon', null, $profile->avatarUrl(AVATAR_PROFILE_SIZE));
$xs->element('updated', null, common_date_w3dtf($this->created));
}
if ($source) {
$xs->elementEnd('source');
}
}
Event::handle('EndActivitySource', array(&$this, &$xs));
}
$xs->element('title', null, common_xml_safe_str($this->content));
$title = common_xml_safe_str($this->content);
if (Event::handle('StartActivityTitle', array(&$this, &$xs, &$title))) {
$xs->element('title', null, $title);
Event::handle('EndActivityTitle', array($this, &$xs, $title));
}
$atomAuthor = '';
if ($author) {
$xs->raw($profile->asAtomAuthor());
$xs->raw($profile->asActivityActor());
$atomAuthor = $profile->asAtomAuthor($cur);
}
if (Event::handle('StartActivityAuthor', array(&$this, &$xs, &$atomAuthor))) {
if (!empty($atomAuthor)) {
$xs->raw($atomAuthor);
Event::handle('EndActivityAuthor', array(&$this, &$xs, &$atomAuthor));
}
}
$actor = '';
if ($author) {
$actor = $profile->asActivityActor();
}
if (Event::handle('StartActivityActor', array(&$this, &$xs, &$actor))) {
if (!empty($actor)) {
$xs->raw($actor);
Event::handle('EndActivityActor', array(&$this, &$xs, &$actor));
}
}
$url = $this->bestUrl();
if (Event::handle('StartActivityLink', array(&$this, &$xs, &$url))) {
$xs->element('link', array('rel' => 'alternate',
'type' => 'text/html',
'href' => $this->bestUrl()));
'href' => $url));
Event::handle('EndActivityLink', array(&$this, &$xs, $url));
}
$xs->element('id', null, $this->uri);
$id = $this->uri;
$xs->element('published', null, common_date_w3dtf($this->created));
$xs->element('updated', null, common_date_w3dtf($this->created));
if (Event::handle('StartActivityId', array(&$this, &$xs, &$id))) {
$xs->element('id', null, $id);
Event::handle('EndActivityId', array(&$this, &$xs, $id));
}
$published = self::utcDate($this->created);
if (Event::handle('StartActivityPublished', array(&$this, &$xs, &$published))) {
$xs->element('published', null, $published);
Event::handle('EndActivityPublished', array(&$this, &$xs, $published));
}
$updated = $published; // XXX: notices are usually immutable
if (Event::handle('StartActivityUpdated', array(&$this, &$xs, &$updated))) {
$xs->element('updated', null, $updated);
Event::handle('EndActivityUpdated', array(&$this, &$xs, $updated));
}
$content = common_xml_safe_str($this->rendered);
if (Event::handle('StartActivityContent', array(&$this, &$xs, &$content))) {
$xs->element('content', array('type' => 'html'), $content);
Event::handle('EndActivityContent', array(&$this, &$xs, $content));
}
// Most of our notices represent POSTing a NOTE. This is the default verb
// for activity streams, so we normally just leave it out.
$verb = ActivityVerb::POST;
if (Event::handle('StartActivityVerb', array(&$this, &$xs, &$verb))) {
$xs->element('activity:verb', null, $verb);
Event::handle('EndActivityVerb', array(&$this, &$xs, $verb));
}
// We use the default behavior for activity streams: if there's no activity:object,
// then treat the entry itself as the object. Here, you can set the type of that object,
// which is normally a NOTE.
$type = ActivityObject::NOTE;
if (Event::handle('StartActivityDefaultObjectType', array(&$this, &$xs, &$type))) {
$xs->element('activity:object-type', null, $type);
Event::handle('EndActivityDefaultObjectType', array(&$this, &$xs, $type));
}
// Since we usually use the entry itself as an object, we don't have an explicit
// object. Some extensions may want to add them (for photo, event, music, etc.).
$objects = array();
if (Event::handle('StartActivityObjects', array(&$this, &$xs, &$objects))) {
foreach ($objects as $object) {
$xs->raw($object->asString());
}
Event::handle('EndActivityObjects', array(&$this, &$xs, $objects));
}
$noticeInfoAttr = array('local_id' => $this->id); // local notice ID (useful to clients for ordering)
$ns = $this->getSource();
if (!empty($ns)) {
$noticeInfoAttr['source'] = $ns->code;
if (!empty($ns->url)) {
$noticeInfoAttr['source_link'] = $ns->url;
if (!empty($ns->name)) {
$noticeInfoAttr['source'] = '<a href="'
. htmlspecialchars($ns->url)
. '" rel="nofollow">'
. htmlspecialchars($ns->name)
. '</a>';
}
}
}
if (!empty($cur)) {
$noticeInfoAttr['favorite'] = ($cur->hasFave($this)) ? "true" : "false";
$profile = $cur->getProfile();
$noticeInfoAttr['repeated'] = ($profile->hasRepeated($this->id)) ? "true" : "false";
}
if (!empty($this->repeat_of)) {
$noticeInfoAttr['repeat_of'] = $this->repeat_of;
}
if (Event::handle('StartActivityNoticeInfo', array(&$this, &$xs, &$noticeInfoAttr))) {
$xs->element('statusnet:notice_info', $noticeInfoAttr, null);
Event::handle('EndActivityNoticeInfo', array(&$this, &$xs, $noticeInfoAttr));
}
$replyNotice = null;
if ($this->reply_to) {
$reply_notice = Notice::staticGet('id', $this->reply_to);
if (!empty($reply_notice)) {
$replyNotice = Notice::staticGet('id', $this->reply_to);
}
if (Event::handle('StartActivityInReplyTo', array(&$this, &$xs, &$replyNotice))) {
if (!empty($replyNotice)) {
$xs->element('link', array('rel' => 'related',
'href' => $reply_notice->bestUrl()));
'href' => $replyNotice->bestUrl()));
$xs->element('thr:in-reply-to',
array('ref' => $reply_notice->uri,
'href' => $reply_notice->bestUrl()));
array('ref' => $replyNotice->uri,
'href' => $replyNotice->bestUrl()));
Event::handle('EndActivityInReplyTo', array(&$this, &$xs, $replyNotice));
}
}
$conv = null;
if (!empty($this->conversation)) {
$conv = Conversation::staticGet('id', $this->conversation);
}
if (Event::handle('StartActivityConversation', array(&$this, &$xs, &$conv))) {
if (!empty($conv)) {
$xs->element(
'link', array(
'rel' => 'ostatus:conversation',
'href' => $conv->uri
)
);
$xs->element('link', array('rel' => 'ostatus:conversation',
'href' => $conv->uri));
}
Event::handle('EndActivityConversation', array(&$this, &$xs, $conv));
}
$replyProfiles = array();
$reply_ids = $this->getReplies();
foreach ($reply_ids as $id) {
$profile = Profile::staticGet('id', $id);
if (!empty($profile)) {
$xs->element(
'link', array(
'rel' => 'ostatus:attention',
'href' => $profile->getUri()
)
);
$replyProfiles[] = $profile;
}
}
if (Event::handle('StartActivityAttentionProfiles', array(&$this, &$xs, &$replyProfiles))) {
foreach ($replyProfiles as $profile) {
$xs->element('link', array('rel' => 'ostatus:attention',
'href' => $profile->getUri()));
$xs->element('link', array('rel' => 'mentioned',
'href' => $profile->getUri()));
}
Event::handle('EndActivityAttentionProfiles', array(&$this, &$xs, $replyProfiles));
}
$groups = $this->getGroups();
if (Event::handle('StartActivityAttentionGroups', array(&$this, &$xs, &$groups))) {
foreach ($groups as $group) {
$xs->element(
'link', array(
'rel' => 'ostatus:attention',
'href' => $group->permalink()
)
);
$xs->element('link', array('rel' => 'ostatus:attention',
'href' => $group->permalink()));
$xs->element('link', array('rel' => 'mentioned',
'href' => $group->permalink()));
}
Event::handle('EndActivityAttentionGroups', array(&$this, &$xs, $groups));
}
$repeat = null;
if (!empty($this->repeat_of)) {
$repeat = Notice::staticGet('id', $this->repeat_of);
}
if (Event::handle('StartActivityForward', array(&$this, &$xs, &$repeat))) {
if (!empty($repeat)) {
$xs->element(
'ostatus:forward',
array('ref' => $repeat->uri, 'href' => $repeat->bestUrl())
);
}
$xs->element('ostatus:forward',
array('ref' => $repeat->uri,
'href' => $repeat->bestUrl()));
}
$xs->element(
'content',
array('type' => 'html'),
common_xml_safe_str($this->rendered)
);
$tag = new Notice_tag();
$tag->notice_id = $this->id;
if ($tag->find()) {
while ($tag->fetch()) {
$xs->element('category', array('term' => $tag->tag));
Event::handle('EndActivityForward', array(&$this, &$xs, $repeat));
}
}
$tag->free();
# Enclosures
$tags = $this->getTags();
if (Event::handle('StartActivityCategories', array(&$this, &$xs, &$tags))) {
foreach ($tags as $tag) {
$xs->element('category', array('term' => $tag));
}
Event::handle('EndActivityCategories', array(&$this, &$xs, $tags));
}
// Enclosures
$enclosures = array();
$attachments = $this->attachments();
if($attachments){
foreach($attachments as $attachment){
$enclosure=$attachment->getEnclosure();
foreach ($attachments as $attachment) {
$enclosure = $attachment->getEnclosure();
if ($enclosure) {
$attributes = array('rel'=>'enclosure','href'=>$enclosure->url,'type'=>$enclosure->mimetype,'length'=>$enclosure->size);
if($enclosure->title){
$attributes['title']=$enclosure->title;
$enclosures[] = $enclosure;
}
}
if (Event::handle('StartActivityEnclosures', array(&$this, &$xs, &$enclosures))) {
foreach ($enclosures as $enclosure) {
$attributes = array('rel' => 'enclosure',
'href' => $enclosure->url,
'type' => $enclosure->mimetype,
'length' => $enclosure->size);
if ($enclosure->title) {
$attributes['title'] = $enclosure->title;
}
$xs->element('link', $attributes, null);
}
}
Event::handle('EndActivityEnclosures', array(&$this, &$xs, $enclosures));
}
if (!empty($this->lat) && !empty($this->lon)) {
$xs->element('georss:point', null, $this->lat . ' ' . $this->lon);
$lat = $this->lat;
$lon = $this->lon;
if (Event::handle('StartActivityGeo', array(&$this, &$xs, &$lat, &$lon))) {
if (!empty($lat) && !empty($lon)) {
$xs->element('georss:point', null, $lat . ' ' . $lon);
}
Event::handle('EndActivityGeo', array(&$this, &$xs, $lat, $lon));
}
if (Event::handle('StartActivityEnd', array(&$this, &$xs))) {
$xs->elementEnd('entry');
Event::handle('EndActivityEnd', array(&$this, &$xs));
}
return $xs->getString();
}
@ -1343,7 +1587,7 @@ class Notice extends Memcached_DataObject
function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0)
{
$cache = common_memcache();
$cache = Cache::instance();
if (empty($cache) ||
$since_id != 0 || $max_id != 0 ||
@ -1353,7 +1597,7 @@ class Notice extends Memcached_DataObject
$max_id)));
}
$idkey = common_cache_key($cachekey);
$idkey = Cache::key($cachekey);
$idstr = $cache->get($idkey);
@ -1535,17 +1779,17 @@ class Notice extends Memcached_DataObject
function repeatStream($limit=100)
{
$cache = common_memcache();
$cache = Cache::instance();
if (empty($cache)) {
$ids = $this->_repeatStreamDirect($limit);
} else {
$idstr = $cache->get(common_cache_key('notice:repeats:'.$this->id));
$idstr = $cache->get(Cache::key('notice:repeats:'.$this->id));
if ($idstr !== false) {
$ids = explode(',', $idstr);
} else {
$ids = $this->_repeatStreamDirect(100);
$cache->set(common_cache_key('notice:repeats:'.$this->id), implode(',', $ids));
$cache->set(Cache::key('notice:repeats:'.$this->id), implode(',', $ids));
}
if ($limit < 100) {
// We do a max of 100, so slice down to limit
@ -1699,10 +1943,10 @@ class Notice extends Memcached_DataObject
if ($tag->find()) {
while ($tag->fetch()) {
self::blow('profile:notice_ids_tagged:%d:%s', $this->profile_id, common_keyize($tag->tag));
self::blow('profile:notice_ids_tagged:%d:%s;last', $this->profile_id, common_keyize($tag->tag));
self::blow('notice_tag:notice_ids:%s', common_keyize($tag->tag));
self::blow('notice_tag:notice_ids:%s;last', common_keyize($tag->tag));
self::blow('profile:notice_ids_tagged:%d:%s', $this->profile_id, Cache::keyize($tag->tag));
self::blow('profile:notice_ids_tagged:%d:%s;last', $this->profile_id, Cache::keyize($tag->tag));
self::blow('notice_tag:notice_ids:%s', Cache::keyize($tag->tag));
self::blow('notice_tag:notice_ids:%s;last', Cache::keyize($tag->tag));
$tag->delete();
}
}
@ -1781,4 +2025,73 @@ class Notice extends Memcached_DataObject
return $result;
}
/**
* Get the source of the notice
*
* @return Notice_source $ns A notice source object. 'code' is the only attribute
* guaranteed to be populated.
*/
function getSource()
{
$ns = new Notice_source();
if (!empty($this->source)) {
switch ($this->source) {
case 'web':
case 'xmpp':
case 'mail':
case 'omb':
case 'system':
case 'api':
$ns->code = $this->source;
break;
default:
$ns = Notice_source::staticGet($this->source);
if (!$ns) {
$ns = new Notice_source();
$ns->code = $this->source;
$app = Oauth_application::staticGet('name', $this->source);
if ($app) {
$ns->name = $app->name;
$ns->url = $app->source_url;
}
}
break;
}
}
return $ns;
}
/**
* Determine whether the notice was locally created
*
* @return boolean locality
*/
public function isLocal()
{
return ($this->is_local == Notice::LOCAL_PUBLIC ||
$this->is_local == Notice::LOCAL_NONPUBLIC);
}
public function getTags()
{
$tags = array();
$tag = new Notice_tag();
$tag->notice_id = $this->id;
if ($tag->find()) {
while ($tag->fetch()) {
$tags[] = $tag->tag;
}
}
$tag->free();
return $tags;
}
static private function utcDate($dt)
{
$dateStr = date('d F Y H:i:s', strtotime($dt));
$d = new DateTime($dateStr, new DateTimeZone('UTC'));
return $d->format(DATE_W3C);
}
}

View File

@ -40,7 +40,7 @@ class Notice_tag extends Memcached_DataObject
$ids = Notice::stream(array('Notice_tag', '_streamDirect'),
array($tag),
'notice_tag:notice_ids:' . common_keyize($tag),
'notice_tag:notice_ids:' . Cache::keyize($tag),
$offset, $limit);
return Notice::getStreamByIds($ids);
@ -82,9 +82,9 @@ class Notice_tag extends Memcached_DataObject
function blowCache($blowLast=false)
{
self::blow('notice_tag:notice_ids:%s', common_keyize($this->tag));
self::blow('notice_tag:notice_ids:%s', Cache::keyize($this->tag));
if ($blowLast) {
self::blow('notice_tag:notice_ids:%s;last', common_keyize($this->tag));
self::blow('notice_tag:notice_ids:%s;last', Cache::keyize($this->tag));
}
}

View File

@ -152,18 +152,17 @@ class Profile extends Memcached_DataObject
*
* @return mixed Notice or null
*/
function getCurrentNotice()
{
$notice = new Notice();
$notice->profile_id = $this->id;
// @fixme change this to sort on notice.id only when indexes are updated
$notice->orderBy('created DESC, notice.id DESC');
$notice->limit(1);
if ($notice->find(true)) {
$notice = $this->getNotices(0, 1);
if ($notice->fetch()) {
return $notice;
}
} else {
return null;
}
}
function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
{
@ -430,10 +429,10 @@ class Profile extends Memcached_DataObject
function subscriptionCount()
{
$c = common_memcache();
$c = Cache::instance();
if (!empty($c)) {
$cnt = $c->get(common_cache_key('profile:subscription_count:'.$this->id));
$cnt = $c->get(Cache::key('profile:subscription_count:'.$this->id));
if (is_integer($cnt)) {
return (int) $cnt;
}
@ -447,7 +446,7 @@ class Profile extends Memcached_DataObject
$cnt = ($cnt > 0) ? $cnt - 1 : $cnt;
if (!empty($c)) {
$c->set(common_cache_key('profile:subscription_count:'.$this->id), $cnt);
$c->set(Cache::key('profile:subscription_count:'.$this->id), $cnt);
}
return $cnt;
@ -455,9 +454,9 @@ class Profile extends Memcached_DataObject
function subscriberCount()
{
$c = common_memcache();
$c = Cache::instance();
if (!empty($c)) {
$cnt = $c->get(common_cache_key('profile:subscriber_count:'.$this->id));
$cnt = $c->get(Cache::key('profile:subscriber_count:'.$this->id));
if (is_integer($cnt)) {
return (int) $cnt;
}
@ -465,13 +464,11 @@ class Profile extends Memcached_DataObject
$sub = new Subscription();
$sub->subscribed = $this->id;
$sub->whereAdd('subscriber != subscribed');
$cnt = (int) $sub->count('distinct subscriber');
$cnt = ($cnt > 0) ? $cnt - 1 : $cnt;
if (!empty($c)) {
$c->set(common_cache_key('profile:subscriber_count:'.$this->id), $cnt);
$c->set(Cache::key('profile:subscriber_count:'.$this->id), $cnt);
}
return $cnt;
@ -479,9 +476,9 @@ class Profile extends Memcached_DataObject
function faveCount()
{
$c = common_memcache();
$c = Cache::instance();
if (!empty($c)) {
$cnt = $c->get(common_cache_key('profile:fave_count:'.$this->id));
$cnt = $c->get(Cache::key('profile:fave_count:'.$this->id));
if (is_integer($cnt)) {
return (int) $cnt;
}
@ -492,7 +489,7 @@ class Profile extends Memcached_DataObject
$cnt = (int) $faves->count('distinct notice_id');
if (!empty($c)) {
$c->set(common_cache_key('profile:fave_count:'.$this->id), $cnt);
$c->set(Cache::key('profile:fave_count:'.$this->id), $cnt);
}
return $cnt;
@ -500,10 +497,10 @@ class Profile extends Memcached_DataObject
function noticeCount()
{
$c = common_memcache();
$c = Cache::instance();
if (!empty($c)) {
$cnt = $c->get(common_cache_key('profile:notice_count:'.$this->id));
$cnt = $c->get(Cache::key('profile:notice_count:'.$this->id));
if (is_integer($cnt)) {
return (int) $cnt;
}
@ -514,7 +511,7 @@ class Profile extends Memcached_DataObject
$cnt = (int) $notices->count('distinct id');
if (!empty($c)) {
$c->set(common_cache_key('profile:notice_count:'.$this->id), $cnt);
$c->set(Cache::key('profile:notice_count:'.$this->id), $cnt);
}
return $cnt;
@ -522,33 +519,33 @@ class Profile extends Memcached_DataObject
function blowSubscriberCount()
{
$c = common_memcache();
$c = Cache::instance();
if (!empty($c)) {
$c->delete(common_cache_key('profile:subscriber_count:'.$this->id));
$c->delete(Cache::key('profile:subscriber_count:'.$this->id));
}
}
function blowSubscriptionCount()
{
$c = common_memcache();
$c = Cache::instance();
if (!empty($c)) {
$c->delete(common_cache_key('profile:subscription_count:'.$this->id));
$c->delete(Cache::key('profile:subscription_count:'.$this->id));
}
}
function blowFaveCount()
{
$c = common_memcache();
$c = Cache::instance();
if (!empty($c)) {
$c->delete(common_cache_key('profile:fave_count:'.$this->id));
$c->delete(Cache::key('profile:fave_count:'.$this->id));
}
}
function blowNoticeCount()
{
$c = common_memcache();
$c = Cache::instance();
if (!empty($c)) {
$c->delete(common_cache_key('profile:notice_count:'.$this->id));
$c->delete(Cache::key('profile:notice_count:'.$this->id));
}
}
@ -735,14 +732,18 @@ class Profile extends Memcached_DataObject
'role' => $name));
if (empty($role)) {
throw new Exception('Cannot revoke role "'.$name.'" for user #'.$this->id.'; does not exist.');
// TRANS: Exception thrown when trying to revoke an existing role for a user that does not exist.
// TRANS: %1$s is the role name, %2$s is the user ID (number).
throw new Exception(sprintf(_('Cannot revoke role "%1$s" for user #%2$d; does not exist.'),$name, $this->id));
}
$result = $role->delete();
if (!$result) {
common_log_db_error($role, 'DELETE', __FILE__);
throw new Exception('Cannot revoke role "'.$name.'" for user #'.$this->id.'; database error.');
// TRANS: Exception thrown when trying to revoke a role for a user with a failing database query.
// TRANS: %1$s is the role name, %2$s is the user ID (number).
throw new Exception(sprintf(_('Cannot revoke role "%1$s" for user #%2$d; database error.'),$name, $this->id));
}
return true;
@ -849,15 +850,23 @@ class Profile extends Memcached_DataObject
*
* Assumes that Atom has been previously set up as the base namespace.
*
* @param Profile $cur the current authenticated user
*
* @return string
*/
function asAtomAuthor()
function asAtomAuthor($cur = null)
{
$xs = new XMLStringer(true);
$xs->elementStart('author');
$xs->element('name', null, $this->nickname);
$xs->element('uri', null, $this->getUri());
if ($cur != null) {
$attrs = Array();
$attrs['following'] = $cur->isSubscribed($this) ? 'true' : 'false';
$attrs['blocking'] = $cur->hasBlocked($this) ? 'true' : 'false';
$xs->element('statusnet:profile_info', $attrs, null);
}
$xs->elementEnd('author');
return $xs->getString();
@ -935,4 +944,41 @@ class Profile extends Memcached_DataObject
return $result;
}
function getAtomFeed()
{
$feed = null;
if (Event::handle('StartProfileGetAtomFeed', array($this, &$feed))) {
$user = User::staticGet('id', $this->id);
if (!empty($user)) {
$feed = common_local_url('ApiTimelineUser', array('id' => $user->id,
'format' => 'atom'));
}
Event::handle('EndProfileGetAtomFeed', array($this, $feed));
}
return $feed;
}
static function fromURI($uri)
{
$profile = null;
if (Event::handle('StartGetProfileFromURI', array($uri, &$profile))) {
// Get a local user or remote (OMB 0.1) profile
$user = User::staticGet('uri', $uri);
if (!empty($user)) {
$profile = $user->getProfile();
} else {
$remote_profile = Remote_profile::staticGet('uri', $uri);
if (!empty($remote_profile)) {
$profile = Profile::staticGet('id', $remote_profile->profile_id);
}
}
Event::handle('EndGetProfileFromURI', array($uri, $profile));
}
return $profile;
}
}

View File

@ -64,4 +64,17 @@ class Queue_item extends Memcached_DataObject
$qi = null;
return null;
}
/**
* Release a claimed item.
*/
function releaseCLaim()
{
// DB_DataObject doesn't let us save nulls right now
$sql = sprintf("UPDATE queue_item SET claimed=NULL WHERE id=%d", $this->id);
$this->query($sql);
$this->claimed = null;
$this->encache();
}
}

View File

@ -50,7 +50,8 @@ class Remote_profile extends Memcached_DataObject
if ($profile) {
return $profile->hasright($right);
} else {
throw new Exception("Missing profile");
// TRANS: Exception thrown when a right for a non-existing user profile is checked.
throw new Exception(_("Missing profile."));
}
}
}

View File

@ -22,6 +22,20 @@ class Reply extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
/**
* Wrapper for record insertion to update related caches
*/
function insert()
{
$result = parent::insert();
if ($result) {
self::blow('reply:stream:%d', $this->profile_id);
}
return $result;
}
function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
{
$ids = Notice::stream(array('Reply', '_streamDirect'),

View File

@ -116,6 +116,7 @@ class Safe_DataObject extends DB_DataObject
if ($this->_call($method, $params, $return)) {
return $return;
} else {
// Low level exception. No need for i18n as discussed with Brion.
throw new Exception('Call to undefined method ' .
get_class($this) . '::' . $method);
}
@ -136,7 +137,6 @@ class Safe_DataObject extends DB_DataObject
*/
function databaseStructure()
{
global $_DB_DATAOBJECT;
// Assignment code
@ -178,8 +178,6 @@ class Safe_DataObject extends DB_DataObject
}
if (!$this->_database) {
$this->_connect();
}
@ -205,7 +203,6 @@ class Safe_DataObject extends DB_DataObject
return true;
}
if (empty($_DB_DATAOBJECT['CONFIG'])) {
DB_DataObject::_loadConfig();
}
@ -223,7 +220,6 @@ class Safe_DataObject extends DB_DataObject
explode(PATH_SEPARATOR,$_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]);
}
/* BEGIN CHANGED FROM UPSTREAM */
$_DB_DATAOBJECT['INI'][$this->_database] = $this->parseIniFiles($schemas);
/* END CHANGED FROM UPSTREAM */
@ -245,7 +241,8 @@ class Safe_DataObject extends DB_DataObject
}
$this->debug("Cant find database schema: {$this->_database}/{$this->__table} \n".
"in links file data: " . print_r($_DB_DATAOBJECT['INI'],true),"databaseStructure",5);
// we have to die here!! - it causes chaos if we dont (including looping forever!)
// we have to die here!! - it causes chaos if we don't (including looping forever!)
// Low level exception. No need for i18n as discussed with Brion.
$this->raiseError( "Unable to load schema for database and table (turn debugging up to 5 for full error message)", DB_DATAOBJECT_ERROR_INVALIDARGS, PEAR_ERROR_DIE);
return false;
}

View File

@ -27,7 +27,8 @@ class Status_network extends Safe_DataObject
/* the code below is auto generated do not remove the above tag */
public $__table = 'status_network'; // table name
public $nickname; // varchar(64) primary_key not_null
public $site_id; // int(4) primary_key not_null
public $nickname; // varchar(64) unique_key not_null
public $hostname; // varchar(255) unique_key
public $pathname; // varchar(255) unique_key
public $dbhost; // varchar(255)
@ -39,7 +40,6 @@ class Status_network extends Safe_DataObject
public $logo; // varchar(255)
public $created; // datetime() not_null
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
public $tags; // text
/* Static get */
function staticGet($k,$v=NULL) {
@ -144,6 +144,35 @@ class Status_network extends Safe_DataObject
return parent::update($orig);
}
/**
* DB_DataObject doesn't allow updating keys (even non-primary)
*/
function updateKeys(&$orig)
{
$this->_connect();
foreach (array('hostname', 'pathname') as $k) {
if (strcmp($this->$k, $orig->$k) != 0) {
$parts[] = $k . ' = ' . $this->_quote($this->$k);
}
}
if (count($parts) == 0) {
// No changes
return true;
}
$toupdate = implode(', ', $parts);
$table = common_database_tablename($this->tableName());
$qry = 'UPDATE ' . $table . ' SET ' . $toupdate .
' WHERE nickname = ' . $this->_quote($this->nickname);
$orig->decache();
$result = $this->query($qry);
if ($result) {
$this->encache();
}
return $result;
}
function delete()
{
$this->decache(); # while we still have the values!
@ -152,18 +181,12 @@ class Status_network extends Safe_DataObject
/**
* @param string $servername hostname
* @param string $pathname URL base path
* @param string $wildcard hostname suffix to match wildcard config
* @return mixed Status_network or null
*/
static function setupSite($servername, $pathname, $wildcard)
static function getFromHostname($servername, $wildcard)
{
global $config;
$sn = null;
// XXX I18N, probably not crucial for hostnames
// XXX This probably needs a tune up
if (0 == strncasecmp(strrev($wildcard), strrev($servername), strlen($wildcard))) {
// special case for exact match
if (0 == strcasecmp($servername, $wildcard)) {
@ -182,6 +205,23 @@ class Status_network extends Safe_DataObject
}
}
}
return $sn;
}
/**
* @param string $servername hostname
* @param string $pathname URL base path
* @param string $wildcard hostname suffix to match wildcard config
*/
static function setupSite($servername, $pathname, $wildcard)
{
global $config;
$sn = null;
// XXX I18N, probably not crucial for hostnames
// XXX This probably needs a tune up
$sn = self::getFromHostname($servername, $wildcard);
if (!empty($sn)) {
@ -268,7 +308,54 @@ class Status_network extends Safe_DataObject
*/
function getTags()
{
return array_filter(explode("|", strval($this->tags)));
$result = Status_network_tag::getTags($this->site_id);
// XXX : for backwards compatibility
if (empty($result)) {
return explode('|', $this->tags);
}
return $result;
}
/**
* Save a given set of tags
* @param array tags
* @fixme only add/remove differentials
*/
function setTags($tags)
{
$this->clearTags();
foreach ($tags as $tag) {
if (!empty($tag)) {
$snt = new Status_network_tag();
$snt->site_id = $this->site_id;
$snt->tag = $tag;
$snt->created = common_sql_now();
$id = $snt->insert();
if (!$id) {
// TRANS: Exception thrown when a tag cannot be saved.
throw new Exception(_("Unable to save tag."));
}
}
}
return true;
}
function clearTags()
{
$tag = new Status_network_tag();
$tag->site_id = $this->site_id;
if ($tag->find()) {
while($tag->fetch()) {
$tag->delete();
}
}
$tag->free();
}
/**

View File

@ -0,0 +1,133 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2008, 2009, 2010 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); }
class Status_network_tag extends Safe_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'status_network_tag'; // table name
public $site_id; // int(4) primary_key not_null
public $tag; // varchar(64) primary_key not_null
public $created; // datetime() not_null
function __construct()
{
global $config;
global $_DB_DATAOBJECT;
$sn = new Status_network();
$sn->_connect();
$config['db']['table_'. $this->__table] = $sn->_database;
$this->_connect();
}
/* Static get */
function staticGet($k,$v=null)
{
$i = DB_DataObject::staticGet('Status_network_tag',$k,$v);
// Don't use local process cache; if we're fetching multiple
// times it's because we're reloading it in a long-running
// process; we need a fresh copy!
global $_DB_DATAOBJECT;
unset($_DB_DATAOBJECT['CACHE']['status_network_tag']);
return $i;
}
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Status_network_tag', $kv);
}
/**
* Fetch the (possibly cached) tag entries for the given site id.
* Uses status_network's cache settings.
*
* @param string $site_id
* @return array of strings
*/
static function getTags($site_id)
{
$key = 'status_network_tags:' . $site_id;
if (Status_network::$cache) {
$packed = Status_network::$cache->get($key);
if (is_string($packed)) {
if ($packed == '') {
return array();
} else {
return explode('|', $packed);
}
}
}
$result = array();
$tags = new Status_network_tag();
$tags->site_id = $site_id;
if ($tags->find()) {
while ($tags->fetch()) {
$result[] = $tags->tag;
}
}
if (Status_network::$cache) {
$packed = implode('|', $result);
Status_network::$cache->set($key, $packed, 3600);
}
return $result;
}
/**
* Drop the cached tag entries for this site.
* Needed after inserting/deleting a tag entry.
*/
function decache()
{
$key = 'status_network_tags:' . $this->site_id;
if (Status_network::$cache) {
Status_network::$cache->delete($key);
}
}
function insert()
{
$ret = parent::insert();
$this->decache();
return $ret;
}
function delete()
{
$ret = parent::delete();
$this->decache();
return $ret;
}
}

View File

@ -71,14 +71,17 @@ class Subscription extends Memcached_DataObject
}
if (!$subscriber->hasRight(Right::SUBSCRIBE)) {
// TRANS: Exception thrown when trying to subscribe while being banned from subscribing.
throw new Exception(_('You have been banned from subscribing.'));
}
if (self::exists($subscriber, $other)) {
// TRANS: Exception thrown when trying to subscribe while already subscribed.
throw new Exception(_('Already subscribed!'));
}
if ($other->hasBlocked($subscriber)) {
// TRANS: Exception thrown when trying to subscribe to a user who has blocked the subscribing user.
throw new Exception(_('User has blocked you.'));
}
@ -129,6 +132,7 @@ class Subscription extends Memcached_DataObject
if (!$result) {
common_log_db_error($sub, 'INSERT', __FILE__);
// TRANS: Exception thrown when a subscription could not be stored on the server.
throw new Exception(_('Could not save subscription.'));
}
@ -160,17 +164,18 @@ class Subscription extends Memcached_DataObject
* Cancel a subscription
*
*/
function cancel($subscriber, $other)
{
if (!self::exists($subscriber, $other)) {
// TRANS: Exception thrown when trying to unsibscribe without a subscription.
throw new Exception(_('Not subscribed!'));
}
// Don't allow deleting self subs
if ($subscriber->id == $other->id) {
throw new Exception(_('Couldn\'t delete self-subscription.'));
// TRANS: Exception thrown when trying to unsubscribe a user from themselves.
throw new Exception(_('Could not delete self-subscription.'));
}
if (Event::handle('StartUnsubscribe', array($subscriber, $other))) {
@ -197,7 +202,8 @@ class Subscription extends Memcached_DataObject
if (!$result) {
common_log_db_error($token, 'DELETE', __FILE__);
throw new Exception(_('Couldn\'t delete subscription OMB token.'));
// TRANS: Exception thrown when the OMB token for a subscription could not deleted on the server.
throw new Exception(_('Could not delete subscription OMB token.'));
}
} else {
common_log(LOG_ERR, "Couldn't find credentials with token {$token->tok}");
@ -208,7 +214,8 @@ class Subscription extends Memcached_DataObject
if (!$result) {
common_log_db_error($sub, 'DELETE', __FILE__);
throw new Exception(_('Couldn\'t delete subscription.'));
// TRANS: Exception thrown when a subscription could not be deleted on the server.
throw new Exception(_('Could not delete subscription.'));
}
self::blow('user:notices_with_friends:%d', $subscriber->id);

View File

@ -355,11 +355,12 @@ class User extends Memcached_DataObject
__FILE__);
} else {
$notice = Notice::saveNew($welcomeuser->id,
// TRANS: Notice given on user registration.
// TRANS: %1$s is the sitename, $2$s is the registering user's nickname.
sprintf(_('Welcome to %1$s, @%2$s!'),
common_config('site', 'name'),
$user->nickname),
'system');
}
}
@ -370,7 +371,6 @@ class User extends Memcached_DataObject
}
// Things we do when the email changes
function emailChanged()
{
@ -388,7 +388,7 @@ class User extends Memcached_DataObject
function hasFave($notice)
{
$cache = common_memcache();
$cache = Cache::instance();
// XXX: Kind of a hack.
@ -459,9 +459,9 @@ class User extends Memcached_DataObject
return $profile->getNotices($offset, $limit, $since_id, $before_id);
}
function favoriteNotices($offset=0, $limit=NOTICES_PER_PAGE, $own=false)
function favoriteNotices($own=false, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
{
$ids = Fave::stream($this->id, $offset, $limit, $own);
$ids = Fave::stream($this->id, $offset, $limit, $own, $since_id, $max_id);
return Notice::getStreamByIds($ids);
}
@ -487,14 +487,14 @@ class User extends Memcached_DataObject
function blowFavesCache()
{
$cache = common_memcache();
$cache = Cache::instance();
if ($cache) {
// Faves don't happen chronologically, so we need to blow
// ;last cache, too
$cache->delete(common_cache_key('fave:ids_by_user:'.$this->id));
$cache->delete(common_cache_key('fave:ids_by_user:'.$this->id.';last'));
$cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id));
$cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id.';last'));
$cache->delete(Cache::key('fave:ids_by_user:'.$this->id));
$cache->delete(Cache::key('fave:ids_by_user:'.$this->id.';last'));
$cache->delete(Cache::key('fave:ids_by_user_own:'.$this->id));
$cache->delete(Cache::key('fave:ids_by_user_own:'.$this->id.';last'));
}
$profile = $this->getProfile();
$profile->blowFaveCount();
@ -519,7 +519,7 @@ class User extends Memcached_DataObject
if ($this->id == $other->id) {
common_log(LOG_WARNING,
sprintf(
"Profile ID %d (%s) tried to block his or herself.",
"Profile ID %d (%s) tried to block themself.",
$this->id,
$this->nickname
)

View File

@ -154,6 +154,21 @@ class User_group extends Memcached_DataObject
return $members;
}
function getMemberCount()
{
// XXX: WORM cache this
$members = $this->getMembers();
$member_count = 0;
/** $member->count() doesn't work. */
while ($members->fetch()) {
$member_count++;
}
return $member_count;
}
function getAdmins($offset=0, $limit=null)
{
$qry =
@ -477,6 +492,7 @@ class User_group extends Memcached_DataObject
if (!$result) {
common_log_db_error($group, 'INSERT', __FILE__);
// TRANS: Server exception thrown when creating a group failed.
throw new ServerException(_('Could not create group.'));
}
@ -486,6 +502,7 @@ class User_group extends Memcached_DataObject
$result = $group->update($orig);
if (!$result) {
common_log_db_error($group, 'UPDATE', __FILE__);
// TRANS: Server exception thrown when updating a group URI failed.
throw new ServerException(_('Could not set group URI.'));
}
}
@ -493,6 +510,7 @@ class User_group extends Memcached_DataObject
$result = $group->setAliases($aliases);
if (!$result) {
// TRANS: Server exception thrown when creating group aliases failed.
throw new ServerException(_('Could not create aliases.'));
}
@ -507,6 +525,7 @@ class User_group extends Memcached_DataObject
if (!$result) {
common_log_db_error($member, 'INSERT', __FILE__);
// TRANS: Server exception thrown when setting group membership failed.
throw new ServerException(_('Could not set group membership.'));
}
@ -521,6 +540,7 @@ class User_group extends Memcached_DataObject
if (!$result) {
common_log_db_error($local_group, 'INSERT', __FILE__);
// TRANS: Server exception thrown when saving local group information failed.
throw new ServerException(_('Could not save local group info.'));
}
}

View File

@ -68,4 +68,27 @@ class User_im_prefs extends Memcached_DataObject
{
return array(false,false);
}
/**
* We have two compound keys with unique constraints:
* (transport, user_id) which is our primary key, and
* (transport, screenname) which is an additional constraint.
*
* Currently there's not a way to represent that second key
* in the general keys list, so we're adding it here to the
* list of keys to use for caching, ensuring that it gets
* cleared as well when we change.
*
* @return array of cache keys
*/
function _allCacheKeys()
{
$ukeys = 'transport,screenname';
$uvals = $this->transport . ',' . $this->screenname;
$ckeys = parent::_allCacheKeys();
$ckeys[] = $this->cacheKey($this->tableName(), $ukeys, $uvals);
return $ckeys;
}
}

View File

@ -1,4 +1,5 @@
[status_network]
site_id = 129
nickname = 130
hostname = 2
pathname = 2
@ -11,9 +12,19 @@ theme = 2
logo = 2
created = 142
modified = 384
tags = 34
[status_network__keys]
nickname = K
site_id = K
nickname = U
hostname = U
pathname = U
[status_network_tag]
site_id = 129
tag = 130
created = 142
[status_network_tag__keys]
site_id = K
tag = K

View File

@ -647,8 +647,10 @@ modified = 384
[user_im_prefs__keys]
user_id = K
transport = K
transport = U
screenname = U
; There's another unique index on (transport, screenname)
; but we have no way to represent a compound index other than
; the primary key in here. To ensure proper cache purging,
; we need to tweak the class.
[user_urlshortener_prefs]
user_id = 129

View File

@ -45,7 +45,7 @@ $config['site']['path'] = 'statusnet';
// lighttpd, nginx), you can enable X-Sendfile support for better
// performance. Presently, only attachment serving when the site is
// in private mode will use X-Sendfile.
// $config['site']['X-Sendfile'] = false;
// $config['site']['use_x_sendfile'] = false;
// You may also need to enable X-Sendfile support for your web server and
// allow it to access files outside of the web root. For Apache with
// mod_xsendfile, you can add these to your .htaccess or server config:

View File

@ -81,3 +81,60 @@ 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_ns integer /* comment 'namespace for location'*/;
ALTER TABLE consumer add COLUMN consumer_secret varchar(255) not null ; /*comment 'secret value'*/
ALTER TABLE token ADD COLUMN verifier varchar(255); /* comment 'verifier string for OAuth 1.0a',*/
ALTER TABLE token ADD COLUMN verified_callback varchar(255); /* comment 'verified callback URL for OAuth 1.0a',*/
create table queue_item_new (
id serial /* comment 'unique identifier'*/,
frame bytea not null /* comment 'data: object reference or opaque string'*/,
transport varchar(8) not null /*comment 'queue for what? "email", "jabber", "sms", "irc", ...'*/,
created timestamp not null default CURRENT_TIMESTAMP /*comment 'date this record was created'*/,
claimed timestamp /*comment 'date this item was claimed'*/,
PRIMARY KEY (id)
);
insert into queue_item_new (frame,transport,created,claimed)
select ('0x' || notice_id::text)::bytea,transport,created,claimed from queue_item;
alter table queue_item rename to queue_item_old;
alter table queue_item_new rename to queue_item;
ALTER TABLE confirm_address ALTER column sent set default CURRENT_TIMESTAMP;
create table user_location_prefs (
user_id integer not null /*comment 'user who has the preference'*/ references "user" (id),
share_location int default 1 /* comment 'Whether to share location data'*/,
created timestamp not null /*comment 'date this record was created'*/,
modified timestamp /* comment 'date this record was modified'*/,
primary key (user_id)
);
create table inbox (
user_id integer not null /* comment 'user receiving the notice' */ references "user" (id),
notice_ids bytea /* comment 'packed list of notice ids' */,
primary key (user_id)
);
create table user_location_prefs (
user_id integer not null /*comment 'user who has the preference'*/ references "user" (id),
share_location int default 1 /* comment 'Whether to share location data'*/,
created timestamp not null /*comment 'date this record was created'*/,
modified timestamp /* comment 'date this record was modified'*/,
primary key (user_id)
);
create table inbox (
user_id integer not null /* comment 'user receiving the notice' */ references "user" (id),
notice_ids bytea /* comment 'packed list of notice ids' */,
primary key (user_id)
);

View File

@ -9,13 +9,16 @@ VALUES
('bti','bti','http://gregkh.github.com/bti/', now()),
('choqok', 'Choqok', 'http://choqok.gnufolks.org/', now()),
('cliqset', 'Cliqset', 'http://www.cliqset.com/', now()),
('DarterosStatus', 'Darteros Status', 'http://www.darteros.com/doc/Darteros_Status', now()),
('deskbar','Deskbar-Applet','http://www.gnome.org/projects/deskbar-applet/', now()),
('Do','Gnome Do','http://do.davebsd.com/wiki/index.php?title=Microblog_Plugin', now()),
('drupal','Drupal','http://drupal.org/', now()),
('eventbox','EventBox','http://thecosmicmachine.com/eventbox/ ', now()),
('eventbox','EventBox','http://thecosmicmachine.com/eventbox/', now()),
('identica-mode','Emacs Identica-mode','http://nongnu.org/identica-mode/', now()),
('Facebook','Facebook','http://apps.facebook.com/identica/', now()),
('feed2omb','feed2omb','http://projects.ciarang.com/p/feed2omb/', now()),
('get2gnow', 'get2gnow', 'http://uberchicgeekchick.com/?projects=get2gnow', now()),
('gNewBook', 'gNewBook', 'http://www.gnewbook.org/', now()),
('gravity', 'Gravity', 'http://mobileways.de/gravity', now()),
('Gwibber','Gwibber','http://launchpad.net/gwibber', now()),
('HelloTxt','HelloTxt','http://hellotxt.com/', now()),
@ -48,11 +51,13 @@ VALUES
('smob','SMOB','http://smob.sioc-project.org/', now()),
('socialoomphBfD4pMqz31', 'SocialOomph', 'http://www.socialoomph.com/', now()),
('spaz','Spaz','http://funkatron.com/spaz', now()),
('StatusNet Desktop', 'StatusNet Desktop', 'http://status.net/desktop', now()),
('tarpipe','tarpipe','http://tarpipe.com/', now()),
('tjunar','Tjunar','http://nederflash.nl/boek/titels/tjunar-air', now()),
('tr.im','tr.im','http://tr.im/', now()),
('triklepost', 'Tricklepost', 'http://github.com/zcopley/tricklepost/tree/master', now()),
('tweenky','Tweenky','http://beta.tweenky.com/', now()),
('TweetDeck', 'TweetDeck', 'http://www.tweetdeck.com/', now()),
('twhirl','Twhirl','http://www.twhirl.org/', now()),
('twibble','twibble','http://www.twibble.de/', now()),
('Twidge','Twidge','http://software.complete.org/twidge', now()),

View File

@ -2,7 +2,8 @@
create table status_network (
nickname varchar(64) primary key comment 'nickname',
site_id integer auto_increment primary key comment 'unique id',
nickname varchar(64) unique key comment 'nickname',
hostname varchar(255) unique key comment 'alternate hostname if any',
pathname varchar(255) unique key comment 'alternate pathname if any',
@ -21,3 +22,12 @@ create table status_network (
modified timestamp comment 'date this record was modified'
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
create table status_network_tag (
site_id integer comment 'unique id',
tag varchar(64) comment 'tag name',
created datetime not null comment 'date the record was created',
constraint primary key (site_id, tag)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;

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