Merge commit 'upstream/0.7.x' into 0.7.x

This commit is contained in:
Sean Murphy 2009-02-09 17:29:39 -05:00
commit bdd9f6ce1d
30 changed files with 897 additions and 347 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ dataobject.ini
*.bak *.bak
*.orig *.orig
*.rej *.rej
.#*

41
EVENTS.txt Normal file
View File

@ -0,0 +1,41 @@
InitializePlugin: a chance to initialize a plugin in a complete
environment
CleanupPlugin: a chance to cleanup a plugin at the end of a program
StartPrimaryNav: Showing the primary nav menu
- $action: the current action
EndPrimaryNav: At the end of the primary nav menu
- $action: the current action
StartSecondaryNav: Showing the secondary nav menu
- $action: the current action
EndSecondaryNav: At the end of the secondary nav menu
- $action: the current action
StartShowScripts: Showing JavaScript links
- $action: the current action
EndShowScripts: End showing JavaScript links; good place to add custom
links like Google Analytics
- $action: the current action
StartShowJQueryScripts: Showing JQuery script links (use this to link to e.g. Google mirrors)
- $action: the current action
EndShowJQueryScripts: End showing JQuery script links
- $action: the current action
StartShowLaconicaScripts: Showing Laconica script links (use this to link to a CDN or something)
- $action: the current action
EndShowLaconicaScripts: End showing Laconica script links
- $action: the current action
StartShowSections: Start the list of sections in the sidebar
- $action: the current action
EndShowSections: End the list of sections in the sidebar
- $action: the current action

View File

@ -191,13 +191,13 @@ class EditgroupAction extends Action
array('http', 'https')))) { array('http', 'https')))) {
$this->showForm(_('Homepage is not a valid URL.')); $this->showForm(_('Homepage is not a valid URL.'));
return; return;
} else if (!is_null($fullname) && strlen($fullname) > 255) { } else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
$this->showForm(_('Full name is too long (max 255 chars).')); $this->showForm(_('Full name is too long (max 255 chars).'));
return; return;
} else if (!is_null($description) && strlen($description) > 140) { } else if (!is_null($description) && mb_strlen($description) > 140) {
$this->showForm(_('description is too long (max 140 chars).')); $this->showForm(_('description is too long (max 140 chars).'));
return; return;
} else if (!is_null($location) && strlen($location) > 255) { } else if (!is_null($location) && mb_strlen($location) > 255) {
$this->showForm(_('Location is too long (max 255 chars).')); $this->showForm(_('Location is too long (max 255 chars).'));
return; return;
} }

View File

@ -71,13 +71,13 @@ class FacebookinviteAction extends FacebookAction
common_config('site', 'name'))); common_config('site', 'name')));
$this->element('p', null, _('Invitations have been sent to the following users:')); $this->element('p', null, _('Invitations have been sent to the following users:'));
$friend_ids = $_POST['ids']; // XXX: Hmm... is this the best way to acces the list? $friend_ids = $_POST['ids']; // XXX: Hmm... is this the best way to access the list?
$this->elementStart('ul', array('id' => 'facebook-friends')); $this->elementStart('ul', array('id' => 'facebook-friends'));
foreach ($friend_ids as $friend) { foreach ($friend_ids as $friend) {
$this->elementStart('li'); $this->elementStart('li');
$this->element('fb:profile-pic', array('uid' => $friend)); $this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square'));
$this->element('fb:name', array('uid' => $friend, $this->element('fb:name', array('uid' => $friend,
'capitalize' => 'true')); 'capitalize' => 'true'));
$this->elementEnd('li'); $this->elementEnd('li');
@ -92,6 +92,12 @@ class FacebookinviteAction extends FacebookAction
// Get a list of users who are already using the app for exclusion // Get a list of users who are already using the app for exclusion
$exclude_ids = $this->facebook->api_client->friends_getAppUsers(); $exclude_ids = $this->facebook->api_client->friends_getAppUsers();
$exclude_ids_csv = null;
// fbml needs these as a csv string, not an array
if ($exclude_ids) {
$exclude_ids_csv = implode(',', $exclude_ids);
}
$content = sprintf(_('You have been invited to %s'), common_config('site', 'name')) . $content = sprintf(_('You have been invited to %s'), common_config('site', 'name')) .
htmlentities('<fb:req-choice url="' . $this->app_uri . '" label="Add"/>'); htmlentities('<fb:req-choice url="' . $this->app_uri . '" label="Add"/>');
@ -103,10 +109,17 @@ class FacebookinviteAction extends FacebookAction
'content' => $content)); 'content' => $content));
$this->hidden('invite', 'true'); $this->hidden('invite', 'true');
$actiontext = sprintf(_('Invite your friends to use %s'), common_config('site', 'name')); $actiontext = sprintf(_('Invite your friends to use %s'), common_config('site', 'name'));
$this->element('fb:multi-friend-selector', array('showborder' => 'false',
'actiontext' => $actiontext, $multi_params = array('showborder' => 'false');
'exclude_ids' => implode(',', $exclude_ids), $multi_params['actiontext'] = $actiontext;
'bypass' => 'cancel'));
if ($exclude_ids_csv) {
$multi_params['exclude_ids'] = $exclude_ids_csv;
}
$multi_params['bypass'] = 'cancel';
$this->element('fb:multi-friend-selector', $multi_params);
$this->elementEnd('fb:request-form'); $this->elementEnd('fb:request-form');

View File

@ -242,7 +242,7 @@ class FinishopenidloginAction extends Action
} }
} }
if ($sreg['fullname'] && strlen($sreg['fullname']) <= 255) { if ($sreg['fullname'] && mb_strlen($sreg['fullname']) <= 255) {
$fullname = $sreg['fullname']; $fullname = $sreg['fullname'];
} }

View File

@ -142,13 +142,13 @@ class NewgroupAction extends Action
array('http', 'https')))) { array('http', 'https')))) {
$this->showForm(_('Homepage is not a valid URL.')); $this->showForm(_('Homepage is not a valid URL.'));
return; return;
} else if (!is_null($fullname) && strlen($fullname) > 255) { } else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
$this->showForm(_('Full name is too long (max 255 chars).')); $this->showForm(_('Full name is too long (max 255 chars).'));
return; return;
} else if (!is_null($description) && strlen($description) > 140) { } else if (!is_null($description) && mb_strlen($description) > 140) {
$this->showForm(_('description is too long (max 140 chars).')); $this->showForm(_('description is too long (max 140 chars).'));
return; return;
} else if (!is_null($location) && strlen($location) > 255) { } else if (!is_null($location) && mb_strlen($location) > 255) {
$this->showForm(_('Location is too long (max 255 chars).')); $this->showForm(_('Location is too long (max 255 chars).'));
return; return;
} }

View File

@ -201,7 +201,7 @@ class NewmessageAction extends Action
function showNoticeForm() function showNoticeForm()
{ {
$message_form = new MessageForm($this, $this->to, $this->content); $message_form = new MessageForm($this, $this->other, $this->content);
$message_form->show(); $message_form->show();
} }
} }

View File

@ -1,9 +1,12 @@
<?php <?php
/* /**
* Laconica - a distributed open-source microblogging tool * Laconica, the distributed open-source microblogging tool
* Copyright (C) 2008, Controlez-Vous, Inc.
* *
* This program is free software: you can redistribute it and/or modify * Action for showing profiles self-tagged with a given tag
*
* 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
@ -15,62 +18,109 @@
* *
* You should have received a copy of the GNU Affero General Public License * 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Zach Copley <zach@controlyourself.ca>
* @copyright 2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/ */
if (!defined('LACONICA')) { exit(1); } if (!defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/lib/profilelist.php'; require_once INSTALLDIR.'/lib/profilelist.php';
/**
* This class outputs a paginated list of profiles self-tagged with a given tag
*
* @category Output
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Zach Copley <zach@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*
* @see Action
*/
class PeopletagAction extends Action class PeopletagAction extends Action
{ {
var $tag = null; var $tag = null;
var $page = null; var $page = null;
function handle($args) /**
* For initializing members of the class.
*
* @param array $argarray misc. arguments
*
* @return boolean true
*/
function prepare($argarray)
{ {
parent::handle($args); parent::prepare($argarray);
parent::prepare($args);
$this->tag = $this->trimmed('tag'); $this->tag = $this->trimmed('tag');
if (!common_valid_profile_tag($this->tag)) { if (!common_valid_profile_tag($this->tag)) {
$this->clientError(sprintf(_('Not a valid people tag: %s'), $this->tag)); $this->clientError(sprintf(_('Not a valid people tag: %s'),
$this->tag));
return; return;
} }
$this->page = $this->trimmed('page'); $this->page = ($this->arg('page')) ? $this->arg('page') : 1;
if (!$this->page) { common_set_returnto($this->selfUrl());
$this->page = 1;
return true;
} }
/**
* Handler method
*
* @param array $argarray is ignored since it's now passed in in prepare()
*
* @return boolean is read only action?
*/
function handle($argarray)
{
parent::handle($argarray);
$this->showPage(); $this->showPage();
} }
/**
* Whips up a query to get a list of profiles based on the provided
* people tag and page, initalizes a ProfileList widget, and displays
* it to the user.
*
* @return nothing
*/
function showContent() function showContent()
{ {
$profile = new Profile(); $profile = new Profile();
$offset = ($page-1)*PROFILES_PER_PAGE; $offset = ($this->page - 1) * PROFILES_PER_PAGE;
$limit = PROFILES_PER_PAGE + 1; $limit = PROFILES_PER_PAGE + 1;
if (common_config('db','type') == 'pgsql') { if (common_config('db', 'type') == 'pgsql') {
$lim = ' LIMIT ' . $limit . ' OFFSET ' . $offset; $lim = ' LIMIT ' . $limit . ' OFFSET ' . $offset;
} else { } else {
$lim = ' LIMIT ' . $offset . ', ' . $limit; $lim = ' LIMIT ' . $offset . ', ' . $limit;
} }
# XXX: memcached this // XXX: memcached this
$qry = 'SELECT profile.* ' . $qry = 'SELECT profile.* ' .
'FROM profile JOIN profile_tag ' . 'FROM profile JOIN profile_tag ' .
'ON profile.id = profile_tag.tagger ' . 'ON profile.id = profile_tag.tagger ' .
'WHERE profile_tag.tagger = profile_tag.tagged ' . 'WHERE profile_tag.tagger = profile_tag.tagged ' .
'AND tag = "%s" ' . 'AND tag = "%s" ' .
'ORDER BY profile_tag.modified DESC'; 'ORDER BY profile_tag.modified DESC%s';
$profile->query(sprintf($qry, $this->tag, $lim)); $profile->query(sprintf($qry, $this->tag, $lim));
@ -80,13 +130,19 @@ class PeopletagAction extends Action
$this->pagination($this->page > 1, $this->pagination($this->page > 1,
$cnt > PROFILES_PER_PAGE, $cnt > PROFILES_PER_PAGE,
$this->page, $this->page,
$this->trimmed('action'), 'peopletag',
array('tag' => $this->tag)); array('tag' => $this->tag));
} }
/**
* Returns the page title
*
* @return string page title
*/
function title() function title()
{ {
return sprintf( _('Users self-tagged with %s - page %d'), $this->tag, $this->page); return sprintf(_('Users self-tagged with %s - page %d'),
$this->tag, $this->page);
} }
} }

View File

@ -198,13 +198,13 @@ class ProfilesettingsAction extends AccountSettingsAction
!Validate::uri($homepage, array('allowed_schemes' => array('http', 'https')))) { !Validate::uri($homepage, array('allowed_schemes' => array('http', 'https')))) {
$this->showForm(_('Homepage is not a valid URL.')); $this->showForm(_('Homepage is not a valid URL.'));
return; return;
} else if (!is_null($fullname) && strlen($fullname) > 255) { } else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
$this->showForm(_('Full name is too long (max 255 chars).')); $this->showForm(_('Full name is too long (max 255 chars).'));
return; return;
} else if (!is_null($bio) && strlen($bio) > 140) { } else if (!is_null($bio) && mb_strlen($bio) > 140) {
$this->showForm(_('Bio is too long (max 140 chars).')); $this->showForm(_('Bio is too long (max 140 chars).'));
return; return;
} else if (!is_null($location) && strlen($location) > 255) { } else if (!is_null($location) && mb_strlen($location) > 255) {
$this->showForm(_('Location is too long (max 255 chars).')); $this->showForm(_('Location is too long (max 255 chars).'));
return; return;
} else if (is_null($timezone) || !in_array($timezone, DateTimeZone::listIdentifiers())) { } else if (is_null($timezone) || !in_array($timezone, DateTimeZone::listIdentifiers())) {

View File

@ -167,13 +167,13 @@ class RegisterAction extends Action
array('http', 'https')))) { array('http', 'https')))) {
$this->showForm(_('Homepage is not a valid URL.')); $this->showForm(_('Homepage is not a valid URL.'));
return; return;
} else if (!is_null($fullname) && strlen($fullname) > 255) { } else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
$this->showForm(_('Full name is too long (max 255 chars).')); $this->showForm(_('Full name is too long (max 255 chars).'));
return; return;
} else if (!is_null($bio) && strlen($bio) > 140) { } else if (!is_null($bio) && mb_strlen($bio) > 140) {
$this->showForm(_('Bio is too long (max 140 chars).')); $this->showForm(_('Bio is too long (max 140 chars).'));
return; return;
} else if (!is_null($location) && strlen($location) > 255) { } else if (!is_null($location) && mb_strlen($location) > 255) {
$this->showForm(_('Location is too long (max 255 chars).')); $this->showForm(_('Location is too long (max 255 chars).'));
return; return;
} else if (strlen($password) < 6) { } else if (strlen($password) < 6) {

View File

@ -56,7 +56,7 @@ class TwitapiaccountAction extends TwitterapiAction
$location = trim($this->arg('location')); $location = trim($this->arg('location'));
if (!is_null($location) && strlen($location) > 255) { if (!is_null($location) && mb_strlen($location) > 255) {
// XXX: But Twitter just truncates and runs with it. -- Zach // XXX: But Twitter just truncates and runs with it. -- Zach
$this->clientError(_('That\'s too long. Max notice size is 255 chars.'), 406, $apidate['content-type']); $this->clientError(_('That\'s too long. Max notice size is 255 chars.'), 406, $apidate['content-type']);

View File

@ -93,22 +93,22 @@ class UpdateprofileAction extends Action
} }
# optional stuff # optional stuff
$fullname = $req->get_parameter('omb_listenee_fullname'); $fullname = $req->get_parameter('omb_listenee_fullname');
if ($fullname && strlen($fullname) > 255) { if ($fullname && mb_strlen($fullname) > 255) {
$this->clientError(_("Full name is too long (max 255 chars).")); $this->clientError(_("Full name is too long (max 255 chars)."));
return false; return false;
} }
$homepage = $req->get_parameter('omb_listenee_homepage'); $homepage = $req->get_parameter('omb_listenee_homepage');
if ($homepage && (!common_valid_http_url($homepage) || strlen($homepage) > 255)) { if ($homepage && (!common_valid_http_url($homepage) || mb_strlen($homepage) > 255)) {
$this->clientError(sprintf(_("Invalid homepage '%s'"), $homepage)); $this->clientError(sprintf(_("Invalid homepage '%s'"), $homepage));
return false; return false;
} }
$bio = $req->get_parameter('omb_listenee_bio'); $bio = $req->get_parameter('omb_listenee_bio');
if ($bio && strlen($bio) > 140) { if ($bio && mb_strlen($bio) > 140) {
$this->clientError(_("Bio is too long (max 140 chars).")); $this->clientError(_("Bio is too long (max 140 chars)."));
return false; return false;
} }
$location = $req->get_parameter('omb_listenee_location'); $location = $req->get_parameter('omb_listenee_location');
if ($location && strlen($location) > 255) { if ($location && mb_strlen($location) > 255) {
$this->clientError(_("Location is too long (max 255 chars).")); $this->clientError(_("Location is too long (max 255 chars)."));
return false; return false;
} }

View File

@ -469,19 +469,19 @@ class UserauthorizationAction extends Action
} }
# optional stuff # optional stuff
$fullname = $req->get_parameter('omb_listenee_fullname'); $fullname = $req->get_parameter('omb_listenee_fullname');
if ($fullname && strlen($fullname) > 255) { if ($fullname && mb_strlen($fullname) > 255) {
throw new OAuthException("Full name '$fullname' too long."); throw new OAuthException("Full name '$fullname' too long.");
} }
$homepage = $req->get_parameter('omb_listenee_homepage'); $homepage = $req->get_parameter('omb_listenee_homepage');
if ($homepage && (!common_valid_http_url($homepage) || strlen($homepage) > 255)) { if ($homepage && (!common_valid_http_url($homepage) || mb_strlen($homepage) > 255)) {
throw new OAuthException("Invalid homepage '$homepage'"); throw new OAuthException("Invalid homepage '$homepage'");
} }
$bio = $req->get_parameter('omb_listenee_bio'); $bio = $req->get_parameter('omb_listenee_bio');
if ($bio && strlen($bio) > 140) { if ($bio && mb_strlen($bio) > 140) {
throw new OAuthException("Bio too long '$bio'"); throw new OAuthException("Bio too long '$bio'");
} }
$location = $req->get_parameter('omb_listenee_location'); $location = $req->get_parameter('omb_listenee_location');
if ($location && strlen($location) > 255) { if ($location && mb_strlen($location) > 255) {
throw new OAuthException("Location too long '$location'"); throw new OAuthException("Location too long '$location'");
} }
$avatar = $req->get_parameter('omb_listenee_avatar'); $avatar = $req->get_parameter('omb_listenee_avatar');

View File

@ -142,3 +142,7 @@ $config['sphinx']['port'] = 3312;
# config section for the built-in Facebook application # config section for the built-in Facebook application
#$config['facebook']['apikey'] = 'APIKEY'; #$config['facebook']['apikey'] = 'APIKEY';
#$config['facebook']['secret'] = 'SECRET'; #$config['facebook']['secret'] = 'SECRET';
# Add Google Analytics
# require_once('plugins/GoogleAnalyticsPlugin.php');
# $ga = new GoogleAnalyticsPlugin('your secret code');

View File

@ -258,7 +258,8 @@ create table notice_tag (
created datetime not null comment 'date this record was created', created datetime not null comment 'date this record was created',
constraint primary key (tag, notice_id), constraint primary key (tag, notice_id),
index notice_tag_created_idx (created) index notice_tag_created_idx (created),
index notice_tag_notice_id_idx (notice_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
/* Synching with foreign services */ /* Synching with foreign services */
@ -356,7 +357,8 @@ create table profile_tag (
constraint primary key (tagger, tagged, tag), constraint primary key (tagger, tagged, tag),
index profile_tag_modified_idx (modified), index profile_tag_modified_idx (modified),
index profile_tag_tagger_tag_idx (tagger, tag) index profile_tag_tagger_tag_idx (tagger, tag),
index profile_tag_tagged_idx (tagged)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table profile_block ( create table profile_block (
@ -400,7 +402,9 @@ create table group_member (
created datetime not null comment 'date this record was created', created datetime not null comment 'date this record was created',
modified timestamp comment 'date this record was modified', modified timestamp comment 'date this record was modified',
constraint primary key (group_id, profile_id) constraint primary key (group_id, profile_id),
index group_member_profile_id_idx (profile_id),
index group_member_created_idx (created)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;

View File

@ -22,6 +22,8 @@ define('LACONICA', true);
require_once INSTALLDIR . '/lib/common.php'; require_once INSTALLDIR . '/lib/common.php';
// XXX: we need a little more structure in this script
// get and cache current user // get and cache current user
$user = common_current_user(); $user = common_current_user();
@ -45,16 +47,16 @@ if (!$user && common_config('site', 'private') &&
common_redirect(common_local_url('login')); common_redirect(common_local_url('login'));
} }
$actionfile = INSTALLDIR."/actions/$action.php"; $action_class = ucfirst($action).'Action';
if (file_exists($actionfile)) {
include_once $actionfile;
$action_class = ucfirst($action).'Action';
if (!class_exists($action_class)) {
$cac = new ClientErrorAction(_('Unknown action'), 404);
$cac->showPage();
} else {
$action_obj = new $action_class(); $action_obj = new $action_class();
// XXX: find somewhere for this little block to live
if ($config['db']['mirror'] && $action_obj->isReadOnly()) { if ($config['db']['mirror'] && $action_obj->isReadOnly()) {
if (is_array($config['db']['mirror'])) { if (is_array($config['db']['mirror'])) {
// "load balancing", ha ha // "load balancing", ha ha
@ -66,9 +68,24 @@ if (file_exists($actionfile)) {
} }
$config['db']['database'] = $mirror; $config['db']['database'] = $mirror;
} }
if (call_user_func(array($action_obj, 'prepare'), $_REQUEST)) {
call_user_func(array($action_obj, 'handle'), $_REQUEST); try {
if ($action_obj->prepare($_REQUEST)) {
$action_obj->handle($_REQUEST);
}
} catch (ClientException $cex) {
$cac = new ClientErrorAction($cex->getMessage(), $cex->getCode());
$cac->showPage();
} catch (ServerException $sex) { // snort snort guffaw
$sac = new ServerErrorAction($sex->getMessage(), $sex->getCode());
$sac->showPage();
} catch (Exception $ex) {
$sac = new ServerErrorAction($ex->getMessage());
$sac->showPage();
} }
} else {
common_user_error(_('Unknown action'));
} }
// XXX: cleanup exit() calls or add an exit handler so
// this always gets called
Event::handle('CleanupPlugin');

192
js/jquery.js vendored
View File

@ -1,13 +1,13 @@
/*! /*!
* jQuery JavaScript Library v1.3 * jQuery JavaScript Library v1.3.1
* http://jquery.com/ * http://jquery.com/
* *
* Copyright (c) 2009 John Resig * Copyright (c) 2009 John Resig
* Dual licensed under the MIT and GPL licenses. * Dual licensed under the MIT and GPL licenses.
* http://docs.jquery.com/License * http://docs.jquery.com/License
* *
* Date: 2009-01-13 12:50:31 -0500 (Tue, 13 Jan 2009) * Date: 2009-01-21 20:42:16 -0500 (Wed, 21 Jan 2009)
* Revision: 6104 * Revision: 6158
*/ */
(function(){ (function(){
@ -60,21 +60,17 @@ jQuery.fn = jQuery.prototype = {
else { else {
var elem = document.getElementById( match[3] ); var elem = document.getElementById( match[3] );
// Make sure an element was located
if ( elem ){
// Handle the case where IE and Opera return items // Handle the case where IE and Opera return items
// by name instead of ID // by name instead of ID
if ( elem.id != match[3] ) if ( elem && elem.id != match[3] )
return jQuery().find( selector ); return jQuery().find( selector );
// Otherwise, we inject the element directly into the jQuery object // Otherwise, we inject the element directly into the jQuery object
var ret = jQuery( elem ); var ret = jQuery( elem || [] );
ret.context = document; ret.context = document;
ret.selector = selector; ret.selector = selector;
return ret; return ret;
} }
selector = [];
}
// HANDLE: $(expr, [context]) // HANDLE: $(expr, [context])
// (which is just equivalent to: $(content).find(expr) // (which is just equivalent to: $(content).find(expr)
@ -99,7 +95,7 @@ jQuery.fn = jQuery.prototype = {
selector: "", selector: "",
// The current version of jQuery being used // The current version of jQuery being used
jquery: "1.3", jquery: "1.3.1",
// The number of elements contained in the matched element set // The number of elements contained in the matched element set
size: function() { size: function() {
@ -634,8 +630,8 @@ jQuery.extend({
// check if an element is in a (or is an) XML document // check if an element is in a (or is an) XML document
isXMLDoc: function( elem ) { isXMLDoc: function( elem ) {
return elem.documentElement && !elem.body || return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument );
}, },
// Evalulates a script in a global context // Evalulates a script in a global context
@ -725,7 +721,7 @@ jQuery.extend({
// internal only, use hasClass("class") // internal only, use hasClass("class")
has: function( elem, className ) { has: function( elem, className ) {
return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
} }
}, },
@ -999,7 +995,9 @@ jQuery.extend({
var attributeNode = elem.getAttributeNode( "tabIndex" ); var attributeNode = elem.getAttributeNode( "tabIndex" );
return attributeNode && attributeNode.specified return attributeNode && attributeNode.specified
? attributeNode.value ? attributeNode.value
: elem.nodeName.match(/^(a|area|button|input|object|select|textarea)$/i) : elem.nodeName.match(/(button|input|object|select|textarea)/i)
? 0
: elem.nodeName.match(/^(a|area)$/i) && elem.href
? 0 ? 0
: undefined; : undefined;
} }
@ -1397,14 +1395,14 @@ jQuery.fn.extend({
}); });
} }
});/*! });/*!
* Sizzle CSS Selector Engine - v0.9.1 * Sizzle CSS Selector Engine - v0.9.3
* Copyright 2009, The Dojo Foundation * Copyright 2009, The Dojo Foundation
* Released under the MIT, BSD, and GPL Licenses. * Released under the MIT, BSD, and GPL Licenses.
* More information: http://sizzlejs.com/ * More information: http://sizzlejs.com/
*/ */
(function(){ (function(){
var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|[^[\]]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g, var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]+['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g,
done = 0, done = 0,
toString = Object.prototype.toString; toString = Object.prototype.toString;
@ -1433,40 +1431,27 @@ var Sizzle = function(selector, context, results, seed) {
} }
} }
if ( parts.length > 1 && Expr.match.POS.exec( selector ) ) { if ( parts.length > 1 && origPOS.exec( selector ) ) {
if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
var later = "", match; set = posProcess( parts[0] + parts[1], context );
// Position selectors must be done after the filter
while ( (match = Expr.match.POS.exec( selector )) ) {
later += match[0];
selector = selector.replace( Expr.match.POS, "" );
}
set = Sizzle.filter( later, Sizzle( /\s$/.test(selector) ? selector + "*" : selector, context ) );
} else { } else {
set = Expr.relative[ parts[0] ] ? set = Expr.relative[ parts[0] ] ?
[ context ] : [ context ] :
Sizzle( parts.shift(), context ); Sizzle( parts.shift(), context );
while ( parts.length ) { while ( parts.length ) {
var tmpSet = [];
selector = parts.shift(); selector = parts.shift();
if ( Expr.relative[ selector ] ) if ( Expr.relative[ selector ] )
selector += parts.shift(); selector += parts.shift();
for ( var i = 0, l = set.length; i < l; i++ ) { set = posProcess( selector, set );
Sizzle( selector, set[i], tmpSet );
}
set = tmpSet;
} }
} }
} else { } else {
var ret = seed ? var ret = seed ?
{ expr: parts.pop(), set: makeArray(seed) } : { expr: parts.pop(), set: makeArray(seed) } :
Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context ); Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context, isXML(context) );
set = Sizzle.filter( ret.expr, ret.set ); set = Sizzle.filter( ret.expr, ret.set );
if ( parts.length > 0 ) { if ( parts.length > 0 ) {
@ -1531,7 +1516,7 @@ Sizzle.matches = function(expr, set){
return Sizzle(expr, null, null, set); return Sizzle(expr, null, null, set);
}; };
Sizzle.find = function(expr, context){ Sizzle.find = function(expr, context, isXML){
var set, match; var set, match;
if ( !expr ) { if ( !expr ) {
@ -1546,7 +1531,7 @@ Sizzle.find = function(expr, context){
if ( left.substr( left.length - 1 ) !== "\\" ) { if ( left.substr( left.length - 1 ) !== "\\" ) {
match[1] = (match[1] || "").replace(/\\/g, ""); match[1] = (match[1] || "").replace(/\\/g, "");
set = Expr.find[ type ]( match, context ); set = Expr.find[ type ]( match, context, isXML );
if ( set != null ) { if ( set != null ) {
expr = expr.replace( Expr.match[ type ], "" ); expr = expr.replace( Expr.match[ type ], "" );
break; break;
@ -1568,7 +1553,7 @@ Sizzle.filter = function(expr, set, inplace, not){
while ( expr && set.length ) { while ( expr && set.length ) {
for ( var type in Expr.filter ) { for ( var type in Expr.filter ) {
if ( (match = Expr.match[ type ].exec( expr )) != null ) { if ( (match = Expr.match[ type ].exec( expr )) != null ) {
var filter = Expr.filter[ type ], goodArray = null, goodPos = 0, found, item; var filter = Expr.filter[ type ], found, item;
anyFound = false; anyFound = false;
if ( curLoop == result ) { if ( curLoop == result ) {
@ -1582,26 +1567,13 @@ Sizzle.filter = function(expr, set, inplace, not){
anyFound = found = true; anyFound = found = true;
} else if ( match === true ) { } else if ( match === true ) {
continue; continue;
} else if ( match[0] === true ) {
goodArray = [];
var last = null, elem;
for ( var i = 0; (elem = curLoop[i]) !== undefined; i++ ) {
if ( elem && last !== elem ) {
goodArray.push( elem );
last = elem;
}
}
} }
} }
if ( match ) { if ( match ) {
for ( var i = 0; (item = curLoop[i]) !== undefined; i++ ) { for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
if ( item ) { if ( item ) {
if ( goodArray && item != goodArray[goodPos] ) { found = filter( item, match, i, curLoop );
goodPos++;
}
found = filter( item, match, goodPos, goodArray );
var pass = not ^ !!found; var pass = not ^ !!found;
if ( inplace && found != null ) { if ( inplace && found != null ) {
@ -1739,14 +1711,16 @@ var Expr = Sizzle.selectors = {
} }
}, },
find: { find: {
ID: function(match, context){ ID: function(match, context, isXML){
if ( context.getElementById ) { if ( typeof context.getElementById !== "undefined" && !isXML ) {
var m = context.getElementById(match[1]); var m = context.getElementById(match[1]);
return m ? [m] : []; return m ? [m] : [];
} }
}, },
NAME: function(match, context){ NAME: function(match, context, isXML){
return context.getElementsByName ? context.getElementsByName(match[1]) : null; if ( typeof context.getElementsByName !== "undefined" && !isXML ) {
return context.getElementsByName(match[1]);
}
}, },
TAG: function(match, context){ TAG: function(match, context){
return context.getElementsByTagName(match[1]); return context.getElementsByTagName(match[1]);
@ -1756,14 +1730,17 @@ var Expr = Sizzle.selectors = {
CLASS: function(match, curLoop, inplace, result, not){ CLASS: function(match, curLoop, inplace, result, not){
match = " " + match[1].replace(/\\/g, "") + " "; match = " " + match[1].replace(/\\/g, "") + " ";
for ( var i = 0; curLoop[i]; i++ ) { var elem;
if ( not ^ (" " + curLoop[i].className + " ").indexOf(match) >= 0 ) { for ( var i = 0; (elem = curLoop[i]) != null; i++ ) {
if ( elem ) {
if ( not ^ (" " + elem.className + " ").indexOf(match) >= 0 ) {
if ( !inplace ) if ( !inplace )
result.push( curLoop[i] ); result.push( elem );
} else if ( inplace ) { } else if ( inplace ) {
curLoop[i] = false; curLoop[i] = false;
} }
} }
}
return false; return false;
}, },
@ -1771,8 +1748,8 @@ var Expr = Sizzle.selectors = {
return match[1].replace(/\\/g, ""); return match[1].replace(/\\/g, "");
}, },
TAG: function(match, curLoop){ TAG: function(match, curLoop){
for ( var i = 0; !curLoop[i]; i++ ){} for ( var i = 0; curLoop[i] === false; i++ ){}
return isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
}, },
CHILD: function(match){ CHILD: function(match){
if ( match[1] == "nth" ) { if ( match[1] == "nth" ) {
@ -1792,7 +1769,7 @@ var Expr = Sizzle.selectors = {
return match; return match;
}, },
ATTR: function(match){ ATTR: function(match){
var name = match[1]; var name = match[1].replace(/\\/g, "");
if ( Expr.attrMap[name] ) { if ( Expr.attrMap[name] ) {
match[1] = Expr.attrMap[name]; match[1] = Expr.attrMap[name];
@ -1916,7 +1893,7 @@ var Expr = Sizzle.selectors = {
CHILD: function(elem, match){ CHILD: function(elem, match){
var type = match[1], parent = elem.parentNode; var type = match[1], parent = elem.parentNode;
var doneName = "child" + parent.childNodes.length; var doneName = match[0];
if ( parent && (!parent[ doneName ] || !elem.nodeIndex) ) { if ( parent && (!parent[ doneName ] || !elem.nodeIndex) ) {
var count = 1; var count = 1;
@ -1985,7 +1962,7 @@ var Expr = Sizzle.selectors = {
ATTR: function(elem, match){ ATTR: function(elem, match){
var result = Expr.attrHandle[ match[1] ] ? Expr.attrHandle[ match[1] ]( elem ) : elem[ match[1] ] || elem.getAttribute( match[1] ), value = result + "", type = match[2], check = match[4]; var result = Expr.attrHandle[ match[1] ] ? Expr.attrHandle[ match[1] ]( elem ) : elem[ match[1] ] || elem.getAttribute( match[1] ), value = result + "", type = match[2], check = match[4];
return result == null ? return result == null ?
false : type === "!=" :
type === "=" ? type === "=" ?
value === check : value === check :
type === "*=" ? type === "*=" ?
@ -2014,6 +1991,8 @@ var Expr = Sizzle.selectors = {
} }
}; };
var origPOS = Expr.match.POS;
for ( var type in Expr.match ) { for ( var type in Expr.match ) {
Expr.match[ type ] = RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); Expr.match[ type ] = RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
} }
@ -2072,15 +2051,15 @@ try {
// The workaround has to do additional checks after a getElementById // The workaround has to do additional checks after a getElementById
// Which slows things down for other browsers (hence the branching) // Which slows things down for other browsers (hence the branching)
if ( !!document.getElementById( id ) ) { if ( !!document.getElementById( id ) ) {
Expr.find.ID = function(match, context){ Expr.find.ID = function(match, context, isXML){
if ( context.getElementById ) { if ( typeof context.getElementById !== "undefined" && !isXML ) {
var m = context.getElementById(match[1]); var m = context.getElementById(match[1]);
return m ? m.id === match[1] || m.getAttributeNode && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
} }
}; };
Expr.filter.ID = function(elem, match){ Expr.filter.ID = function(elem, match){
var node = elem.getAttributeNode && elem.getAttributeNode("id"); var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
return elem.nodeType === 1 && node && node.nodeValue === match; return elem.nodeType === 1 && node && node.nodeValue === match;
}; };
} }
@ -2120,7 +2099,7 @@ try {
// Check to see if an attribute returns normalized href attributes // Check to see if an attribute returns normalized href attributes
div.innerHTML = "<a href='#'></a>"; div.innerHTML = "<a href='#'></a>";
if ( div.firstChild.getAttribute("href") !== "#" ) { if ( div.firstChild && div.firstChild.getAttribute("href") !== "#" ) {
Expr.attrHandle.href = function(elem){ Expr.attrHandle.href = function(elem){
return elem.getAttribute("href", 2); return elem.getAttribute("href", 2);
}; };
@ -2128,12 +2107,21 @@ try {
})(); })();
if ( document.querySelectorAll ) (function(){ if ( document.querySelectorAll ) (function(){
var oldSizzle = Sizzle; var oldSizzle = Sizzle, div = document.createElement("div");
div.innerHTML = "<p class='TEST'></p>";
// Safari can't handle uppercase or unicode characters when
// in quirks mode.
if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
return;
}
Sizzle = function(query, context, extra, seed){ Sizzle = function(query, context, extra, seed){
context = context || document; context = context || document;
if ( !seed && context.nodeType === 9 ) { // Only use querySelectorAll on non-XML documents
// (ID selectors don't work in non-HTML documents)
if ( !seed && context.nodeType === 9 && !isXML(context) ) {
try { try {
return makeArray( context.querySelectorAll(query), extra ); return makeArray( context.querySelectorAll(query), extra );
} catch(e){} } catch(e){}
@ -2148,7 +2136,7 @@ if ( document.querySelectorAll ) (function(){
Sizzle.matches = oldSizzle.matches; Sizzle.matches = oldSizzle.matches;
})(); })();
if ( document.documentElement.getElementsByClassName ) { if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) {
Expr.order.splice(1, 0, "CLASS"); Expr.order.splice(1, 0, "CLASS");
Expr.find.CLASS = function(match, context) { Expr.find.CLASS = function(match, context) {
return context.getElementsByClassName(match[1]); return context.getElementsByClassName(match[1]);
@ -2229,8 +2217,28 @@ var contains = document.compareDocumentPosition ? function(a, b){
}; };
var isXML = function(elem){ var isXML = function(elem){
return elem.documentElement && !elem.body || return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; !!elem.ownerDocument && isXML( elem.ownerDocument );
};
var posProcess = function(selector, context){
var tmpSet = [], later = "", match,
root = context.nodeType ? [context] : context;
// Position selectors must be done after the filter
// And so must :not(positional) so we move all PSEUDOs to the end
while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
later += match[0];
selector = selector.replace( Expr.match.PSEUDO, "" );
}
selector = Expr.relative[selector] ? selector + "*" : selector;
for ( var i = 0, l = root.length; i < l; i++ ) {
Sizzle( selector, root[i], tmpSet );
}
return Sizzle.filter( later, tmpSet );
}; };
// EXPOSE // EXPOSE
@ -2681,12 +2689,12 @@ jQuery.Event = function( src ){
if( src && src.type ){ if( src && src.type ){
this.originalEvent = src; this.originalEvent = src;
this.type = src.type; this.type = src.type;
this.timeStamp = src.timeStamp;
// Event type // Event type
}else }else
this.type = src; this.type = src;
if( !this.timeStamp ) // timeStamp is buggy for some events on Firefox(#3843)
// So we won't rely on the native value
this.timeStamp = now(); this.timeStamp = now();
// Mark it as fixed // Mark it as fixed
@ -2876,8 +2884,7 @@ function liveHandler( event ){
}); });
jQuery.each(elems, function(){ jQuery.each(elems, function(){
if ( !event.isImmediatePropagationStopped() && if ( this.fn.call(this.elem, event, this.fn.data) === false )
this.fn.call(this.elem, event, this.fn.data) === false )
stop = false; stop = false;
}); });
@ -2942,7 +2949,7 @@ function bindReady(){
// If IE and not an iframe // If IE and not an iframe
// continually check to see if the document is ready // continually check to see if the document is ready
if ( document.documentElement.doScroll && !window.frameElement ) (function(){ if ( document.documentElement.doScroll && typeof window.frameElement === "undefined" ) (function(){
if ( jQuery.isReady ) return; if ( jQuery.isReady ) return;
try { try {
@ -3477,6 +3484,9 @@ jQuery.extend({
// Fire the complete handlers // Fire the complete handlers
complete(); complete();
if ( isTimeout )
xhr.abort();
// Stop memory leaks // Stop memory leaks
if ( s.async ) if ( s.async )
xhr = null; xhr = null;
@ -3491,14 +3501,8 @@ jQuery.extend({
if ( s.timeout > 0 ) if ( s.timeout > 0 )
setTimeout(function(){ setTimeout(function(){
// Check to see if the request is still happening // Check to see if the request is still happening
if ( xhr ) { if ( xhr && !requestDone )
if( !requestDone )
onreadystatechange( "timeout" ); onreadystatechange( "timeout" );
// Cancel the request
if ( xhr )
xhr.abort();
}
}, s.timeout); }, s.timeout);
} }
@ -3637,6 +3641,7 @@ jQuery.extend({
}); });
var elemdisplay = {}, var elemdisplay = {},
timerId,
fxAttrs = [ fxAttrs = [
// height animations // height animations
[ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
@ -3859,7 +3864,6 @@ jQuery.extend({
}, },
timers: [], timers: [],
timerId: null,
fx: function( elem, options, prop ){ fx: function( elem, options, prop ){
this.options = options; this.options = options;
@ -3911,10 +3915,8 @@ jQuery.fx.prototype = {
t.elem = this.elem; t.elem = this.elem;
jQuery.timers.push(t); if ( t() && jQuery.timers.push(t) == 1 ) {
timerId = setInterval(function(){
if ( t() && jQuery.timerId == null ) {
jQuery.timerId = setInterval(function(){
var timers = jQuery.timers; var timers = jQuery.timers;
for ( var i = 0; i < timers.length; i++ ) for ( var i = 0; i < timers.length; i++ )
@ -3922,8 +3924,7 @@ jQuery.fx.prototype = {
timers.splice(i--, 1); timers.splice(i--, 1);
if ( !timers.length ) { if ( !timers.length ) {
clearInterval( jQuery.timerId ); clearInterval( timerId );
jQuery.timerId = null;
} }
}, 13); }, 13);
} }
@ -3989,11 +3990,10 @@ jQuery.fx.prototype = {
if ( this.options.hide || this.options.show ) if ( this.options.hide || this.options.show )
for ( var p in this.options.curAnim ) for ( var p in this.options.curAnim )
jQuery.attr(this.elem.style, p, this.options.orig[p]); jQuery.attr(this.elem.style, p, this.options.orig[p]);
}
if ( done )
// Execute the complete function // Execute the complete function
this.options.complete.call( this.elem ); this.options.complete.call( this.elem );
}
return false; return false;
} else { } else {
@ -4087,7 +4087,7 @@ jQuery.offset = {
initialize: function() { initialize: function() {
if ( this.initialized ) return; if ( this.initialized ) return;
var body = document.body, container = document.createElement('div'), innerDiv, checkDiv, table, td, rules, prop, bodyMarginTop = body.style.marginTop, var body = document.body, container = document.createElement('div'), innerDiv, checkDiv, table, td, rules, prop, bodyMarginTop = body.style.marginTop,
html = '<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"cellpadding="0"cellspacing="0"><tr><td></td></tr></table>'; html = '<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';
rules = { position: 'absolute', top: 0, left: 0, margin: 0, border: 0, width: '1px', height: '1px', visibility: 'hidden' }; rules = { position: 'absolute', top: 0, left: 0, margin: 0, border: 0, width: '1px', height: '1px', visibility: 'hidden' };
for ( prop in rules ) container.style[prop] = rules[prop]; for ( prop in rules ) container.style[prop] = rules[prop];

12
js/jquery.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -179,18 +179,27 @@ class Action extends HTMLOutputter // lawsuit
*/ */
function showScripts() function showScripts()
{ {
if (Event::handle('StartShowScripts', array($this))) {
if (Event::handle('StartShowJQueryScripts', array($this))) {
$this->element('script', array('type' => 'text/javascript', $this->element('script', array('type' => 'text/javascript',
'src' => common_path('js/jquery.min.js')), 'src' => common_path('js/jquery.min.js')),
' '); ' ');
$this->element('script', array('type' => 'text/javascript', $this->element('script', array('type' => 'text/javascript',
'src' => common_path('js/jquery.form.js')), 'src' => common_path('js/jquery.form.js')),
' '); ' ');
Event::handle('EndShowJQueryScripts', array($this));
}
if (Event::handle('StartShowLaconicaScripts', array($this))) {
$this->element('script', array('type' => 'text/javascript', $this->element('script', array('type' => 'text/javascript',
'src' => common_path('js/xbImportNode.js')), 'src' => common_path('js/xbImportNode.js')),
' '); ' ');
$this->element('script', array('type' => 'text/javascript', $this->element('script', array('type' => 'text/javascript',
'src' => common_path('js/util.js?version='.LACONICA_VERSION)), 'src' => common_path('js/util.js?version='.LACONICA_VERSION)),
' '); ' ');
Event::handle('EndShowLaconicaScripts', array($this));
}
Event::handle('EndShowScripts', array($this));
}
} }
/** /**
@ -312,11 +321,13 @@ class Action extends HTMLOutputter // lawsuit
*/ */
function showPrimaryNav() function showPrimaryNav()
{ {
$user = common_current_user();
$this->elementStart('dl', array('id' => 'site_nav_global_primary')); $this->elementStart('dl', array('id' => 'site_nav_global_primary'));
$this->element('dt', null, _('Primary site navigation')); $this->element('dt', null, _('Primary site navigation'));
$this->elementStart('dd'); $this->elementStart('dd');
$user = common_current_user();
$this->elementStart('ul', array('class' => 'nav')); $this->elementStart('ul', array('class' => 'nav'));
if (Event::handle('StartPrimaryNav', array($this))) {
if ($user) { if ($user) {
$this->menuItem(common_local_url('all', array('nickname' => $user->nickname)), $this->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
_('Home'), _('Personal profile and friends timeline'), false, 'nav_home'); _('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
@ -348,6 +359,8 @@ class Action extends HTMLOutputter // lawsuit
} }
$this->menuItem(common_local_url('doc', array('title' => 'help')), $this->menuItem(common_local_url('doc', array('title' => 'help')),
_('Help'), _('Help me!'), false, 'nav_help'); _('Help'), _('Help me!'), false, 'nav_help');
Event::handle('EndPrimaryNav', array($this));
}
$this->elementEnd('ul'); $this->elementEnd('ul');
$this->elementEnd('dd'); $this->elementEnd('dd');
$this->elementEnd('dl'); $this->elementEnd('dl');
@ -511,12 +524,16 @@ class Action extends HTMLOutputter // lawsuit
* *
* @return nothing * @return nothing
*/ */
function showAside() function showAside()
{ {
$this->elementStart('div', array('id' => 'aside_primary', $this->elementStart('div', array('id' => 'aside_primary',
'class' => 'aside')); 'class' => 'aside'));
$this->showExportData(); $this->showExportData();
if (Event::handle('StartShowSections', array($this))) {
$this->showSections(); $this->showSections();
Event::handle('EndShowSections', array($this));
}
$this->elementEnd('div'); $this->elementEnd('div');
} }
@ -570,6 +587,7 @@ class Action extends HTMLOutputter // lawsuit
$this->element('dt', null, _('Secondary site navigation')); $this->element('dt', null, _('Secondary site navigation'));
$this->elementStart('dd', null); $this->elementStart('dd', null);
$this->elementStart('ul', array('class' => 'nav')); $this->elementStart('ul', array('class' => 'nav'));
if (Event::handle('StartSecondaryNav', array($this))) {
$this->menuItem(common_local_url('doc', array('title' => 'help')), $this->menuItem(common_local_url('doc', array('title' => 'help')),
_('Help')); _('Help'));
$this->menuItem(common_local_url('doc', array('title' => 'about')), $this->menuItem(common_local_url('doc', array('title' => 'about')),
@ -582,6 +600,8 @@ class Action extends HTMLOutputter // lawsuit
_('Source')); _('Source'));
$this->menuItem(common_local_url('doc', array('title' => 'contact')), $this->menuItem(common_local_url('doc', array('title' => 'contact')),
_('Contact')); _('Contact'));
Event::handle('EndSecondaryNav', array($this));
}
$this->elementEnd('ul'); $this->elementEnd('ul');
$this->elementEnd('dd'); $this->elementEnd('dd');
$this->elementEnd('dl'); $this->elementEnd('dl');
@ -789,11 +809,12 @@ class Action extends HTMLOutputter // lawsuit
* *
* @return nothing * @return nothing
*/ */
function serverError($msg, $code=500) function serverError($msg, $code=500)
{ {
$action = $this->trimmed('action'); $action = $this->trimmed('action');
common_debug("Server error '$code' on '$action': $msg", __FILE__); common_debug("Server error '$code' on '$action': $msg", __FILE__);
common_server_error($msg, $code); throw new ServerException($msg, $code);
} }
/** /**
@ -804,11 +825,12 @@ class Action extends HTMLOutputter // lawsuit
* *
* @return nothing * @return nothing
*/ */
function clientError($msg, $code=400) function clientError($msg, $code=400)
{ {
$action = $this->trimmed('action'); $action = $this->trimmed('action');
common_debug("User error '$code' on '$action': $msg", __FILE__); common_debug("User error '$code' on '$action': $msg", __FILE__);
common_user_error($msg, $code); throw new ClientException($msg, $code);
} }
/** /**

56
lib/clientexception.php Normal file
View File

@ -0,0 +1,56 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* class for a client exception (user error)
*
* 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 Exception
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @copyright 2008 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) {
exit(1);
}
/**
* Class for client exceptions
*
* Subclass of PHP Exception for user errors.
*
* @category Exception
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
class ClientException extends Exception
{
public function __construct($message = null, $code = 400) {
parent::__construct($message, $code);
}
// custom string representation of object
public function __toString() {
return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
}
}

View File

@ -49,6 +49,12 @@ require_once('DB/DataObject/Cast.php'); # for dates
require_once(INSTALLDIR.'/lib/language.php'); require_once(INSTALLDIR.'/lib/language.php');
// This gets included before the config file, so that admin code and plugins
// can use it
require_once(INSTALLDIR.'/lib/event.php');
require_once(INSTALLDIR.'/lib/plugin.php');
// try to figure out where we are // try to figure out where we are
$_server = array_key_exists('SERVER_NAME', $_SERVER) ? $_server = array_key_exists('SERVER_NAME', $_SERVER) ?
@ -177,6 +183,8 @@ foreach ($_config_files as $_config_file) {
} }
} }
// XXX: how many of these could be auto-loaded on use?
require_once('Validate.php'); require_once('Validate.php');
require_once('markdown.php'); require_once('markdown.php');
@ -188,6 +196,9 @@ require_once(INSTALLDIR.'/lib/subs.php');
require_once(INSTALLDIR.'/lib/Shorturl_api.php'); require_once(INSTALLDIR.'/lib/Shorturl_api.php');
require_once(INSTALLDIR.'/lib/twitter.php'); require_once(INSTALLDIR.'/lib/twitter.php');
require_once(INSTALLDIR.'/lib/clientexception.php');
require_once(INSTALLDIR.'/lib/serverexception.php');
// XXX: other formats here // XXX: other formats here
define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER); define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER);
@ -200,5 +211,12 @@ function __autoload($class)
require_once(INSTALLDIR.'/classes/' . $class . '.php'); require_once(INSTALLDIR.'/classes/' . $class . '.php');
} else if (file_exists(INSTALLDIR.'/lib/' . strtolower($class) . '.php')) { } else if (file_exists(INSTALLDIR.'/lib/' . strtolower($class) . '.php')) {
require_once(INSTALLDIR.'/lib/' . strtolower($class) . '.php'); require_once(INSTALLDIR.'/lib/' . strtolower($class) . '.php');
} else if (mb_substr($class, -6) == 'Action' &&
file_exists(INSTALLDIR.'/actions/' . strtolower(mb_substr($class, 0, -6)) . '.php')) {
require_once(INSTALLDIR.'/actions/' . strtolower(mb_substr($class, 0, -6)) . '.php');
} }
} }
// Give plugins a chance to initialize in a fully-prepared environment
Event::handle('InitializePlugin');

113
lib/event.php Normal file
View File

@ -0,0 +1,113 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* utilities for defining and running event handlers
*
* 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 Event
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @copyright 2008 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) {
exit(1);
}
/**
* Class for events
*
* This "class" two static functions for managing events in the Laconica code.
*
* @category Event
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*
* @todo Define a system for using Event instances
*/
class Event {
/* Global array of hooks, mapping eventname => array of callables */
protected static $_handlers = array();
/**
* Add an event handler
*
* To run some code at a particular point in Laconica processing.
* Named events include receiving an XMPP message, adding a new notice,
* or showing part of an HTML page.
*
* The arguments to the handler vary by the event. Handlers can return
* two possible values: false means that the event has been replaced by
* the handler completely, and no default processing should be done.
* Non-false means successful handling, and that the default processing
* should succeed. (Note that this only makes sense for some events.)
*
* Handlers can also abort processing by throwing an exception; these will
* be caught by the closest code and displayed as errors.
*
* @param string $name Name of the event
* @param callable $handler Code to run
*
* @return void
*/
public static function addHandler($name, $handler) {
if (array_key_exists($name, Event::$_handlers)) {
Event::$_handlers[$name][] = $handler;
} else {
Event::$_handlers[$name] = array($handler);
}
}
/**
* Handle an event
*
* Events are any point in the code that we want to expose for admins
* or third-party developers to use.
*
* We pass in an array of arguments (including references, for stuff
* that can be changed), and each assigned handler gets run with those
* arguments. Exceptions can be thrown to indicate an error.
*
* @param string $name Name of the event that's happening
* @param array $args Arguments for handlers
*
* @return boolean flag saying whether to continue processing, based
* on results of handlers.
*/
public static function handle($name, $args=array()) {
$result = null;
if (array_key_exists($name, Event::$_handlers)) {
foreach (Event::$_handlers[$name] as $handler) {
$result = call_user_func_array($handler, $args);
if ($result === false) {
break;
}
}
}
return ($result !== false);
}
}

View File

@ -255,7 +255,7 @@ class HTMLOutputter extends XMLOutputter
foreach ($content as $value => $option) { foreach ($content as $value => $option) {
if ($value == $selected) { if ($value == $selected) {
$this->element('option', array('value' => $value, $this->element('option', array('value' => $value,
'selected' => $value), 'selected' => 'selected'),
$option); $option);
} else { } else {
$this->element('option', array('value' => $value), $option); $this->element('option', array('value' => $value), $option);

79
lib/plugin.php Normal file
View File

@ -0,0 +1,79 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* Utility class for plugins
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @copyright 2008 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) {
exit(1);
}
/**
* Base class for plugins
*
* A base class for Laconica plugins. Mostly a light wrapper around
* the Event framework.
*
* Subclasses of Plugin will automatically handle an event if they define
* a method called "onEventName". (Well, OK -- only if they call parent::__construct()
* in their constructors.)
*
* They will also automatically handle the InitializePlugin and CleanupPlugin with the
* initialize() and cleanup() methods, respectively.
*
* @category Plugin
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*
* @see Event
*/
class Plugin
{
function __construct()
{
Event::addHandler('InitializePlugin', array($this, 'initialize'));
Event::addHandler('CleanupPlugin', array($this, 'cleanup'));
foreach (get_class_methods($this) as $method) {
if (mb_substr($method, 0, 2) == 'on') {
Event::addHandler(mb_substr($method, 2), array($this, $method));
}
}
}
function initialize()
{
return true;
}
function cleanup()
{
return true;
}
}

55
lib/serverexception.php Normal file
View File

@ -0,0 +1,55 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* class for a server exception (user error)
*
* 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 Exception
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @copyright 2008 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) {
exit(1);
}
/**
* Class for server exceptions
*
* Subclass of PHP Exception for server errors. The user typically can't fix these.
*
* @category Exception
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
class ServerException extends Exception
{
public function __construct($message = null, $code = 400) {
parent::__construct($message, $code);
}
public function __toString() {
return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
}
}

View File

@ -481,7 +481,7 @@ function common_linkify($url) {
} }
else $title = ''; else $title = '';
return "<a href=\"$url\" $title class=\"extlink\">$display</a>"; return "<a href=\"$url\" $title rel=\"external\">$display</a>";
} }
function common_longurl($short_url) function common_longurl($short_url)

View File

@ -0,0 +1,77 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* Plugin to use Google Analytics
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @copyright 2008 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) {
exit(1);
}
/**
* Plugin to use Google Analytics
*
* This plugin will spoot out the correct JavaScript spell to invoke Google Analytics on a page.
*
* Note that Google Analytics is not compatible with the Franklin Street Statement; consider using
* Pikiw (http://www.pikiw.org/) instead!
*
* @category Plugin
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*
* @see Event
*/
class GoogleAnalyticsPlugin extends Plugin
{
var $code = null;
function __construct($code=null)
{
$this->code = $code;
parent::__construct();
}
function onEndShowScripts($action)
{
$js1 = 'var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");'.
'document.write(unescape("%3Cscript src=\'" + gaJsHost + "google-analytics.com/ga.js\' type=\'text/javascript\'%3E%3C/script%3E"));';
$js2 = sprintf('try{'.
'var pageTracker = _gat._getTracker("%s");'.
'pageTracker._trackPageview();'.
'} catch(err) {}',
$this->code);
$action->elementStart('script', array('type' => 'text/javascript'));
$action->raw($js1);
$action->elementEnd('script');
$action->elementStart('script', array('type' => 'text/javascript'));
$action->raw($js2);
$action->elementEnd('script');
}
}

View File

@ -34,22 +34,19 @@ require_once INSTALLDIR . '/lib/facebookutil.php';
$last_updated_file = INSTALLDIR . '/scripts/facebook_last_updated'; $last_updated_file = INSTALLDIR . '/scripts/facebook_last_updated';
// Lock file name // Lock file name
$tmp_file = INSTALLDIR . '/scripts/update_facebook.lock'; $lock_file = INSTALLDIR . '/scripts/update_facebook.lock';
// Make sure only one copy of the script is running at a time // Make sure only one copy of the script is running at a time
if (!($tmp_file = @fopen($tmp_file, "w"))) $lock_file = @fopen($lock_file, "w+");
{ if (!flock( $lock_file, LOCK_EX | LOCK_NB, &$wouldblock) || $wouldblock) {
die("Can't open lock file. Script already running?"); die("Can't open lock file. Script already running?\n");
} }
$facebook = getFacebook(); $facebook = getFacebook();
$current_time = time(); $current_time = time();
$since = getLastUpdated(); $since = getLastUpdated();
updateLastUpdated($current_time);
$notice = getFacebookNotices($since); $notice = getFacebookNotices($since);
$cnt = 0; $cnt = 0;
while($notice->fetch()) { while($notice->fetch()) {
@ -73,9 +70,18 @@ while($notice->fetch()) {
// Avoid a Loop // Avoid a Loop
if ($notice->source != 'Facebook') { if ($notice->source != 'Facebook') {
updateStatus($fbuid, $content);
try {
$facebook->api_client->users_setStatus($content,
$fbuid, false, true);
updateProfileBox($facebook, $flink, $notice); updateProfileBox($facebook, $flink, $notice);
$cnt++; $cnt++;
} catch(FacebookRestClientException $e) {
print "Couldn't sent notice $notice->id!\n";
print $e->getMessage();
// Remove flink?
}
} }
} }
} }
@ -83,16 +89,11 @@ while($notice->fetch()) {
if ($cnt > 0) { if ($cnt > 0) {
print date('r', $current_time) . print date('r', $current_time) .
": Found $cnt new notices to send to Facebook since last run at " . ": Found $cnt new notices for Facebook since last run at " .
date('Y-m-d H:i:s', $since) . "\n"; date('r', $since) . "\n";
} }
#Save the last updated time. It needs to do this even if there were no fclose($lock_file);
#changes made, otherwise it will never create it and thus never send
#any updates at all.
updateLastUpdated($current_time);
exit(0); exit(0);
@ -111,37 +112,30 @@ function userCanUpdate($fbuid) {
return $result; return $result;
} }
function updateStatus($fbuid, $content) {
global $facebook;
try {
$result = $facebook->api_client->users_setStatus($content, $fbuid, false, true);
} catch(FacebookRestClientException $e){
print_r($e);
}
}
function getLastUpdated(){ function getLastUpdated(){
global $last_updated_file, $current_time; global $last_updated_file, $current_time;
$last = $current_time;
$file = fopen($last_updated_file, 'r'); if (file_exists($last_updated_file) &&
($file = fopen($last_updated_file, 'r'))) {
if ($file) {
$last = fgets($file); $last = fgets($file);
} else { } else {
print "Unable to read $last_updated_file. Using current time.\n"; print "$last_updated_file doesn't exit. Trying to create it...\n";
return $current_time; $file = fopen($last_updated_file, 'w+') or
die("Can't open $last_updated_file for writing!\n");
print 'Success. Using current time (' . date('r', $last) .
") to look for new notices.\n";
} }
fclose($file); fclose($file);
return $last; return $last;
} }
function updateLastUpdated($time){ function updateLastUpdated($time){
global $last_updated_file; global $last_updated_file;
$file = fopen($last_updated_file, 'w') or die("Can't open $last_updated_file for writing!"); $file = fopen($last_updated_file, 'w') or
die("Can't open $last_updated_file for writing!");
fwrite($file, $time); fwrite($file, $time);
fclose($file); fclose($file);
} }