From 32f81b3c0e7be3da35d5813e5409fa3b65dc220e Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 24 Jan 2009 18:38:12 +0100 Subject: [PATCH 001/189] Initial support for ping service It makes sense to use the weblogs.com ping service to alert people to changes on the site. So, we do. Includes an extra ping queue handler. --- lib/common.php | 2 + lib/ping.php | 79 ++++++++++++++++++++++++++++++++++++ lib/util.php | 2 +- scripts/pingqueuehandler.php | 64 +++++++++++++++++++++++++++++ 4 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 lib/ping.php create mode 100644 scripts/pingqueuehandler.php diff --git a/lib/common.php b/lib/common.php index a2f9b9bfe7..cc82f8bd73 100644 --- a/lib/common.php +++ b/lib/common.php @@ -128,6 +128,8 @@ $config = array('enabled' => false, 'server' => 'localhost', 'port' => 11211), + 'ping' => + array('notify' => array()), 'inboxes' => array('enabled' => true), # on by default for new sites ); diff --git a/lib/ping.php b/lib/ping.php new file mode 100644 index 0000000000..32c0b9806a --- /dev/null +++ b/lib/ping.php @@ -0,0 +1,79 @@ +. + */ + +if (!defined('LACONICA')) { exit(1); } + +function ping_broadcast_notice($notice) { + if (!$notice->is_local) { + return; + } + + # Array of servers, URL => type + $notify = common_config('ping', 'notify'); + $profile = $notice->getProfile(); + $tags = ping_notice_tags($notice); + + foreach ($notify as $notify_url => $type) { + switch ($type) { + case 'xmlrpc': + case 'extended': + $req = xmlrpc_encode_request('weblogUpdates.ping', + array($profile->nickname, # site name + common_local_url('showstream', + array('nickname' => $profile->nickname)), + common_local_url('shownotice', + array('notice' => $notice->id)), + common_local_url('userrss', + array('nickname' => $profile->nickname)), + $tags)); + + # We re-use this tool's fetcher, since it's pretty good + + $fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); + + if (!$fetcher) { + common_log(LOG_WARNING, 'Failed to initialize Yadis fetcher.', __FILE__); + return false; + } + + $result = $fetcher->post($notify_url, + $req); + + case 'get': + case 'post': + default: + common_log(LOG_WARNING, 'Unknown notify type for ' . $notify_url . ': ' . $type); + } + } +} + +function ping_notice_tags($notice) { + $tag = new Notice_tag(); + $tag->notice_id = $notice->id; + $tags = array(); + if ($tag->find()) { + while ($tag->fetch()) { + $tags[] = $tag->tag; + } + $tag->free(); + unset($tag); + return implode('|', $tags); + } + return NULL; +} \ No newline at end of file diff --git a/lib/util.php b/lib/util.php index b5b194519e..419e21e828 100644 --- a/lib/util.php +++ b/lib/util.php @@ -1124,7 +1124,7 @@ function common_twitter_broadcast($notice, $flink) function common_enqueue_notice($notice) { - foreach (array('jabber', 'omb', 'sms', 'public') as $transport) { + foreach (array('jabber', 'omb', 'sms', 'public', 'ping') as $transport) { $qi = new Queue_item(); $qi->notice_id = $notice->id; $qi->transport = $transport; diff --git a/scripts/pingqueuehandler.php b/scripts/pingqueuehandler.php new file mode 100644 index 0000000000..55a266e4a4 --- /dev/null +++ b/scripts/pingqueuehandler.php @@ -0,0 +1,64 @@ +#!/usr/bin/env php +. + */ + +# Abort if called from a web server +if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { + print "This script must be run from the command line\n"; + exit(); +} + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); +define('LACONICA', true); + +require_once(INSTALLDIR . '/lib/common.php'); +require_once(INSTALLDIR . '/lib/ping.php'); +require_once(INSTALLDIR . '/lib/queuehandler.php'); + +set_error_handler('common_error_handler'); + +class PingQueueHandler extends QueueHandler { + + function transport() { + return 'ping'; + } + + function start() { + $this->log(LOG_INFO, "INITIALIZE"); + return true; + } + + function handle_notice($notice) { + return ping_broadcast_notice($notice); + } + + function finish() { + } +} + +ini_set("max_execution_time", "0"); +ini_set("max_input_time", "0"); +set_time_limit(0); +mb_internal_encoding('UTF-8'); + +$id = ($argc > 1) ? $argv[1] : NULL; + +$handler = new PingQueueHandler($id); + +$handler->runOnce(); From d6245dca6347a670445701e9c726b0719ea945c8 Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Fri, 6 Feb 2009 18:36:34 -0500 Subject: [PATCH 002/189] Fixed #1048: Edit avatar link on profile page. --- actions/showstream.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/actions/showstream.php b/actions/showstream.php index eab1fc0a2d..3b4c97865b 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -245,6 +245,15 @@ class ShowstreamAction extends Action 'height' => AVATAR_PROFILE_SIZE, 'alt' => $this->profile->nickname)); $this->elementEnd('dd'); + + $user = User::staticGet('id', $this->profile->id); + $cur = common_current_user(); + if ($cur && $cur->id == $user->id) { + $this->elementStart('dd'); + $this->element('a', array('href' => common_local_url('avatarsettings')), _('Edit Avatar')); + $this->elementEnd('dd'); + } + $this->elementEnd('dl'); $this->elementStart('dl', 'entity_nickname'); @@ -317,7 +326,6 @@ class ShowstreamAction extends Action $this->element('h2', null, _('User actions')); $this->elementStart('ul'); $this->elementStart('li', array('class' => 'entity_subscribe')); - $cur = common_current_user(); if ($cur) { if ($cur->id != $this->profile->id) { if ($cur->isSubscribed($this->profile)) { @@ -335,7 +343,6 @@ class ShowstreamAction extends Action // common_profile_new_message_nudge($cur, $this->user, $this->profile); - $user = User::staticGet('id', $this->profile->id); if ($cur && $cur->id != $user->id && $cur->mutuallySubscribed($user)) { $this->elementStart('li', array('class' => 'entity_send-a-message')); $this->element('a', array('href' => common_local_url('newmessage', array('to' => $user->id)), From d90089314944ed1696f66cabbb6935ea61e4b2e6 Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Sat, 7 Feb 2009 10:01:08 -0500 Subject: [PATCH 003/189] Fixed #1152: Needless image scaling and poor JPG quality --- lib/imagefile.php | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/imagefile.php b/lib/imagefile.php index 74c3d14f03..faec7f0ff4 100644 --- a/lib/imagefile.php +++ b/lib/imagefile.php @@ -112,6 +112,23 @@ class ImageFile throw new Exception(_('Lost our file.')); return; } + + // Don't crop/scale if it isn't necessary + if ($size === $this->width + && $size === $this->height + && $x === 0 + && $y === 0 + && $w === $this->width + && $h === $this->height) { + + $outname = common_avatar_filename($this->id, + image_type_to_extension($this->type), + $size, + common_timestamp()); + $outpath = common_avatar_path($outname); + @copy($this->filepath, $outpath); + return $outname; + } switch ($this->type) { case IMAGETYPE_GIF: @@ -165,7 +182,7 @@ class ImageFile imagegif($image_dest, $outpath); break; case IMAGETYPE_JPEG: - imagejpeg($image_dest, $outpath); + imagejpeg($image_dest, $outpath, 100); break; case IMAGETYPE_PNG: imagepng($image_dest, $outpath); @@ -174,6 +191,9 @@ class ImageFile throw new Exception(_('Unknown file type')); return; } + + imagedestroy($image_src); + imagedestroy($image_dest); return $outname; } From 805560677bc66a58c270551fa54b613642b3af97 Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Sat, 7 Feb 2009 11:10:46 -0500 Subject: [PATCH 004/189] Fixed references to common_avatar_*. --- lib/imagefile.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/imagefile.php b/lib/imagefile.php index a8e9633708..ea24029a30 100644 --- a/lib/imagefile.php +++ b/lib/imagefile.php @@ -121,11 +121,11 @@ class ImageFile && $w === $this->width && $h === $this->height) { - $outname = common_avatar_filename($this->id, + $outname = Avatar::filename($this->id, image_type_to_extension($this->type), $size, common_timestamp()); - $outpath = common_avatar_path($outname); + $outpath = Avatar::path($outname); @copy($this->filepath, $outpath); return $outname; } From 43888b523919f816e45a956a2e9f7d10416067df Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Mon, 9 Feb 2009 15:35:38 +0000 Subject: [PATCH 005/189] trac #1160 fix dropdown xmloutput function for the selected attribute and fix newmessage auto-selected dropdown. --- actions/newmessage.php | 2 +- lib/htmloutputter.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/newmessage.php b/actions/newmessage.php index f83015a372..82276ff341 100644 --- a/actions/newmessage.php +++ b/actions/newmessage.php @@ -201,7 +201,7 @@ class NewmessageAction extends Action function showNoticeForm() { - $message_form = new MessageForm($this, $this->to, $this->content); + $message_form = new MessageForm($this, $this->other, $this->content); $message_form->show(); } } diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index 7780b1c19d..e2319b1fdc 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -255,7 +255,7 @@ class HTMLOutputter extends XMLOutputter foreach ($content as $value => $option) { if ($value == $selected) { $this->element('option', array('value' => $value, - 'selected' => $value), + 'selected' => 'selected'), $option); } else { $this->element('option', array('value' => $value), $option); From 9e23b5c5d706f4573261d6530688a44a8b80bcf4 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 11:46:26 -0500 Subject: [PATCH 006/189] Change action autoloading to allow actions in plugins Since plugins may define custom actions, we shouldn't require that there be a file in our actions/ subdir for every action. So, I changed the (admittedly hackish) auto-loading code in index.php so it instead checks whether a class exists with the expected name. This, in turn, uses the increasingly hacking __autoload() function, which I changed to auto-load stuff named "BlahblahAction" from the actions subdir if available. --- index.php | 13 ++++++------- lib/common.php | 3 +++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/index.php b/index.php index 0a79b9731d..e62d9469ab 100644 --- a/index.php +++ b/index.php @@ -22,6 +22,8 @@ define('LACONICA', true); require_once INSTALLDIR . '/lib/common.php'; +// XXX: we need a little more structure in this script + // get and cache current user $user = common_current_user(); @@ -45,19 +47,16 @@ if (!$user && common_config('site', 'private') && common_redirect(common_local_url('login')); } -$actionfile = INSTALLDIR."/actions/$action.php"; +$action_class = ucfirst($action).'Action'; -if (!file_exists($actionfile)) { +if (!class_exists($action_class)) { $cac = new ClientErrorAction(_('Unknown action'), 404); $cac->showPage(); } else { - - include_once $actionfile; - - $action_class = ucfirst($action).'Action'; - $action_obj = new $action_class(); + // XXX: find somewhere for this little block to live + if ($config['db']['mirror'] && $action_obj->isReadOnly()) { if (is_array($config['db']['mirror'])) { // "load balancing", ha ha diff --git a/lib/common.php b/lib/common.php index 041459cf34..7bfd14c429 100644 --- a/lib/common.php +++ b/lib/common.php @@ -211,6 +211,9 @@ function __autoload($class) require_once(INSTALLDIR.'/classes/' . $class . '.php'); } else if (file_exists(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'); } } From c1bc77efd968aecd465fb6173d20bd386016eae2 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 12:06:06 -0500 Subject: [PATCH 007/189] whitespace and formatting --- lib/imagefile.php | 48 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/imagefile.php b/lib/imagefile.php index ea24029a30..0c93b257ed 100644 --- a/lib/imagefile.php +++ b/lib/imagefile.php @@ -68,17 +68,17 @@ class ImageFile static function fromUpload($param='upload') { switch ($_FILES[$param]['error']) { - case UPLOAD_ERR_OK: // success, jump out + case UPLOAD_ERR_OK: // success, jump out break; - case UPLOAD_ERR_INI_SIZE: - case UPLOAD_ERR_FORM_SIZE: + case UPLOAD_ERR_INI_SIZE: + case UPLOAD_ERR_FORM_SIZE: throw new Exception(sprintf(_('That file is too big. The maximum file size is %d.'), $this->maxFileSize())); return; - case UPLOAD_ERR_PARTIAL: + case UPLOAD_ERR_PARTIAL: @unlink($_FILES[$param]['tmp_name']); throw new Exception(_('Partial upload.')); return; - default: + default: throw new Exception(_('System error uploading file.')); return; } @@ -112,19 +112,19 @@ class ImageFile throw new Exception(_('Lost our file.')); return; } - + // Don't crop/scale if it isn't necessary - if ($size === $this->width - && $size === $this->height - && $x === 0 - && $y === 0 + if ($size === $this->width + && $size === $this->height + && $x === 0 + && $y === 0 && $w === $this->width && $h === $this->height) { - + $outname = Avatar::filename($this->id, - image_type_to_extension($this->type), - $size, - common_timestamp()); + image_type_to_extension($this->type), + $size, + common_timestamp()); $outpath = Avatar::path($outname); @copy($this->filepath, $outpath); return $outname; @@ -171,9 +171,9 @@ class ImageFile imagecopyresampled($image_dest, $image_src, 0, 0, $x, $y, $size, $size, $w, $h); $outname = Avatar::filename($this->id, - image_type_to_extension($this->type), - $size, - common_timestamp()); + image_type_to_extension($this->type), + $size, + common_timestamp()); $outpath = Avatar::path($outname); @@ -191,7 +191,7 @@ class ImageFile throw new Exception(_('Unknown file type')); return; } - + imagedestroy($image_src); imagedestroy($image_dest); @@ -229,12 +229,12 @@ class ImageFile $num = substr($str, 0, -1); switch(strtoupper($unit)){ - case 'G': - $num *= 1024; - case 'M': - $num *= 1024; - case 'K': - $num *= 1024; + case 'G': + $num *= 1024; + case 'M': + $num *= 1024; + case 'K': + $num *= 1024; } return $num; From 9b7c57d094ef5fc52a8a2c4ac6b7a89b1c221c54 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 12:07:21 -0500 Subject: [PATCH 008/189] whitespace and formatting in showstream.php --- actions/showstream.php | 44 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/actions/showstream.php b/actions/showstream.php index 6bed700c2d..962f4b4524 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -111,7 +111,7 @@ class ShowstreamAction extends Action $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; common_set_returnto($this->selfUrl()); - + return true; } @@ -178,21 +178,21 @@ class ShowstreamAction extends Action function showFeeds() { $this->element('link', array('rel' => 'alternate', - 'type' => 'application/rss+xml', - 'href' => common_local_url('userrss', - array('nickname' => $this->user->nickname)), - 'title' => sprintf(_('Notice feed for %s (RSS)'), - $this->user->nickname))); + 'type' => 'application/rss+xml', + 'href' => common_local_url('userrss', + array('nickname' => $this->user->nickname)), + 'title' => sprintf(_('Notice feed for %s (RSS)'), + $this->user->nickname))); - $this->element('link', - array('rel' => 'alternate', - 'href' => common_local_url('api', - array('apiaction' => 'statuses', - 'method' => 'user_timeline.atom', - 'argument' => $this->user->nickname)), - 'type' => 'application/atom+xml', - 'title' => sprintf(_('Notice feed for %s (Atom)'), - $this->user->nickname))); + $this->element('link', + array('rel' => 'alternate', + 'href' => common_local_url('api', + array('apiaction' => 'statuses', + 'method' => 'user_timeline.atom', + 'argument' => $this->user->nickname)), + 'type' => 'application/atom+xml', + 'title' => sprintf(_('Notice feed for %s (Atom)'), + $this->user->nickname))); } function extraHead() @@ -206,7 +206,7 @@ class ShowstreamAction extends Action // for remote subscriptions etc. $this->element('meta', array('http-equiv' => 'X-XRDS-Location', 'content' => common_local_url('xrds', array('nickname' => - $this->user->nickname)))); + $this->user->nickname)))); if ($this->profile->bio) { $this->element('meta', array('name' => 'description', @@ -248,7 +248,7 @@ class ShowstreamAction extends Action 'height' => AVATAR_PROFILE_SIZE, 'alt' => $this->profile->nickname)); $this->elementEnd('dd'); - + $user = User::staticGet('id', $this->profile->id); $cur = common_current_user(); if ($cur && $cur->id == $user->id) { @@ -256,7 +256,7 @@ class ShowstreamAction extends Action $this->element('a', array('href' => common_local_url('avatarsettings')), _('Edit Avatar')); $this->elementEnd('dd'); } - + $this->elementEnd('dl'); $this->elementStart('dl', 'entity_nickname'); @@ -265,7 +265,7 @@ class ShowstreamAction extends Action $hasFN = ($this->profile->fullname) ? 'nickname url uid' : 'fn nickname url uid'; $this->element('a', array('href' => $this->profile->profileurl, 'rel' => 'me', 'class' => $hasFN), - $this->profile->nickname); + $this->profile->nickname); $this->elementEnd('dd'); $this->elementEnd('dl'); @@ -333,7 +333,7 @@ class ShowstreamAction extends Action $this->elementStart('li', 'entity_edit'); $this->element('a', array('href' => common_local_url('profilesettings'), 'title' => _('Edit profile settings')), - _('Edit')); + _('Edit')); $this->elementEnd('li'); } @@ -356,7 +356,7 @@ class ShowstreamAction extends Action } if ($cur && $cur->id != $user->id && $cur->mutuallySubscribed($user)) { - $this->elementStart('li', 'entity_send-a-message'); + $this->elementStart('li', 'entity_send-a-message'); $this->element('a', array('href' => common_local_url('newmessage', array('to' => $user->id)), 'title' => _('Send a direct message to this user')), _('Message')); @@ -498,7 +498,7 @@ class ShowstreamAction extends Action $this->elementStart('dl', 'entity_member-since'); $this->element('dt', null, _('Member since')); $this->element('dd', null, date('j M Y', - strtotime($this->profile->created))); + strtotime($this->profile->created))); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_subscriptions'); From 42e3c928dd4784813528826fd31e16cce60c0b34 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 12:29:10 -0500 Subject: [PATCH 009/189] ignore .#* files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f5a3e0212f..f2e96d3eb6 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ dataobject.ini *.bak *.orig *.rej +.#* From e5bad1526422f346041978a0812b0b29dc7f78bc Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 12:39:03 -0500 Subject: [PATCH 010/189] upgrade jQuery from 1.3 to 1.3.1 --- js/jquery.js | 222 +++++++++++++++++++++++------------------------ js/jquery.min.js | 12 +-- 2 files changed, 117 insertions(+), 117 deletions(-) diff --git a/js/jquery.js b/js/jquery.js index fc06ace272..94e9c1755e 100644 --- a/js/jquery.js +++ b/js/jquery.js @@ -1,13 +1,13 @@ /*! - * jQuery JavaScript Library v1.3 + * jQuery JavaScript Library v1.3.1 * http://jquery.com/ * * Copyright (c) 2009 John Resig * Dual licensed under the MIT and GPL licenses. * http://docs.jquery.com/License * - * Date: 2009-01-13 12:50:31 -0500 (Tue, 13 Jan 2009) - * Revision: 6104 + * Date: 2009-01-21 20:42:16 -0500 (Wed, 21 Jan 2009) + * Revision: 6158 */ (function(){ @@ -60,20 +60,16 @@ jQuery.fn = jQuery.prototype = { else { var elem = document.getElementById( match[3] ); - // Make sure an element was located - if ( elem ){ - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id != match[3] ) - return jQuery().find( selector ); + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem && elem.id != match[3] ) + return jQuery().find( selector ); - // Otherwise, we inject the element directly into the jQuery object - var ret = jQuery( elem ); - ret.context = document; - ret.selector = selector; - return ret; - } - selector = []; + // Otherwise, we inject the element directly into the jQuery object + var ret = jQuery( elem || [] ); + ret.context = document; + ret.selector = selector; + return ret; } // HANDLE: $(expr, [context]) @@ -99,7 +95,7 @@ jQuery.fn = jQuery.prototype = { selector: "", // The current version of jQuery being used - jquery: "1.3", + jquery: "1.3.1", // The number of elements contained in the matched element set size: function() { @@ -634,8 +630,8 @@ jQuery.extend({ // check if an element is in a (or is an) XML document isXMLDoc: function( elem ) { - return elem.documentElement && !elem.body || - elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument ); }, // Evalulates a script in a global context @@ -725,7 +721,7 @@ jQuery.extend({ // internal only, use hasClass("class") 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,9 +995,11 @@ jQuery.extend({ var attributeNode = elem.getAttributeNode( "tabIndex" ); return attributeNode && attributeNode.specified ? attributeNode.value - : elem.nodeName.match(/^(a|area|button|input|object|select|textarea)$/i) + : elem.nodeName.match(/(button|input|object|select|textarea)/i) ? 0 - : undefined; + : elem.nodeName.match(/^(a|area)$/i) && elem.href + ? 0 + : undefined; } return elem[ name ]; @@ -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 * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ (function(){ -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|[^[\]]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g, +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]+['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g, done = 0, 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] ] ) { - var later = "", match; - - // 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 ) ); + set = posProcess( parts[0] + parts[1], context ); } else { set = Expr.relative[ parts[0] ] ? [ context ] : Sizzle( parts.shift(), context ); while ( parts.length ) { - var tmpSet = []; - selector = parts.shift(); + if ( Expr.relative[ selector ] ) selector += parts.shift(); - for ( var i = 0, l = set.length; i < l; i++ ) { - Sizzle( selector, set[i], tmpSet ); - } - - set = tmpSet; + set = posProcess( selector, set ); } } } else { var ret = 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 ); if ( parts.length > 0 ) { @@ -1531,7 +1516,7 @@ Sizzle.matches = function(expr, set){ return Sizzle(expr, null, null, set); }; -Sizzle.find = function(expr, context){ +Sizzle.find = function(expr, context, isXML){ var set, match; if ( !expr ) { @@ -1546,7 +1531,7 @@ Sizzle.find = function(expr, context){ if ( left.substr( left.length - 1 ) !== "\\" ) { match[1] = (match[1] || "").replace(/\\/g, ""); - set = Expr.find[ type ]( match, context ); + set = Expr.find[ type ]( match, context, isXML ); if ( set != null ) { expr = expr.replace( Expr.match[ type ], "" ); break; @@ -1568,7 +1553,7 @@ Sizzle.filter = function(expr, set, inplace, not){ while ( expr && set.length ) { for ( var type in Expr.filter ) { 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; if ( curLoop == result ) { @@ -1582,26 +1567,13 @@ Sizzle.filter = function(expr, set, inplace, not){ anyFound = found = true; } else if ( match === true ) { 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 ) { - for ( var i = 0; (item = curLoop[i]) !== undefined; i++ ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { if ( item ) { - if ( goodArray && item != goodArray[goodPos] ) { - goodPos++; - } - - found = filter( item, match, goodPos, goodArray ); + found = filter( item, match, i, curLoop ); var pass = not ^ !!found; if ( inplace && found != null ) { @@ -1739,14 +1711,16 @@ var Expr = Sizzle.selectors = { } }, find: { - ID: function(match, context){ - if ( context.getElementById ) { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); return m ? [m] : []; } }, - NAME: function(match, context){ - return context.getElementsByName ? context.getElementsByName(match[1]) : null; + NAME: function(match, context, isXML){ + if ( typeof context.getElementsByName !== "undefined" && !isXML ) { + return context.getElementsByName(match[1]); + } }, TAG: function(match, context){ return context.getElementsByTagName(match[1]); @@ -1756,12 +1730,15 @@ var Expr = Sizzle.selectors = { CLASS: function(match, curLoop, inplace, result, not){ match = " " + match[1].replace(/\\/g, "") + " "; - for ( var i = 0; curLoop[i]; i++ ) { - if ( not ^ (" " + curLoop[i].className + " ").indexOf(match) >= 0 ) { - if ( !inplace ) - result.push( curLoop[i] ); - } else if ( inplace ) { - curLoop[i] = false; + var elem; + for ( var i = 0; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (" " + elem.className + " ").indexOf(match) >= 0 ) { + if ( !inplace ) + result.push( elem ); + } else if ( inplace ) { + curLoop[i] = false; + } } } @@ -1771,8 +1748,8 @@ var Expr = Sizzle.selectors = { return match[1].replace(/\\/g, ""); }, TAG: function(match, curLoop){ - for ( var i = 0; !curLoop[i]; i++ ){} - return isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); + for ( var i = 0; curLoop[i] === false; i++ ){} + return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); }, CHILD: function(match){ if ( match[1] == "nth" ) { @@ -1792,7 +1769,7 @@ var Expr = Sizzle.selectors = { return match; }, ATTR: function(match){ - var name = match[1]; + var name = match[1].replace(/\\/g, ""); if ( Expr.attrMap[name] ) { match[1] = Expr.attrMap[name]; @@ -1916,7 +1893,7 @@ var Expr = Sizzle.selectors = { CHILD: function(elem, match){ var type = match[1], parent = elem.parentNode; - var doneName = "child" + parent.childNodes.length; + var doneName = match[0]; if ( parent && (!parent[ doneName ] || !elem.nodeIndex) ) { var count = 1; @@ -1985,7 +1962,7 @@ var Expr = Sizzle.selectors = { 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]; return result == null ? - false : + type === "!=" : type === "=" ? value === check : type === "*=" ? @@ -2014,6 +1991,8 @@ var Expr = Sizzle.selectors = { } }; +var origPOS = Expr.match.POS; + for ( var type in Expr.match ) { Expr.match[ type ] = RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); } @@ -2072,15 +2051,15 @@ try { // The workaround has to do additional checks after a getElementById // Which slows things down for other browsers (hence the branching) if ( !!document.getElementById( id ) ) { - Expr.find.ID = function(match, context){ - if ( context.getElementById ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { 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){ - var node = elem.getAttributeNode && elem.getAttributeNode("id"); + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); return elem.nodeType === 1 && node && node.nodeValue === match; }; } @@ -2120,7 +2099,7 @@ try { // Check to see if an attribute returns normalized href attributes div.innerHTML = ""; - if ( div.firstChild.getAttribute("href") !== "#" ) { + if ( div.firstChild && div.firstChild.getAttribute("href") !== "#" ) { Expr.attrHandle.href = function(elem){ return elem.getAttribute("href", 2); }; @@ -2128,12 +2107,21 @@ try { })(); if ( document.querySelectorAll ) (function(){ - var oldSizzle = Sizzle; + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

"; + + // 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){ 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 { return makeArray( context.querySelectorAll(query), extra ); } catch(e){} @@ -2148,7 +2136,7 @@ if ( document.querySelectorAll ) (function(){ Sizzle.matches = oldSizzle.matches; })(); -if ( document.documentElement.getElementsByClassName ) { +if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) { Expr.order.splice(1, 0, "CLASS"); Expr.find.CLASS = function(match, context) { return context.getElementsByClassName(match[1]); @@ -2229,8 +2217,28 @@ var contains = document.compareDocumentPosition ? function(a, b){ }; var isXML = function(elem){ - return elem.documentElement && !elem.body || - elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!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 @@ -2681,13 +2689,13 @@ jQuery.Event = function( src ){ if( src && src.type ){ this.originalEvent = src; this.type = src.type; - this.timeStamp = src.timeStamp; // Event type }else this.type = src; - if( !this.timeStamp ) - this.timeStamp = now(); + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = now(); // Mark it as fixed this[expando] = true; @@ -2876,9 +2884,8 @@ function liveHandler( event ){ }); jQuery.each(elems, function(){ - if ( !event.isImmediatePropagationStopped() && - this.fn.call(this.elem, event, this.fn.data) === false ) - stop = false; + if ( this.fn.call(this.elem, event, this.fn.data) === false ) + stop = false; }); return stop; @@ -2942,7 +2949,7 @@ function bindReady(){ // If IE and not an iframe // 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; try { @@ -3477,6 +3484,9 @@ jQuery.extend({ // Fire the complete handlers complete(); + if ( isTimeout ) + xhr.abort(); + // Stop memory leaks if ( s.async ) xhr = null; @@ -3491,14 +3501,8 @@ jQuery.extend({ if ( s.timeout > 0 ) setTimeout(function(){ // Check to see if the request is still happening - if ( xhr ) { - if( !requestDone ) - onreadystatechange( "timeout" ); - - // Cancel the request - if ( xhr ) - xhr.abort(); - } + if ( xhr && !requestDone ) + onreadystatechange( "timeout" ); }, s.timeout); } @@ -3637,6 +3641,7 @@ jQuery.extend({ }); var elemdisplay = {}, + timerId, fxAttrs = [ // height animations [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], @@ -3859,7 +3864,6 @@ jQuery.extend({ }, timers: [], - timerId: null, fx: function( elem, options, prop ){ this.options = options; @@ -3911,10 +3915,8 @@ jQuery.fx.prototype = { t.elem = this.elem; - jQuery.timers.push(t); - - if ( t() && jQuery.timerId == null ) { - jQuery.timerId = setInterval(function(){ + if ( t() && jQuery.timers.push(t) == 1 ) { + timerId = setInterval(function(){ var timers = jQuery.timers; for ( var i = 0; i < timers.length; i++ ) @@ -3922,8 +3924,7 @@ jQuery.fx.prototype = { timers.splice(i--, 1); if ( !timers.length ) { - clearInterval( jQuery.timerId ); - jQuery.timerId = null; + clearInterval( timerId ); } }, 13); } @@ -3989,11 +3990,10 @@ jQuery.fx.prototype = { if ( this.options.hide || this.options.show ) for ( var p in this.options.curAnim ) jQuery.attr(this.elem.style, p, this.options.orig[p]); - } - - if ( done ) + // Execute the complete function this.options.complete.call( this.elem ); + } return false; } else { @@ -4087,7 +4087,7 @@ jQuery.offset = { initialize: function() { if ( this.initialized ) return; var body = document.body, container = document.createElement('div'), innerDiv, checkDiv, table, td, rules, prop, bodyMarginTop = body.style.marginTop, - html = '
'; + html = '
'; 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]; diff --git a/js/jquery.min.js b/js/jquery.min.js index 396646c842..c327fae812 100644 --- a/js/jquery.min.js +++ b/js/jquery.min.js @@ -1,19 +1,19 @@ /* - * jQuery JavaScript Library v1.3 + * jQuery JavaScript Library v1.3.1 * http://jquery.com/ * * Copyright (c) 2009 John Resig * Dual licensed under the MIT and GPL licenses. * http://docs.jquery.com/License * - * Date: 2009-01-13 12:50:31 -0500 (Tue, 13 Jan 2009) - * Revision: 6104 + * Date: 2009-01-21 20:42:16 -0500 (Wed, 21 Jan 2009) + * Revision: 6158 */ -(function(){var l=this,g,x=l.jQuery,o=l.$,n=l.jQuery=l.$=function(D,E){return new n.fn.init(D,E)},C=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;n.fn=n.prototype={init:function(D,G){D=D||document;if(D.nodeType){this[0]=D;this.length=1;this.context=D;return this}if(typeof D==="string"){var F=C.exec(D);if(F&&(F[1]||!G)){if(F[1]){D=n.clean([F[1]],G)}else{var H=document.getElementById(F[3]);if(H){if(H.id!=F[3]){return n().find(D)}var E=n(H);E.context=document;E.selector=D;return E}D=[]}}else{return n(G).find(D)}}else{if(n.isFunction(D)){return n(document).ready(D)}}if(D.selector&&D.context){this.selector=D.selector;this.context=D.context}return this.setArray(n.makeArray(D))},selector:"",jquery:"1.3",size:function(){return this.length},get:function(D){return D===g?n.makeArray(this):this[D]},pushStack:function(E,G,D){var F=n(E);F.prevObject=this;F.context=this.context;if(G==="find"){F.selector=this.selector+(this.selector?" ":"")+D}else{if(G){F.selector=this.selector+"."+G+"("+D+")"}}return F},setArray:function(D){this.length=0;Array.prototype.push.apply(this,D);return this},each:function(E,D){return n.each(this,E,D)},index:function(D){return n.inArray(D&&D.jquery?D[0]:D,this)},attr:function(E,G,F){var D=E;if(typeof E==="string"){if(G===g){return this[0]&&n[F||"attr"](this[0],E)}else{D={};D[E]=G}}return this.each(function(H){for(E in D){n.attr(F?this.style:this,E,n.prop(this,D[E],F,H,E))}})},css:function(D,E){if((D=="width"||D=="height")&&parseFloat(E)<0){E=g}return this.attr(D,E,"curCSS")},text:function(E){if(typeof E!=="object"&&E!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(E))}var D="";n.each(E||this,function(){n.each(this.childNodes,function(){if(this.nodeType!=8){D+=this.nodeType!=1?this.nodeValue:n.fn.text([this])}})});return D},wrapAll:function(D){if(this[0]){var E=n(D,this[0].ownerDocument).clone();if(this[0].parentNode){E.insertBefore(this[0])}E.map(function(){var F=this;while(F.firstChild){F=F.firstChild}return F}).append(this)}return this},wrapInner:function(D){return this.each(function(){n(this).contents().wrapAll(D)})},wrap:function(D){return this.each(function(){n(this).wrapAll(D)})},append:function(){return this.domManip(arguments,true,function(D){if(this.nodeType==1){this.appendChild(D)}})},prepend:function(){return this.domManip(arguments,true,function(D){if(this.nodeType==1){this.insertBefore(D,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(D){this.parentNode.insertBefore(D,this)})},after:function(){return this.domManip(arguments,false,function(D){this.parentNode.insertBefore(D,this.nextSibling)})},end:function(){return this.prevObject||n([])},push:[].push,find:function(D){if(this.length===1&&!/,/.test(D)){var F=this.pushStack([],"find",D);F.length=0;n.find(D,this[0],F);return F}else{var E=n.map(this,function(G){return n.find(D,G)});return this.pushStack(/[^+>] [^+>]/.test(D)?n.unique(E):E,"find",D)}},clone:function(E){var D=this.map(function(){if(!n.support.noCloneEvent&&!n.isXMLDoc(this)){var H=this.cloneNode(true),G=document.createElement("div");G.appendChild(H);return n.clean([G.innerHTML])[0]}else{return this.cloneNode(true)}});var F=D.find("*").andSelf().each(function(){if(this[h]!==g){this[h]=null}});if(E===true){this.find("*").andSelf().each(function(H){if(this.nodeType==3){return}var G=n.data(this,"events");for(var J in G){for(var I in G[J]){n.event.add(F[H],J,G[J][I],G[J][I].data)}}})}return D},filter:function(D){return this.pushStack(n.isFunction(D)&&n.grep(this,function(F,E){return D.call(F,E)})||n.multiFilter(D,n.grep(this,function(E){return E.nodeType===1})),"filter",D)},closest:function(D){var E=n.expr.match.POS.test(D)?n(D):null;return this.map(function(){var F=this;while(F&&F.ownerDocument){if(E?E.index(F)>-1:n(F).is(D)){return F}F=F.parentNode}})},not:function(D){if(typeof D==="string"){if(f.test(D)){return this.pushStack(n.multiFilter(D,this,true),"not",D)}else{D=n.multiFilter(D,this)}}var E=D.length&&D[D.length-1]!==g&&!D.nodeType;return this.filter(function(){return E?n.inArray(this,D)<0:this!=D})},add:function(D){return this.pushStack(n.unique(n.merge(this.get(),typeof D==="string"?n(D):n.makeArray(D))))},is:function(D){return !!D&&n.multiFilter(D,this).length>0},hasClass:function(D){return !!D&&this.is("."+D)},val:function(J){if(J===g){var D=this[0];if(D){if(n.nodeName(D,"option")){return(D.attributes.value||{}).specified?D.value:D.text}if(n.nodeName(D,"select")){var H=D.selectedIndex,K=[],L=D.options,G=D.type=="select-one";if(H<0){return null}for(var E=G?H:0,I=G?H+1:L.length;E=0||n.inArray(this.name,J)>=0)}else{if(n.nodeName(this,"select")){var M=n.makeArray(J);n("option",this).each(function(){this.selected=(n.inArray(this.value,M)>=0||n.inArray(this.text,M)>=0)});if(!M.length){this.selectedIndex=-1}}else{this.value=J}}})},html:function(D){return D===g?(this[0]?this[0].innerHTML:null):this.empty().append(D)},replaceWith:function(D){return this.after(D).remove()},eq:function(D){return this.slice(D,+D+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(D){return this.pushStack(n.map(this,function(F,E){return D.call(F,E,F)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=n.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild,D=this.length>1?I.cloneNode(true):I;if(H){for(var G=0,E=this.length;G0?D.cloneNode(true):I)}}if(F){n.each(F,y)}}return this;function K(N,O){return M&&n.nodeName(N,"table")&&n.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};n.fn.init.prototype=n.fn;function y(D,E){if(E.src){n.ajax({url:E.src,async:false,dataType:"script"})}else{n.globalEval(E.text||E.textContent||E.innerHTML||"")}if(E.parentNode){E.parentNode.removeChild(E)}}function e(){return +new Date}n.extend=n.fn.extend=function(){var I=arguments[0]||{},G=1,H=arguments.length,D=false,F;if(typeof I==="boolean"){D=I;I=arguments[1]||{};G=2}if(typeof I!=="object"&&!n.isFunction(I)){I={}}if(H==G){I=this;--G}for(;G-1}},swap:function(G,F,H){var D={};for(var E in F){D[E]=G.style[E];G.style[E]=F[E]}H.call(G);for(var E in F){G.style[E]=D[E]}},css:function(F,D,H){if(D=="width"||D=="height"){var J,E={position:"absolute",visibility:"hidden",display:"block"},I=D=="width"?["Left","Right"]:["Top","Bottom"];function G(){J=D=="width"?F.offsetWidth:F.offsetHeight;var L=0,K=0;n.each(I,function(){L+=parseFloat(n.curCSS(F,"padding"+this,true))||0;K+=parseFloat(n.curCSS(F,"border"+this+"Width",true))||0});J-=Math.round(L+K)}if(n(F).is(":visible")){G()}else{n.swap(F,E,G)}return Math.max(0,J)}return n.curCSS(F,D,H)},curCSS:function(H,E,F){var K,D=H.style;if(E=="opacity"&&!n.support.opacity){K=n.attr(D,"opacity");return K==""?"1":K}if(E.match(/float/i)){E=v}if(!F&&D&&D[E]){K=D[E]}else{if(p.getComputedStyle){if(E.match(/float/i)){E="float"}E=E.replace(/([A-Z])/g,"-$1").toLowerCase();var L=p.getComputedStyle(H,null);if(L){K=L.getPropertyValue(E)}if(E=="opacity"&&K==""){K="1"}}else{if(H.currentStyle){var I=E.replace(/\-(\w)/g,function(M,N){return N.toUpperCase()});K=H.currentStyle[E]||H.currentStyle[I];if(!/^\d+(px)?$/i.test(K)&&/^\d/.test(K)){var G=D.left,J=H.runtimeStyle.left;H.runtimeStyle.left=H.currentStyle.left;D.left=K||0;K=D.pixelLeft+"px";D.left=G;H.runtimeStyle.left=J}}}}return K},clean:function(E,J,H){J=J||document;if(typeof J.createElement==="undefined"){J=J.ownerDocument||J[0]&&J[0].ownerDocument||document}if(!H&&E.length===1&&typeof E[0]==="string"){var G=/^<(\w+)\s*\/?>$/.exec(E[0]);if(G){return[J.createElement(G[1])]}}var F=[],D=[],K=J.createElement("div");n.each(E,function(O,Q){if(typeof Q==="number"){Q+=""}if(!Q){return}if(typeof Q==="string"){Q=Q.replace(/(<(\w+)[^>]*?)\/>/g,function(S,T,R){return R.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?S:T+">"});var N=n.trim(Q).toLowerCase();var P=!N.indexOf("",""]||!N.indexOf("",""]||N.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!N.indexOf("",""]||(!N.indexOf("",""]||!N.indexOf("",""]||!n.support.htmlSerialize&&[1,"div
","
"]||[0,"",""];K.innerHTML=P[1]+Q+P[2];while(P[0]--){K=K.lastChild}if(!n.support.tbody){var M=!N.indexOf(""&&N.indexOf("=0;--L){if(n.nodeName(M[L],"tbody")&&!M[L].childNodes.length){M[L].parentNode.removeChild(M[L])}}}if(!n.support.leadingWhitespace&&/^\s/.test(Q)){K.insertBefore(J.createTextNode(Q.match(/^\s*/)[0]),K.firstChild)}Q=n.makeArray(K.childNodes)}if(Q.nodeType){F.push(Q)}else{F=n.merge(F,Q)}});if(H){for(var I=0;F[I];I++){if(n.nodeName(F[I],"script")&&(!F[I].type||F[I].type.toLowerCase()==="text/javascript")){D.push(F[I].parentNode?F[I].parentNode.removeChild(F[I]):F[I])}else{if(F[I].nodeType===1){F.splice.apply(F,[I+1,0].concat(n.makeArray(F[I].getElementsByTagName("script"))))}H.appendChild(F[I])}}return D}return F},attr:function(I,F,J){if(!I||I.nodeType==3||I.nodeType==8){return g}var G=!n.isXMLDoc(I),K=J!==g;F=G&&n.props[F]||F;if(I.tagName){var E=/href|src|style/.test(F);if(F=="selected"&&I.parentNode){I.parentNode.selectedIndex}if(F in I&&G&&!E){if(K){if(F=="type"&&n.nodeName(I,"input")&&I.parentNode){throw"type property can't be changed"}I[F]=J}if(n.nodeName(I,"form")&&I.getAttributeNode(F)){return I.getAttributeNode(F).nodeValue}if(F=="tabIndex"){var H=I.getAttributeNode("tabIndex");return H&&H.specified?H.value:I.nodeName.match(/^(a|area|button|input|object|select|textarea)$/i)?0:g}return I[F]}if(!n.support.style&&G&&F=="style"){return n.attr(I.style,"cssText",J)}if(K){I.setAttribute(F,""+J)}var D=!n.support.hrefNormalized&&G&&E?I.getAttribute(F,2):I.getAttribute(F);return D===null?g:D}if(!n.support.opacity&&F=="opacity"){if(K){I.zoom=1;I.filter=(I.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(J)+""=="NaN"?"":"alpha(opacity="+J*100+")")}return I.filter&&I.filter.indexOf("opacity=")>=0?(parseFloat(I.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}F=F.replace(/-([a-z])/ig,function(L,M){return M.toUpperCase()});if(K){I[F]=J}return I[F]},trim:function(D){return(D||"").replace(/^\s+|\s+$/g,"")},makeArray:function(F){var D=[];if(F!=null){var E=F.length;if(E==null||typeof F==="string"||n.isFunction(F)||F.setInterval){D[0]=F}else{while(E){D[--E]=F[E]}}}return D},inArray:function(F,G){for(var D=0,E=G.length;D*",this).remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(D,E){n.fn[D]=function(){return this.each(E,arguments)}});function j(D,E){return D[0]&&parseInt(n.curCSS(D[0],E,true),10)||0}var h="jQuery"+e(),u=0,z={};n.extend({cache:{},data:function(E,D,F){E=E==l?z:E;var G=E[h];if(!G){G=E[h]=++u}if(D&&!n.cache[G]){n.cache[G]={}}if(F!==g){n.cache[G][D]=F}return D?n.cache[G][D]:G},removeData:function(E,D){E=E==l?z:E;var G=E[h];if(D){if(n.cache[G]){delete n.cache[G][D];D="";for(D in n.cache[G]){break}if(!D){n.removeData(E)}}}else{try{delete E[h]}catch(F){if(E.removeAttribute){E.removeAttribute(h)}}delete n.cache[G]}},queue:function(E,D,G){if(E){D=(D||"fx")+"queue";var F=n.data(E,D);if(!F||n.isArray(G)){F=n.data(E,D,n.makeArray(G))}else{if(G){F.push(G)}}}return F},dequeue:function(G,F){var D=n.queue(G,F),E=D.shift();if(!F||F==="fx"){E=D[0]}if(E!==g){E.call(G)}}});n.fn.extend({data:function(D,F){var G=D.split(".");G[1]=G[1]?"."+G[1]:"";if(F===g){var E=this.triggerHandler("getData"+G[1]+"!",[G[0]]);if(E===g&&this.length){E=n.data(this[0],D)}return E===g&&G[1]?this.data(G[0]):E}else{return this.trigger("setData"+G[1]+"!",[G[0],F]).each(function(){n.data(this,D,F)})}},removeData:function(D){return this.each(function(){n.removeData(this,D)})},queue:function(D,E){if(typeof D!=="string"){E=D;D="fx"}if(E===g){return n.queue(this[0],D)}return this.each(function(){var F=n.queue(this,D,E);if(D=="fx"&&F.length==1){F[0].call(this)}})},dequeue:function(D){return this.each(function(){n.dequeue(this,D)})}}); +(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.makeArray(E))},selector:"",jquery:"1.3.1",size:function(){return this.length},get:function(E){return E===g?o.makeArray(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,find:function(E){if(this.length===1&&!/,/.test(E)){var G=this.pushStack([],"find",E);G.length=0;o.find(E,this[0],G);return G}else{var F=o.map(this,function(H){return o.find(E,H)});return this.pushStack(/[^+>] [^+>]/.test(E)?o.unique(F):F,"find",E)}},clone:function(F){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.cloneNode(true),H=document.createElement("div");H.appendChild(I);return o.clean([H.innerHTML])[0]}else{return this.cloneNode(true)}});var G=E.find("*").andSelf().each(function(){if(this[h]!==g){this[h]=null}});if(F===true){this.find("*").andSelf().each(function(I){if(this.nodeType==3){return}var H=o.data(this,"events");for(var K in H){for(var J in H[K]){o.event.add(G[I],K,H[K][J],H[K][J].data)}}})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var F=o.expr.match.POS.test(E)?o(E):null;return this.map(function(){var G=this;while(G&&G.ownerDocument){if(F?F.index(G)>-1:o(G).is(E)){return G}G=G.parentNode}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML:null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(K,N,M){if(this[0]){var J=(this[0].ownerDocument||this[0]).createDocumentFragment(),G=o.clean(K,(this[0].ownerDocument||this[0]),J),I=J.firstChild,E=this.length>1?J.cloneNode(true):J;if(I){for(var H=0,F=this.length;H0?E.cloneNode(true):J)}}if(G){o.each(G,z)}}return this;function L(O,P){return N&&o.nodeName(O,"table")&&o.nodeName(P,"tr")?(O.getElementsByTagName("tbody")[0]||O.appendChild(O.ownerDocument.createElement("tbody"))):O}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(G,E,I){if(E=="width"||E=="height"){var K,F={position:"absolute",visibility:"hidden",display:"block"},J=E=="width"?["Left","Right"]:["Top","Bottom"];function H(){K=E=="width"?G.offsetWidth:G.offsetHeight;var M=0,L=0;o.each(J,function(){M+=parseFloat(o.curCSS(G,"padding"+this,true))||0;L+=parseFloat(o.curCSS(G,"border"+this+"Width",true))||0});K-=Math.round(M+L)}if(o(G).is(":visible")){H()}else{o.swap(G,F,H)}return Math.max(0,K)}return o.curCSS(G,E,I)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,R){if(typeof R==="number"){R+=""}if(!R){return}if(typeof R==="string"){R=R.replace(/(<(\w+)[^>]*?)\/>/g,function(T,U,S){return S.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?T:U+">"});var O=o.trim(R).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
","
"]||[0,"",""];L.innerHTML=Q[1]+R+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var N=!O.indexOf(""&&O.indexOf("=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(R)){L.insertBefore(K.createTextNode(R.match(/^\s*/)[0]),L.firstChild)}R=o.makeArray(L.childNodes)}if(R.nodeType){G.push(R)}else{G=o.merge(G,R)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E*",this).remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); /* - * Sizzle CSS Selector Engine - v0.9.1 + * Sizzle CSS Selector Engine - v0.9.3 * Copyright 2009, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ -(function(){var N=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|[^[\]]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g,I=0,F=Object.prototype.toString;var E=function(ae,S,aa,V){aa=aa||[];S=S||document;if(S.nodeType!==1&&S.nodeType!==9){return[]}if(!ae||typeof ae!=="string"){return aa}var ab=[],ac,Y,ah,ag,Z,R,Q=true;N.lastIndex=0;while((ac=N.exec(ae))!==null){ab.push(ac[1]);if(ac[2]){R=RegExp.rightContext;break}}if(ab.length>1&&G.match.POS.exec(ae)){if(ab.length===2&&G.relative[ab[0]]){var U="",X;while((X=G.match.POS.exec(ae))){U+=X[0];ae=ae.replace(G.match.POS,"")}Y=E.filter(U,E(/\s$/.test(ae)?ae+"*":ae,S))}else{Y=G.relative[ab[0]]?[S]:E(ab.shift(),S);while(ab.length){var P=[];ae=ab.shift();if(G.relative[ae]){ae+=ab.shift()}for(var af=0,ad=Y.length;af0){ah=D(Y)}else{Q=false}while(ab.length){var T=ab.pop(),W=T;if(!G.relative[T]){T=""}else{W=ab.pop()}if(W==null){W=S}G.relative[T](ah,W,M(S))}}if(!ah){ah=Y}if(!ah){throw"Syntax error, unrecognized expression: "+(T||ae)}if(F.call(ah)==="[object Array]"){if(!Q){aa.push.apply(aa,ah)}else{if(S.nodeType===1){for(var af=0;ah[af]!=null;af++){if(ah[af]&&(ah[af]===true||ah[af].nodeType===1&&H(S,ah[af]))){aa.push(Y[af])}}}else{for(var af=0;ah[af]!=null;af++){if(ah[af]&&ah[af].nodeType===1){aa.push(Y[af])}}}}}else{D(ah,aa)}if(R){E(R,S,aa,V)}return aa};E.matches=function(P,Q){return E(P,null,null,Q)};E.find=function(V,S){var W,Q;if(!V){return[]}for(var R=0,P=G.order.length;R":function(U,Q,V){if(typeof Q==="string"&&!/\W/.test(Q)){Q=V?Q:Q.toUpperCase();for(var R=0,P=U.length;R=0){if(!R){P.push(Q[T])}}else{if(R){Q[T]=false}}}return false},ID:function(P){return P[1].replace(/\\/g,"")},TAG:function(Q,P){for(var R=0;!P[R];R++){}return M(P[R])?Q[1]:Q[1].toUpperCase()},CHILD:function(P){if(P[1]=="nth"){var Q=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(P[2]=="even"&&"2n"||P[2]=="odd"&&"2n+1"||!/\D/.test(P[2])&&"0n+"+P[2]||P[2]);P[2]=(Q[1]+(Q[2]||1))-0;P[3]=Q[3]-0}P[0]="done"+(I++);return P},ATTR:function(Q){var P=Q[1];if(G.attrMap[P]){Q[1]=G.attrMap[P]}if(Q[2]==="~="){Q[4]=" "+Q[4]+" "}return Q},PSEUDO:function(T,Q,R,P,U){if(T[1]==="not"){if(T[3].match(N).length>1){T[3]=E(T[3],null,null,Q)}else{var S=E.filter(T[3],Q,R,true^U);if(!R){P.push.apply(P,S)}return false}}else{if(G.match.POS.test(T[0])){return true}}return T},POS:function(P){P.unshift(true);return P}},filters:{enabled:function(P){return P.disabled===false&&P.type!=="hidden"},disabled:function(P){return P.disabled===true},checked:function(P){return P.checked===true},selected:function(P){P.parentNode.selectedIndex;return P.selected===true},parent:function(P){return !!P.firstChild},empty:function(P){return !P.firstChild},has:function(R,Q,P){return !!E(P[3],R).length},header:function(P){return/h\d/i.test(P.nodeName)},text:function(P){return"text"===P.type},radio:function(P){return"radio"===P.type},checkbox:function(P){return"checkbox"===P.type},file:function(P){return"file"===P.type},password:function(P){return"password"===P.type},submit:function(P){return"submit"===P.type},image:function(P){return"image"===P.type},reset:function(P){return"reset"===P.type},button:function(P){return"button"===P.type||P.nodeName.toUpperCase()==="BUTTON"},input:function(P){return/input|select|textarea|button/i.test(P.nodeName)}},setFilters:{first:function(Q,P){return P===0},last:function(R,Q,P,S){return Q===S.length-1},even:function(Q,P){return P%2===0},odd:function(Q,P){return P%2===1},lt:function(R,Q,P){return QP[3]-0},nth:function(R,Q,P){return P[3]-0==Q},eq:function(R,Q,P){return P[3]-0==Q}},filter:{CHILD:function(P,S){var V=S[1],W=P.parentNode;var U="child"+W.childNodes.length;if(W&&(!W[U]||!P.nodeIndex)){var T=1;for(var Q=W.firstChild;Q;Q=Q.nextSibling){if(Q.nodeType==1){Q.nodeIndex=T++}}W[U]=T-1}if(V=="first"){return P.nodeIndex==1}else{if(V=="last"){return P.nodeIndex==W[U]}else{if(V=="only"){return W[U]==1}else{if(V=="nth"){var Y=false,R=S[2],X=S[3];if(R==1&&X==0){return true}if(R==0){if(P.nodeIndex==X){Y=true}}else{if((P.nodeIndex-X)%R==0&&(P.nodeIndex-X)/R>=0){Y=true}}return Y}}}}},PSEUDO:function(V,R,S,W){var Q=R[1],T=G.filters[Q];if(T){return T(V,S,R,W)}else{if(Q==="contains"){return(V.textContent||V.innerText||"").indexOf(R[3])>=0}else{if(Q==="not"){var U=R[3];for(var S=0,P=U.length;S=0:S==="~="?(" "+U+" ").indexOf(Q)>=0:!R[4]?P:S==="!="?U!=Q:S==="^="?U.indexOf(Q)===0:S==="$="?U.substr(U.length-Q.length)===Q:S==="|="?U===Q||U.substr(0,Q.length+1)===Q+"-":false},POS:function(T,Q,R,U){var P=Q[2],S=G.setFilters[P];if(S){return S(T,R,Q,U)}}}};for(var K in G.match){G.match[K]=RegExp(G.match[K].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var D=function(Q,P){Q=Array.prototype.slice.call(Q);if(P){P.push.apply(P,Q);return P}return Q};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(J){D=function(T,S){var Q=S||[];if(F.call(T)==="[object Array]"){Array.prototype.push.apply(Q,T)}else{if(typeof T.length==="number"){for(var R=0,P=T.length;R";var P=document.documentElement;P.insertBefore(Q,P.firstChild);if(!!document.getElementById(R)){G.find.ID=function(T,U){if(U.getElementById){var S=U.getElementById(T[1]);return S?S.id===T[1]||S.getAttributeNode&&S.getAttributeNode("id").nodeValue===T[1]?[S]:g:[]}};G.filter.ID=function(U,S){var T=U.getAttributeNode&&U.getAttributeNode("id");return U.nodeType===1&&T&&T.nodeValue===S}}P.removeChild(Q)})();(function(){var P=document.createElement("div");P.appendChild(document.createComment(""));if(P.getElementsByTagName("*").length>0){G.find.TAG=function(Q,U){var T=U.getElementsByTagName(Q[1]);if(Q[1]==="*"){var S=[];for(var R=0;T[R];R++){if(T[R].nodeType===1){S.push(T[R])}}T=S}return T}}P.innerHTML="";if(P.firstChild.getAttribute("href")!=="#"){G.attrHandle.href=function(Q){return Q.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var P=E;E=function(T,S,Q,R){S=S||document;if(!R&&S.nodeType===9){try{return D(S.querySelectorAll(T),Q)}catch(U){}}return P(T,S,Q,R)};E.find=P.find;E.filter=P.filter;E.selectors=P.selectors;E.matches=P.matches})()}if(document.documentElement.getElementsByClassName){G.order.splice(1,0,"CLASS");G.find.CLASS=function(P,Q){return Q.getElementsByClassName(P[1])}}function L(Q,W,V,Z,X,Y){for(var T=0,R=Z.length;T0){T=P;break}}}P=P[Q]}Y[S]=T}}}var H=document.compareDocumentPosition?function(Q,P){return Q.compareDocumentPosition(P)&16}:function(Q,P){return Q!==P&&(Q.contains?Q.contains(P):true)};var M=function(P){return P.documentElement&&!P.body||P.tagName&&P.ownerDocument&&!P.ownerDocument.body};n.find=E;n.filter=E.filter;n.expr=E.selectors;n.expr[":"]=n.expr.filters;E.selectors.filters.hidden=function(P){return"hidden"===P.type||n.css(P,"display")==="none"||n.css(P,"visibility")==="hidden"};E.selectors.filters.visible=function(P){return"hidden"!==P.type&&n.css(P,"display")!=="none"&&n.css(P,"visibility")!=="hidden"};E.selectors.filters.animated=function(P){return n.grep(n.timers,function(Q){return P===Q.elem}).length};n.multiFilter=function(R,P,Q){if(Q){R=":not("+R+")"}return E.matches(R,P)};n.dir=function(R,Q){var P=[],S=R[Q];while(S&&S!=document){if(S.nodeType==1){P.push(S)}S=S[Q]}return P};n.nth=function(T,P,R,S){P=P||1;var Q=0;for(;T;T=T[R]){if(T.nodeType==1&&++Q==P){break}}return T};n.sibling=function(R,Q){var P=[];for(;R;R=R.nextSibling){if(R.nodeType==1&&R!=Q){P.push(R)}}return P};return;l.Sizzle=E})();n.event={add:function(H,E,G,J){if(H.nodeType==3||H.nodeType==8){return}if(H.setInterval&&H!=l){H=l}if(!G.guid){G.guid=this.guid++}if(J!==g){var F=G;G=this.proxy(F);G.data=J}var D=n.data(H,"events")||n.data(H,"events",{}),I=n.data(H,"handle")||n.data(H,"handle",function(){return typeof n!=="undefined"&&!n.event.triggered?n.event.handle.apply(arguments.callee.elem,arguments):g});I.elem=H;n.each(E.split(/\s+/),function(L,M){var N=M.split(".");M=N.shift();G.type=N.slice().sort().join(".");var K=D[M];if(n.event.specialAll[M]){n.event.specialAll[M].setup.call(H,J,N)}if(!K){K=D[M]={};if(!n.event.special[M]||n.event.special[M].setup.call(H,J,N)===false){if(H.addEventListener){H.addEventListener(M,I,false)}else{if(H.attachEvent){H.attachEvent("on"+M,I)}}}}K[G.guid]=G;n.event.global[M]=true});H=null},guid:1,global:{},remove:function(J,G,I){if(J.nodeType==3||J.nodeType==8){return}var F=n.data(J,"events"),E,D;if(F){if(G===g||(typeof G==="string"&&G.charAt(0)==".")){for(var H in F){this.remove(J,H+(G||""))}}else{if(G.type){I=G.handler;G=G.type}n.each(G.split(/\s+/),function(L,N){var P=N.split(".");N=P.shift();var M=RegExp("(^|\\.)"+P.slice().sort().join(".*\\.")+"(\\.|$)");if(F[N]){if(I){delete F[N][I.guid]}else{for(var O in F[N]){if(M.test(F[N][O].type)){delete F[N][O]}}}if(n.event.specialAll[N]){n.event.specialAll[N].teardown.call(J,P)}for(E in F[N]){break}if(!E){if(!n.event.special[N]||n.event.special[N].teardown.call(J,P)===false){if(J.removeEventListener){J.removeEventListener(N,n.data(J,"handle"),false)}else{if(J.detachEvent){J.detachEvent("on"+N,n.data(J,"handle"))}}}E=null;delete F[N]}}})}for(E in F){break}if(!E){var K=n.data(J,"handle");if(K){K.elem=null}n.removeData(J,"events");n.removeData(J,"handle")}}},trigger:function(H,J,G,D){var F=H.type||H;if(!D){H=typeof H==="object"?H[h]?H:n.extend(n.Event(F),H):n.Event(F);if(F.indexOf("!")>=0){H.type=F=F.slice(0,-1);H.exclusive=true}if(!G){H.stopPropagation();if(this.global[F]){n.each(n.cache,function(){if(this.events&&this.events[F]){n.event.trigger(H,J,this.handle.elem)}})}}if(!G||G.nodeType==3||G.nodeType==8){return g}H.result=g;H.target=G;J=n.makeArray(J);J.unshift(H)}H.currentTarget=G;var I=n.data(G,"handle");if(I){I.apply(G,J)}if((!G[F]||(n.nodeName(G,"a")&&F=="click"))&&G["on"+F]&&G["on"+F].apply(G,J)===false){H.result=false}if(!D&&G[F]&&!H.isDefaultPrevented()&&!(n.nodeName(G,"a")&&F=="click")){this.triggered=true;try{G[F]()}catch(K){}}this.triggered=false;if(!H.isPropagationStopped()){var E=G.parentNode||G.ownerDocument;if(E){n.event.trigger(H,J,E,true)}}},handle:function(J){var I,D;J=arguments[0]=n.event.fix(J||l.event);var K=J.type.split(".");J.type=K.shift();I=!K.length&&!J.exclusive;var H=RegExp("(^|\\.)"+K.slice().sort().join(".*\\.")+"(\\.|$)");D=(n.data(this,"events")||{})[J.type];for(var F in D){var G=D[F];if(I||H.test(G.type)){J.handler=G;J.data=G.data;var E=G.apply(this,arguments);if(E!==g){J.result=E;if(E===false){J.preventDefault();J.stopPropagation()}}if(J.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(G){if(G[h]){return G}var E=G;G=n.Event(E);for(var F=this.props.length,I;F;){I=this.props[--F];G[I]=E[I]}if(!G.target){G.target=G.srcElement||document}if(G.target.nodeType==3){G.target=G.target.parentNode}if(!G.relatedTarget&&G.fromElement){G.relatedTarget=G.fromElement==G.target?G.toElement:G.fromElement}if(G.pageX==null&&G.clientX!=null){var H=document.documentElement,D=document.body;G.pageX=G.clientX+(H&&H.scrollLeft||D&&D.scrollLeft||0)-(H.clientLeft||0);G.pageY=G.clientY+(H&&H.scrollTop||D&&D.scrollTop||0)-(H.clientTop||0)}if(!G.which&&((G.charCode||G.charCode===0)?G.charCode:G.keyCode)){G.which=G.charCode||G.keyCode}if(!G.metaKey&&G.ctrlKey){G.metaKey=G.ctrlKey}if(!G.which&&G.button){G.which=(G.button&1?1:(G.button&2?3:(G.button&4?2:0)))}return G},proxy:function(E,D){D=D||function(){return E.apply(this,arguments)};D.guid=E.guid=E.guid||D.guid||this.guid++;return D},special:{ready:{setup:A,teardown:function(){}}},specialAll:{live:{setup:function(D,E){n.event.add(this,E[0],c)},teardown:function(F){if(F.length){var D=0,E=RegExp("(^|\\.)"+F[0]+"(\\.|$)");n.each((n.data(this,"events").live||{}),function(){if(E.test(this.type)){D++}});if(D<1){n.event.remove(this,F[0],c)}}}}}};n.Event=function(D){if(!this.preventDefault){return new n.Event(D)}if(D&&D.type){this.originalEvent=D;this.type=D.type;this.timeStamp=D.timeStamp}else{this.type=D}if(!this.timeStamp){this.timeStamp=e()}this[h]=true};function k(){return false}function t(){return true}n.Event.prototype={preventDefault:function(){this.isDefaultPrevented=t;var D=this.originalEvent;if(!D){return}if(D.preventDefault){D.preventDefault()}D.returnValue=false},stopPropagation:function(){this.isPropagationStopped=t;var D=this.originalEvent;if(!D){return}if(D.stopPropagation){D.stopPropagation()}D.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=t;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(E){var D=E.relatedTarget;while(D&&D!=this){try{D=D.parentNode}catch(F){D=this}}if(D!=this){E.type=E.data;n.event.handle.apply(this,arguments)}};n.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(E,D){n.event.special[D]={setup:function(){n.event.add(this,E,a,D)},teardown:function(){n.event.remove(this,E,a)}}});n.fn.extend({bind:function(E,F,D){return E=="unload"?this.one(E,F,D):this.each(function(){n.event.add(this,E,D||F,D&&F)})},one:function(F,G,E){var D=n.event.proxy(E||G,function(H){n(this).unbind(H,D);return(E||G).apply(this,arguments)});return this.each(function(){n.event.add(this,F,D,E&&G)})},unbind:function(E,D){return this.each(function(){n.event.remove(this,E,D)})},trigger:function(D,E){return this.each(function(){n.event.trigger(D,E,this)})},triggerHandler:function(D,F){if(this[0]){var E=n.Event(D);E.preventDefault();E.stopPropagation();n.event.trigger(E,F,this[0]);return E.result}},toggle:function(F){var D=arguments,E=1;while(E=0){var D=F.slice(H,F.length);F=F.slice(0,H)}var G="GET";if(I){if(n.isFunction(I)){J=I;I=null}else{if(typeof I==="object"){I=n.param(I);G="POST"}}}var E=this;n.ajax({url:F,type:G,dataType:"html",data:I,complete:function(L,K){if(K=="success"||K=="notmodified"){E.html(D?n("
").append(L.responseText.replace(//g,"")).find(D):L.responseText)}if(J){E.each(J,[L.responseText,K,L])}}});return this},serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?n.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type))}).map(function(D,E){var F=n(this).val();return F==null?null:n.isArray(F)?n.map(F,function(H,G){return{name:E.name,value:H}}):{name:E.name,value:F}}).get()}});n.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(D,E){n.fn[E]=function(F){return this.bind(E,F)}});var q=e();n.extend({get:function(D,F,G,E){if(n.isFunction(F)){G=F;F=null}return n.ajax({type:"GET",url:D,data:F,success:G,dataType:E})},getScript:function(D,E){return n.get(D,null,E,"script")},getJSON:function(D,E,F){return n.get(D,E,F,"json")},post:function(D,F,G,E){if(n.isFunction(F)){G=F;F={}}return n.ajax({type:"POST",url:D,data:F,success:G,dataType:E})},ajaxSetup:function(D){n.extend(n.ajaxSettings,D)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(L){L=n.extend(true,L,n.extend(true,{},n.ajaxSettings,L));var V,E=/=\?(&|$)/g,Q,U,F=L.type.toUpperCase();if(L.data&&L.processData&&typeof L.data!=="string"){L.data=n.param(L.data)}if(L.dataType=="jsonp"){if(F=="GET"){if(!L.url.match(E)){L.url+=(L.url.match(/\?/)?"&":"?")+(L.jsonp||"callback")+"=?"}}else{if(!L.data||!L.data.match(E)){L.data=(L.data?L.data+"&":"")+(L.jsonp||"callback")+"=?"}}L.dataType="json"}if(L.dataType=="json"&&(L.data&&L.data.match(E)||L.url.match(E))){V="jsonp"+q++;if(L.data){L.data=(L.data+"").replace(E,"="+V+"$1")}L.url=L.url.replace(E,"="+V+"$1");L.dataType="script";l[V]=function(W){U=W;H();K();l[V]=g;try{delete l[V]}catch(X){}if(G){G.removeChild(S)}}}if(L.dataType=="script"&&L.cache==null){L.cache=false}if(L.cache===false&&F=="GET"){var D=e();var T=L.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+D+"$2");L.url=T+((T==L.url)?(L.url.match(/\?/)?"&":"?")+"_="+D:"")}if(L.data&&F=="GET"){L.url+=(L.url.match(/\?/)?"&":"?")+L.data;L.data=null}if(L.global&&!n.active++){n.event.trigger("ajaxStart")}var P=/^(\w+:)?\/\/([^\/?#]+)/.exec(L.url);if(L.dataType=="script"&&F=="GET"&&P&&(P[1]&&P[1]!=location.protocol||P[2]!=location.host)){var G=document.getElementsByTagName("head")[0];var S=document.createElement("script");S.src=L.url;if(L.scriptCharset){S.charset=L.scriptCharset}if(!V){var N=false;S.onload=S.onreadystatechange=function(){if(!N&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){N=true;H();K();G.removeChild(S)}}}G.appendChild(S);return g}var J=false;var I=L.xhr();if(L.username){I.open(F,L.url,L.async,L.username,L.password)}else{I.open(F,L.url,L.async)}try{if(L.data){I.setRequestHeader("Content-Type",L.contentType)}if(L.ifModified){I.setRequestHeader("If-Modified-Since",n.lastModified[L.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}I.setRequestHeader("X-Requested-With","XMLHttpRequest");I.setRequestHeader("Accept",L.dataType&&L.accepts[L.dataType]?L.accepts[L.dataType]+", */*":L.accepts._default)}catch(R){}if(L.beforeSend&&L.beforeSend(I,L)===false){if(L.global&&!--n.active){n.event.trigger("ajaxStop")}I.abort();return false}if(L.global){n.event.trigger("ajaxSend",[I,L])}var M=function(W){if(I.readyState==0){if(O){clearInterval(O);O=null;if(L.global&&!--n.active){n.event.trigger("ajaxStop")}}}else{if(!J&&I&&(I.readyState==4||W=="timeout")){J=true;if(O){clearInterval(O);O=null}Q=W=="timeout"?"timeout":!n.httpSuccess(I)?"error":L.ifModified&&n.httpNotModified(I,L.url)?"notmodified":"success";if(Q=="success"){try{U=n.httpData(I,L.dataType,L)}catch(Y){Q="parsererror"}}if(Q=="success"){var X;try{X=I.getResponseHeader("Last-Modified")}catch(Y){}if(L.ifModified&&X){n.lastModified[L.url]=X}if(!V){H()}}else{n.handleError(L,I,Q)}K();if(L.async){I=null}}}};if(L.async){var O=setInterval(M,13);if(L.timeout>0){setTimeout(function(){if(I){if(!J){M("timeout")}if(I){I.abort()}}},L.timeout)}}try{I.send(L.data)}catch(R){n.handleError(L,I,null,R)}if(!L.async){M()}function H(){if(L.success){L.success(U,Q)}if(L.global){n.event.trigger("ajaxSuccess",[I,L])}}function K(){if(L.complete){L.complete(I,Q)}if(L.global){n.event.trigger("ajaxComplete",[I,L])}if(L.global&&!--n.active){n.event.trigger("ajaxStop")}}return I},handleError:function(E,G,D,F){if(E.error){E.error(G,D,F)}if(E.global){n.event.trigger("ajaxError",[G,E,F])}},active:0,httpSuccess:function(E){try{return !E.status&&location.protocol=="file:"||(E.status>=200&&E.status<300)||E.status==304||E.status==1223}catch(D){}return false},httpNotModified:function(F,D){try{var G=F.getResponseHeader("Last-Modified");return F.status==304||G==n.lastModified[D]}catch(E){}return false},httpData:function(I,G,F){var E=I.getResponseHeader("content-type"),D=G=="xml"||!G&&E&&E.indexOf("xml")>=0,H=D?I.responseXML:I.responseText;if(D&&H.documentElement.tagName=="parsererror"){throw"parsererror"}if(F&&F.dataFilter){H=F.dataFilter(H,G)}if(typeof H==="string"){if(G=="script"){n.globalEval(H)}if(G=="json"){H=l["eval"]("("+H+")")}}return H},param:function(D){var F=[];function G(H,I){F[F.length]=encodeURIComponent(H)+"="+encodeURIComponent(I)}if(n.isArray(D)||D.jquery){n.each(D,function(){G(this.name,this.value)})}else{for(var E in D){if(n.isArray(D[E])){n.each(D[E],function(){G(E,this)})}else{G(E,n.isFunction(D[E])?D[E]():D[E])}}}return F.join("&").replace(/%20/g,"+")}});var m={},d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function s(E,D){var F={};n.each(d.concat.apply([],d.slice(0,D)),function(){F[this]=E});return F}n.fn.extend({show:function(I,K){if(I){return this.animate(s("show",3),I,K)}else{for(var G=0,E=this.length;G").appendTo("body");J=H.css("display");if(J==="none"){J="block"}H.remove();m[F]=J}this[G].style.display=n.data(this[G],"olddisplay",J)}}return this}},hide:function(G,H){if(G){return this.animate(s("hide",3),G,H)}else{for(var F=0,E=this.length;F=0;G--){if(F[G].elem==this){if(D){F[G](true)}F.splice(G,1)}}});if(!D){this.dequeue()}return this}});n.each({slideDown:s("show",1),slideUp:s("hide",1),slideToggle:s("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(D,E){n.fn[D]=function(F,G){return this.animate(E,F,G)}});n.extend({speed:function(F,G,E){var D=typeof F==="object"?F:{complete:E||!E&&G||n.isFunction(F)&&F,duration:F,easing:E&&G||G&&!n.isFunction(G)&&G};D.duration=n.fx.off?0:typeof D.duration==="number"?D.duration:n.fx.speeds[D.duration]||n.fx.speeds._default;D.old=D.complete;D.complete=function(){if(D.queue!==false){n(this).dequeue()}if(n.isFunction(D.old)){D.old.call(this)}};return D},easing:{linear:function(F,G,D,E){return D+E*F},swing:function(F,G,D,E){return((-Math.cos(F*Math.PI)/2)+0.5)*E+D}},timers:[],timerId:null,fx:function(E,D,F){this.options=D;this.elem=E;this.prop=F;if(!D.orig){D.orig={}}}});n.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(n.fx.step[this.prop]||n.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(E){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var D=parseFloat(n.css(this.elem,this.prop,E));return D&&D>-10000?D:parseFloat(n.curCSS(this.elem,this.prop))||0},custom:function(H,G,F){this.startTime=e();this.start=H;this.end=G;this.unit=F||this.unit||"px";this.now=this.start;this.pos=this.state=0;var D=this;function E(I){return D.step(I)}E.elem=this.elem;n.timers.push(E);if(E()&&n.timerId==null){n.timerId=setInterval(function(){var J=n.timers;for(var I=0;I=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var D=true;for(var E in this.options.curAnim){if(this.options.curAnim[E]!==true){D=false}}if(D){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(n.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){n(this.elem).hide()}if(this.options.hide||this.options.show){for(var H in this.options.curAnim){n.attr(this.elem.style,H,this.options.orig[H])}}}if(D){this.options.complete.call(this.elem)}return false}else{var I=F-this.startTime;this.state=I/this.options.duration;this.pos=n.easing[this.options.easing||(n.easing.swing?"swing":"linear")](this.state,I,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};n.extend(n.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(D){n.attr(D.elem.style,"opacity",D.now)},_default:function(D){if(D.elem.style&&D.elem.style[D.prop]!=null){D.elem.style[D.prop]=D.now+D.unit}else{D.elem[D.prop]=D.now}}}});if(document.documentElement.getBoundingClientRect){n.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return n.offset.bodyOffset(this[0])}var F=this[0].getBoundingClientRect(),I=this[0].ownerDocument,E=I.body,D=I.documentElement,K=D.clientTop||E.clientTop||0,J=D.clientLeft||E.clientLeft||0,H=F.top+(self.pageYOffset||n.boxModel&&D.scrollTop||E.scrollTop)-K,G=F.left+(self.pageXOffset||n.boxModel&&D.scrollLeft||E.scrollLeft)-J;return{top:H,left:G}}}else{n.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return n.offset.bodyOffset(this[0])}n.offset.initialized||n.offset.initialize();var I=this[0],F=I.offsetParent,E=I,N=I.ownerDocument,L,G=N.documentElement,J=N.body,K=N.defaultView,D=K.getComputedStyle(I,null),M=I.offsetTop,H=I.offsetLeft;while((I=I.parentNode)&&I!==J&&I!==G){L=K.getComputedStyle(I,null);M-=I.scrollTop,H-=I.scrollLeft;if(I===F){M+=I.offsetTop,H+=I.offsetLeft;if(n.offset.doesNotAddBorder&&!(n.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(I.tagName))){M+=parseInt(L.borderTopWidth,10)||0,H+=parseInt(L.borderLeftWidth,10)||0}E=F,F=I.offsetParent}if(n.offset.subtractsBorderForOverflowNotVisible&&L.overflow!=="visible"){M+=parseInt(L.borderTopWidth,10)||0,H+=parseInt(L.borderLeftWidth,10)||0}D=L}if(D.position==="relative"||D.position==="static"){M+=J.offsetTop,H+=J.offsetLeft}if(D.position==="fixed"){M+=Math.max(G.scrollTop,J.scrollTop),H+=Math.max(G.scrollLeft,J.scrollLeft)}return{top:M,left:H}}}n.offset={initialize:function(){if(this.initialized){return}var K=document.body,E=document.createElement("div"),G,F,M,H,L,D,I=K.style.marginTop,J='
';L={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(D in L){E.style[D]=L[D]}E.innerHTML=J;K.insertBefore(E,K.firstChild);G=E.firstChild,F=G.firstChild,H=G.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(F.offsetTop!==5);this.doesAddBorderForTableAndCells=(H.offsetTop===5);G.style.overflow="hidden",G.style.position="relative";this.subtractsBorderForOverflowNotVisible=(F.offsetTop===-5);K.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(K.offsetTop===0);K.style.marginTop=I;K.removeChild(E);this.initialized=true},bodyOffset:function(D){n.offset.initialized||n.offset.initialize();var F=D.offsetTop,E=D.offsetLeft;if(n.offset.doesNotIncludeMarginInBodyOffset){F+=parseInt(n.curCSS(D,"marginTop",true),10)||0,E+=parseInt(n.curCSS(D,"marginLeft",true),10)||0}return{top:F,left:E}}};n.fn.extend({position:function(){var H=0,G=0,E;if(this[0]){var F=this.offsetParent(),I=this.offset(),D=/^body|html$/i.test(F[0].tagName)?{top:0,left:0}:F.offset();I.top-=j(this,"marginTop");I.left-=j(this,"marginLeft");D.top+=j(F,"borderTopWidth");D.left+=j(F,"borderLeftWidth");E={top:I.top-D.top,left:I.left-D.left}}return E},offsetParent:function(){var D=this[0].offsetParent||document.body;while(D&&(!/^body|html$/i.test(D.tagName)&&n.css(D,"position")=="static")){D=D.offsetParent}return n(D)}});n.each(["Left","Top"],function(E,D){var F="scroll"+D;n.fn[F]=function(G){if(!this[0]){return null}return G!==g?this.each(function(){this==l||this==document?l.scrollTo(!E?G:n(l).scrollLeft(),E?G:n(l).scrollTop()):this[F]=G}):this[0]==l||this[0]==document?self[E?"pageYOffset":"pageXOffset"]||n.boxModel&&document.documentElement[F]||document.body[F]:this[0][F]}});n.each(["Height","Width"],function(G,E){var D=G?"Left":"Top",F=G?"Right":"Bottom";n.fn["inner"+E]=function(){return this[E.toLowerCase()]()+j(this,"padding"+D)+j(this,"padding"+F)};n.fn["outer"+E]=function(I){return this["inner"+E]()+j(this,"border"+D+"Width")+j(this,"border"+F+"Width")+(I?j(this,"margin"+D)+j(this,"margin"+F):0)};var H=E.toLowerCase();n.fn[H]=function(I){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+E]||document.body["client"+E]:this[0]==document?Math.max(document.documentElement["client"+E],document.body["scroll"+E],document.documentElement["scroll"+E],document.body["offset"+E],document.documentElement["offset"+E]):I===g?(this.length?n.css(this[0],H):null):this.css(H,typeof I==="string"?I:I+"px")}})})(); \ No newline at end of file +(function(){var Q=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]+['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g,K=0,G=Object.prototype.toString;var F=function(X,T,aa,ab){aa=aa||[];T=T||document;if(T.nodeType!==1&&T.nodeType!==9){return[]}if(!X||typeof X!=="string"){return aa}var Y=[],V,ae,ah,S,ac,U,W=true;Q.lastIndex=0;while((V=Q.exec(X))!==null){Y.push(V[1]);if(V[2]){U=RegExp.rightContext;break}}if(Y.length>1&&L.exec(X)){if(Y.length===2&&H.relative[Y[0]]){ae=I(Y[0]+Y[1],T)}else{ae=H.relative[Y[0]]?[T]:F(Y.shift(),T);while(Y.length){X=Y.shift();if(H.relative[X]){X+=Y.shift()}ae=I(X,ae)}}}else{var ad=ab?{expr:Y.pop(),set:E(ab)}:F.find(Y.pop(),Y.length===1&&T.parentNode?T.parentNode:T,P(T));ae=F.filter(ad.expr,ad.set);if(Y.length>0){ah=E(ae)}else{W=false}while(Y.length){var ag=Y.pop(),af=ag;if(!H.relative[ag]){ag=""}else{af=Y.pop()}if(af==null){af=T}H.relative[ag](ah,af,P(T))}}if(!ah){ah=ae}if(!ah){throw"Syntax error, unrecognized expression: "+(ag||X)}if(G.call(ah)==="[object Array]"){if(!W){aa.push.apply(aa,ah)}else{if(T.nodeType===1){for(var Z=0;ah[Z]!=null;Z++){if(ah[Z]&&(ah[Z]===true||ah[Z].nodeType===1&&J(T,ah[Z]))){aa.push(ae[Z])}}}else{for(var Z=0;ah[Z]!=null;Z++){if(ah[Z]&&ah[Z].nodeType===1){aa.push(ae[Z])}}}}}else{E(ah,aa)}if(U){F(U,T,aa,ab)}return aa};F.matches=function(S,T){return F(S,null,null,T)};F.find=function(Z,S,aa){var Y,W;if(!Z){return[]}for(var V=0,U=H.order.length;V":function(X,T,Y){if(typeof T==="string"&&!/\W/.test(T)){T=Y?T:T.toUpperCase();for(var U=0,S=X.length;U=0){if(!U){S.push(X)}}else{if(U){T[W]=false}}}}return false},ID:function(S){return S[1].replace(/\\/g,"")},TAG:function(T,S){for(var U=0;S[U]===false;U++){}return S[U]&&P(S[U])?T[1]:T[1].toUpperCase()},CHILD:function(S){if(S[1]=="nth"){var T=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(S[2]=="even"&&"2n"||S[2]=="odd"&&"2n+1"||!/\D/.test(S[2])&&"0n+"+S[2]||S[2]);S[2]=(T[1]+(T[2]||1))-0;S[3]=T[3]-0}S[0]="done"+(K++);return S},ATTR:function(T){var S=T[1].replace(/\\/g,"");if(H.attrMap[S]){T[1]=H.attrMap[S]}if(T[2]==="~="){T[4]=" "+T[4]+" "}return T},PSEUDO:function(W,T,U,S,X){if(W[1]==="not"){if(W[3].match(Q).length>1){W[3]=F(W[3],null,null,T)}else{var V=F.filter(W[3],T,U,true^X);if(!U){S.push.apply(S,V)}return false}}else{if(H.match.POS.test(W[0])){return true}}return W},POS:function(S){S.unshift(true);return S}},filters:{enabled:function(S){return S.disabled===false&&S.type!=="hidden"},disabled:function(S){return S.disabled===true},checked:function(S){return S.checked===true},selected:function(S){S.parentNode.selectedIndex;return S.selected===true},parent:function(S){return !!S.firstChild},empty:function(S){return !S.firstChild},has:function(U,T,S){return !!F(S[3],U).length},header:function(S){return/h\d/i.test(S.nodeName)},text:function(S){return"text"===S.type},radio:function(S){return"radio"===S.type},checkbox:function(S){return"checkbox"===S.type},file:function(S){return"file"===S.type},password:function(S){return"password"===S.type},submit:function(S){return"submit"===S.type},image:function(S){return"image"===S.type},reset:function(S){return"reset"===S.type},button:function(S){return"button"===S.type||S.nodeName.toUpperCase()==="BUTTON"},input:function(S){return/input|select|textarea|button/i.test(S.nodeName)}},setFilters:{first:function(T,S){return S===0},last:function(U,T,S,V){return T===V.length-1},even:function(T,S){return S%2===0},odd:function(T,S){return S%2===1},lt:function(U,T,S){return TS[3]-0},nth:function(U,T,S){return S[3]-0==T},eq:function(U,T,S){return S[3]-0==T}},filter:{CHILD:function(S,V){var Y=V[1],Z=S.parentNode;var X=V[0];if(Z&&(!Z[X]||!S.nodeIndex)){var W=1;for(var T=Z.firstChild;T;T=T.nextSibling){if(T.nodeType==1){T.nodeIndex=W++}}Z[X]=W-1}if(Y=="first"){return S.nodeIndex==1}else{if(Y=="last"){return S.nodeIndex==Z[X]}else{if(Y=="only"){return Z[X]==1}else{if(Y=="nth"){var ab=false,U=V[2],aa=V[3];if(U==1&&aa==0){return true}if(U==0){if(S.nodeIndex==aa){ab=true}}else{if((S.nodeIndex-aa)%U==0&&(S.nodeIndex-aa)/U>=0){ab=true}}return ab}}}}},PSEUDO:function(Y,U,V,Z){var T=U[1],W=H.filters[T];if(W){return W(Y,V,U,Z)}else{if(T==="contains"){return(Y.textContent||Y.innerText||"").indexOf(U[3])>=0}else{if(T==="not"){var X=U[3];for(var V=0,S=X.length;V=0:V==="~="?(" "+X+" ").indexOf(T)>=0:!U[4]?S:V==="!="?X!=T:V==="^="?X.indexOf(T)===0:V==="$="?X.substr(X.length-T.length)===T:V==="|="?X===T||X.substr(0,T.length+1)===T+"-":false},POS:function(W,T,U,X){var S=T[2],V=H.setFilters[S];if(V){return V(W,U,T,X)}}}};var L=H.match.POS;for(var N in H.match){H.match[N]=RegExp(H.match[N].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(T,S){T=Array.prototype.slice.call(T);if(S){S.push.apply(S,T);return S}return T};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(M){E=function(W,V){var T=V||[];if(G.call(W)==="[object Array]"){Array.prototype.push.apply(T,W)}else{if(typeof W.length==="number"){for(var U=0,S=W.length;U";var S=document.documentElement;S.insertBefore(T,S.firstChild);if(!!document.getElementById(U)){H.find.ID=function(W,X,Y){if(typeof X.getElementById!=="undefined"&&!Y){var V=X.getElementById(W[1]);return V?V.id===W[1]||typeof V.getAttributeNode!=="undefined"&&V.getAttributeNode("id").nodeValue===W[1]?[V]:g:[]}};H.filter.ID=function(X,V){var W=typeof X.getAttributeNode!=="undefined"&&X.getAttributeNode("id");return X.nodeType===1&&W&&W.nodeValue===V}}S.removeChild(T)})();(function(){var S=document.createElement("div");S.appendChild(document.createComment(""));if(S.getElementsByTagName("*").length>0){H.find.TAG=function(T,X){var W=X.getElementsByTagName(T[1]);if(T[1]==="*"){var V=[];for(var U=0;W[U];U++){if(W[U].nodeType===1){V.push(W[U])}}W=V}return W}}S.innerHTML="";if(S.firstChild&&S.firstChild.getAttribute("href")!=="#"){H.attrHandle.href=function(T){return T.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var S=F,T=document.createElement("div");T.innerHTML="

";if(T.querySelectorAll&&T.querySelectorAll(".TEST").length===0){return}F=function(X,W,U,V){W=W||document;if(!V&&W.nodeType===9&&!P(W)){try{return E(W.querySelectorAll(X),U)}catch(Y){}}return S(X,W,U,V)};F.find=S.find;F.filter=S.filter;F.selectors=S.selectors;F.matches=S.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){H.order.splice(1,0,"CLASS");H.find.CLASS=function(S,T){return T.getElementsByClassName(S[1])}}function O(T,Z,Y,ac,aa,ab){for(var W=0,U=ac.length;W0){W=S;break}}}S=S[T]}ab[V]=W}}}var J=document.compareDocumentPosition?function(T,S){return T.compareDocumentPosition(S)&16}:function(T,S){return T!==S&&(T.contains?T.contains(S):true)};var P=function(S){return S.nodeType===9&&S.documentElement.nodeName!=="HTML"||!!S.ownerDocument&&P(S.ownerDocument)};var I=function(S,Z){var V=[],W="",X,U=Z.nodeType?[Z]:Z;while((X=H.match.PSEUDO.exec(S))){W+=X[0];S=S.replace(H.match.PSEUDO,"")}S=H.relative[S]?S+"*":S;for(var Y=0,T=U.length;Y=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}this[H].style.display=o.data(this[H],"olddisplay",K)}}return this}},hide:function(H,I){if(H){return this.animate(t("hide",3),H,I)}else{for(var G=0,F=this.length;G=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)==1){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(H,F){var E=H?"Left":"Top",G=H?"Right":"Bottom";o.fn["inner"+F]=function(){return this[F.toLowerCase()]()+j(this,"padding"+E)+j(this,"padding"+G)};o.fn["outer"+F]=function(J){return this["inner"+F]()+j(this,"border"+E+"Width")+j(this,"border"+G+"Width")+(J?j(this,"margin"+E)+j(this,"margin"+G):0)};var I=F.toLowerCase();o.fn[I]=function(J){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+F]||document.body["client"+F]:this[0]==document?Math.max(document.documentElement["client"+F],document.body["scroll"+F],document.documentElement["scroll"+F],document.body["offset"+F],document.documentElement["offset"+F]):J===g?(this.length?o.css(this[0],I):null):this.css(I,typeof J==="string"?J:J+"px")}})})(); \ No newline at end of file From 20bfa6daed290378231a2a4fd8b7d9fd98fed1ce Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 12:39:03 -0500 Subject: [PATCH 011/189] upgrade jQuery from 1.3 to 1.3.1 --- js/jquery.js | 222 +++++++++++++++++++++++------------------------ js/jquery.min.js | 12 +-- 2 files changed, 117 insertions(+), 117 deletions(-) diff --git a/js/jquery.js b/js/jquery.js index fc06ace272..94e9c1755e 100644 --- a/js/jquery.js +++ b/js/jquery.js @@ -1,13 +1,13 @@ /*! - * jQuery JavaScript Library v1.3 + * jQuery JavaScript Library v1.3.1 * http://jquery.com/ * * Copyright (c) 2009 John Resig * Dual licensed under the MIT and GPL licenses. * http://docs.jquery.com/License * - * Date: 2009-01-13 12:50:31 -0500 (Tue, 13 Jan 2009) - * Revision: 6104 + * Date: 2009-01-21 20:42:16 -0500 (Wed, 21 Jan 2009) + * Revision: 6158 */ (function(){ @@ -60,20 +60,16 @@ jQuery.fn = jQuery.prototype = { else { var elem = document.getElementById( match[3] ); - // Make sure an element was located - if ( elem ){ - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id != match[3] ) - return jQuery().find( selector ); + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem && elem.id != match[3] ) + return jQuery().find( selector ); - // Otherwise, we inject the element directly into the jQuery object - var ret = jQuery( elem ); - ret.context = document; - ret.selector = selector; - return ret; - } - selector = []; + // Otherwise, we inject the element directly into the jQuery object + var ret = jQuery( elem || [] ); + ret.context = document; + ret.selector = selector; + return ret; } // HANDLE: $(expr, [context]) @@ -99,7 +95,7 @@ jQuery.fn = jQuery.prototype = { selector: "", // The current version of jQuery being used - jquery: "1.3", + jquery: "1.3.1", // The number of elements contained in the matched element set size: function() { @@ -634,8 +630,8 @@ jQuery.extend({ // check if an element is in a (or is an) XML document isXMLDoc: function( elem ) { - return elem.documentElement && !elem.body || - elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument ); }, // Evalulates a script in a global context @@ -725,7 +721,7 @@ jQuery.extend({ // internal only, use hasClass("class") 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,9 +995,11 @@ jQuery.extend({ var attributeNode = elem.getAttributeNode( "tabIndex" ); return attributeNode && attributeNode.specified ? attributeNode.value - : elem.nodeName.match(/^(a|area|button|input|object|select|textarea)$/i) + : elem.nodeName.match(/(button|input|object|select|textarea)/i) ? 0 - : undefined; + : elem.nodeName.match(/^(a|area)$/i) && elem.href + ? 0 + : undefined; } return elem[ name ]; @@ -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 * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ (function(){ -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|[^[\]]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g, +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]+['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g, done = 0, 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] ] ) { - var later = "", match; - - // 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 ) ); + set = posProcess( parts[0] + parts[1], context ); } else { set = Expr.relative[ parts[0] ] ? [ context ] : Sizzle( parts.shift(), context ); while ( parts.length ) { - var tmpSet = []; - selector = parts.shift(); + if ( Expr.relative[ selector ] ) selector += parts.shift(); - for ( var i = 0, l = set.length; i < l; i++ ) { - Sizzle( selector, set[i], tmpSet ); - } - - set = tmpSet; + set = posProcess( selector, set ); } } } else { var ret = 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 ); if ( parts.length > 0 ) { @@ -1531,7 +1516,7 @@ Sizzle.matches = function(expr, set){ return Sizzle(expr, null, null, set); }; -Sizzle.find = function(expr, context){ +Sizzle.find = function(expr, context, isXML){ var set, match; if ( !expr ) { @@ -1546,7 +1531,7 @@ Sizzle.find = function(expr, context){ if ( left.substr( left.length - 1 ) !== "\\" ) { match[1] = (match[1] || "").replace(/\\/g, ""); - set = Expr.find[ type ]( match, context ); + set = Expr.find[ type ]( match, context, isXML ); if ( set != null ) { expr = expr.replace( Expr.match[ type ], "" ); break; @@ -1568,7 +1553,7 @@ Sizzle.filter = function(expr, set, inplace, not){ while ( expr && set.length ) { for ( var type in Expr.filter ) { 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; if ( curLoop == result ) { @@ -1582,26 +1567,13 @@ Sizzle.filter = function(expr, set, inplace, not){ anyFound = found = true; } else if ( match === true ) { 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 ) { - for ( var i = 0; (item = curLoop[i]) !== undefined; i++ ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { if ( item ) { - if ( goodArray && item != goodArray[goodPos] ) { - goodPos++; - } - - found = filter( item, match, goodPos, goodArray ); + found = filter( item, match, i, curLoop ); var pass = not ^ !!found; if ( inplace && found != null ) { @@ -1739,14 +1711,16 @@ var Expr = Sizzle.selectors = { } }, find: { - ID: function(match, context){ - if ( context.getElementById ) { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); return m ? [m] : []; } }, - NAME: function(match, context){ - return context.getElementsByName ? context.getElementsByName(match[1]) : null; + NAME: function(match, context, isXML){ + if ( typeof context.getElementsByName !== "undefined" && !isXML ) { + return context.getElementsByName(match[1]); + } }, TAG: function(match, context){ return context.getElementsByTagName(match[1]); @@ -1756,12 +1730,15 @@ var Expr = Sizzle.selectors = { CLASS: function(match, curLoop, inplace, result, not){ match = " " + match[1].replace(/\\/g, "") + " "; - for ( var i = 0; curLoop[i]; i++ ) { - if ( not ^ (" " + curLoop[i].className + " ").indexOf(match) >= 0 ) { - if ( !inplace ) - result.push( curLoop[i] ); - } else if ( inplace ) { - curLoop[i] = false; + var elem; + for ( var i = 0; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (" " + elem.className + " ").indexOf(match) >= 0 ) { + if ( !inplace ) + result.push( elem ); + } else if ( inplace ) { + curLoop[i] = false; + } } } @@ -1771,8 +1748,8 @@ var Expr = Sizzle.selectors = { return match[1].replace(/\\/g, ""); }, TAG: function(match, curLoop){ - for ( var i = 0; !curLoop[i]; i++ ){} - return isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); + for ( var i = 0; curLoop[i] === false; i++ ){} + return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); }, CHILD: function(match){ if ( match[1] == "nth" ) { @@ -1792,7 +1769,7 @@ var Expr = Sizzle.selectors = { return match; }, ATTR: function(match){ - var name = match[1]; + var name = match[1].replace(/\\/g, ""); if ( Expr.attrMap[name] ) { match[1] = Expr.attrMap[name]; @@ -1916,7 +1893,7 @@ var Expr = Sizzle.selectors = { CHILD: function(elem, match){ var type = match[1], parent = elem.parentNode; - var doneName = "child" + parent.childNodes.length; + var doneName = match[0]; if ( parent && (!parent[ doneName ] || !elem.nodeIndex) ) { var count = 1; @@ -1985,7 +1962,7 @@ var Expr = Sizzle.selectors = { 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]; return result == null ? - false : + type === "!=" : type === "=" ? value === check : type === "*=" ? @@ -2014,6 +1991,8 @@ var Expr = Sizzle.selectors = { } }; +var origPOS = Expr.match.POS; + for ( var type in Expr.match ) { Expr.match[ type ] = RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); } @@ -2072,15 +2051,15 @@ try { // The workaround has to do additional checks after a getElementById // Which slows things down for other browsers (hence the branching) if ( !!document.getElementById( id ) ) { - Expr.find.ID = function(match, context){ - if ( context.getElementById ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { 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){ - var node = elem.getAttributeNode && elem.getAttributeNode("id"); + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); return elem.nodeType === 1 && node && node.nodeValue === match; }; } @@ -2120,7 +2099,7 @@ try { // Check to see if an attribute returns normalized href attributes div.innerHTML = ""; - if ( div.firstChild.getAttribute("href") !== "#" ) { + if ( div.firstChild && div.firstChild.getAttribute("href") !== "#" ) { Expr.attrHandle.href = function(elem){ return elem.getAttribute("href", 2); }; @@ -2128,12 +2107,21 @@ try { })(); if ( document.querySelectorAll ) (function(){ - var oldSizzle = Sizzle; + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

"; + + // 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){ 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 { return makeArray( context.querySelectorAll(query), extra ); } catch(e){} @@ -2148,7 +2136,7 @@ if ( document.querySelectorAll ) (function(){ Sizzle.matches = oldSizzle.matches; })(); -if ( document.documentElement.getElementsByClassName ) { +if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) { Expr.order.splice(1, 0, "CLASS"); Expr.find.CLASS = function(match, context) { return context.getElementsByClassName(match[1]); @@ -2229,8 +2217,28 @@ var contains = document.compareDocumentPosition ? function(a, b){ }; var isXML = function(elem){ - return elem.documentElement && !elem.body || - elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!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 @@ -2681,13 +2689,13 @@ jQuery.Event = function( src ){ if( src && src.type ){ this.originalEvent = src; this.type = src.type; - this.timeStamp = src.timeStamp; // Event type }else this.type = src; - if( !this.timeStamp ) - this.timeStamp = now(); + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = now(); // Mark it as fixed this[expando] = true; @@ -2876,9 +2884,8 @@ function liveHandler( event ){ }); jQuery.each(elems, function(){ - if ( !event.isImmediatePropagationStopped() && - this.fn.call(this.elem, event, this.fn.data) === false ) - stop = false; + if ( this.fn.call(this.elem, event, this.fn.data) === false ) + stop = false; }); return stop; @@ -2942,7 +2949,7 @@ function bindReady(){ // If IE and not an iframe // 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; try { @@ -3477,6 +3484,9 @@ jQuery.extend({ // Fire the complete handlers complete(); + if ( isTimeout ) + xhr.abort(); + // Stop memory leaks if ( s.async ) xhr = null; @@ -3491,14 +3501,8 @@ jQuery.extend({ if ( s.timeout > 0 ) setTimeout(function(){ // Check to see if the request is still happening - if ( xhr ) { - if( !requestDone ) - onreadystatechange( "timeout" ); - - // Cancel the request - if ( xhr ) - xhr.abort(); - } + if ( xhr && !requestDone ) + onreadystatechange( "timeout" ); }, s.timeout); } @@ -3637,6 +3641,7 @@ jQuery.extend({ }); var elemdisplay = {}, + timerId, fxAttrs = [ // height animations [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], @@ -3859,7 +3864,6 @@ jQuery.extend({ }, timers: [], - timerId: null, fx: function( elem, options, prop ){ this.options = options; @@ -3911,10 +3915,8 @@ jQuery.fx.prototype = { t.elem = this.elem; - jQuery.timers.push(t); - - if ( t() && jQuery.timerId == null ) { - jQuery.timerId = setInterval(function(){ + if ( t() && jQuery.timers.push(t) == 1 ) { + timerId = setInterval(function(){ var timers = jQuery.timers; for ( var i = 0; i < timers.length; i++ ) @@ -3922,8 +3924,7 @@ jQuery.fx.prototype = { timers.splice(i--, 1); if ( !timers.length ) { - clearInterval( jQuery.timerId ); - jQuery.timerId = null; + clearInterval( timerId ); } }, 13); } @@ -3989,11 +3990,10 @@ jQuery.fx.prototype = { if ( this.options.hide || this.options.show ) for ( var p in this.options.curAnim ) jQuery.attr(this.elem.style, p, this.options.orig[p]); - } - - if ( done ) + // Execute the complete function this.options.complete.call( this.elem ); + } return false; } else { @@ -4087,7 +4087,7 @@ jQuery.offset = { initialize: function() { if ( this.initialized ) return; var body = document.body, container = document.createElement('div'), innerDiv, checkDiv, table, td, rules, prop, bodyMarginTop = body.style.marginTop, - html = '
'; + html = '
'; 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]; diff --git a/js/jquery.min.js b/js/jquery.min.js index 396646c842..c327fae812 100644 --- a/js/jquery.min.js +++ b/js/jquery.min.js @@ -1,19 +1,19 @@ /* - * jQuery JavaScript Library v1.3 + * jQuery JavaScript Library v1.3.1 * http://jquery.com/ * * Copyright (c) 2009 John Resig * Dual licensed under the MIT and GPL licenses. * http://docs.jquery.com/License * - * Date: 2009-01-13 12:50:31 -0500 (Tue, 13 Jan 2009) - * Revision: 6104 + * Date: 2009-01-21 20:42:16 -0500 (Wed, 21 Jan 2009) + * Revision: 6158 */ -(function(){var l=this,g,x=l.jQuery,o=l.$,n=l.jQuery=l.$=function(D,E){return new n.fn.init(D,E)},C=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;n.fn=n.prototype={init:function(D,G){D=D||document;if(D.nodeType){this[0]=D;this.length=1;this.context=D;return this}if(typeof D==="string"){var F=C.exec(D);if(F&&(F[1]||!G)){if(F[1]){D=n.clean([F[1]],G)}else{var H=document.getElementById(F[3]);if(H){if(H.id!=F[3]){return n().find(D)}var E=n(H);E.context=document;E.selector=D;return E}D=[]}}else{return n(G).find(D)}}else{if(n.isFunction(D)){return n(document).ready(D)}}if(D.selector&&D.context){this.selector=D.selector;this.context=D.context}return this.setArray(n.makeArray(D))},selector:"",jquery:"1.3",size:function(){return this.length},get:function(D){return D===g?n.makeArray(this):this[D]},pushStack:function(E,G,D){var F=n(E);F.prevObject=this;F.context=this.context;if(G==="find"){F.selector=this.selector+(this.selector?" ":"")+D}else{if(G){F.selector=this.selector+"."+G+"("+D+")"}}return F},setArray:function(D){this.length=0;Array.prototype.push.apply(this,D);return this},each:function(E,D){return n.each(this,E,D)},index:function(D){return n.inArray(D&&D.jquery?D[0]:D,this)},attr:function(E,G,F){var D=E;if(typeof E==="string"){if(G===g){return this[0]&&n[F||"attr"](this[0],E)}else{D={};D[E]=G}}return this.each(function(H){for(E in D){n.attr(F?this.style:this,E,n.prop(this,D[E],F,H,E))}})},css:function(D,E){if((D=="width"||D=="height")&&parseFloat(E)<0){E=g}return this.attr(D,E,"curCSS")},text:function(E){if(typeof E!=="object"&&E!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(E))}var D="";n.each(E||this,function(){n.each(this.childNodes,function(){if(this.nodeType!=8){D+=this.nodeType!=1?this.nodeValue:n.fn.text([this])}})});return D},wrapAll:function(D){if(this[0]){var E=n(D,this[0].ownerDocument).clone();if(this[0].parentNode){E.insertBefore(this[0])}E.map(function(){var F=this;while(F.firstChild){F=F.firstChild}return F}).append(this)}return this},wrapInner:function(D){return this.each(function(){n(this).contents().wrapAll(D)})},wrap:function(D){return this.each(function(){n(this).wrapAll(D)})},append:function(){return this.domManip(arguments,true,function(D){if(this.nodeType==1){this.appendChild(D)}})},prepend:function(){return this.domManip(arguments,true,function(D){if(this.nodeType==1){this.insertBefore(D,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(D){this.parentNode.insertBefore(D,this)})},after:function(){return this.domManip(arguments,false,function(D){this.parentNode.insertBefore(D,this.nextSibling)})},end:function(){return this.prevObject||n([])},push:[].push,find:function(D){if(this.length===1&&!/,/.test(D)){var F=this.pushStack([],"find",D);F.length=0;n.find(D,this[0],F);return F}else{var E=n.map(this,function(G){return n.find(D,G)});return this.pushStack(/[^+>] [^+>]/.test(D)?n.unique(E):E,"find",D)}},clone:function(E){var D=this.map(function(){if(!n.support.noCloneEvent&&!n.isXMLDoc(this)){var H=this.cloneNode(true),G=document.createElement("div");G.appendChild(H);return n.clean([G.innerHTML])[0]}else{return this.cloneNode(true)}});var F=D.find("*").andSelf().each(function(){if(this[h]!==g){this[h]=null}});if(E===true){this.find("*").andSelf().each(function(H){if(this.nodeType==3){return}var G=n.data(this,"events");for(var J in G){for(var I in G[J]){n.event.add(F[H],J,G[J][I],G[J][I].data)}}})}return D},filter:function(D){return this.pushStack(n.isFunction(D)&&n.grep(this,function(F,E){return D.call(F,E)})||n.multiFilter(D,n.grep(this,function(E){return E.nodeType===1})),"filter",D)},closest:function(D){var E=n.expr.match.POS.test(D)?n(D):null;return this.map(function(){var F=this;while(F&&F.ownerDocument){if(E?E.index(F)>-1:n(F).is(D)){return F}F=F.parentNode}})},not:function(D){if(typeof D==="string"){if(f.test(D)){return this.pushStack(n.multiFilter(D,this,true),"not",D)}else{D=n.multiFilter(D,this)}}var E=D.length&&D[D.length-1]!==g&&!D.nodeType;return this.filter(function(){return E?n.inArray(this,D)<0:this!=D})},add:function(D){return this.pushStack(n.unique(n.merge(this.get(),typeof D==="string"?n(D):n.makeArray(D))))},is:function(D){return !!D&&n.multiFilter(D,this).length>0},hasClass:function(D){return !!D&&this.is("."+D)},val:function(J){if(J===g){var D=this[0];if(D){if(n.nodeName(D,"option")){return(D.attributes.value||{}).specified?D.value:D.text}if(n.nodeName(D,"select")){var H=D.selectedIndex,K=[],L=D.options,G=D.type=="select-one";if(H<0){return null}for(var E=G?H:0,I=G?H+1:L.length;E=0||n.inArray(this.name,J)>=0)}else{if(n.nodeName(this,"select")){var M=n.makeArray(J);n("option",this).each(function(){this.selected=(n.inArray(this.value,M)>=0||n.inArray(this.text,M)>=0)});if(!M.length){this.selectedIndex=-1}}else{this.value=J}}})},html:function(D){return D===g?(this[0]?this[0].innerHTML:null):this.empty().append(D)},replaceWith:function(D){return this.after(D).remove()},eq:function(D){return this.slice(D,+D+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(D){return this.pushStack(n.map(this,function(F,E){return D.call(F,E,F)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=n.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild,D=this.length>1?I.cloneNode(true):I;if(H){for(var G=0,E=this.length;G0?D.cloneNode(true):I)}}if(F){n.each(F,y)}}return this;function K(N,O){return M&&n.nodeName(N,"table")&&n.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};n.fn.init.prototype=n.fn;function y(D,E){if(E.src){n.ajax({url:E.src,async:false,dataType:"script"})}else{n.globalEval(E.text||E.textContent||E.innerHTML||"")}if(E.parentNode){E.parentNode.removeChild(E)}}function e(){return +new Date}n.extend=n.fn.extend=function(){var I=arguments[0]||{},G=1,H=arguments.length,D=false,F;if(typeof I==="boolean"){D=I;I=arguments[1]||{};G=2}if(typeof I!=="object"&&!n.isFunction(I)){I={}}if(H==G){I=this;--G}for(;G-1}},swap:function(G,F,H){var D={};for(var E in F){D[E]=G.style[E];G.style[E]=F[E]}H.call(G);for(var E in F){G.style[E]=D[E]}},css:function(F,D,H){if(D=="width"||D=="height"){var J,E={position:"absolute",visibility:"hidden",display:"block"},I=D=="width"?["Left","Right"]:["Top","Bottom"];function G(){J=D=="width"?F.offsetWidth:F.offsetHeight;var L=0,K=0;n.each(I,function(){L+=parseFloat(n.curCSS(F,"padding"+this,true))||0;K+=parseFloat(n.curCSS(F,"border"+this+"Width",true))||0});J-=Math.round(L+K)}if(n(F).is(":visible")){G()}else{n.swap(F,E,G)}return Math.max(0,J)}return n.curCSS(F,D,H)},curCSS:function(H,E,F){var K,D=H.style;if(E=="opacity"&&!n.support.opacity){K=n.attr(D,"opacity");return K==""?"1":K}if(E.match(/float/i)){E=v}if(!F&&D&&D[E]){K=D[E]}else{if(p.getComputedStyle){if(E.match(/float/i)){E="float"}E=E.replace(/([A-Z])/g,"-$1").toLowerCase();var L=p.getComputedStyle(H,null);if(L){K=L.getPropertyValue(E)}if(E=="opacity"&&K==""){K="1"}}else{if(H.currentStyle){var I=E.replace(/\-(\w)/g,function(M,N){return N.toUpperCase()});K=H.currentStyle[E]||H.currentStyle[I];if(!/^\d+(px)?$/i.test(K)&&/^\d/.test(K)){var G=D.left,J=H.runtimeStyle.left;H.runtimeStyle.left=H.currentStyle.left;D.left=K||0;K=D.pixelLeft+"px";D.left=G;H.runtimeStyle.left=J}}}}return K},clean:function(E,J,H){J=J||document;if(typeof J.createElement==="undefined"){J=J.ownerDocument||J[0]&&J[0].ownerDocument||document}if(!H&&E.length===1&&typeof E[0]==="string"){var G=/^<(\w+)\s*\/?>$/.exec(E[0]);if(G){return[J.createElement(G[1])]}}var F=[],D=[],K=J.createElement("div");n.each(E,function(O,Q){if(typeof Q==="number"){Q+=""}if(!Q){return}if(typeof Q==="string"){Q=Q.replace(/(<(\w+)[^>]*?)\/>/g,function(S,T,R){return R.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?S:T+">"});var N=n.trim(Q).toLowerCase();var P=!N.indexOf("",""]||!N.indexOf("",""]||N.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!N.indexOf("",""]||(!N.indexOf("",""]||!N.indexOf("",""]||!n.support.htmlSerialize&&[1,"div
","
"]||[0,"",""];K.innerHTML=P[1]+Q+P[2];while(P[0]--){K=K.lastChild}if(!n.support.tbody){var M=!N.indexOf(""&&N.indexOf("=0;--L){if(n.nodeName(M[L],"tbody")&&!M[L].childNodes.length){M[L].parentNode.removeChild(M[L])}}}if(!n.support.leadingWhitespace&&/^\s/.test(Q)){K.insertBefore(J.createTextNode(Q.match(/^\s*/)[0]),K.firstChild)}Q=n.makeArray(K.childNodes)}if(Q.nodeType){F.push(Q)}else{F=n.merge(F,Q)}});if(H){for(var I=0;F[I];I++){if(n.nodeName(F[I],"script")&&(!F[I].type||F[I].type.toLowerCase()==="text/javascript")){D.push(F[I].parentNode?F[I].parentNode.removeChild(F[I]):F[I])}else{if(F[I].nodeType===1){F.splice.apply(F,[I+1,0].concat(n.makeArray(F[I].getElementsByTagName("script"))))}H.appendChild(F[I])}}return D}return F},attr:function(I,F,J){if(!I||I.nodeType==3||I.nodeType==8){return g}var G=!n.isXMLDoc(I),K=J!==g;F=G&&n.props[F]||F;if(I.tagName){var E=/href|src|style/.test(F);if(F=="selected"&&I.parentNode){I.parentNode.selectedIndex}if(F in I&&G&&!E){if(K){if(F=="type"&&n.nodeName(I,"input")&&I.parentNode){throw"type property can't be changed"}I[F]=J}if(n.nodeName(I,"form")&&I.getAttributeNode(F)){return I.getAttributeNode(F).nodeValue}if(F=="tabIndex"){var H=I.getAttributeNode("tabIndex");return H&&H.specified?H.value:I.nodeName.match(/^(a|area|button|input|object|select|textarea)$/i)?0:g}return I[F]}if(!n.support.style&&G&&F=="style"){return n.attr(I.style,"cssText",J)}if(K){I.setAttribute(F,""+J)}var D=!n.support.hrefNormalized&&G&&E?I.getAttribute(F,2):I.getAttribute(F);return D===null?g:D}if(!n.support.opacity&&F=="opacity"){if(K){I.zoom=1;I.filter=(I.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(J)+""=="NaN"?"":"alpha(opacity="+J*100+")")}return I.filter&&I.filter.indexOf("opacity=")>=0?(parseFloat(I.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}F=F.replace(/-([a-z])/ig,function(L,M){return M.toUpperCase()});if(K){I[F]=J}return I[F]},trim:function(D){return(D||"").replace(/^\s+|\s+$/g,"")},makeArray:function(F){var D=[];if(F!=null){var E=F.length;if(E==null||typeof F==="string"||n.isFunction(F)||F.setInterval){D[0]=F}else{while(E){D[--E]=F[E]}}}return D},inArray:function(F,G){for(var D=0,E=G.length;D*",this).remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(D,E){n.fn[D]=function(){return this.each(E,arguments)}});function j(D,E){return D[0]&&parseInt(n.curCSS(D[0],E,true),10)||0}var h="jQuery"+e(),u=0,z={};n.extend({cache:{},data:function(E,D,F){E=E==l?z:E;var G=E[h];if(!G){G=E[h]=++u}if(D&&!n.cache[G]){n.cache[G]={}}if(F!==g){n.cache[G][D]=F}return D?n.cache[G][D]:G},removeData:function(E,D){E=E==l?z:E;var G=E[h];if(D){if(n.cache[G]){delete n.cache[G][D];D="";for(D in n.cache[G]){break}if(!D){n.removeData(E)}}}else{try{delete E[h]}catch(F){if(E.removeAttribute){E.removeAttribute(h)}}delete n.cache[G]}},queue:function(E,D,G){if(E){D=(D||"fx")+"queue";var F=n.data(E,D);if(!F||n.isArray(G)){F=n.data(E,D,n.makeArray(G))}else{if(G){F.push(G)}}}return F},dequeue:function(G,F){var D=n.queue(G,F),E=D.shift();if(!F||F==="fx"){E=D[0]}if(E!==g){E.call(G)}}});n.fn.extend({data:function(D,F){var G=D.split(".");G[1]=G[1]?"."+G[1]:"";if(F===g){var E=this.triggerHandler("getData"+G[1]+"!",[G[0]]);if(E===g&&this.length){E=n.data(this[0],D)}return E===g&&G[1]?this.data(G[0]):E}else{return this.trigger("setData"+G[1]+"!",[G[0],F]).each(function(){n.data(this,D,F)})}},removeData:function(D){return this.each(function(){n.removeData(this,D)})},queue:function(D,E){if(typeof D!=="string"){E=D;D="fx"}if(E===g){return n.queue(this[0],D)}return this.each(function(){var F=n.queue(this,D,E);if(D=="fx"&&F.length==1){F[0].call(this)}})},dequeue:function(D){return this.each(function(){n.dequeue(this,D)})}}); +(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.makeArray(E))},selector:"",jquery:"1.3.1",size:function(){return this.length},get:function(E){return E===g?o.makeArray(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,find:function(E){if(this.length===1&&!/,/.test(E)){var G=this.pushStack([],"find",E);G.length=0;o.find(E,this[0],G);return G}else{var F=o.map(this,function(H){return o.find(E,H)});return this.pushStack(/[^+>] [^+>]/.test(E)?o.unique(F):F,"find",E)}},clone:function(F){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.cloneNode(true),H=document.createElement("div");H.appendChild(I);return o.clean([H.innerHTML])[0]}else{return this.cloneNode(true)}});var G=E.find("*").andSelf().each(function(){if(this[h]!==g){this[h]=null}});if(F===true){this.find("*").andSelf().each(function(I){if(this.nodeType==3){return}var H=o.data(this,"events");for(var K in H){for(var J in H[K]){o.event.add(G[I],K,H[K][J],H[K][J].data)}}})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var F=o.expr.match.POS.test(E)?o(E):null;return this.map(function(){var G=this;while(G&&G.ownerDocument){if(F?F.index(G)>-1:o(G).is(E)){return G}G=G.parentNode}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML:null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(K,N,M){if(this[0]){var J=(this[0].ownerDocument||this[0]).createDocumentFragment(),G=o.clean(K,(this[0].ownerDocument||this[0]),J),I=J.firstChild,E=this.length>1?J.cloneNode(true):J;if(I){for(var H=0,F=this.length;H0?E.cloneNode(true):J)}}if(G){o.each(G,z)}}return this;function L(O,P){return N&&o.nodeName(O,"table")&&o.nodeName(P,"tr")?(O.getElementsByTagName("tbody")[0]||O.appendChild(O.ownerDocument.createElement("tbody"))):O}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(G,E,I){if(E=="width"||E=="height"){var K,F={position:"absolute",visibility:"hidden",display:"block"},J=E=="width"?["Left","Right"]:["Top","Bottom"];function H(){K=E=="width"?G.offsetWidth:G.offsetHeight;var M=0,L=0;o.each(J,function(){M+=parseFloat(o.curCSS(G,"padding"+this,true))||0;L+=parseFloat(o.curCSS(G,"border"+this+"Width",true))||0});K-=Math.round(M+L)}if(o(G).is(":visible")){H()}else{o.swap(G,F,H)}return Math.max(0,K)}return o.curCSS(G,E,I)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,R){if(typeof R==="number"){R+=""}if(!R){return}if(typeof R==="string"){R=R.replace(/(<(\w+)[^>]*?)\/>/g,function(T,U,S){return S.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?T:U+">"});var O=o.trim(R).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
","
"]||[0,"",""];L.innerHTML=Q[1]+R+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var N=!O.indexOf(""&&O.indexOf("=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(R)){L.insertBefore(K.createTextNode(R.match(/^\s*/)[0]),L.firstChild)}R=o.makeArray(L.childNodes)}if(R.nodeType){G.push(R)}else{G=o.merge(G,R)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E*",this).remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); /* - * Sizzle CSS Selector Engine - v0.9.1 + * Sizzle CSS Selector Engine - v0.9.3 * Copyright 2009, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ -(function(){var N=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|[^[\]]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g,I=0,F=Object.prototype.toString;var E=function(ae,S,aa,V){aa=aa||[];S=S||document;if(S.nodeType!==1&&S.nodeType!==9){return[]}if(!ae||typeof ae!=="string"){return aa}var ab=[],ac,Y,ah,ag,Z,R,Q=true;N.lastIndex=0;while((ac=N.exec(ae))!==null){ab.push(ac[1]);if(ac[2]){R=RegExp.rightContext;break}}if(ab.length>1&&G.match.POS.exec(ae)){if(ab.length===2&&G.relative[ab[0]]){var U="",X;while((X=G.match.POS.exec(ae))){U+=X[0];ae=ae.replace(G.match.POS,"")}Y=E.filter(U,E(/\s$/.test(ae)?ae+"*":ae,S))}else{Y=G.relative[ab[0]]?[S]:E(ab.shift(),S);while(ab.length){var P=[];ae=ab.shift();if(G.relative[ae]){ae+=ab.shift()}for(var af=0,ad=Y.length;af0){ah=D(Y)}else{Q=false}while(ab.length){var T=ab.pop(),W=T;if(!G.relative[T]){T=""}else{W=ab.pop()}if(W==null){W=S}G.relative[T](ah,W,M(S))}}if(!ah){ah=Y}if(!ah){throw"Syntax error, unrecognized expression: "+(T||ae)}if(F.call(ah)==="[object Array]"){if(!Q){aa.push.apply(aa,ah)}else{if(S.nodeType===1){for(var af=0;ah[af]!=null;af++){if(ah[af]&&(ah[af]===true||ah[af].nodeType===1&&H(S,ah[af]))){aa.push(Y[af])}}}else{for(var af=0;ah[af]!=null;af++){if(ah[af]&&ah[af].nodeType===1){aa.push(Y[af])}}}}}else{D(ah,aa)}if(R){E(R,S,aa,V)}return aa};E.matches=function(P,Q){return E(P,null,null,Q)};E.find=function(V,S){var W,Q;if(!V){return[]}for(var R=0,P=G.order.length;R":function(U,Q,V){if(typeof Q==="string"&&!/\W/.test(Q)){Q=V?Q:Q.toUpperCase();for(var R=0,P=U.length;R=0){if(!R){P.push(Q[T])}}else{if(R){Q[T]=false}}}return false},ID:function(P){return P[1].replace(/\\/g,"")},TAG:function(Q,P){for(var R=0;!P[R];R++){}return M(P[R])?Q[1]:Q[1].toUpperCase()},CHILD:function(P){if(P[1]=="nth"){var Q=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(P[2]=="even"&&"2n"||P[2]=="odd"&&"2n+1"||!/\D/.test(P[2])&&"0n+"+P[2]||P[2]);P[2]=(Q[1]+(Q[2]||1))-0;P[3]=Q[3]-0}P[0]="done"+(I++);return P},ATTR:function(Q){var P=Q[1];if(G.attrMap[P]){Q[1]=G.attrMap[P]}if(Q[2]==="~="){Q[4]=" "+Q[4]+" "}return Q},PSEUDO:function(T,Q,R,P,U){if(T[1]==="not"){if(T[3].match(N).length>1){T[3]=E(T[3],null,null,Q)}else{var S=E.filter(T[3],Q,R,true^U);if(!R){P.push.apply(P,S)}return false}}else{if(G.match.POS.test(T[0])){return true}}return T},POS:function(P){P.unshift(true);return P}},filters:{enabled:function(P){return P.disabled===false&&P.type!=="hidden"},disabled:function(P){return P.disabled===true},checked:function(P){return P.checked===true},selected:function(P){P.parentNode.selectedIndex;return P.selected===true},parent:function(P){return !!P.firstChild},empty:function(P){return !P.firstChild},has:function(R,Q,P){return !!E(P[3],R).length},header:function(P){return/h\d/i.test(P.nodeName)},text:function(P){return"text"===P.type},radio:function(P){return"radio"===P.type},checkbox:function(P){return"checkbox"===P.type},file:function(P){return"file"===P.type},password:function(P){return"password"===P.type},submit:function(P){return"submit"===P.type},image:function(P){return"image"===P.type},reset:function(P){return"reset"===P.type},button:function(P){return"button"===P.type||P.nodeName.toUpperCase()==="BUTTON"},input:function(P){return/input|select|textarea|button/i.test(P.nodeName)}},setFilters:{first:function(Q,P){return P===0},last:function(R,Q,P,S){return Q===S.length-1},even:function(Q,P){return P%2===0},odd:function(Q,P){return P%2===1},lt:function(R,Q,P){return QP[3]-0},nth:function(R,Q,P){return P[3]-0==Q},eq:function(R,Q,P){return P[3]-0==Q}},filter:{CHILD:function(P,S){var V=S[1],W=P.parentNode;var U="child"+W.childNodes.length;if(W&&(!W[U]||!P.nodeIndex)){var T=1;for(var Q=W.firstChild;Q;Q=Q.nextSibling){if(Q.nodeType==1){Q.nodeIndex=T++}}W[U]=T-1}if(V=="first"){return P.nodeIndex==1}else{if(V=="last"){return P.nodeIndex==W[U]}else{if(V=="only"){return W[U]==1}else{if(V=="nth"){var Y=false,R=S[2],X=S[3];if(R==1&&X==0){return true}if(R==0){if(P.nodeIndex==X){Y=true}}else{if((P.nodeIndex-X)%R==0&&(P.nodeIndex-X)/R>=0){Y=true}}return Y}}}}},PSEUDO:function(V,R,S,W){var Q=R[1],T=G.filters[Q];if(T){return T(V,S,R,W)}else{if(Q==="contains"){return(V.textContent||V.innerText||"").indexOf(R[3])>=0}else{if(Q==="not"){var U=R[3];for(var S=0,P=U.length;S=0:S==="~="?(" "+U+" ").indexOf(Q)>=0:!R[4]?P:S==="!="?U!=Q:S==="^="?U.indexOf(Q)===0:S==="$="?U.substr(U.length-Q.length)===Q:S==="|="?U===Q||U.substr(0,Q.length+1)===Q+"-":false},POS:function(T,Q,R,U){var P=Q[2],S=G.setFilters[P];if(S){return S(T,R,Q,U)}}}};for(var K in G.match){G.match[K]=RegExp(G.match[K].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var D=function(Q,P){Q=Array.prototype.slice.call(Q);if(P){P.push.apply(P,Q);return P}return Q};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(J){D=function(T,S){var Q=S||[];if(F.call(T)==="[object Array]"){Array.prototype.push.apply(Q,T)}else{if(typeof T.length==="number"){for(var R=0,P=T.length;R";var P=document.documentElement;P.insertBefore(Q,P.firstChild);if(!!document.getElementById(R)){G.find.ID=function(T,U){if(U.getElementById){var S=U.getElementById(T[1]);return S?S.id===T[1]||S.getAttributeNode&&S.getAttributeNode("id").nodeValue===T[1]?[S]:g:[]}};G.filter.ID=function(U,S){var T=U.getAttributeNode&&U.getAttributeNode("id");return U.nodeType===1&&T&&T.nodeValue===S}}P.removeChild(Q)})();(function(){var P=document.createElement("div");P.appendChild(document.createComment(""));if(P.getElementsByTagName("*").length>0){G.find.TAG=function(Q,U){var T=U.getElementsByTagName(Q[1]);if(Q[1]==="*"){var S=[];for(var R=0;T[R];R++){if(T[R].nodeType===1){S.push(T[R])}}T=S}return T}}P.innerHTML="";if(P.firstChild.getAttribute("href")!=="#"){G.attrHandle.href=function(Q){return Q.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var P=E;E=function(T,S,Q,R){S=S||document;if(!R&&S.nodeType===9){try{return D(S.querySelectorAll(T),Q)}catch(U){}}return P(T,S,Q,R)};E.find=P.find;E.filter=P.filter;E.selectors=P.selectors;E.matches=P.matches})()}if(document.documentElement.getElementsByClassName){G.order.splice(1,0,"CLASS");G.find.CLASS=function(P,Q){return Q.getElementsByClassName(P[1])}}function L(Q,W,V,Z,X,Y){for(var T=0,R=Z.length;T0){T=P;break}}}P=P[Q]}Y[S]=T}}}var H=document.compareDocumentPosition?function(Q,P){return Q.compareDocumentPosition(P)&16}:function(Q,P){return Q!==P&&(Q.contains?Q.contains(P):true)};var M=function(P){return P.documentElement&&!P.body||P.tagName&&P.ownerDocument&&!P.ownerDocument.body};n.find=E;n.filter=E.filter;n.expr=E.selectors;n.expr[":"]=n.expr.filters;E.selectors.filters.hidden=function(P){return"hidden"===P.type||n.css(P,"display")==="none"||n.css(P,"visibility")==="hidden"};E.selectors.filters.visible=function(P){return"hidden"!==P.type&&n.css(P,"display")!=="none"&&n.css(P,"visibility")!=="hidden"};E.selectors.filters.animated=function(P){return n.grep(n.timers,function(Q){return P===Q.elem}).length};n.multiFilter=function(R,P,Q){if(Q){R=":not("+R+")"}return E.matches(R,P)};n.dir=function(R,Q){var P=[],S=R[Q];while(S&&S!=document){if(S.nodeType==1){P.push(S)}S=S[Q]}return P};n.nth=function(T,P,R,S){P=P||1;var Q=0;for(;T;T=T[R]){if(T.nodeType==1&&++Q==P){break}}return T};n.sibling=function(R,Q){var P=[];for(;R;R=R.nextSibling){if(R.nodeType==1&&R!=Q){P.push(R)}}return P};return;l.Sizzle=E})();n.event={add:function(H,E,G,J){if(H.nodeType==3||H.nodeType==8){return}if(H.setInterval&&H!=l){H=l}if(!G.guid){G.guid=this.guid++}if(J!==g){var F=G;G=this.proxy(F);G.data=J}var D=n.data(H,"events")||n.data(H,"events",{}),I=n.data(H,"handle")||n.data(H,"handle",function(){return typeof n!=="undefined"&&!n.event.triggered?n.event.handle.apply(arguments.callee.elem,arguments):g});I.elem=H;n.each(E.split(/\s+/),function(L,M){var N=M.split(".");M=N.shift();G.type=N.slice().sort().join(".");var K=D[M];if(n.event.specialAll[M]){n.event.specialAll[M].setup.call(H,J,N)}if(!K){K=D[M]={};if(!n.event.special[M]||n.event.special[M].setup.call(H,J,N)===false){if(H.addEventListener){H.addEventListener(M,I,false)}else{if(H.attachEvent){H.attachEvent("on"+M,I)}}}}K[G.guid]=G;n.event.global[M]=true});H=null},guid:1,global:{},remove:function(J,G,I){if(J.nodeType==3||J.nodeType==8){return}var F=n.data(J,"events"),E,D;if(F){if(G===g||(typeof G==="string"&&G.charAt(0)==".")){for(var H in F){this.remove(J,H+(G||""))}}else{if(G.type){I=G.handler;G=G.type}n.each(G.split(/\s+/),function(L,N){var P=N.split(".");N=P.shift();var M=RegExp("(^|\\.)"+P.slice().sort().join(".*\\.")+"(\\.|$)");if(F[N]){if(I){delete F[N][I.guid]}else{for(var O in F[N]){if(M.test(F[N][O].type)){delete F[N][O]}}}if(n.event.specialAll[N]){n.event.specialAll[N].teardown.call(J,P)}for(E in F[N]){break}if(!E){if(!n.event.special[N]||n.event.special[N].teardown.call(J,P)===false){if(J.removeEventListener){J.removeEventListener(N,n.data(J,"handle"),false)}else{if(J.detachEvent){J.detachEvent("on"+N,n.data(J,"handle"))}}}E=null;delete F[N]}}})}for(E in F){break}if(!E){var K=n.data(J,"handle");if(K){K.elem=null}n.removeData(J,"events");n.removeData(J,"handle")}}},trigger:function(H,J,G,D){var F=H.type||H;if(!D){H=typeof H==="object"?H[h]?H:n.extend(n.Event(F),H):n.Event(F);if(F.indexOf("!")>=0){H.type=F=F.slice(0,-1);H.exclusive=true}if(!G){H.stopPropagation();if(this.global[F]){n.each(n.cache,function(){if(this.events&&this.events[F]){n.event.trigger(H,J,this.handle.elem)}})}}if(!G||G.nodeType==3||G.nodeType==8){return g}H.result=g;H.target=G;J=n.makeArray(J);J.unshift(H)}H.currentTarget=G;var I=n.data(G,"handle");if(I){I.apply(G,J)}if((!G[F]||(n.nodeName(G,"a")&&F=="click"))&&G["on"+F]&&G["on"+F].apply(G,J)===false){H.result=false}if(!D&&G[F]&&!H.isDefaultPrevented()&&!(n.nodeName(G,"a")&&F=="click")){this.triggered=true;try{G[F]()}catch(K){}}this.triggered=false;if(!H.isPropagationStopped()){var E=G.parentNode||G.ownerDocument;if(E){n.event.trigger(H,J,E,true)}}},handle:function(J){var I,D;J=arguments[0]=n.event.fix(J||l.event);var K=J.type.split(".");J.type=K.shift();I=!K.length&&!J.exclusive;var H=RegExp("(^|\\.)"+K.slice().sort().join(".*\\.")+"(\\.|$)");D=(n.data(this,"events")||{})[J.type];for(var F in D){var G=D[F];if(I||H.test(G.type)){J.handler=G;J.data=G.data;var E=G.apply(this,arguments);if(E!==g){J.result=E;if(E===false){J.preventDefault();J.stopPropagation()}}if(J.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(G){if(G[h]){return G}var E=G;G=n.Event(E);for(var F=this.props.length,I;F;){I=this.props[--F];G[I]=E[I]}if(!G.target){G.target=G.srcElement||document}if(G.target.nodeType==3){G.target=G.target.parentNode}if(!G.relatedTarget&&G.fromElement){G.relatedTarget=G.fromElement==G.target?G.toElement:G.fromElement}if(G.pageX==null&&G.clientX!=null){var H=document.documentElement,D=document.body;G.pageX=G.clientX+(H&&H.scrollLeft||D&&D.scrollLeft||0)-(H.clientLeft||0);G.pageY=G.clientY+(H&&H.scrollTop||D&&D.scrollTop||0)-(H.clientTop||0)}if(!G.which&&((G.charCode||G.charCode===0)?G.charCode:G.keyCode)){G.which=G.charCode||G.keyCode}if(!G.metaKey&&G.ctrlKey){G.metaKey=G.ctrlKey}if(!G.which&&G.button){G.which=(G.button&1?1:(G.button&2?3:(G.button&4?2:0)))}return G},proxy:function(E,D){D=D||function(){return E.apply(this,arguments)};D.guid=E.guid=E.guid||D.guid||this.guid++;return D},special:{ready:{setup:A,teardown:function(){}}},specialAll:{live:{setup:function(D,E){n.event.add(this,E[0],c)},teardown:function(F){if(F.length){var D=0,E=RegExp("(^|\\.)"+F[0]+"(\\.|$)");n.each((n.data(this,"events").live||{}),function(){if(E.test(this.type)){D++}});if(D<1){n.event.remove(this,F[0],c)}}}}}};n.Event=function(D){if(!this.preventDefault){return new n.Event(D)}if(D&&D.type){this.originalEvent=D;this.type=D.type;this.timeStamp=D.timeStamp}else{this.type=D}if(!this.timeStamp){this.timeStamp=e()}this[h]=true};function k(){return false}function t(){return true}n.Event.prototype={preventDefault:function(){this.isDefaultPrevented=t;var D=this.originalEvent;if(!D){return}if(D.preventDefault){D.preventDefault()}D.returnValue=false},stopPropagation:function(){this.isPropagationStopped=t;var D=this.originalEvent;if(!D){return}if(D.stopPropagation){D.stopPropagation()}D.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=t;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(E){var D=E.relatedTarget;while(D&&D!=this){try{D=D.parentNode}catch(F){D=this}}if(D!=this){E.type=E.data;n.event.handle.apply(this,arguments)}};n.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(E,D){n.event.special[D]={setup:function(){n.event.add(this,E,a,D)},teardown:function(){n.event.remove(this,E,a)}}});n.fn.extend({bind:function(E,F,D){return E=="unload"?this.one(E,F,D):this.each(function(){n.event.add(this,E,D||F,D&&F)})},one:function(F,G,E){var D=n.event.proxy(E||G,function(H){n(this).unbind(H,D);return(E||G).apply(this,arguments)});return this.each(function(){n.event.add(this,F,D,E&&G)})},unbind:function(E,D){return this.each(function(){n.event.remove(this,E,D)})},trigger:function(D,E){return this.each(function(){n.event.trigger(D,E,this)})},triggerHandler:function(D,F){if(this[0]){var E=n.Event(D);E.preventDefault();E.stopPropagation();n.event.trigger(E,F,this[0]);return E.result}},toggle:function(F){var D=arguments,E=1;while(E=0){var D=F.slice(H,F.length);F=F.slice(0,H)}var G="GET";if(I){if(n.isFunction(I)){J=I;I=null}else{if(typeof I==="object"){I=n.param(I);G="POST"}}}var E=this;n.ajax({url:F,type:G,dataType:"html",data:I,complete:function(L,K){if(K=="success"||K=="notmodified"){E.html(D?n("
").append(L.responseText.replace(//g,"")).find(D):L.responseText)}if(J){E.each(J,[L.responseText,K,L])}}});return this},serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?n.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type))}).map(function(D,E){var F=n(this).val();return F==null?null:n.isArray(F)?n.map(F,function(H,G){return{name:E.name,value:H}}):{name:E.name,value:F}}).get()}});n.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(D,E){n.fn[E]=function(F){return this.bind(E,F)}});var q=e();n.extend({get:function(D,F,G,E){if(n.isFunction(F)){G=F;F=null}return n.ajax({type:"GET",url:D,data:F,success:G,dataType:E})},getScript:function(D,E){return n.get(D,null,E,"script")},getJSON:function(D,E,F){return n.get(D,E,F,"json")},post:function(D,F,G,E){if(n.isFunction(F)){G=F;F={}}return n.ajax({type:"POST",url:D,data:F,success:G,dataType:E})},ajaxSetup:function(D){n.extend(n.ajaxSettings,D)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(L){L=n.extend(true,L,n.extend(true,{},n.ajaxSettings,L));var V,E=/=\?(&|$)/g,Q,U,F=L.type.toUpperCase();if(L.data&&L.processData&&typeof L.data!=="string"){L.data=n.param(L.data)}if(L.dataType=="jsonp"){if(F=="GET"){if(!L.url.match(E)){L.url+=(L.url.match(/\?/)?"&":"?")+(L.jsonp||"callback")+"=?"}}else{if(!L.data||!L.data.match(E)){L.data=(L.data?L.data+"&":"")+(L.jsonp||"callback")+"=?"}}L.dataType="json"}if(L.dataType=="json"&&(L.data&&L.data.match(E)||L.url.match(E))){V="jsonp"+q++;if(L.data){L.data=(L.data+"").replace(E,"="+V+"$1")}L.url=L.url.replace(E,"="+V+"$1");L.dataType="script";l[V]=function(W){U=W;H();K();l[V]=g;try{delete l[V]}catch(X){}if(G){G.removeChild(S)}}}if(L.dataType=="script"&&L.cache==null){L.cache=false}if(L.cache===false&&F=="GET"){var D=e();var T=L.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+D+"$2");L.url=T+((T==L.url)?(L.url.match(/\?/)?"&":"?")+"_="+D:"")}if(L.data&&F=="GET"){L.url+=(L.url.match(/\?/)?"&":"?")+L.data;L.data=null}if(L.global&&!n.active++){n.event.trigger("ajaxStart")}var P=/^(\w+:)?\/\/([^\/?#]+)/.exec(L.url);if(L.dataType=="script"&&F=="GET"&&P&&(P[1]&&P[1]!=location.protocol||P[2]!=location.host)){var G=document.getElementsByTagName("head")[0];var S=document.createElement("script");S.src=L.url;if(L.scriptCharset){S.charset=L.scriptCharset}if(!V){var N=false;S.onload=S.onreadystatechange=function(){if(!N&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){N=true;H();K();G.removeChild(S)}}}G.appendChild(S);return g}var J=false;var I=L.xhr();if(L.username){I.open(F,L.url,L.async,L.username,L.password)}else{I.open(F,L.url,L.async)}try{if(L.data){I.setRequestHeader("Content-Type",L.contentType)}if(L.ifModified){I.setRequestHeader("If-Modified-Since",n.lastModified[L.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}I.setRequestHeader("X-Requested-With","XMLHttpRequest");I.setRequestHeader("Accept",L.dataType&&L.accepts[L.dataType]?L.accepts[L.dataType]+", */*":L.accepts._default)}catch(R){}if(L.beforeSend&&L.beforeSend(I,L)===false){if(L.global&&!--n.active){n.event.trigger("ajaxStop")}I.abort();return false}if(L.global){n.event.trigger("ajaxSend",[I,L])}var M=function(W){if(I.readyState==0){if(O){clearInterval(O);O=null;if(L.global&&!--n.active){n.event.trigger("ajaxStop")}}}else{if(!J&&I&&(I.readyState==4||W=="timeout")){J=true;if(O){clearInterval(O);O=null}Q=W=="timeout"?"timeout":!n.httpSuccess(I)?"error":L.ifModified&&n.httpNotModified(I,L.url)?"notmodified":"success";if(Q=="success"){try{U=n.httpData(I,L.dataType,L)}catch(Y){Q="parsererror"}}if(Q=="success"){var X;try{X=I.getResponseHeader("Last-Modified")}catch(Y){}if(L.ifModified&&X){n.lastModified[L.url]=X}if(!V){H()}}else{n.handleError(L,I,Q)}K();if(L.async){I=null}}}};if(L.async){var O=setInterval(M,13);if(L.timeout>0){setTimeout(function(){if(I){if(!J){M("timeout")}if(I){I.abort()}}},L.timeout)}}try{I.send(L.data)}catch(R){n.handleError(L,I,null,R)}if(!L.async){M()}function H(){if(L.success){L.success(U,Q)}if(L.global){n.event.trigger("ajaxSuccess",[I,L])}}function K(){if(L.complete){L.complete(I,Q)}if(L.global){n.event.trigger("ajaxComplete",[I,L])}if(L.global&&!--n.active){n.event.trigger("ajaxStop")}}return I},handleError:function(E,G,D,F){if(E.error){E.error(G,D,F)}if(E.global){n.event.trigger("ajaxError",[G,E,F])}},active:0,httpSuccess:function(E){try{return !E.status&&location.protocol=="file:"||(E.status>=200&&E.status<300)||E.status==304||E.status==1223}catch(D){}return false},httpNotModified:function(F,D){try{var G=F.getResponseHeader("Last-Modified");return F.status==304||G==n.lastModified[D]}catch(E){}return false},httpData:function(I,G,F){var E=I.getResponseHeader("content-type"),D=G=="xml"||!G&&E&&E.indexOf("xml")>=0,H=D?I.responseXML:I.responseText;if(D&&H.documentElement.tagName=="parsererror"){throw"parsererror"}if(F&&F.dataFilter){H=F.dataFilter(H,G)}if(typeof H==="string"){if(G=="script"){n.globalEval(H)}if(G=="json"){H=l["eval"]("("+H+")")}}return H},param:function(D){var F=[];function G(H,I){F[F.length]=encodeURIComponent(H)+"="+encodeURIComponent(I)}if(n.isArray(D)||D.jquery){n.each(D,function(){G(this.name,this.value)})}else{for(var E in D){if(n.isArray(D[E])){n.each(D[E],function(){G(E,this)})}else{G(E,n.isFunction(D[E])?D[E]():D[E])}}}return F.join("&").replace(/%20/g,"+")}});var m={},d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function s(E,D){var F={};n.each(d.concat.apply([],d.slice(0,D)),function(){F[this]=E});return F}n.fn.extend({show:function(I,K){if(I){return this.animate(s("show",3),I,K)}else{for(var G=0,E=this.length;G").appendTo("body");J=H.css("display");if(J==="none"){J="block"}H.remove();m[F]=J}this[G].style.display=n.data(this[G],"olddisplay",J)}}return this}},hide:function(G,H){if(G){return this.animate(s("hide",3),G,H)}else{for(var F=0,E=this.length;F=0;G--){if(F[G].elem==this){if(D){F[G](true)}F.splice(G,1)}}});if(!D){this.dequeue()}return this}});n.each({slideDown:s("show",1),slideUp:s("hide",1),slideToggle:s("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(D,E){n.fn[D]=function(F,G){return this.animate(E,F,G)}});n.extend({speed:function(F,G,E){var D=typeof F==="object"?F:{complete:E||!E&&G||n.isFunction(F)&&F,duration:F,easing:E&&G||G&&!n.isFunction(G)&&G};D.duration=n.fx.off?0:typeof D.duration==="number"?D.duration:n.fx.speeds[D.duration]||n.fx.speeds._default;D.old=D.complete;D.complete=function(){if(D.queue!==false){n(this).dequeue()}if(n.isFunction(D.old)){D.old.call(this)}};return D},easing:{linear:function(F,G,D,E){return D+E*F},swing:function(F,G,D,E){return((-Math.cos(F*Math.PI)/2)+0.5)*E+D}},timers:[],timerId:null,fx:function(E,D,F){this.options=D;this.elem=E;this.prop=F;if(!D.orig){D.orig={}}}});n.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(n.fx.step[this.prop]||n.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(E){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var D=parseFloat(n.css(this.elem,this.prop,E));return D&&D>-10000?D:parseFloat(n.curCSS(this.elem,this.prop))||0},custom:function(H,G,F){this.startTime=e();this.start=H;this.end=G;this.unit=F||this.unit||"px";this.now=this.start;this.pos=this.state=0;var D=this;function E(I){return D.step(I)}E.elem=this.elem;n.timers.push(E);if(E()&&n.timerId==null){n.timerId=setInterval(function(){var J=n.timers;for(var I=0;I=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var D=true;for(var E in this.options.curAnim){if(this.options.curAnim[E]!==true){D=false}}if(D){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(n.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){n(this.elem).hide()}if(this.options.hide||this.options.show){for(var H in this.options.curAnim){n.attr(this.elem.style,H,this.options.orig[H])}}}if(D){this.options.complete.call(this.elem)}return false}else{var I=F-this.startTime;this.state=I/this.options.duration;this.pos=n.easing[this.options.easing||(n.easing.swing?"swing":"linear")](this.state,I,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};n.extend(n.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(D){n.attr(D.elem.style,"opacity",D.now)},_default:function(D){if(D.elem.style&&D.elem.style[D.prop]!=null){D.elem.style[D.prop]=D.now+D.unit}else{D.elem[D.prop]=D.now}}}});if(document.documentElement.getBoundingClientRect){n.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return n.offset.bodyOffset(this[0])}var F=this[0].getBoundingClientRect(),I=this[0].ownerDocument,E=I.body,D=I.documentElement,K=D.clientTop||E.clientTop||0,J=D.clientLeft||E.clientLeft||0,H=F.top+(self.pageYOffset||n.boxModel&&D.scrollTop||E.scrollTop)-K,G=F.left+(self.pageXOffset||n.boxModel&&D.scrollLeft||E.scrollLeft)-J;return{top:H,left:G}}}else{n.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return n.offset.bodyOffset(this[0])}n.offset.initialized||n.offset.initialize();var I=this[0],F=I.offsetParent,E=I,N=I.ownerDocument,L,G=N.documentElement,J=N.body,K=N.defaultView,D=K.getComputedStyle(I,null),M=I.offsetTop,H=I.offsetLeft;while((I=I.parentNode)&&I!==J&&I!==G){L=K.getComputedStyle(I,null);M-=I.scrollTop,H-=I.scrollLeft;if(I===F){M+=I.offsetTop,H+=I.offsetLeft;if(n.offset.doesNotAddBorder&&!(n.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(I.tagName))){M+=parseInt(L.borderTopWidth,10)||0,H+=parseInt(L.borderLeftWidth,10)||0}E=F,F=I.offsetParent}if(n.offset.subtractsBorderForOverflowNotVisible&&L.overflow!=="visible"){M+=parseInt(L.borderTopWidth,10)||0,H+=parseInt(L.borderLeftWidth,10)||0}D=L}if(D.position==="relative"||D.position==="static"){M+=J.offsetTop,H+=J.offsetLeft}if(D.position==="fixed"){M+=Math.max(G.scrollTop,J.scrollTop),H+=Math.max(G.scrollLeft,J.scrollLeft)}return{top:M,left:H}}}n.offset={initialize:function(){if(this.initialized){return}var K=document.body,E=document.createElement("div"),G,F,M,H,L,D,I=K.style.marginTop,J='
';L={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(D in L){E.style[D]=L[D]}E.innerHTML=J;K.insertBefore(E,K.firstChild);G=E.firstChild,F=G.firstChild,H=G.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(F.offsetTop!==5);this.doesAddBorderForTableAndCells=(H.offsetTop===5);G.style.overflow="hidden",G.style.position="relative";this.subtractsBorderForOverflowNotVisible=(F.offsetTop===-5);K.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(K.offsetTop===0);K.style.marginTop=I;K.removeChild(E);this.initialized=true},bodyOffset:function(D){n.offset.initialized||n.offset.initialize();var F=D.offsetTop,E=D.offsetLeft;if(n.offset.doesNotIncludeMarginInBodyOffset){F+=parseInt(n.curCSS(D,"marginTop",true),10)||0,E+=parseInt(n.curCSS(D,"marginLeft",true),10)||0}return{top:F,left:E}}};n.fn.extend({position:function(){var H=0,G=0,E;if(this[0]){var F=this.offsetParent(),I=this.offset(),D=/^body|html$/i.test(F[0].tagName)?{top:0,left:0}:F.offset();I.top-=j(this,"marginTop");I.left-=j(this,"marginLeft");D.top+=j(F,"borderTopWidth");D.left+=j(F,"borderLeftWidth");E={top:I.top-D.top,left:I.left-D.left}}return E},offsetParent:function(){var D=this[0].offsetParent||document.body;while(D&&(!/^body|html$/i.test(D.tagName)&&n.css(D,"position")=="static")){D=D.offsetParent}return n(D)}});n.each(["Left","Top"],function(E,D){var F="scroll"+D;n.fn[F]=function(G){if(!this[0]){return null}return G!==g?this.each(function(){this==l||this==document?l.scrollTo(!E?G:n(l).scrollLeft(),E?G:n(l).scrollTop()):this[F]=G}):this[0]==l||this[0]==document?self[E?"pageYOffset":"pageXOffset"]||n.boxModel&&document.documentElement[F]||document.body[F]:this[0][F]}});n.each(["Height","Width"],function(G,E){var D=G?"Left":"Top",F=G?"Right":"Bottom";n.fn["inner"+E]=function(){return this[E.toLowerCase()]()+j(this,"padding"+D)+j(this,"padding"+F)};n.fn["outer"+E]=function(I){return this["inner"+E]()+j(this,"border"+D+"Width")+j(this,"border"+F+"Width")+(I?j(this,"margin"+D)+j(this,"margin"+F):0)};var H=E.toLowerCase();n.fn[H]=function(I){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+E]||document.body["client"+E]:this[0]==document?Math.max(document.documentElement["client"+E],document.body["scroll"+E],document.documentElement["scroll"+E],document.body["offset"+E],document.documentElement["offset"+E]):I===g?(this.length?n.css(this[0],H):null):this.css(H,typeof I==="string"?I:I+"px")}})})(); \ No newline at end of file +(function(){var Q=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]+['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g,K=0,G=Object.prototype.toString;var F=function(X,T,aa,ab){aa=aa||[];T=T||document;if(T.nodeType!==1&&T.nodeType!==9){return[]}if(!X||typeof X!=="string"){return aa}var Y=[],V,ae,ah,S,ac,U,W=true;Q.lastIndex=0;while((V=Q.exec(X))!==null){Y.push(V[1]);if(V[2]){U=RegExp.rightContext;break}}if(Y.length>1&&L.exec(X)){if(Y.length===2&&H.relative[Y[0]]){ae=I(Y[0]+Y[1],T)}else{ae=H.relative[Y[0]]?[T]:F(Y.shift(),T);while(Y.length){X=Y.shift();if(H.relative[X]){X+=Y.shift()}ae=I(X,ae)}}}else{var ad=ab?{expr:Y.pop(),set:E(ab)}:F.find(Y.pop(),Y.length===1&&T.parentNode?T.parentNode:T,P(T));ae=F.filter(ad.expr,ad.set);if(Y.length>0){ah=E(ae)}else{W=false}while(Y.length){var ag=Y.pop(),af=ag;if(!H.relative[ag]){ag=""}else{af=Y.pop()}if(af==null){af=T}H.relative[ag](ah,af,P(T))}}if(!ah){ah=ae}if(!ah){throw"Syntax error, unrecognized expression: "+(ag||X)}if(G.call(ah)==="[object Array]"){if(!W){aa.push.apply(aa,ah)}else{if(T.nodeType===1){for(var Z=0;ah[Z]!=null;Z++){if(ah[Z]&&(ah[Z]===true||ah[Z].nodeType===1&&J(T,ah[Z]))){aa.push(ae[Z])}}}else{for(var Z=0;ah[Z]!=null;Z++){if(ah[Z]&&ah[Z].nodeType===1){aa.push(ae[Z])}}}}}else{E(ah,aa)}if(U){F(U,T,aa,ab)}return aa};F.matches=function(S,T){return F(S,null,null,T)};F.find=function(Z,S,aa){var Y,W;if(!Z){return[]}for(var V=0,U=H.order.length;V":function(X,T,Y){if(typeof T==="string"&&!/\W/.test(T)){T=Y?T:T.toUpperCase();for(var U=0,S=X.length;U=0){if(!U){S.push(X)}}else{if(U){T[W]=false}}}}return false},ID:function(S){return S[1].replace(/\\/g,"")},TAG:function(T,S){for(var U=0;S[U]===false;U++){}return S[U]&&P(S[U])?T[1]:T[1].toUpperCase()},CHILD:function(S){if(S[1]=="nth"){var T=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(S[2]=="even"&&"2n"||S[2]=="odd"&&"2n+1"||!/\D/.test(S[2])&&"0n+"+S[2]||S[2]);S[2]=(T[1]+(T[2]||1))-0;S[3]=T[3]-0}S[0]="done"+(K++);return S},ATTR:function(T){var S=T[1].replace(/\\/g,"");if(H.attrMap[S]){T[1]=H.attrMap[S]}if(T[2]==="~="){T[4]=" "+T[4]+" "}return T},PSEUDO:function(W,T,U,S,X){if(W[1]==="not"){if(W[3].match(Q).length>1){W[3]=F(W[3],null,null,T)}else{var V=F.filter(W[3],T,U,true^X);if(!U){S.push.apply(S,V)}return false}}else{if(H.match.POS.test(W[0])){return true}}return W},POS:function(S){S.unshift(true);return S}},filters:{enabled:function(S){return S.disabled===false&&S.type!=="hidden"},disabled:function(S){return S.disabled===true},checked:function(S){return S.checked===true},selected:function(S){S.parentNode.selectedIndex;return S.selected===true},parent:function(S){return !!S.firstChild},empty:function(S){return !S.firstChild},has:function(U,T,S){return !!F(S[3],U).length},header:function(S){return/h\d/i.test(S.nodeName)},text:function(S){return"text"===S.type},radio:function(S){return"radio"===S.type},checkbox:function(S){return"checkbox"===S.type},file:function(S){return"file"===S.type},password:function(S){return"password"===S.type},submit:function(S){return"submit"===S.type},image:function(S){return"image"===S.type},reset:function(S){return"reset"===S.type},button:function(S){return"button"===S.type||S.nodeName.toUpperCase()==="BUTTON"},input:function(S){return/input|select|textarea|button/i.test(S.nodeName)}},setFilters:{first:function(T,S){return S===0},last:function(U,T,S,V){return T===V.length-1},even:function(T,S){return S%2===0},odd:function(T,S){return S%2===1},lt:function(U,T,S){return TS[3]-0},nth:function(U,T,S){return S[3]-0==T},eq:function(U,T,S){return S[3]-0==T}},filter:{CHILD:function(S,V){var Y=V[1],Z=S.parentNode;var X=V[0];if(Z&&(!Z[X]||!S.nodeIndex)){var W=1;for(var T=Z.firstChild;T;T=T.nextSibling){if(T.nodeType==1){T.nodeIndex=W++}}Z[X]=W-1}if(Y=="first"){return S.nodeIndex==1}else{if(Y=="last"){return S.nodeIndex==Z[X]}else{if(Y=="only"){return Z[X]==1}else{if(Y=="nth"){var ab=false,U=V[2],aa=V[3];if(U==1&&aa==0){return true}if(U==0){if(S.nodeIndex==aa){ab=true}}else{if((S.nodeIndex-aa)%U==0&&(S.nodeIndex-aa)/U>=0){ab=true}}return ab}}}}},PSEUDO:function(Y,U,V,Z){var T=U[1],W=H.filters[T];if(W){return W(Y,V,U,Z)}else{if(T==="contains"){return(Y.textContent||Y.innerText||"").indexOf(U[3])>=0}else{if(T==="not"){var X=U[3];for(var V=0,S=X.length;V=0:V==="~="?(" "+X+" ").indexOf(T)>=0:!U[4]?S:V==="!="?X!=T:V==="^="?X.indexOf(T)===0:V==="$="?X.substr(X.length-T.length)===T:V==="|="?X===T||X.substr(0,T.length+1)===T+"-":false},POS:function(W,T,U,X){var S=T[2],V=H.setFilters[S];if(V){return V(W,U,T,X)}}}};var L=H.match.POS;for(var N in H.match){H.match[N]=RegExp(H.match[N].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(T,S){T=Array.prototype.slice.call(T);if(S){S.push.apply(S,T);return S}return T};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(M){E=function(W,V){var T=V||[];if(G.call(W)==="[object Array]"){Array.prototype.push.apply(T,W)}else{if(typeof W.length==="number"){for(var U=0,S=W.length;U";var S=document.documentElement;S.insertBefore(T,S.firstChild);if(!!document.getElementById(U)){H.find.ID=function(W,X,Y){if(typeof X.getElementById!=="undefined"&&!Y){var V=X.getElementById(W[1]);return V?V.id===W[1]||typeof V.getAttributeNode!=="undefined"&&V.getAttributeNode("id").nodeValue===W[1]?[V]:g:[]}};H.filter.ID=function(X,V){var W=typeof X.getAttributeNode!=="undefined"&&X.getAttributeNode("id");return X.nodeType===1&&W&&W.nodeValue===V}}S.removeChild(T)})();(function(){var S=document.createElement("div");S.appendChild(document.createComment(""));if(S.getElementsByTagName("*").length>0){H.find.TAG=function(T,X){var W=X.getElementsByTagName(T[1]);if(T[1]==="*"){var V=[];for(var U=0;W[U];U++){if(W[U].nodeType===1){V.push(W[U])}}W=V}return W}}S.innerHTML="";if(S.firstChild&&S.firstChild.getAttribute("href")!=="#"){H.attrHandle.href=function(T){return T.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var S=F,T=document.createElement("div");T.innerHTML="

";if(T.querySelectorAll&&T.querySelectorAll(".TEST").length===0){return}F=function(X,W,U,V){W=W||document;if(!V&&W.nodeType===9&&!P(W)){try{return E(W.querySelectorAll(X),U)}catch(Y){}}return S(X,W,U,V)};F.find=S.find;F.filter=S.filter;F.selectors=S.selectors;F.matches=S.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){H.order.splice(1,0,"CLASS");H.find.CLASS=function(S,T){return T.getElementsByClassName(S[1])}}function O(T,Z,Y,ac,aa,ab){for(var W=0,U=ac.length;W0){W=S;break}}}S=S[T]}ab[V]=W}}}var J=document.compareDocumentPosition?function(T,S){return T.compareDocumentPosition(S)&16}:function(T,S){return T!==S&&(T.contains?T.contains(S):true)};var P=function(S){return S.nodeType===9&&S.documentElement.nodeName!=="HTML"||!!S.ownerDocument&&P(S.ownerDocument)};var I=function(S,Z){var V=[],W="",X,U=Z.nodeType?[Z]:Z;while((X=H.match.PSEUDO.exec(S))){W+=X[0];S=S.replace(H.match.PSEUDO,"")}S=H.relative[S]?S+"*":S;for(var Y=0,T=U.length;Y=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}this[H].style.display=o.data(this[H],"olddisplay",K)}}return this}},hide:function(H,I){if(H){return this.animate(t("hide",3),H,I)}else{for(var G=0,F=this.length;G=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)==1){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(H,F){var E=H?"Left":"Top",G=H?"Right":"Bottom";o.fn["inner"+F]=function(){return this[F.toLowerCase()]()+j(this,"padding"+E)+j(this,"padding"+G)};o.fn["outer"+F]=function(J){return this["inner"+F]()+j(this,"border"+E+"Width")+j(this,"border"+G+"Width")+(J?j(this,"margin"+E)+j(this,"margin"+G):0)};var I=F.toLowerCase();o.fn[I]=function(J){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+F]||document.body["client"+F]:this[0]==document?Math.max(document.documentElement["client"+F],document.body["scroll"+F],document.documentElement["scroll"+F],document.body["offset"+F],document.documentElement["offset"+F]):J===g?(this.length?o.css(this[0],I):null):this.css(I,typeof J==="string"?J:J+"px")}})})(); \ No newline at end of file From 28ef2ccf427683837dde29f8b89ea8d5378f287b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sun, 8 Feb 2009 23:07:56 +0000 Subject: [PATCH 012/189] Ticket #1094 Facebook app invites page was failing if no friends had added the app yet --- actions/facebookinvite.php | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/actions/facebookinvite.php b/actions/facebookinvite.php index 3c872f94bf..0d1cb8c4c7 100644 --- a/actions/facebookinvite.php +++ b/actions/facebookinvite.php @@ -92,6 +92,12 @@ class FacebookinviteAction extends FacebookAction // Get a list of users who are already using the app for exclusion $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')) . htmlentities(''); @@ -103,10 +109,17 @@ class FacebookinviteAction extends FacebookAction 'content' => $content)); $this->hidden('invite', 'true'); $actiontext = sprintf(_('Invite your friends to use %s'), common_config('site', 'name')); - $this->element('fb:multi-friend-selector', array('showborder' => 'false', - 'actiontext' => $actiontext, - 'exclude_ids' => implode(',', $exclude_ids), - 'bypass' => 'cancel')); + + $multi_params = array('showborder' => 'false'); + $multi_params['actiontext'] = $actiontext; + + 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'); From bb13d896423691237c7598e8223c9bfcc94af996 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sun, 8 Feb 2009 21:58:25 +0000 Subject: [PATCH 013/189] Minor update to the way Facebook app handles listing of friends you've invited. --- actions/facebookinvite.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/facebookinvite.php b/actions/facebookinvite.php index 0d1cb8c4c7..1302064ad7 100644 --- a/actions/facebookinvite.php +++ b/actions/facebookinvite.php @@ -71,13 +71,13 @@ class FacebookinviteAction extends FacebookAction common_config('site', 'name'))); $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')); foreach ($friend_ids as $friend) { $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, 'capitalize' => 'true')); $this->elementEnd('li'); From ddb67cda1799f38f214574e838434b36d40a36e9 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sun, 8 Feb 2009 13:25:48 -0800 Subject: [PATCH 014/189] Trac #1159: Fix peopletag pagination. Also phpcs cleanup. --- actions/peopletag.php | 126 ++++++++++++++++++++++++++++++------------ 1 file changed, 91 insertions(+), 35 deletions(-) diff --git a/actions/peopletag.php b/actions/peopletag.php index 3578c53fdf..6b1e34f1ab 100644 --- a/actions/peopletag.php +++ b/actions/peopletag.php @@ -1,9 +1,12 @@ . + * + * @category Action + * @package Laconica + * @author Evan Prodromou + * @author Zach Copley + * @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'; +/** + * This class outputs a paginated list of profiles self-tagged with a given tag + * + * @category Output + * @package Laconica + * @author Evan Prodromou + * @author Zach Copley + * @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 { - - var $tag = null; + + var $tag = 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($args); + parent::prepare($argarray); $this->tag = $this->trimmed('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; } - $this->page = $this->trimmed('page'); + $this->page = ($this->arg('page')) ? $this->arg('page') : 1; - if (!$this->page) { - $this->page = 1; - } - + common_set_returnto($this->selfUrl()); + + 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(); } - + + /** + * 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() { - + $profile = new Profile(); - $offset = ($page-1)*PROFILES_PER_PAGE; - $limit = PROFILES_PER_PAGE + 1; - - if (common_config('db','type') == 'pgsql') { + $offset = ($this->page - 1) * PROFILES_PER_PAGE; + $limit = PROFILES_PER_PAGE + 1; + + if (common_config('db', 'type') == 'pgsql') { $lim = ' LIMIT ' . $limit . ' OFFSET ' . $offset; } else { $lim = ' LIMIT ' . $offset . ', ' . $limit; } - # XXX: memcached this - + // XXX: memcached this + $qry = 'SELECT profile.* ' . 'FROM profile JOIN profile_tag ' . 'ON profile.id = profile_tag.tagger ' . 'WHERE profile_tag.tagger = profile_tag.tagged ' . 'AND tag = "%s" ' . - 'ORDER BY profile_tag.modified DESC'; - + 'ORDER BY profile_tag.modified DESC%s'; + $profile->query(sprintf($qry, $this->tag, $lim)); - $pl = new ProfileList($profile, null, $this); + $pl = new ProfileList($profile, null, $this); $cnt = $pl->show(); - + $this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE, $this->page, - $this->trimmed('action'), + 'peopletag', array('tag' => $this->tag)); } - - function title() + + /** + * Returns the page title + * + * @return string page 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); } - + } From 37a1a6b9d8c50c490d4b7fe42b40e2af7d8c00b3 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sat, 7 Feb 2009 18:16:34 -0800 Subject: [PATCH 015/189] Safer, better script for automatically updating Facebook statuses --- scripts/update_facebook.php | 90 +++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 48 deletions(-) diff --git a/scripts/update_facebook.php b/scripts/update_facebook.php index 141bcfe0ca..60e10417fa 100755 --- a/scripts/update_facebook.php +++ b/scripts/update_facebook.php @@ -34,22 +34,19 @@ require_once INSTALLDIR . '/lib/facebookutil.php'; $last_updated_file = INSTALLDIR . '/scripts/facebook_last_updated'; // 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 -if (!($tmp_file = @fopen($tmp_file, "w"))) -{ - die("Can't open lock file. Script already running?"); +$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?\n"); } $facebook = getFacebook(); - $current_time = time(); - $since = getLastUpdated(); - +updateLastUpdated($current_time); $notice = getFacebookNotices($since); - $cnt = 0; while($notice->fetch()) { @@ -73,26 +70,30 @@ while($notice->fetch()) { // Avoid a Loop if ($notice->source != 'Facebook') { - updateStatus($fbuid, $content); - updateProfileBox($facebook, $flink, $notice); - $cnt++; + + try { + $facebook->api_client->users_setStatus($content, + $fbuid, false, true); + updateProfileBox($facebook, $flink, $notice); + $cnt++; + } catch(FacebookRestClientException $e) { + print "Couldn't sent notice $notice->id!\n"; + print $e->getMessage(); + + // Remove flink? + } } - } + } } } if ($cnt > 0) { print date('r', $current_time) . - ": Found $cnt new notices to send to Facebook since last run at " . - date('Y-m-d H:i:s', $since) . "\n"; - + ": Found $cnt new notices for Facebook since last run at " . + date('r', $since) . "\n"; } -#Save the last updated time. It needs to do this even if there were no -#changes made, otherwise it will never create it and thus never send -#any updates at all. -updateLastUpdated($current_time); - +fclose($lock_file); exit(0); @@ -111,37 +112,30 @@ function userCanUpdate($fbuid) { 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(){ - global $last_updated_file, $current_time; + global $last_updated_file, $current_time; + $last = $current_time; - $file = fopen($last_updated_file, 'r'); - - if ($file) { - $last = fgets($file); - } else { - print "Unable to read $last_updated_file. Using current time.\n"; - return $current_time; - } - - fclose($file); - - return $last; + if (file_exists($last_updated_file) && + ($file = fopen($last_updated_file, 'r'))) { + $last = fgets($file); + } else { + print "$last_updated_file doesn't exit. Trying to create it...\n"; + $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); + return $last; } function updateLastUpdated($time){ - global $last_updated_file; - $file = fopen($last_updated_file, 'w') or die("Can't open $last_updated_file for writing!"); - fwrite($file, $time); - fclose($file); + global $last_updated_file; + $file = fopen($last_updated_file, 'w') or + die("Can't open $last_updated_file for writing!"); + fwrite($file, $time); + fclose($file); } + From 09d8a73eceba11ed021a697b3b8e283c5bc2dc6a Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Sat, 7 Feb 2009 19:33:18 +0000 Subject: [PATCH 016/189] trac #1155 ++ replace strlen with mb_strlen for all utf8 strings. --- actions/editgroup.php | 6 +++--- actions/finishopenidlogin.php | 2 +- actions/newgroup.php | 6 +++--- actions/profilesettings.php | 6 +++--- actions/register.php | 6 +++--- actions/twitapiaccount.php | 2 +- actions/updateprofile.php | 8 ++++---- actions/userauthorization.php | 8 ++++---- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/actions/editgroup.php b/actions/editgroup.php index 98ebcb87ac..e7e79040a4 100644 --- a/actions/editgroup.php +++ b/actions/editgroup.php @@ -191,13 +191,13 @@ class EditgroupAction extends Action array('http', 'https')))) { $this->showForm(_('Homepage is not a valid URL.')); 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).')); 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).')); 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).')); return; } diff --git a/actions/finishopenidlogin.php b/actions/finishopenidlogin.php index bc91511207..1e7b73a7f3 100644 --- a/actions/finishopenidlogin.php +++ b/actions/finishopenidlogin.php @@ -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']; } diff --git a/actions/newgroup.php b/actions/newgroup.php index 42fd380dfe..cbd8dfeec5 100644 --- a/actions/newgroup.php +++ b/actions/newgroup.php @@ -142,13 +142,13 @@ class NewgroupAction extends Action array('http', 'https')))) { $this->showForm(_('Homepage is not a valid URL.')); 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).')); 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).')); 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).')); return; } diff --git a/actions/profilesettings.php b/actions/profilesettings.php index 82e6c3c82f..60f7c0796e 100644 --- a/actions/profilesettings.php +++ b/actions/profilesettings.php @@ -198,13 +198,13 @@ class ProfilesettingsAction extends AccountSettingsAction !Validate::uri($homepage, array('allowed_schemes' => array('http', 'https')))) { $this->showForm(_('Homepage is not a valid URL.')); 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).')); 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).')); 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).')); return; } else if (is_null($timezone) || !in_array($timezone, DateTimeZone::listIdentifiers())) { diff --git a/actions/register.php b/actions/register.php index 01d94f4884..5d7a8ce690 100644 --- a/actions/register.php +++ b/actions/register.php @@ -167,13 +167,13 @@ class RegisterAction extends Action array('http', 'https')))) { $this->showForm(_('Homepage is not a valid URL.')); 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).')); 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).')); 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).')); return; } else if (strlen($password) < 6) { diff --git a/actions/twitapiaccount.php b/actions/twitapiaccount.php index dc8e2e798b..b7c09cc9dc 100644 --- a/actions/twitapiaccount.php +++ b/actions/twitapiaccount.php @@ -56,7 +56,7 @@ class TwitapiaccountAction extends TwitterapiAction $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 $this->clientError(_('That\'s too long. Max notice size is 255 chars.'), 406, $apidate['content-type']); diff --git a/actions/updateprofile.php b/actions/updateprofile.php index c79112dace..898c535432 100644 --- a/actions/updateprofile.php +++ b/actions/updateprofile.php @@ -93,22 +93,22 @@ class UpdateprofileAction extends Action } # optional stuff $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).")); return false; } $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)); return false; } $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).")); return false; } $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).")); return false; } diff --git a/actions/userauthorization.php b/actions/userauthorization.php index 58fc96c0eb..7455a41a6f 100644 --- a/actions/userauthorization.php +++ b/actions/userauthorization.php @@ -469,19 +469,19 @@ class UserauthorizationAction extends Action } # optional stuff $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."); } $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'"); } $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'"); } $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'"); } $avatar = $req->get_parameter('omb_listenee_avatar'); From 3e005f2d1bf01c295c132b6ca52e1139ef60b733 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 6 Feb 2009 21:17:45 -0800 Subject: [PATCH 017/189] "Change your email address..." msg was printing out \n instead of a newline --- lib/mail.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mail.php b/lib/mail.php index b424d579fe..a1faefc806 100644 --- a/lib/mail.php +++ b/lib/mail.php @@ -246,7 +246,7 @@ function mail_subscribe_notify_profile($listenee, $other) "\n".'Faithfully yours,'."\n".'%7$s.'."\n\n". "----\n". "Change your email address or ". - "notification options at ".'%8$s\n'), + "notification options at ".'%8$s' ."\n"), $long_name, common_config('site', 'name'), $other->profileurl, From 2393fbec6062da4e681b4315e89b27c934a641f1 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 14:47:23 -0500 Subject: [PATCH 018/189] add some indices for performance --- db/laconica.sql | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/db/laconica.sql b/db/laconica.sql index 012270b51e..16f482134b 100644 --- a/db/laconica.sql +++ b/db/laconica.sql @@ -258,7 +258,8 @@ create table notice_tag ( created datetime not null comment 'date this record was created', 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; /* Synching with foreign services */ @@ -356,7 +357,8 @@ create table profile_tag ( constraint primary key (tagger, tagged, tag), 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; create table profile_block ( @@ -400,7 +402,9 @@ create table group_member ( created datetime not null comment 'date this record was created', 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; From 32744124bcb8aa0683490a56defd4a79f072d278 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 16:56:38 -0500 Subject: [PATCH 019/189] Add a hook for showing sidebar sections --- EVENTS.txt | 5 +++++ lib/action.php | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/EVENTS.txt b/EVENTS.txt index 4b8260b3ce..d9634325db 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -34,3 +34,8 @@ StartShowLaconicaScripts: Showing Laconica script links (use this to link to a C 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 diff --git a/lib/action.php b/lib/action.php index 0628dc70db..ce92addf5c 100644 --- a/lib/action.php +++ b/lib/action.php @@ -524,12 +524,16 @@ class Action extends HTMLOutputter // lawsuit * * @return nothing */ + function showAside() { $this->elementStart('div', array('id' => 'aside_primary', 'class' => 'aside')); $this->showExportData(); - $this->showSections(); + if (Event::handle('StartShowSections', array($this))) { + $this->showSections(); + Event::handle('EndShowSections', array($this)); + } $this->elementEnd('div'); } From f6705f06c0a8251c0f3eb0fe88532e75645f7705 Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Mon, 9 Feb 2009 17:29:27 -0500 Subject: [PATCH 020/189] Fixed #1170: Auto-linking bug when URL cotains special chars. --- lib/util.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/util.php b/lib/util.php index 7ce4e229eb..5204693bc5 100644 --- a/lib/util.php +++ b/lib/util.php @@ -412,8 +412,8 @@ function common_replace_urls_callback($text, $callback) { // Then clean up what the regex left behind $offset = 0; - foreach($matches[0] as $url) { - $url = htmlspecialchars_decode($url); + foreach($matches[0] as $orig_url) { + $url = htmlspecialchars_decode($orig_url); // Make sure we didn't pick up an email address if (preg_match('#^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$#i', $url)) continue; @@ -456,6 +456,9 @@ function common_replace_urls_callback($text, $callback) { if (!in_array($url_parts[2], $tlds)) continue; + // Put the url back the way we found it. + $url = (mb_strpos($orig_url, htmlspecialchars($url)) === FALSE) ? $url:htmlspecialchars($url); + // Call user specified func $modified_url = $callback($url); From 0d586524874560c51277b90f33bcbed164748f6d Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Mon, 9 Feb 2009 15:35:38 +0000 Subject: [PATCH 021/189] trac #1160 fix dropdown xmloutput function for the selected attribute and fix newmessage auto-selected dropdown. --- actions/newmessage.php | 2 +- lib/htmloutputter.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/newmessage.php b/actions/newmessage.php index f83015a372..82276ff341 100644 --- a/actions/newmessage.php +++ b/actions/newmessage.php @@ -201,7 +201,7 @@ class NewmessageAction extends Action function showNoticeForm() { - $message_form = new MessageForm($this, $this->to, $this->content); + $message_form = new MessageForm($this, $this->other, $this->content); $message_form->show(); } } diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index 7780b1c19d..e2319b1fdc 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -255,7 +255,7 @@ class HTMLOutputter extends XMLOutputter foreach ($content as $value => $option) { if ($value == $selected) { $this->element('option', array('value' => $value, - 'selected' => $value), + 'selected' => 'selected'), $option); } else { $this->element('option', array('value' => $value), $option); From 47a5d2b7f06cd1612734c47ad21fc397bbff5276 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 23:13:11 +0000 Subject: [PATCH 022/189] Fixed remote subscribe avatar problems Had some Avatar file-copying issues; seem to be fixed. --- actions/finishremotesubscribe.php | 8 +++++++- actions/userauthorization.php | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/actions/finishremotesubscribe.php b/actions/finishremotesubscribe.php index f9094a50ca..76db887deb 100644 --- a/actions/finishremotesubscribe.php +++ b/actions/finishremotesubscribe.php @@ -237,7 +237,13 @@ class FinishremotesubscribeAction extends Action { $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar'); copy($url, $temp_filename); - return $profile->setOriginal($temp_filename); + $imagefile = new ImageFile($profile->id, $temp_filename); + $filename = Avatar::filename($profile->id, + image_type_to_extension($imagefile->type), + null, + common_timestamp()); + rename($temp_filename, Avatar::path($filename)); + return $profile->setOriginal($filename); } function access_token($omb) diff --git a/actions/userauthorization.php b/actions/userauthorization.php index 7455a41a6f..ed17ceec97 100644 --- a/actions/userauthorization.php +++ b/actions/userauthorization.php @@ -330,7 +330,13 @@ class UserauthorizationAction extends Action { $temp_filename = tempnam(sys_get_temp_dir(), 'listenee_avatar'); copy($url, $temp_filename); - return $profile->setOriginal($temp_filename); + $imagefile = new ImageFile($profile->id, $temp_filename); + $filename = Avatar::filename($profile->id, + image_type_to_extension($imagefile->type), + null, + common_timestamp()); + rename($temp_filename, Avatar::path($filename)); + return $profile->setOriginal($filename); } function showAcceptMessage($tok) From 80429ca70c5ec3f057197914ea9817a25c4a86c7 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 23:13:11 +0000 Subject: [PATCH 023/189] Fixed remote subscribe avatar problems Had some Avatar file-copying issues; seem to be fixed. --- actions/finishremotesubscribe.php | 8 +++++++- actions/userauthorization.php | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/actions/finishremotesubscribe.php b/actions/finishremotesubscribe.php index f9094a50ca..76db887deb 100644 --- a/actions/finishremotesubscribe.php +++ b/actions/finishremotesubscribe.php @@ -237,7 +237,13 @@ class FinishremotesubscribeAction extends Action { $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar'); copy($url, $temp_filename); - return $profile->setOriginal($temp_filename); + $imagefile = new ImageFile($profile->id, $temp_filename); + $filename = Avatar::filename($profile->id, + image_type_to_extension($imagefile->type), + null, + common_timestamp()); + rename($temp_filename, Avatar::path($filename)); + return $profile->setOriginal($filename); } function access_token($omb) diff --git a/actions/userauthorization.php b/actions/userauthorization.php index 7455a41a6f..ed17ceec97 100644 --- a/actions/userauthorization.php +++ b/actions/userauthorization.php @@ -330,7 +330,13 @@ class UserauthorizationAction extends Action { $temp_filename = tempnam(sys_get_temp_dir(), 'listenee_avatar'); copy($url, $temp_filename); - return $profile->setOriginal($temp_filename); + $imagefile = new ImageFile($profile->id, $temp_filename); + $filename = Avatar::filename($profile->id, + image_type_to_extension($imagefile->type), + null, + common_timestamp()); + rename($temp_filename, Avatar::path($filename)); + return $profile->setOriginal($filename); } function showAcceptMessage($tok) From cf29ef2bc484c1d9719e852d1d3b6eeaff74e094 Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Mon, 9 Feb 2009 19:15:30 -0500 Subject: [PATCH 024/189] Fixed remaining substr_replace with multibyte equivalent. --- lib/util.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util.php b/lib/util.php index be92f422fb..75d1e21a47 100644 --- a/lib/util.php +++ b/lib/util.php @@ -446,7 +446,7 @@ function common_replace_urls_callback($text, $callback) { // If the first part wasn't cap'd but the last part was, we captured too much if ((!$prev_part && $last_part)) { - $url = substr_replace($url, '', mb_strpos($url, '.'.$url_parts[2], 0)); + $url = mb_substr($url, 0 , mb_strpos($url, '.'.$url_parts['2'], 0)); } // Capture the new TLD From 55fcc150fe90156ad33f8627128c7ae555662528 Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Mon, 9 Feb 2009 20:17:56 -0500 Subject: [PATCH 025/189] Fixed #1025: Delete avatar option. --- actions/avatarsettings.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/actions/avatarsettings.php b/actions/avatarsettings.php index 7dd53f6eb5..f38a44a24a 100644 --- a/actions/avatarsettings.php +++ b/actions/avatarsettings.php @@ -145,6 +145,7 @@ class AvatarsettingsAction extends AccountSettingsAction 'height' => AVATAR_PROFILE_SIZE, 'alt' => $user->nickname)); $this->elementEnd('div'); + $this->submit('delete', _('Delete')); $this->elementEnd('li'); } @@ -256,6 +257,8 @@ class AvatarsettingsAction extends AccountSettingsAction $this->uploadAvatar(); } else if ($this->arg('crop')) { $this->cropAvatar(); + } else if ($this->arg('delete')) { + $this->deleteAvatar(); } else { $this->showForm(_('Unexpected form submission.')); } @@ -344,6 +347,29 @@ class AvatarsettingsAction extends AccountSettingsAction $this->showForm(_('Failed updating avatar.')); } } + + /** + * Get rid of the current avatar. + * + * @return void + */ + + function deleteAvatar() + { + $user = common_current_user(); + $profile = $user->getProfile(); + + $avatar = $profile->getOriginalAvatar(); + $avatar->delete(); + $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); + $avatar->delete(); + $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE); + $avatar->delete(); + $avatar = $profile->getAvatar(AVATAR_MINI_SIZE); + $avatar->delete(); + + $this->showForm(_('Avatar deleted.'), true); + } /** * Add the jCrop stylesheet From c9e8b1e5c380904e479927e2f24754d8709f590e Mon Sep 17 00:00:00 2001 From: Meitar Moscovitz Date: Wed, 11 Feb 2009 03:03:16 +1100 Subject: [PATCH 026/189] Add streamlined mobile device-friendly styles when enabled in config. A new mobile-specific style sheet is added and loaded only if the `$config['site']['mobile']` configuration variable is set to true. --- config.php.sample | 2 ++ lib/action.php | 7 ++++++ theme/base/css/mobile.css | 48 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 theme/base/css/mobile.css diff --git a/config.php.sample b/config.php.sample index a2c5801f45..d1191ea01b 100644 --- a/config.php.sample +++ b/config.php.sample @@ -18,6 +18,8 @@ $config['site']['server'] = 'localhost'; $config['site']['path'] = 'laconica'; #$config['site']['fancy'] = false; #$config['site']['theme'] = 'default'; +#To enable the built-in mobile style sheet, defaults to false. +#$config['site']['mobile'] = true; #For contact email, defaults to $_SERVER["SERVER_ADMIN"] #$config['site']['email'] = 'admin@example.net'; #Brought by... diff --git a/lib/action.php b/lib/action.php index ce92addf5c..ce37f4760a 100644 --- a/lib/action.php +++ b/lib/action.php @@ -170,6 +170,13 @@ class Action extends HTMLOutputter // lawsuit } $this->comment('[if IE]>element('link', array('rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => theme_path('css/mobile.css', 'base') . '?version=' . LACONICA_VERSION, + // TODO: "handheld" CSS for other mobile devices + 'media' => 'screen and (max-device-width: 480px)')); // Mobile WebKit + } } /** diff --git a/theme/base/css/mobile.css b/theme/base/css/mobile.css new file mode 100644 index 0000000000..6cd717a4df --- /dev/null +++ b/theme/base/css/mobile.css @@ -0,0 +1,48 @@ +/** theme: base + * + * @package Laconica + * @author Meitar Moscovitz + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +/* Go linear. */ +#header, +#header address, +#site_nav_global_primary, +#anon_notice, +#site_nav_local_views .nav, +#core, +#content_inner, +#notices_primary, +.notice, +.notice .entry-title, +.notice div.entry-content, +.pagination, +.pagination .nav, +.aside .section { float: none; } + +/* And liquid. */ +#wrap { width: 95%; } + +body { font-size: 2em; } /* Make things bigger on smaller screens. */ + +#site_nav_global_primary, #site_nav_global_secondary { text-align: center; } + +.notice div.entry-content { margin-left: 0; } +address { margin: 0; } + +#anon_notice, #footer { clear: left; width: auto; font-size: .5em; } + +#content { padding: 18px 0; width: 100%; } +#content h1, #page_notice, #content_inner { padding: 0 18px; } +#content_inner { width: auto; } +.pagination .nav { overflow: auto; } + +#aside_primary { margin: 10px 0 0 0; border: none; padding: 0; width: 100%; } +#popular_notices { float: none; width: auto; } +/* Columns for supplemental info. */ +.aside .section { clear: none; padding: 9px; width: 45%; } +#top_groups_by_post { float: left; } +#featured_users { float: right; } +#export_data { display: none; } From beddf906634054b115d41046ac112cd0264dbfe1 Mon Sep 17 00:00:00 2001 From: Meitar Moscovitz Date: Wed, 11 Feb 2009 03:12:14 +1100 Subject: [PATCH 027/189] Trigger only on handheld device screens, not on browser screens, d'oh! --- lib/action.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/action.php b/lib/action.php index ce37f4760a..3e236d7146 100644 --- a/lib/action.php +++ b/lib/action.php @@ -175,7 +175,7 @@ class Action extends HTMLOutputter // lawsuit 'type' => 'text/css', 'href' => theme_path('css/mobile.css', 'base') . '?version=' . LACONICA_VERSION, // TODO: "handheld" CSS for other mobile devices - 'media' => 'screen and (max-device-width: 480px)')); // Mobile WebKit + 'media' => 'only screen and (max-device-width: 480px)')); // Mobile WebKit } } From 3f859026e6bba0dfa55ba5bf034eadc7bd64a875 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 10 Feb 2009 15:54:13 -0500 Subject: [PATCH 028/189] Add Net_URL_Mapper to extlib --- extlib/Mapper.php | 324 ++++++++++++++++++++++++ extlib/Mapper/Exception.php | 104 ++++++++ extlib/Mapper/Part.php | 142 +++++++++++ extlib/Mapper/Part/Dynamic.php | 81 ++++++ extlib/Mapper/Part/Fixed.php | 70 ++++++ extlib/Mapper/Part/Wildcard.php | 80 ++++++ extlib/Mapper/Path.php | 430 ++++++++++++++++++++++++++++++++ 7 files changed, 1231 insertions(+) create mode 100644 extlib/Mapper.php create mode 100644 extlib/Mapper/Exception.php create mode 100644 extlib/Mapper/Part.php create mode 100644 extlib/Mapper/Part/Dynamic.php create mode 100644 extlib/Mapper/Part/Fixed.php create mode 100644 extlib/Mapper/Part/Wildcard.php create mode 100644 extlib/Mapper/Path.php diff --git a/extlib/Mapper.php b/extlib/Mapper.php new file mode 100644 index 0000000000..65e38818bb --- /dev/null +++ b/extlib/Mapper.php @@ -0,0 +1,324 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Net + * @package Net_URL_Mapper + * @author Bertrand Mansion + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Mapper.php,v 1.1 2007/03/28 10:23:04 mansion Exp $ + * @link http://pear.php.net/package/Net_URL_Mapper + */ + +require_once 'Net/URL/Mapper/Path.php'; +require_once 'Net/URL/Mapper/Exception.php'; + +/** + * URL parser and mapper class + * + * This class takes an URL and a configuration and returns formatted data + * about the request according to a configuration parameter + * + * @category Net + * @package Net_URL_Mapper + * @author Bertrand Mansion + * @version Release: @package_version@ + */ +class Net_URL_Mapper +{ + /** + * Array of Net_URL_Mapper instances + * @var array + */ + private static $instances = array(); + + /** + * Mapped paths collection + * @var array + */ + protected $paths = array(); + + /** + * Prefix used for url mapping + * @var string + */ + protected $prefix = ''; + + /** + * Optional scriptname if mod_rewrite is not available + * @var string + */ + protected $scriptname = ''; + + /** + * Mapper instance id + * @var string + */ + protected $id = '__default__'; + + /** + * Class constructor + * Constructor is private, you should use getInstance() instead. + */ + private function __construct() { } + + /** + * Returns a singleton object corresponding to the requested instance id + * @param string Requested instance name + * @return Object Net_URL_Mapper Singleton + */ + public static function getInstance($id = '__default__') + { + if (!isset(self::$instances[$id])) { + $m = new Net_URL_Mapper(); + $m->id = $id; + self::$instances[$id] = $m; + } + return self::$instances[$id]; + } + + /** + * Returns the instance id + * @return string Mapper instance id + */ + public function getId() + { + return $this->id; + } + + /** + * Parses a path and creates a connection + * @param string The path to connect + * @param array Default values for path parts + * @param array Regular expressions for path parts + * @return object Net_URL_Mapper_Path + */ + public function connect($path, $defaults = array(), $rules = array()) + { + $pathObj = new Net_URL_Mapper_Path($path, $defaults, $rules); + $this->addPath($pathObj); + return $pathObj; + } + + /** + * Set the url prefix if needed + * + * Example: using the prefix to differenciate mapper instances + * + * $fr = Net_URL_Mapper::getInstance('fr'); + * $fr->setPrefix('/fr'); + * $en = Net_URL_Mapper::getInstance('en'); + * $en->setPrefix('/en'); + * + * + * @param string URL prefix + */ + public function setPrefix($prefix) + { + $this->prefix = '/'.trim($prefix, '/'); + } + + /** + * Set the scriptname if mod_rewrite not available + * + * Example: will match and generate url like + * - index.php/view/product/1 + * + * $m = Net_URL_Mapper::getInstance(); + * $m->setScriptname('index.php'); + * + * @param string URL prefix + */ + public function setScriptname($scriptname) + { + $this->scriptname = $scriptname; + } + + /** + * Will attempt to match an url with a defined path + * + * If an url corresponds to a path, the resulting values are returned + * in an array. If none is found, null is returned. In case an url is + * matched but its content doesn't validate the path rules, an exception is + * thrown. + * + * @param string URL + * @return array|null array if match found, null otherwise + * @throws Net_URL_Mapper_InvalidException + */ + public function match($url) + { + $nurl = '/'.trim($url, '/'); + + // Remove scriptname if needed + + if (!empty($this->scriptname) && + strpos($nurl, $this->scriptname) === 0) { + $nurl = substr($nurl, strlen($this->scriptname)); + if (empty($nurl)) { + $nurl = '/'; + } + } + + // Remove prefix + + if (!empty($this->prefix)) { + if (strpos($nurl, $this->prefix) !== 0) { + return null; + } + $nurl = substr($nurl, strlen($this->prefix)); + if (empty($nurl)) { + $nurl = '/'; + } + } + + // Remove query string + + if (($pos = strpos($nurl, '?')) !== false) { + $nurl = substr($nurl, 0, $pos); + } + + $paths = array(); + $values = null; + + // Make a list of paths that conform to route format + + foreach ($this->paths as $path) { + $regex = $path->getFormat(); + if (preg_match($regex, $nurl)) { + $paths[] = $path; + } + } + + // Make sure one of the paths found is valid + + foreach ($paths as $path) { + $regex = $path->getRule(); + if (preg_match($regex, $nurl, $matches)) { + $values = $path->getDefaults(); + array_shift($matches); + $clean = array(); + foreach ($matches as $k => $v) { + $v = trim($v, '/'); + if (!is_int($k) && $v !== '') { + $values[$k] = $v; + } + } + break; + } + } + + // A path conforms but does not validate + + if (is_null($values) && !empty($paths)) { + $e = new Net_URL_Mapper_InvalidException('A path was found but is invalid.'); + $e->setPath($paths[0]); + $e->setUrl($url); + throw $e; + } + + return $values; + } + + /** + * Generate an url based on given parameters + * + * Will attempt to find a path definition that matches the given parameters and + * will generate an url based on this path. + * + * @param array Values to be used for the url generation + * @param array Key/value pairs for query string if needed + * @param string Anchor (fragment) if needed + * @return string|false String if a rule was found, false otherwise + */ + public function generate($values = array(), $qstring = array(), $anchor = '') + { + // Use root path if any + + if (empty($values) && isset($this->paths['/'])) { + return $this->scriptname.$this->prefix.$this->paths['/']->generate($values, $qstring, $anchor); + } + + foreach ($this->paths as $path) { + $set = array(); + foreach ($values as $k => $v) { + if ($path->hasKey($k, $v)) { + $set[$k] = $v; + } + } + + if (count($set) == count($values) && + count($set) <= $path->getMaxKeys()) { + + $req = $path->getRequired(); + if (count(array_intersect(array_keys($set), $req)) != count($req)) { + continue; + } + $gen = $path->generate($set, $qstring, $anchor); + return $this->scriptname.$this->prefix.$gen; + } + } + return false; + } + + /** + * Returns defined paths + * @return array Array of paths + */ + public function getPaths() + { + return $this->paths; + } + + /** + * Reset all paths + * This is probably only useful for testing + */ + public function reset() + { + $this->paths = array(); + $this->prefix = ''; + } + + /** + * Add a new path to the mapper + * @param object Net_URL_Mapper_Path object + */ + public function addPath(Net_URL_Mapper_Path $path) + { + $this->paths[$path->getPath()] = $path; + } + +} +?> \ No newline at end of file diff --git a/extlib/Mapper/Exception.php b/extlib/Mapper/Exception.php new file mode 100644 index 0000000000..ac3ad172b1 --- /dev/null +++ b/extlib/Mapper/Exception.php @@ -0,0 +1,104 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Net + * @package Net_URL_Mapper + * @author Bertrand Mansion + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Exception.php,v 1.1 2007/03/28 10:23:04 mansion Exp $ + * @link http://pear.php.net/package/Net_URL_Mapper + */ + +/** + * Base class for exceptions in PEAR + */ +require_once 'PEAR/Exception.php'; + +/** + * Base class for exceptions in Net_URL_Mapper package + * + * Such a base class is required by the Exception RFC: + * http://pear.php.net/pepr/pepr-proposal-show.php?id=132 + * It will rarely be thrown directly, its specialized subclasses will be + * thrown most of the time. + * + * @category Net + * @package Net_URL_Mapper + * @version Release: @package_version@ + */ +class Net_URL_Mapper_Exception extends PEAR_Exception +{ +} + +/** + * Exception thrown when a path is invalid + * + * A path can conform to a given structure, but contain invalid parameters. + * + * $m = Net_URL_Mapper::getInstance(); + * $m->connect('hi/:name', null, array('name'=>'[a-z]+')); + * $m->match('/hi/FOXY'); // Will throw the exception + * + * + * @category Net + * @package Net_URL_Mapper + * @version Release: @package_version@ + */ +class Net_URL_Mapper_InvalidException extends Net_URL_Mapper_Exception +{ + protected $path; + protected $url; + + public function setPath($path) + { + $this->path = $path; + } + + public function getPath() + { + return $this->path; + } + + public function setUrl($url) + { + $this->url = $url; + } + + public function getUrl() + { + return $this->url; + } +} +?> \ No newline at end of file diff --git a/extlib/Mapper/Part.php b/extlib/Mapper/Part.php new file mode 100644 index 0000000000..2f15b2c162 --- /dev/null +++ b/extlib/Mapper/Part.php @@ -0,0 +1,142 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Net + * @package Net_URL_Mapper + * @author Bertrand Mansion + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Part.php,v 1.1 2007/03/28 10:23:04 mansion Exp $ + * @link http://pear.php.net/package/Net_URL_Mapper + */ + +abstract class Net_URL_Mapper_Part +{ + protected $defaults; + protected $rule; + protected $public; + protected $type; + protected $required = false; + + /** + * Part name if dynamic or content, generated from path + * @var string + */ + public $content; + + const DYNAMIC = 1; + const WILDCARD = 2; + const FIXED = 3; + + public function __construct($content, $path) + { + $this->content = $content; + $this->path = $path; + } + + public function setRule($rule) + { + $this->rule = $rule; + } + + abstract public function getFormat(); + + abstract public function getRule(); + + public function addSlash($str) + { + $str = trim($str, '/'); + if (($pos = strpos($this->path, '/')) !== false) { + if ($pos == 0) { + $str = '/'.$str; + } else { + $str .= '/'; + } + } + return $str; + } + + public function addSlashRegex($str) + { + $str = trim($str, '/'); + if (($pos = strpos($this->path, '/')) !== false) { + if ($pos == 0) { + $str = '\/'.$str; + } else { + $str .= '\/'; + } + } + if (!$this->isRequired()) { + $str = '('.$str.'|)'; + } + return $str; + } + + public function setDefaults($defaults) + { + $this->defaults = (string)$defaults; + } + + public function getType() + { + return $this->type; + } + + public function accept($visitor, $method = null) + { + $args = func_get_args(); + $visitor->$method($this, $args); + } + + public function setRequired($required) + { + $this->required = $required; + } + + public function isRequired() + { + return $this->required; + } + + abstract public function generate($value = null); + + public function match($value) + { + $rule = $this->getRule(); + return preg_match('/^'.$rule.'$/', $this->addSlash($value)); + } + +} + +?> \ No newline at end of file diff --git a/extlib/Mapper/Part/Dynamic.php b/extlib/Mapper/Part/Dynamic.php new file mode 100644 index 0000000000..349d87338c --- /dev/null +++ b/extlib/Mapper/Part/Dynamic.php @@ -0,0 +1,81 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Net + * @package Net_URL_Mapper + * @author Bertrand Mansion + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Dynamic.php,v 1.1 2007/03/28 10:23:04 mansion Exp $ + * @link http://pear.php.net/package/Net_URL_Mapper + */ + +require_once 'Net/URL/Mapper/Part.php'; + +class Net_URL_Mapper_Part_Dynamic extends Net_URL_Mapper_Part +{ + + public function __construct($content, $path) + { + $this->type = Net_URL_Mapper_Part::DYNAMIC; + $this->setRequired(true); + parent::__construct($content, $path); + } + + public function getFormat() + { + return $this->addSlashRegex('[^\/]+'); + } + + public function getRule() + { + if (!empty($this->rule)) { + return '(?P<'.$this->content.'>'.$this->addSlashRegex($this->rule).')'; + } + return '(?P<'.$this->content.'>'.$this->addSlashRegex('[^\/]+').')'; + } + + public function generate($value = null) + { + if (is_array($value) && isset($value[$this->content])) { + $val = $value[$this->content]; + } elseif (!is_array($value) && !is_null($value)) { + $val = $value; + } else { + $val = $this->defaults; + } + return $this->addSlash(urlencode($val)); + } +} +?> \ No newline at end of file diff --git a/extlib/Mapper/Part/Fixed.php b/extlib/Mapper/Part/Fixed.php new file mode 100644 index 0000000000..b315b442db --- /dev/null +++ b/extlib/Mapper/Part/Fixed.php @@ -0,0 +1,70 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Net + * @package Net_URL_Mapper + * @author Bertrand Mansion + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Fixed.php,v 1.1 2007/03/28 10:23:04 mansion Exp $ + * @link http://pear.php.net/package/Net_URL_Mapper + */ + +require_once 'Net/URL/Mapper/Part.php'; + +class Net_URL_Mapper_Part_Fixed extends Net_URL_Mapper_Part +{ + + public function __construct($content, $path) + { + $this->type = Net_URL_Mapper_Part::FIXED; + parent::__construct($content, $path); + } + + public function getFormat() + { + return $this->getRule(); + } + + public function getRule() + { + return preg_quote($this->path, '/'); + } + + public function generate($value = null) + { + return $this->path; + } +} +?> \ No newline at end of file diff --git a/extlib/Mapper/Part/Wildcard.php b/extlib/Mapper/Part/Wildcard.php new file mode 100644 index 0000000000..6085ff6489 --- /dev/null +++ b/extlib/Mapper/Part/Wildcard.php @@ -0,0 +1,80 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Net + * @package Net_URL_Mapper + * @author Bertrand Mansion + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Wildcard.php,v 1.1 2007/03/28 10:23:04 mansion Exp $ + * @link http://pear.php.net/package/Net_URL_Mapper + */ + +require_once 'Net/URL/Mapper/Part.php'; + +class Net_URL_Mapper_Part_Wildcard extends Net_URL_Mapper_Part +{ + + public function __construct($content, $path) + { + $this->type = Net_URL_Mapper_Part::WILDCARD; + $this->setRequired(true); + parent::__construct($content, $path); + } + + public function getFormat() + { + return $this->addSlashRegex('.*');; + } + + public function getRule() + { + return '(?P<'.$this->content.'>'.$this->addSlashRegex('.*').')'; + } + + public function generate($value = null) + { + if (is_array($value) && isset($value[$this->content])) { + $val = $value[$this->content]; + } elseif (!is_array($value) && !is_null($value)) { + $val = $value; + } else { + $val = $this->defaults; + } + return $this->addSlash(str_replace( + array('%2F', '%23'), + array('/', '#'), urlencode($val))); + } +} +?> \ No newline at end of file diff --git a/extlib/Mapper/Path.php b/extlib/Mapper/Path.php new file mode 100644 index 0000000000..b541002c7a --- /dev/null +++ b/extlib/Mapper/Path.php @@ -0,0 +1,430 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Net + * @package Net_URL_Mapper + * @author Bertrand Mansion + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Path.php,v 1.1 2007/03/28 10:23:04 mansion Exp $ + * @link http://pear.php.net/package/Net_URL_Mapper + */ + +require_once 'Net/URL.php'; +require_once 'Net/URL/Mapper/Part/Dynamic.php'; +require_once 'Net/URL/Mapper/Part/Wildcard.php'; +require_once 'Net/URL/Mapper/Part/Fixed.php'; + +class Net_URL_Mapper_Path +{ + private $path = ''; + private $N = 0; + public $token; + public $value; + private $line = 1; + private $state = 1; + + + protected $alias; + protected $rules = array(); + protected $defaults = array(); + protected $parts = array(); + protected $rule; + protected $format; + protected $minKeys; + protected $maxKeys; + protected $fixed = true; + protected $required; + + public function __construct($path = '', $defaults = array(), $rules = array()) + { + $this->path = '/'.trim(Net_URL::resolvePath($path), '/'); + $this->setDefaults($defaults); + $this->setRules($rules); + + try { + $this->parsePath(); + } catch (Exception $e) { + // The path could not be parsed correctly, treat it as fixed + $this->fixed = true; + $part = self::createPart(Net_URL_Mapper_Part::FIXED, $this->path, $this->path); + $this->parts = array($part); + } + $this->getRequired(); + } + + public function getPath() + { + return $this->path; + } + + protected function parsePath() + { + while ($this->yylex()) { } + } + + /** + * Get the path alias + * Path aliases can be used instead of full path + * @return null|string + */ + public function getAlias() + { + return $this->alias; + } + + /** + * Set the path name + * @param string Set the path name + * @see getAlias() + */ + public function setAlias($alias) + { + $this->alias = $alias; + return $this; + } + + /** + * Get the path parts default values + * @return null|array + */ + public function getDefaults() + { + return $this->defaults; + } + + /** + * Set the path parts default values + * @param array Associative array with format partname => value + */ + public function setDefaults($defaults) + { + if (is_array($defaults)) { + $this->defaults = $defaults; + } else { + $this->defaults = array(); + } + } + + /** + * Set the path parts default values + * @param array Associative array with format partname => value + */ + public function setRules($rules) + { + if (is_array($rules)) { + $this->rules = $rules; + } else { + $this->rules = array(); + } + } + + /** + * Returns the regular expression used to match this path + * @return string PERL Regular expression + */ + public function getRule() + { + if (is_null($this->rule)) { + $this->rule = '/^'; + foreach ($this->parts as $path => $part) { + $this->rule .= $part->getRule(); + } + $this->rule .= '$/'; + } + return $this->rule; + } + + public function getFormat() + { + if (is_null($this->format)) { + $this->format = '/^'; + foreach ($this->parts as $path => $part) { + $this->format .= $part->getFormat(); + } + $this->format .= '$/'; + } + return $this->format; + } + + protected function addPart($part) + { + if (array_key_exists($part->content, $this->defaults)) { + $part->setRequired(false); + $part->setDefaults($this->defaults[$part->content]); + } + if (isset($this->rules[$part->content])) { + $part->setRule($this->rules[$part->content]); + } + $this->rule = null; + if ($part->getType() != Net_URL_Mapper_Part::FIXED) { + $this->fixed = false; + $this->parts[$part->content] = $part; + } else { + $this->parts[] = $part; + } + return $part; + } + + public static function createPart($type, $content, $path) + { + switch ($type) { + case Net_URL_Mapper_Part::DYNAMIC: + return new Net_URL_Mapper_Part_Dynamic($content, $path); + break; + case Net_URL_Mapper_Part::WILDCARD: + return new Net_URL_Mapper_Part_Wildcard($content, $path); + break; + default: + return new Net_URL_Mapper_Part_Fixed($content, $path); + } + } + + /** + * Checks whether the path contains the given part by name + * If value parameter is given, the part also checks if the + * given value conforms to the part rule. + * @param string Part name + * @param mixed The value to check against + */ + public function hasKey($partName, $value = null) + { + if (array_key_exists($partName, $this->parts)) { + if (!is_null($value) && $value !== false) { + return $this->parts[$partName]->match($value); + } else { + return true; + } + } elseif (array_key_exists($partName, $this->defaults) && + $value == $this->defaults[$partName]) { + return true; + } + return false; + } + + public function generate($values = array(), $qstring = array(), $anchor = '') + { + $path = ''; + foreach ($this->parts as $part) { + $path .= $part->generate($values); + } + $path = '/'.trim(Net_URL::resolvePath($path), '/'); + if (!empty($qstring)) { + $path .= '?'.http_build_query($qstring); + } + if (!empty($anchor)) { + $path .= '#'.ltrim($anchor, '#'); + } + return $path; + } + + public function getRequired() + { + if (!isset($this->required)) { + $req = array(); + foreach ($this->parts as $part) { + if ($part->isRequired()) { + $req[] = $part->content; + } + } + $this->required = $req; + } + return $this->required; + } + + public function getMaxKeys() + { + if (is_null($this->maxKeys)) { + $this->maxKeys = count($this->required); + $this->maxKeys += count($this->defaults); + } + return $this->maxKeys; + } + + + + + private $_yy_state = 1; + private $_yy_stack = array(); + + function yylex() + { + return $this->{'yylex' . $this->_yy_state}(); + } + + function yypushstate($state) + { + array_push($this->_yy_stack, $this->_yy_state); + $this->_yy_state = $state; + } + + function yypopstate() + { + $this->_yy_state = array_pop($this->_yy_stack); + } + + function yybegin($state) + { + $this->_yy_state = $state; + } + + + + function yylex1() + { + $tokenMap = array ( + 1 => 1, + 3 => 1, + 5 => 1, + 7 => 1, + 9 => 1, + ); + if ($this->N >= strlen($this->path)) { + return false; // end of input + } + $yy_global_pattern = "/^(\/?:\/?\\(([a-zA-Z0-9_]+)\\))|^(\/?\\*\/?\\(([a-zA-Z0-9_]+)\\))|^(\/?:([a-zA-Z0-9_]+))|^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))/"; + + do { + if (preg_match($yy_global_pattern, substr($this->path, $this->N), $yymatches)) { + $yysubmatches = $yymatches; + $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns + if (!count($yymatches)) { + throw new Exception('Error: lexing failed because a rule matched' . + 'an empty string. Input "' . substr($this->path, + $this->N, 5) . '... state START'); + } + next($yymatches); // skip global match + $this->token = key($yymatches); // token number + if ($tokenMap[$this->token]) { + // extract sub-patterns for passing to lex function + $yysubmatches = array_slice($yysubmatches, $this->token + 1, + $tokenMap[$this->token]); + } else { + $yysubmatches = array(); + } + $this->value = current($yymatches); // token value + $r = $this->{'yy_r1_' . $this->token}($yysubmatches); + if ($r === null) { + $this->N += strlen($this->value); + $this->line += substr_count("\n", $this->value); + // accept this token + return true; + } elseif ($r === true) { + // we have changed state + // process this token in the new state + return $this->yylex(); + } elseif ($r === false) { + $this->N += strlen($this->value); + $this->line += substr_count("\n", $this->value); + if ($this->N >= strlen($this->path)) { + return false; // end of input + } + // skip this token + continue; + } else { $yy_yymore_patterns = array( + 1 => "^(\/?\\*\/?\\(([a-zA-Z0-9_]+)\\))|^(\/?:([a-zA-Z0-9_]+))|^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))", + 3 => "^(\/?:([a-zA-Z0-9_]+))|^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))", + 5 => "^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))", + 7 => "^(\/?([^\/:*]+))", + 9 => "", + ); + + // yymore is needed + do { + if (!strlen($yy_yymore_patterns[$this->token])) { + throw new Exception('cannot do yymore for the last token'); + } + if (preg_match($yy_yymore_patterns[$this->token], + substr($this->path, $this->N), $yymatches)) { + $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns + next($yymatches); // skip global match + $this->token = key($yymatches); // token number + $this->value = current($yymatches); // token value + $this->line = substr_count("\n", $this->value); + } + } while ($this->{'yy_r1_' . $this->token}() !== null); + // accept + $this->N += strlen($this->value); + $this->line += substr_count("\n", $this->value); + return true; + } + } else { + throw new Exception('Unexpected input at line' . $this->line . + ': ' . $this->path[$this->N]); + } + break; + } while (true); + } // end function + + + const START = 1; + function yy_r1_1($yy_subpatterns) + { + + $c = $yy_subpatterns[0]; + $part = self::createPart(Net_URL_Mapper_Part::DYNAMIC, $c, $this->value); + $this->addPart($part); + } + function yy_r1_3($yy_subpatterns) + { + + $c = $yy_subpatterns[0]; + $part = self::createPart(Net_URL_Mapper_Part::WILDCARD, $c, $this->value); + $this->addPart($part); + } + function yy_r1_5($yy_subpatterns) + { + + $c = $yy_subpatterns[0]; + $part = self::createPart(Net_URL_Mapper_Part::DYNAMIC, $c, $this->value); + $this->addPart($part); + } + function yy_r1_7($yy_subpatterns) + { + + $c = $yy_subpatterns[0]; + $part = self::createPart(Net_URL_Mapper_Part::WILDCARD, $c, $this->value); + $this->addPart($part); + } + function yy_r1_9($yy_subpatterns) + { + + $c = $yy_subpatterns[0]; + $part = self::createPart(Net_URL_Mapper_Part::FIXED, $c, $this->value); + $this->addPart($part); + } + +} + +?> \ No newline at end of file From e21f2cf29ea6495b74ce79f71e3ea0af013590fd Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 10 Feb 2009 15:55:37 -0500 Subject: [PATCH 029/189] Move Mapper to correct spot in the hierarchy --- extlib/{ => Net/URL}/Mapper.php | 0 extlib/{ => Net/URL}/Mapper/Exception.php | 0 extlib/{ => Net/URL}/Mapper/Part.php | 0 extlib/{ => Net/URL}/Mapper/Part/Dynamic.php | 0 extlib/{ => Net/URL}/Mapper/Part/Fixed.php | 0 extlib/{ => Net/URL}/Mapper/Part/Wildcard.php | 0 extlib/{ => Net/URL}/Mapper/Path.php | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename extlib/{ => Net/URL}/Mapper.php (100%) rename extlib/{ => Net/URL}/Mapper/Exception.php (100%) rename extlib/{ => Net/URL}/Mapper/Part.php (100%) rename extlib/{ => Net/URL}/Mapper/Part/Dynamic.php (100%) rename extlib/{ => Net/URL}/Mapper/Part/Fixed.php (100%) rename extlib/{ => Net/URL}/Mapper/Part/Wildcard.php (100%) rename extlib/{ => Net/URL}/Mapper/Path.php (100%) diff --git a/extlib/Mapper.php b/extlib/Net/URL/Mapper.php similarity index 100% rename from extlib/Mapper.php rename to extlib/Net/URL/Mapper.php diff --git a/extlib/Mapper/Exception.php b/extlib/Net/URL/Mapper/Exception.php similarity index 100% rename from extlib/Mapper/Exception.php rename to extlib/Net/URL/Mapper/Exception.php diff --git a/extlib/Mapper/Part.php b/extlib/Net/URL/Mapper/Part.php similarity index 100% rename from extlib/Mapper/Part.php rename to extlib/Net/URL/Mapper/Part.php diff --git a/extlib/Mapper/Part/Dynamic.php b/extlib/Net/URL/Mapper/Part/Dynamic.php similarity index 100% rename from extlib/Mapper/Part/Dynamic.php rename to extlib/Net/URL/Mapper/Part/Dynamic.php diff --git a/extlib/Mapper/Part/Fixed.php b/extlib/Net/URL/Mapper/Part/Fixed.php similarity index 100% rename from extlib/Mapper/Part/Fixed.php rename to extlib/Net/URL/Mapper/Part/Fixed.php diff --git a/extlib/Mapper/Part/Wildcard.php b/extlib/Net/URL/Mapper/Part/Wildcard.php similarity index 100% rename from extlib/Mapper/Part/Wildcard.php rename to extlib/Net/URL/Mapper/Part/Wildcard.php diff --git a/extlib/Mapper/Path.php b/extlib/Net/URL/Mapper/Path.php similarity index 100% rename from extlib/Mapper/Path.php rename to extlib/Net/URL/Mapper/Path.php From b45ea01dab70d538aaec8359b1e6e25dec0975e6 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 10 Feb 2009 16:19:45 -0500 Subject: [PATCH 030/189] Required Net URL needed for Net URL Mapper --- extlib/Net/URL.php | 485 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 485 insertions(+) create mode 100644 extlib/Net/URL.php diff --git a/extlib/Net/URL.php b/extlib/Net/URL.php new file mode 100644 index 0000000000..3dcfef60d0 --- /dev/null +++ b/extlib/Net/URL.php @@ -0,0 +1,485 @@ + | +// +-----------------------------------------------------------------------+ +// +// $Id: URL.php,v 1.49 2007/06/28 14:43:07 davidc Exp $ +// +// Net_URL Class + + +class Net_URL +{ + var $options = array('encode_query_keys' => false); + /** + * Full url + * @var string + */ + var $url; + + /** + * Protocol + * @var string + */ + var $protocol; + + /** + * Username + * @var string + */ + var $username; + + /** + * Password + * @var string + */ + var $password; + + /** + * Host + * @var string + */ + var $host; + + /** + * Port + * @var integer + */ + var $port; + + /** + * Path + * @var string + */ + var $path; + + /** + * Query string + * @var array + */ + var $querystring; + + /** + * Anchor + * @var string + */ + var $anchor; + + /** + * Whether to use [] + * @var bool + */ + var $useBrackets; + + /** + * PHP4 Constructor + * + * @see __construct() + */ + function Net_URL($url = null, $useBrackets = true) + { + $this->__construct($url, $useBrackets); + } + + /** + * PHP5 Constructor + * + * Parses the given url and stores the various parts + * Defaults are used in certain cases + * + * @param string $url Optional URL + * @param bool $useBrackets Whether to use square brackets when + * multiple querystrings with the same name + * exist + */ + function __construct($url = null, $useBrackets = true) + { + $this->url = $url; + $this->useBrackets = $useBrackets; + + $this->initialize(); + } + + function initialize() + { + $HTTP_SERVER_VARS = !empty($_SERVER) ? $_SERVER : $GLOBALS['HTTP_SERVER_VARS']; + + $this->user = ''; + $this->pass = ''; + $this->host = ''; + $this->port = 80; + $this->path = ''; + $this->querystring = array(); + $this->anchor = ''; + + // Only use defaults if not an absolute URL given + if (!preg_match('/^[a-z0-9]+:\/\//i', $this->url)) { + $this->protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 'https' : 'http'); + + /** + * Figure out host/port + */ + if (!empty($HTTP_SERVER_VARS['HTTP_HOST']) && + preg_match('/^(.*)(:([0-9]+))?$/U', $HTTP_SERVER_VARS['HTTP_HOST'], $matches)) + { + $host = $matches[1]; + if (!empty($matches[3])) { + $port = $matches[3]; + } else { + $port = $this->getStandardPort($this->protocol); + } + } + + $this->user = ''; + $this->pass = ''; + $this->host = !empty($host) ? $host : (isset($HTTP_SERVER_VARS['SERVER_NAME']) ? $HTTP_SERVER_VARS['SERVER_NAME'] : 'localhost'); + $this->port = !empty($port) ? $port : (isset($HTTP_SERVER_VARS['SERVER_PORT']) ? $HTTP_SERVER_VARS['SERVER_PORT'] : $this->getStandardPort($this->protocol)); + $this->path = !empty($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : '/'; + $this->querystring = isset($HTTP_SERVER_VARS['QUERY_STRING']) ? $this->_parseRawQuerystring($HTTP_SERVER_VARS['QUERY_STRING']) : null; + $this->anchor = ''; + } + + // Parse the url and store the various parts + if (!empty($this->url)) { + $urlinfo = parse_url($this->url); + + // Default querystring + $this->querystring = array(); + + foreach ($urlinfo as $key => $value) { + switch ($key) { + case 'scheme': + $this->protocol = $value; + $this->port = $this->getStandardPort($value); + break; + + case 'user': + case 'pass': + case 'host': + case 'port': + $this->$key = $value; + break; + + case 'path': + if ($value{0} == '/') { + $this->path = $value; + } else { + $path = dirname($this->path) == DIRECTORY_SEPARATOR ? '' : dirname($this->path); + $this->path = sprintf('%s/%s', $path, $value); + } + break; + + case 'query': + $this->querystring = $this->_parseRawQueryString($value); + break; + + case 'fragment': + $this->anchor = $value; + break; + } + } + } + } + /** + * Returns full url + * + * @return string Full url + * @access public + */ + function getURL() + { + $querystring = $this->getQueryString(); + + $this->url = $this->protocol . '://' + . $this->user . (!empty($this->pass) ? ':' : '') + . $this->pass . (!empty($this->user) ? '@' : '') + . $this->host . ($this->port == $this->getStandardPort($this->protocol) ? '' : ':' . $this->port) + . $this->path + . (!empty($querystring) ? '?' . $querystring : '') + . (!empty($this->anchor) ? '#' . $this->anchor : ''); + + return $this->url; + } + + /** + * Adds or updates a querystring item (URL parameter). + * Automatically encodes parameters with rawurlencode() if $preencoded + * is false. + * You can pass an array to $value, it gets mapped via [] in the URL if + * $this->useBrackets is activated. + * + * @param string $name Name of item + * @param string $value Value of item + * @param bool $preencoded Whether value is urlencoded or not, default = not + * @access public + */ + function addQueryString($name, $value, $preencoded = false) + { + if ($this->getOption('encode_query_keys')) { + $name = rawurlencode($name); + } + + if ($preencoded) { + $this->querystring[$name] = $value; + } else { + $this->querystring[$name] = is_array($value) ? array_map('rawurlencode', $value): rawurlencode($value); + } + } + + /** + * Removes a querystring item + * + * @param string $name Name of item + * @access public + */ + function removeQueryString($name) + { + if ($this->getOption('encode_query_keys')) { + $name = rawurlencode($name); + } + + if (isset($this->querystring[$name])) { + unset($this->querystring[$name]); + } + } + + /** + * Sets the querystring to literally what you supply + * + * @param string $querystring The querystring data. Should be of the format foo=bar&x=y etc + * @access public + */ + function addRawQueryString($querystring) + { + $this->querystring = $this->_parseRawQueryString($querystring); + } + + /** + * Returns flat querystring + * + * @return string Querystring + * @access public + */ + function getQueryString() + { + if (!empty($this->querystring)) { + foreach ($this->querystring as $name => $value) { + // Encode var name + $name = rawurlencode($name); + + if (is_array($value)) { + foreach ($value as $k => $v) { + $querystring[] = $this->useBrackets ? sprintf('%s[%s]=%s', $name, $k, $v) : ($name . '=' . $v); + } + } elseif (!is_null($value)) { + $querystring[] = $name . '=' . $value; + } else { + $querystring[] = $name; + } + } + $querystring = implode(ini_get('arg_separator.output'), $querystring); + } else { + $querystring = ''; + } + + return $querystring; + } + + /** + * Parses raw querystring and returns an array of it + * + * @param string $querystring The querystring to parse + * @return array An array of the querystring data + * @access private + */ + function _parseRawQuerystring($querystring) + { + $parts = preg_split('/[' . preg_quote(ini_get('arg_separator.input'), '/') . ']/', $querystring, -1, PREG_SPLIT_NO_EMPTY); + $return = array(); + + foreach ($parts as $part) { + if (strpos($part, '=') !== false) { + $value = substr($part, strpos($part, '=') + 1); + $key = substr($part, 0, strpos($part, '=')); + } else { + $value = null; + $key = $part; + } + + if (!$this->getOption('encode_query_keys')) { + $key = rawurldecode($key); + } + + if (preg_match('#^(.*)\[([0-9a-z_-]*)\]#i', $key, $matches)) { + $key = $matches[1]; + $idx = $matches[2]; + + // Ensure is an array + if (empty($return[$key]) || !is_array($return[$key])) { + $return[$key] = array(); + } + + // Add data + if ($idx === '') { + $return[$key][] = $value; + } else { + $return[$key][$idx] = $value; + } + } elseif (!$this->useBrackets AND !empty($return[$key])) { + $return[$key] = (array)$return[$key]; + $return[$key][] = $value; + } else { + $return[$key] = $value; + } + } + + return $return; + } + + /** + * Resolves //, ../ and ./ from a path and returns + * the result. Eg: + * + * /foo/bar/../boo.php => /foo/boo.php + * /foo/bar/../../boo.php => /boo.php + * /foo/bar/.././/boo.php => /foo/boo.php + * + * This method can also be called statically. + * + * @param string $path URL path to resolve + * @return string The result + */ + function resolvePath($path) + { + $path = explode('/', str_replace('//', '/', $path)); + + for ($i=0; $i 1 OR ($i == 1 AND $path[0] != '') ) ) { + unset($path[$i]); + unset($path[$i-1]); + $path = array_values($path); + $i -= 2; + + } elseif ($path[$i] == '..' AND $i == 1 AND $path[0] == '') { + unset($path[$i]); + $path = array_values($path); + $i--; + + } else { + continue; + } + } + + return implode('/', $path); + } + + /** + * Returns the standard port number for a protocol + * + * @param string $scheme The protocol to lookup + * @return integer Port number or NULL if no scheme matches + * + * @author Philippe Jausions + */ + function getStandardPort($scheme) + { + switch (strtolower($scheme)) { + case 'http': return 80; + case 'https': return 443; + case 'ftp': return 21; + case 'imap': return 143; + case 'imaps': return 993; + case 'pop3': return 110; + case 'pop3s': return 995; + default: return null; + } + } + + /** + * Forces the URL to a particular protocol + * + * @param string $protocol Protocol to force the URL to + * @param integer $port Optional port (standard port is used by default) + */ + function setProtocol($protocol, $port = null) + { + $this->protocol = $protocol; + $this->port = is_null($port) ? $this->getStandardPort($protocol) : $port; + } + + /** + * Set an option + * + * This function set an option + * to be used thorough the script. + * + * @access public + * @param string $optionName The optionname to set + * @param string $value The value of this option. + */ + function setOption($optionName, $value) + { + if (!array_key_exists($optionName, $this->options)) { + return false; + } + + $this->options[$optionName] = $value; + $this->initialize(); + } + + /** + * Get an option + * + * This function gets an option + * from the $this->options array + * and return it's value. + * + * @access public + * @param string $opionName The name of the option to retrieve + * @see $this->options + */ + function getOption($optionName) + { + if (!isset($this->options[$optionName])) { + return false; + } + + return $this->options[$optionName]; + } + +} +?> From 7763f804cafa6f41316e30512c4ceab9b78f2c08 Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Tue, 10 Feb 2009 22:04:47 +0000 Subject: [PATCH 031/189] trac #233 Explicitely show we have an rss feed for notice searches. --- actions/noticesearch.php | 76 +++++++++++++++++++++++++++++----------- lib/feedlist.php | 7 ++++ 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/actions/noticesearch.php b/actions/noticesearch.php index a5f01350c4..2d94a7906f 100644 --- a/actions/noticesearch.php +++ b/actions/noticesearch.php @@ -78,6 +78,62 @@ class NoticesearchAction extends SearchAction return _('Text search'); } + + function showExportData() + { + $q = $this->trimmed('q'); + if (!$q) { + return; + } + $fl = new FeedList($this); + $fl->show(array(0 => array('href' => common_local_url('noticesearchrss', array('q' => $q)), + 'type' => 'rss', + 'version' => 'RSS 1.0', + 'item' => 'noticesearchrss'))); + } + + + + function showFeeds() + { + $q = $this->trimmed('q'); + if (!$q) { + return; + } + + $this->element('link', array('rel' => 'alternate', + 'href' => common_local_url('noticesearchrss', + array('q' => $q)), + 'type' => 'application/rss+xml', + 'title' => _('Search Stream Feed'))); + } + + + /** + * Show header + * + * @param array $arr array containing the query + * + * @return void + */ + + function extraHead2() + { + $q = $this->trimmed('q'); + if ($q) { + $this->element('link', array('rel' => 'alternate', + 'href' => common_local_url('noticesearchrss', + array('q' => $q)), + 'type' => 'application/rss+xml', + 'title' => _('Search Stream Feed'))); + } + } + + + + + + /** * Show results * @@ -119,26 +175,6 @@ class NoticesearchAction extends SearchAction $page, 'noticesearch', array('q' => $q)); } - /** - * Show header - * - * @param array $arr array containing the query - * - * @return void - */ - - function extraHead() - { - $q = $this->trimmed('q'); - if ($q) { - $this->element('link', array('rel' => 'alternate', - 'href' => common_local_url('noticesearchrss', - array('q' => $q)), - 'type' => 'application/rss+xml', - 'title' => _('Search Stream Feed'))); - } - } - /** * Show notice * diff --git a/lib/feedlist.php b/lib/feedlist.php index 47d909e969..8bfcb9c5ab 100644 --- a/lib/feedlist.php +++ b/lib/feedlist.php @@ -112,6 +112,13 @@ class FeedList extends Widget $feed['textContent'] = "Atom"; break; + case 'noticesearchrss': + $feed_classname = $feed['type']; + $feed_mimetype = "application/".$feed['type']."+xml"; + $feed_title = $feed['version']." feed for this notice search"; + $feed['textContent'] = "RSS"; + break; + case 'tagrss': $feed_classname = $feed['type']; $feed_mimetype = "application/".$feed['type']."+xml"; From 646fdea1bf8a93bbfb60212982a218994ffa8bbe Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Tue, 10 Feb 2009 17:42:58 -0500 Subject: [PATCH 032/189] Fixed 1174: schemeless URL auto-linking bug --- lib/util.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/util.php b/lib/util.php index 75d1e21a47..a130c7d49c 100644 --- a/lib/util.php +++ b/lib/util.php @@ -418,8 +418,8 @@ function common_replace_urls_callback($text, $callback) { // Make sure we didn't pick up an email address if (preg_match('#^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$#i', $url)) continue; - // Remove trailing punctuation - $url = rtrim($url, '.?!,;:\'"`'); + // Remove surrounding punctuation + $url = trim($url, '.?!,;:\'"`([<'); // Remove surrounding parens and the like preg_match('/[)\]>]+$/', $url, $trailing); From 7b9e69eb8923ef3018f0ce3e6042d9901ed16abd Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 10 Feb 2009 22:32:38 -0500 Subject: [PATCH 033/189] integrate URL routing into core code --- htaccess.sample | 162 +---------------------- index.php | 148 +++++++++++++-------- lib/router.php | 340 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 435 insertions(+), 215 deletions(-) create mode 100644 lib/router.php diff --git a/htaccess.sample b/htaccess.sample index 5f9827f963..634900dbf6 100644 --- a/htaccess.sample +++ b/htaccess.sample @@ -4,165 +4,9 @@ RewriteEngine On RewriteBase /mublog/ -RewriteRule ^$ index.php?action=public [L,QSA] -RewriteRule ^rss$ index.php?action=publicrss [L,QSA] -RewriteRule ^xrds$ index.php?action=publicxrds [L,QSA] -RewriteRule ^featuredrss$ index.php?action=featuredrss [L,QSA] -RewriteRule ^favoritedrss$ index.php?action=favoritedrss [L,QSA] -RewriteRule ^opensearch/people$ index.php?action=opensearch&type=people [L,QSA] -RewriteRule ^opensearch/notice$ index.php?action=opensearch&type=notice [L,QSA] - -RewriteRule ^doc/about$ index.php?action=doc&title=about [L,QSA] -RewriteRule ^doc/contact$ index.php?action=doc&title=contact [L,QSA] -RewriteRule ^doc/faq$ index.php?action=doc&title=faq [L,QSA] -RewriteRule ^doc/help$ index.php?action=doc&title=help [L,QSA] -RewriteRule ^doc/im$ index.php?action=doc&title=im [L,QSA] -RewriteRule ^doc/openid$ index.php?action=doc&title=openid [L,QSA] -RewriteRule ^doc/openmublog$ index.php?action=doc&title=openmublog [L,QSA] -RewriteRule ^doc/privacy$ index.php?action=doc&title=privacy [L,QSA] -RewriteRule ^doc/source$ index.php?action=doc&title=source [L,QSA] -RewriteRule ^doc/tags$ index.php?action=doc&title=tags [L,QSA] -RewriteRule ^doc/groups$ index.php?action=doc&title=groups [L,QSA] -RewriteRule ^doc/sms$ index.php?action=doc&title=sms [L,QSA] - -RewriteRule ^facebook/$ index.php?action=facebookhome [L,QSA] -RewriteRule ^facebook/index.php$ index.php?action=facebookhome [L,QSA] -RewriteRule ^facebook/settings.php$ index.php?action=facebooksettings [L,QSA] -RewriteRule ^facebook/invite.php$ index.php?action=facebookinvite [L,QSA] -RewriteRule ^facebook/remove$ index.php?action=facebookremove [L,QSA] - -RewriteRule ^main/login$ index.php?action=login [L,QSA] -RewriteRule ^main/logout$ index.php?action=logout [L,QSA] -RewriteRule ^main/register/(.*)$ index.php?action=register&code=$1 [L,QSA] -RewriteRule ^main/register$ index.php?action=register [L,QSA] -RewriteRule ^main/openid$ index.php?action=openidlogin [L,QSA] -RewriteRule ^main/remote$ index.php?action=remotesubscribe [L,QSA] - -RewriteRule ^main/subscribe$ index.php?action=subscribe [L,QSA] -RewriteRule ^main/unsubscribe$ index.php?action=unsubscribe [L,QSA] -RewriteRule ^main/confirmaddress$ index.php?action=confirmaddress [L,QSA] -RewriteRule ^main/confirmaddress/(.*)$ index.php?action=confirmaddress&code=$1 [L,QSA] -RewriteRule ^main/recoverpassword$ index.php?action=recoverpassword [L,QSA] -RewriteRule ^main/recoverpassword/(.*)$ index.php?action=recoverpassword&code=$1 [L,QSA] -RewriteRule ^main/invite$ index.php?action=invite [L,QSA] - -RewriteRule ^main/favor$ index.php?action=favor [L,QSA] -RewriteRule ^main/disfavor$ index.php?action=disfavor [L,QSA] - -RewriteRule ^main/sup$ index.php?action=sup [L,QSA] - -RewriteRule ^main/tagother$ index.php?action=tagother [L,QSA] - -RewriteRule ^main/block$ index.php?action=block [L,QSA] - -RewriteRule ^settings/profile$ index.php?action=profilesettings [L,QSA] -RewriteRule ^settings/avatar$ index.php?action=avatarsettings [L,QSA] -RewriteRule ^settings/password$ index.php?action=passwordsettings [L,QSA] -RewriteRule ^settings/openid$ index.php?action=openidsettings [L,QSA] -RewriteRule ^settings/im$ index.php?action=imsettings [L,QSA] -RewriteRule ^settings/email$ index.php?action=emailsettings [L,QSA] -RewriteRule ^settings/sms$ index.php?action=smssettings [L,QSA] -RewriteRule ^settings/twitter$ index.php?action=twittersettings [L,QSA] -RewriteRule ^settings/other$ index.php?action=othersettings [L,QSA] - -RewriteRule ^search/group$ index.php?action=groupsearch [L,QSA] -RewriteRule ^search/people$ index.php?action=peoplesearch [L,QSA] -RewriteRule ^search/notice$ index.php?action=noticesearch [L,QSA] -RewriteRule ^search/notice/rss$ index.php?action=noticesearchrss [L,QSA] - -RewriteRule ^notice/new$ index.php?action=newnotice [L,QSA] -RewriteRule ^notice/(\d+)$ index.php?action=shownotice¬ice=$1 [L,QSA] -RewriteRule ^notice/delete/((\d+))?$ index.php?action=deletenotice¬ice=$2 [L,QSA] -RewriteRule ^notice/delete$ index.php?action=deletenotice [L,QSA] - -RewriteRule ^message/new$ index.php?action=newmessage [L,QSA] -RewriteRule ^message/(\d+)$ index.php?action=showmessage&message=$1 [L,QSA] - -RewriteRule ^user/(\d+)$ index.php?action=userbyid&id=$1 [L,QSA] - -RewriteRule ^tags/?$ index.php?action=publictagcloud [L,QSA] -RewriteRule ^tag/([a-zA-Z0-9]+)/rss$ index.php?action=tagrss&tag=$1 [L,QSA] -RewriteRule ^tag(/(.*))?$ index.php?action=tag&tag=$2 [L,QSA] - -RewriteRule ^peopletag/([a-zA-Z0-9]+)$ index.php?action=peopletag&tag=$1 [L,QSA] - -RewriteRule ^featured/?$ index.php?action=featured [L,QSA] -RewriteRule ^favorited/?$ index.php?action=favorited [L,QSA] - -RewriteRule ^group/new$ index.php?action=newgroup [L,QSA] -RewriteRule ^group/([a-zA-Z0-9]+)/edit$ index.php?action=editgroup&nickname=$1 [L,QSA] -RewriteRule ^group/([a-zA-Z0-9]+)/join$ index.php?action=joingroup&nickname=$1 [L,QSA] -RewriteRule ^group/([a-zA-Z0-9]+)/leave$ index.php?action=leavegroup&nickname=$1 [L,QSA] -RewriteRule ^group/([a-zA-Z0-9]+)/members$ index.php?action=groupmembers&nickname=$1 [L,QSA] -RewriteRule ^group/([a-zA-Z0-9]+)/logo$ index.php?action=grouplogo&nickname=$1 [L,QSA] -RewriteRule ^group/([0-9]+)/id$ index.php?action=groupbyid&id=$1 [L,QSA] -RewriteRule ^group/([a-zA-Z0-9]+)/rss$ index.php?action=grouprss&nickname=$1 [L,QSA] -RewriteRule ^group/([a-zA-Z0-9]+)$ index.php?action=showgroup&nickname=$1 [L,QSA] -RewriteRule ^group$ index.php?action=groups [L,QSA] - -# Twitter-compatible API rewrites -# XXX: Surely these can be refactored a little -- Zach -RewriteRule ^api/statuses/public_timeline(.*)$ index.php?action=api&apiaction=statuses&method=public_timeline$1 [L,QSA] -RewriteRule ^api/statuses/friends_timeline(.*)$ index.php?action=api&apiaction=statuses&method=friends_timeline$1 [L,QSA] -RewriteRule ^api/statuses/user_timeline/(.*)$ index.php?action=api&apiaction=statuses&method=user_timeline&argument=$1 [L,QSA] -RewriteRule ^api/statuses/user_timeline(.*)$ index.php?action=api&apiaction=statuses&method=user_timeline$1 [L,QSA] -RewriteRule ^api/statuses/show/(.*)$ index.php?action=api&apiaction=statuses&method=show&argument=$1 [L,QSA] -RewriteRule ^api/statuses/update(.*)$ index.php?action=api&apiaction=statuses&method=update$1 [L,QSA] -RewriteRule ^api/statuses/replies(.*)$ index.php?action=api&apiaction=statuses&method=replies&argument=$1 [L,QSA] -RewriteRule ^api/statuses/destroy/(.*)$ index.php?action=api&apiaction=statuses&method=destroy&argument=$1 [L,QSA] -RewriteRule ^api/statuses/friends/(.*)$ index.php?action=api&apiaction=statuses&method=friends&argument=$1 [L,QSA] -RewriteRule ^api/statuses/friends(.*)$ index.php?action=api&apiaction=statuses&method=friends$1 [L,QSA] -RewriteRule ^api/statuses/followers/(.*)$ index.php?action=api&apiaction=statuses&method=followers&argument=$1 [L,QSA] -RewriteRule ^api/statuses/followers(.*)$ index.php?action=api&apiaction=statuses&method=followers$1 [L,QSA] -RewriteRule ^api/statuses/featured(.*)$ index.php?action=api&apiaction=statuses&method=featured$1 [L,QSA] -RewriteRule ^api/users/show/(.*)$ index.php?action=api&apiaction=users&method=show&argument=$1 [L,QSA] -RewriteRule ^api/users/show(.*)$ index.php?action=api&apiaction=users&method=show$1 [L,QSA] -RewriteRule ^api/direct_messages/sent(.*)$ index.php?action=api&apiaction=direct_messages&method=sent$1 [L,QSA] -RewriteRule ^api/direct_messages/destroy/(.*)$ index.php?action=api&apiaction=direct_messages&method=destroy&argument=$1 [L,QSA] -RewriteRule ^api/direct_messages/new(.*)$ index.php?action=api&apiaction=direct_messages&method=create$1 [L,QSA] -RewriteRule ^api/direct_messages(.*)$ index.php?action=api&apiaction=direct_messages&method=direct_messages$1 [L,QSA] -RewriteRule ^api/friendships/create/(.*)$ index.php?action=api&apiaction=friendships&method=create&argument=$1 [L,QSA] -RewriteRule ^api/friendships/destroy/(.*)$ index.php?action=api&apiaction=friendships&method=destroy&argument=$1 [L,QSA] -RewriteRule ^api/friendships/exists(.*)$ index.php?action=api&apiaction=friendships&method=exists$1 [L,QSA] -RewriteRule ^api/account/verify_credentials(.*)$ index.php?action=api&apiaction=account&method=verify_credentials$1 [L,QSA] -RewriteRule ^api/account/end_session$ index.php?action=api&apiaction=account&method=end_session$1 [L,QSA] -RewriteRule ^api/account/update_location(.*)$ index.php?action=api&apiaction=account&method=update_location$1 [L,QSA] -RewriteRule ^api/account/update_delivery_device(.*)$ index.php?action=api&apiaction=account&method=update_delivery_device$1 [L,QSA] -RewriteRule ^api/account/rate_limit_status(.*)$ index.php?action=api&apiaction=account&method=rate_limit_status$1 [L,QSA] -RewriteRule ^api/favorites/create/(.*)$ index.php?action=api&apiaction=favorites&method=create&argument=$1 [L,QSA] -RewriteRule ^api/favorites/destroy/(.*)$ index.php?action=api&apiaction=favorites&method=destroy&argument=$1 [L,QSA] -RewriteRule ^api/favorites/(.*)$ index.php?action=api&apiaction=favorites&method=favorites&argument=$1 [L,QSA] -RewriteRule ^api/favorites(.*)$ index.php?action=api&apiaction=favorites&method=favorites$1 [L,QSA] -RewriteRule ^api/notifications/follow/(.*)$ index.php?action=api&apiaction=notifications&method=follow&argument=$1 [L,QSA] -RewriteRule ^api/notifications/leave/(.*)$ index.php?action=api&apiaction=notifications&method=leave&argument=$1 [L,QSA] -RewriteRule ^api/blocks/create/(.*)$ index.php?action=api&apiaction=blocks&method=create&argument=$1 [L,QSA] -RewriteRule ^api/blocks/destroy/(.*)$ index.php?action=api&apiaction=blocks&method=destroy&argument=$1 [L,QSA] -RewriteRule ^api/help/(.*)$ index.php?action=api&apiaction=help&method=$1 [L,QSA] -RewriteRule ^api/laconica/version(.*)$ index.php?action=api&apiaction=laconica&method=version$1 [L,QSA] -RewriteRule ^api/laconica/config(.*)$ index.php?action=api&apiaction=laconica&method=config$1 [L,QSA] -RewriteRule ^api/laconica/wadl\.xml$ index.php?action=api&apiaction=laconica&method=wadl.xml [L,QSA] - -RewriteRule ^(\w+)/subscriptions$ index.php?action=subscriptions&nickname=$1 [L,QSA] -RewriteRule ^(\w+)/subscriptions/([a-zA-Z0-9]+)$ index.php?action=subscriptions&nickname=$1&tag=$2 [L,QSA] -RewriteRule ^(\w+)/subscribers/([a-zA-Z0-9]+)$ index.php?action=subscribers&nickname=$1&tag=$2 [L,QSA] -RewriteRule ^(\w+)/subscribers$ index.php?action=subscribers&nickname=$1 [L,QSA] -RewriteRule ^(\w+)/nudge$ index.php?action=nudge&nickname=$1 [L,QSA] -RewriteRule ^(\w+)/xrds$ index.php?action=xrds&nickname=$1 [L,QSA] -RewriteRule ^(\w+)/rss$ index.php?action=userrss&nickname=$1 [L,QSA] -RewriteRule ^(\w+)/all$ index.php?action=all&nickname=$1 [L,QSA] -RewriteRule ^(\w+)/all/rss$ index.php?action=allrss&nickname=$1 [L,QSA] -RewriteRule ^(\w+)/foaf$ index.php?action=foaf&nickname=$1 [L,QSA] -RewriteRule ^(\w+)/replies$ index.php?action=replies&nickname=$1 [L,QSA] -RewriteRule ^(\w+)/replies/rss$ index.php?action=repliesrss&nickname=$1 [L,QSA] -RewriteRule ^(\w+)/avatar/(original|96|48|24)$ index.php?action=avatarbynickname&nickname=$1&size=$2 [L,QSA] -RewriteRule ^(\w+)/favorites$ index.php?action=showfavorites&nickname=$1 [L,QSA] -RewriteRule ^(\w+)/favorites/rss$ index.php?action=favoritesrss&nickname=$1 [L,QSA] -RewriteRule ^(\w+)/inbox$ index.php?action=inbox&nickname=$1 [L,QSA] -RewriteRule ^(\w+)/outbox$ index.php?action=outbox&nickname=$1 [L,QSA] -RewriteRule ^(\w+)/microsummary$ index.php?action=microsummary&nickname=$1 [L,QSA] -RewriteRule ^(\w+)/groups$ index.php?action=usergroups&nickname=$1 [L,QSA] - -RewriteRule ^(\w+)$ index.php?action=showstream&nickname=$1 [L,QSA] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule (.*) index.php?p=$1 [L,QSA] Order allow,deny diff --git a/index.php b/index.php index e62d9469ab..f334e2c345 100644 --- a/index.php +++ b/index.php @@ -22,69 +22,105 @@ define('LACONICA', true); require_once INSTALLDIR . '/lib/common.php'; -// XXX: we need a little more structure in this script +$user = null; +$action = null; -// get and cache current user - -$user = common_current_user(); - -// initialize language env - -common_init_language(); - -$action = $_REQUEST['action']; - -if (!$action || !preg_match('/^[a-zA-Z0-9_-]*$/', $action)) { - common_redirect(common_local_url('public')); -} - -// If the site is private, and they're not on one of the "public" -// parts of the site, redirect to login - -if (!$user && common_config('site', 'private') && - !in_array($action, array('login', 'openidlogin', 'finishopenidlogin', - 'recoverpassword', 'api', 'doc', 'register'))) { - common_redirect(common_local_url('login')); -} - -$action_class = ucfirst($action).'Action'; - -if (!class_exists($action_class)) { - $cac = new ClientErrorAction(_('Unknown action'), 404); - $cac->showPage(); -} else { - $action_obj = new $action_class(); - - // XXX: find somewhere for this little block to live - - if ($config['db']['mirror'] && $action_obj->isReadOnly()) { - if (is_array($config['db']['mirror'])) { - // "load balancing", ha ha - $k = array_rand($config['db']['mirror']); - - $mirror = $config['db']['mirror'][$k]; - } else { - $mirror = $config['db']['mirror']; - } - $config['db']['database'] = $mirror; +function getPath($req) { + if (common_config('site', 'fancy')) { + return $req['p']; + } else if ($_SERVER['PATH_INFO']) { + return $_SERVER['PATH_INFO']; + } else { + return $req['p']; } +} - try { - if ($action_obj->prepare($_REQUEST)) { - $action_obj->handle($_REQUEST); - } - } catch (ClientException $cex) { - $cac = new ClientErrorAction($cex->getMessage(), $cex->getCode()); +function main() { + + global $user, $action; + + // XXX: we need a little more structure in this script + + // get and cache current user + + $user = common_current_user(); + + // initialize language env + + common_init_language(); + + $path = getPath($_REQUEST); + + $r = new Router(); + + $args = $r->map($path); + + if (!$args) { + $cac = new ClientErrorAction(_('Unknown page'), 404); $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(); + return; + } + + $args = array_merge($args, $_REQUEST); + + $action = $args['action']; + + if (!$action || !preg_match('/^[a-zA-Z0-9_-]*$/', $action)) { + common_redirect(common_local_url('public')); + return; + } + + // If the site is private, and they're not on one of the "public" + // parts of the site, redirect to login + + if (!$user && common_config('site', 'private') && + !in_array($action, array('login', 'openidlogin', 'finishopenidlogin', + 'recoverpassword', 'api', 'doc', 'register'))) { + common_redirect(common_local_url('login')); + return; + } + + $action_class = ucfirst($action).'Action'; + + if (!class_exists($action_class)) { + $cac = new ClientErrorAction(_('Unknown action'), 404); + $cac->showPage(); + } else { + $action_obj = new $action_class(); + + // XXX: find somewhere for this little block to live + + if ($config['db']['mirror'] && $action_obj->isReadOnly()) { + if (is_array($config['db']['mirror'])) { + // "load balancing", ha ha + $k = array_rand($config['db']['mirror']); + + $mirror = $config['db']['mirror'][$k]; + } else { + $mirror = $config['db']['mirror']; + } + $config['db']['database'] = $mirror; + } + + try { + if ($action_obj->prepare($args)) { + $action_obj->handle($args); + } + } 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(); + } } } +main(); + // XXX: cleanup exit() calls or add an exit handler so // this always gets called diff --git a/lib/router.php b/lib/router.php new file mode 100644 index 0000000000..6781322a29 --- /dev/null +++ b/lib/router.php @@ -0,0 +1,340 @@ +. + * + * @category URL + * @package Laconica + * @author Evan Prodromou + * @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); +} + +require_once 'Net/URL/Mapper.php'; + +/** + * URL Router + * + * Cheap wrapper around Net_URL_Mapper + * + * @category URL + * @package Laconica + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +class Router +{ + var $m = null; + + function __construct() + { + $m = Net_URL_Mapper::getInstance(); + + // In the "root" + + $m->connect('', array('action' => 'public')); + $m->connect('rss', array('action' => 'publicrss')); + $m->connect('xrds', array('action' => 'publicxrds')); + $m->connect('featuredrss', array('action' => 'featuredrss')); + $m->connect('favoritedrss', array('action' => 'favoritedrss')); + $m->connect('opensearch/people', array('action' => 'opensearch', + 'type' => 'people')); + $m->connect('opensearch/notice', array('action' => 'opensearch', + 'type' => 'notice')); + + // docs + + $m->connect('doc/:title', array('action' => 'doc')); + + // facebook + + $m->connect('facebook', array('action' => 'facebookhome')); + $m->connect('facebook/index.php', array('action' => 'facebookhome')); + $m->connect('facebook/settings.php', array('action' => 'facebooksettings')); + $m->connect('facebook/invite.php', array('action' => 'facebookinvite')); + $m->connect('facebook/remove', array('action' => 'facebookremove')); + + // main stuff is repetitive + + $main = array('login', 'logout', 'register', 'subscribe', + 'unsubscribe', 'confirmaddress', 'recoverpassword', + 'invite', 'favor', 'disfavor', 'sup', + 'tagother', 'block'); + + foreach ($main as $a) { + $m->connect('main/'.$a, array('action' => $a)); + } + + // these take a code + + foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) { + $m->connect('main/'.$c.'/:code', array('action' => $c)); + } + + // exceptional + + $m->connect('main/openid', array('action' => 'openidlogin')); + $m->connect('main/remote', array('action' => 'remotesubscribe')); + + // settings + + foreach (array('profile', 'avatar', 'password', 'openid', 'im', + 'email', 'sms', 'twitter', 'other') as $s) { + $m->connect('settings/'.$s, array('action' => $s.'settings')); + } + + // search + + foreach (array('group', 'people', 'notice') as $s) { + $m->connect('search/'.$s, array('action' => $s.'search')); + } + + $m->connect('search/notice/rss', array('action' => 'noticesearchrss')); + + // notice + + $m->connect('notice/new', array('action' => 'newnotice')); + $m->connect('notice/:notice', + array('action' => 'shownotice'), + array('notice' => '[0-9]+')); + $m->connect('notice/delete', array('action' => 'deletenotice')); + $m->connect('notice/delete/:notice', + array('action' => 'deletenotice'), + array('notice' => '[0-9]+')); + + $m->connect('message/new', array('action' => 'newmessage')); + $m->connect('message/:message', + array('action' => 'showmessage'), + array('message' => '[0-9]+')); + + $m->connect('user/:id', + array('action' => 'userbyid'), + array('id' => '[0-9]+')); + + $m->connect('tags/?', array('action' => 'publictagcloud')); + $m->connect('tag/?', array('action' => 'publictagcloud')); + $m->connect('tag/:tag/rss', + array('action' => 'tagrss'), + array('tag' => '[a-zA-Z0-9]+')); + $m->connect('tag/:tag', + array('action' => 'tag'), + array('tag' => '[a-zA-Z0-9]+')); + + $m->connect('peopletag/:tag', + array('action' => 'peopletag'), + array('tag' => '[a-zA-Z0-9]+')); + + $m->connect('featured/?', array('action' => 'featured')); + $m->connect('favorited/?', array('action' => 'favorited')); + + // groups + + $m->connect('group/new', array('action' => 'newgroup')); + + foreach (array('edit', 'join', 'leave') as $v) { + $m->connect('group/:nickname/'.$v, + array('action' => $v.'group'), + array('nickname' => '[a-zA-Z0-9]+')); + } + + foreach (array('members', 'logo', 'rss') as $n) { + $m->connect('group/:nickname/'.$n, + array('action' => 'group'.$n), + array('nickname' => '[a-zA-Z0-9]+')); + } + + $m->connect('group/:id/id', + array('action' => 'groupbyid'), + array('id' => '[0-9]+')); + + $m->connect('group/:nickname', + array('action' => 'showgroup'), + array('nickname' => '[a-zA-Z0-9]+')); + + $m->connect('group/?', array('action' => 'groups')); + + // Twitter-compatible API + + // statuses API + + $m->connect('api/statuses/:method', + array('action' => 'api', + 'apiaction' => 'statuses'), + array('method' => '(public_timeline|friends_timeline|user_timeline|update|replies|friends|followers|featured)(\.(atom|rss|xml|json))?')); + + $m->connect('api/statuses/:method/:argument', + array('action' => 'api', + 'apiaction' => 'statuses'), + array('method' => '(user_timeline|show|destroy|friends|followers)')); + + // users + + $m->connect('api/users/show/:argument', + array('action' => 'api', + 'apiaction' => 'users')); + + $m->connect('api/users/:method', + array('action' => 'api', + 'apiaction' => 'users'), + array('method' => 'show(\.(xml|json|atom|rss))?')); + + // direct messages + + $m->connect('api/direct_messages/:method', + array('action' => 'api', + 'apiaction' => 'direct_messages'), + array('method' => '(sent|new)(\.(xml|json|atom|rss))?')); + + $m->connect('api/direct_messages/destroy/:argument', + array('action' => 'api', + 'apiaction' => 'direct_messages')); + + $m->connect('api/:method', + array('action' => 'api', + 'apiaction' => 'direct_messages'), + array('method' => 'direct_messages(\.(xml|json|atom|rss))?')); + + // friendships + + $m->connect('api/friendships/:method/:argument', + array('action' => 'api', + 'apiaction' => 'friendships'), + array('method' => '(create|destroy)')); + + $m->connect('api/friendships/:method', + array('action' => 'api', + 'apiaction' => 'friendships'), + array('method' => 'exists(\.(xml|json|rss|atom))')); + + // account + + $m->connect('api/account/:method', + array('action' => 'api', + 'apiaction' => 'account')); + + // favorites + + $m->connect('api/favorites/:method/:argument', + array('action' => 'api', + 'apiaction' => 'favorites')); + + $m->connect('api/favorites/:argument', + array('action' => 'api', + 'apiaction' => 'favorites', + 'method' => 'favorites')); + + $m->connect('api/:method', + array('action' => 'api', + 'apiaction' => 'favorites'), + array('method' => 'favorites(\.(xml|json|rss|atom))?')); + + // notifications + + $m->connect('api/notifications/:method/:argument', + array('action' => 'api', + 'apiaction' => 'favorites')); + + // blocks + + $m->connect('api/blocks/:method/:argument', + array('action' => 'api', + 'apiaction' => 'blocks')); + + // help + + $m->connect('api/help/:method', + array('action' => 'api', + 'apiaction' => 'help')); + + // laconica + + $m->connect('api/laconica/:method', + array('action' => 'api', + 'apiaction' => 'laconica')); + + // user stuff + + foreach (array('subscriptions', 'subscribers', + 'nudge', 'xrds', 'all', 'foaf', + 'replies', 'inbox', 'outbox', 'microsummary') as $a) { + $m->connect(':nickname/'.$a, + array('action' => $a), + array('nickname' => '[a-zA-Z0-9]{1,64}')); + } + + foreach (array('subscriptions', 'subscribers') as $a) { + $m->connect(':nickname/'.$a.'/:tag', + array('action' => $a), + array('tag' => '[a-zA-Z0-9]+', + 'nickname' => '[a-zA-Z0-9]{1,64}')); + } + + foreach (array('rss', 'groups') as $a) { + $m->connect(':nickname/'.$a, + array('action' => 'user'.$a), + array('nickname' => '[a-zA-Z0-9]{1,64}')); + } + + foreach (array('all', 'replies', 'favorites') as $a) { + $m->connect(':nickname/'.$a.'/rss', + array('action' => $a.'rss'), + array('nickname' => '[a-zA-Z0-9]{1,64}')); + } + + $m->connect(':nickname/favorites', + array('action' => 'showfavorites'), + array('nickname' => '[a-zA-Z0-9]{1,64}')); + + $m->connect(':nickname/avatar/:size', + array('action' => 'avatarbynickname'), + array('size' => '(original|96|48|24)', + 'nickname' => '[a-zA-Z0-9]{1,64}')); + + $m->connect(':nickname', + array('action' => 'showstream'), + array('nickname' => '[a-zA-Z0-9]{1,64}')); + + $this->m = $m; + } + + function map($path) + { + return $this->m->match($path); + } + + function build($action, $args=null, $fragment=null) + { + $action_arg = array('action' => $action); + + if ($args) { + $args = array_merge($args, $action_arg); + } else { + $args = $action_arg; + } + + return $this->m->generate($args, null, $fragment); + } +} \ No newline at end of file From 1a7337f2fdc106b80a48692628d0b749ee090c03 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 10 Feb 2009 22:33:57 -0500 Subject: [PATCH 034/189] Moved /doc/ to /doc-src/ Moved the doc/ dir to /doc-src/ so that the actual markdown files aren't substituted for the docs. --- actions/doc.php | 12 ++++++------ {doc => doc-src}/about | 0 {doc => doc-src}/contact | 0 {doc => doc-src}/faq | 0 {doc => doc-src}/groups | 0 {doc => doc-src}/help | 0 {doc => doc-src}/im | 0 {doc => doc-src}/openid | 0 {doc => doc-src}/openmublog | 0 {doc => doc-src}/privacy | 0 {doc => doc-src}/sms | 0 {doc => doc-src}/source | 0 {doc => doc-src}/tags | 0 13 files changed, 6 insertions(+), 6 deletions(-) rename {doc => doc-src}/about (100%) rename {doc => doc-src}/contact (100%) rename {doc => doc-src}/faq (100%) rename {doc => doc-src}/groups (100%) rename {doc => doc-src}/help (100%) rename {doc => doc-src}/im (100%) rename {doc => doc-src}/openid (100%) rename {doc => doc-src}/openmublog (100%) rename {doc => doc-src}/privacy (100%) rename {doc => doc-src}/sms (100%) rename {doc => doc-src}/source (100%) rename {doc => doc-src}/tags (100%) diff --git a/actions/doc.php b/actions/doc.php index 6957659add..ebffb7c154 100644 --- a/actions/doc.php +++ b/actions/doc.php @@ -50,7 +50,7 @@ class DocAction extends Action /** * Class handler. - * + * * @param array $args array of arguments * * @return nothing @@ -59,7 +59,7 @@ class DocAction extends Action { parent::handle($args); $this->title = $this->trimmed('title'); - $this->filename = INSTALLDIR.'/doc/'.$this->title; + $this->filename = INSTALLDIR.'/doc-src/'.$this->title; if (!file_exists($this->filename)) { $this->clientError(_('No such document.')); return; @@ -71,14 +71,14 @@ class DocAction extends Action function showPageTitle() { $this->element('h1', array('class' => 'entry-title'), $this->title()); } - + // overrided to add hentry, and content-inner classes function showContentBlock() { $this->elementStart('div', array('id' => 'content', 'class' => 'hentry')); $this->showPageTitle(); $this->showPageNoticeBlock(); - $this->elementStart('div', array('id' => 'content_inner', + $this->elementStart('div', array('id' => 'content_inner', 'class' => 'entry-content')); // show the actual content (forms, lists, whatever) $this->showContent(); @@ -88,7 +88,7 @@ class DocAction extends Action /** * Display content. - * + * * @return nothing */ function showContent() @@ -100,7 +100,7 @@ class DocAction extends Action /** * Page title. - * + * * @return page title */ function title() diff --git a/doc/about b/doc-src/about similarity index 100% rename from doc/about rename to doc-src/about diff --git a/doc/contact b/doc-src/contact similarity index 100% rename from doc/contact rename to doc-src/contact diff --git a/doc/faq b/doc-src/faq similarity index 100% rename from doc/faq rename to doc-src/faq diff --git a/doc/groups b/doc-src/groups similarity index 100% rename from doc/groups rename to doc-src/groups diff --git a/doc/help b/doc-src/help similarity index 100% rename from doc/help rename to doc-src/help diff --git a/doc/im b/doc-src/im similarity index 100% rename from doc/im rename to doc-src/im diff --git a/doc/openid b/doc-src/openid similarity index 100% rename from doc/openid rename to doc-src/openid diff --git a/doc/openmublog b/doc-src/openmublog similarity index 100% rename from doc/openmublog rename to doc-src/openmublog diff --git a/doc/privacy b/doc-src/privacy similarity index 100% rename from doc/privacy rename to doc-src/privacy diff --git a/doc/sms b/doc-src/sms similarity index 100% rename from doc/sms rename to doc-src/sms diff --git a/doc/source b/doc-src/source similarity index 100% rename from doc/source rename to doc-src/source diff --git a/doc/tags b/doc-src/tags similarity index 100% rename from doc/tags rename to doc-src/tags From fbecbcb693e9d8fc810cf316e8739a22ac501043 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 10 Feb 2009 22:49:25 -0500 Subject: [PATCH 035/189] Build urls using Net_URL_Mapper, too --- lib/router.php | 28 +++-- lib/util.php | 271 ++----------------------------------------------- 2 files changed, 28 insertions(+), 271 deletions(-) diff --git a/lib/router.php b/lib/router.php index 6781322a29..6443bf3541 100644 --- a/lib/router.php +++ b/lib/router.php @@ -47,10 +47,17 @@ require_once 'Net/URL/Mapper.php'; class Router { - var $m = null; + static $m = null; function __construct() { + if (!$this->m) { + $this->m = $this->initialize(); + } + } + + function initialize() { + $m = Net_URL_Mapper::getInstance(); // In the "root" @@ -134,8 +141,10 @@ class Router array('action' => 'userbyid'), array('id' => '[0-9]+')); - $m->connect('tags/?', array('action' => 'publictagcloud')); - $m->connect('tag/?', array('action' => 'publictagcloud')); + $m->connect('tags/', array('action' => 'publictagcloud')); + $m->connect('tag/', array('action' => 'publictagcloud')); + $m->connect('tags', array('action' => 'publictagcloud')); + $m->connect('tag', array('action' => 'publictagcloud')); $m->connect('tag/:tag/rss', array('action' => 'tagrss'), array('tag' => '[a-zA-Z0-9]+')); @@ -147,8 +156,10 @@ class Router array('action' => 'peopletag'), array('tag' => '[a-zA-Z0-9]+')); - $m->connect('featured/?', array('action' => 'featured')); - $m->connect('favorited/?', array('action' => 'favorited')); + $m->connect('featured/', array('action' => 'featured')); + $m->connect('featured', array('action' => 'featured')); + $m->connect('favorited/', array('action' => 'favorited')); + $m->connect('favorited', array('action' => 'favorited')); // groups @@ -174,7 +185,10 @@ class Router array('action' => 'showgroup'), array('nickname' => '[a-zA-Z0-9]+')); - $m->connect('group/?', array('action' => 'groups')); + $m->connect('group/', array('action' => 'groups')); + $m->connect('group', array('action' => 'groups')); + $m->connect('groups/', array('action' => 'groups')); + $m->connect('groups', array('action' => 'groups')); // Twitter-compatible API @@ -317,7 +331,7 @@ class Router array('action' => 'showstream'), array('nickname' => '[a-zA-Z0-9]{1,64}')); - $this->m = $m; + return $m; } function map($path) diff --git a/lib/util.php b/lib/util.php index c5a092f630..a78af8be96 100644 --- a/lib/util.php +++ b/lib/util.php @@ -669,275 +669,18 @@ function common_relative_profile($sender, $nickname, $dt=null) function common_local_url($action, $args=null, $fragment=null) { - $url = null; - if (common_config('site','fancy')) { - $url = common_fancy_url($action, $args); - } else { - $url = common_simple_url($action, $args); + $r = new Router(); + $path = $r->build($action, $args, $fragment); + if ($path) { } - if (!is_null($fragment)) { - $url .= '#'.$fragment; + if (common_config('site','fancy')) { + $url = common_path(mb_substr($path, 1)); + } else { + $url = common_path('index.php'.$path); } return $url; } -function common_fancy_url($action, $args=null) -{ - switch (strtolower($action)) { - case 'public': - if ($args && isset($args['page'])) { - return common_path('?page=' . $args['page']); - } else { - return common_path(''); - } - case 'featured': - if ($args && isset($args['page'])) { - return common_path('featured?page=' . $args['page']); - } else { - return common_path('featured'); - } - case 'favorited': - if ($args && isset($args['page'])) { - return common_path('favorited?page=' . $args['page']); - } else { - return common_path('favorited'); - } - case 'publicrss': - return common_path('rss'); - case 'publicatom': - return common_path("api/statuses/public_timeline.atom"); - case 'publicxrds': - return common_path('xrds'); - case 'tagrss': - return common_path('tag/' . $args['tag'] . '/rss'); - case 'featuredrss': - return common_path('featuredrss'); - case 'favoritedrss': - return common_path('favoritedrss'); - case 'opensearch': - if ($args && $args['type']) { - return common_path('opensearch/'.$args['type']); - } else { - return common_path('opensearch/people'); - } - case 'doc': - return common_path('doc/'.$args['title']); - case 'block': - case 'login': - case 'logout': - case 'subscribe': - case 'unsubscribe': - case 'invite': - return common_path('main/'.$action); - case 'tagother': - return common_path('main/tagother?id='.$args['id']); - case 'register': - if ($args && $args['code']) { - return common_path('main/register/'.$args['code']); - } else { - return common_path('main/register'); - } - case 'remotesubscribe': - if ($args && $args['nickname']) { - return common_path('main/remote?nickname=' . $args['nickname']); - } else { - return common_path('main/remote'); - } - case 'nudge': - return common_path($args['nickname'].'/nudge'); - case 'openidlogin': - return common_path('main/openid'); - case 'profilesettings': - return common_path('settings/profile'); - case 'passwordsettings': - return common_path('settings/password'); - case 'emailsettings': - return common_path('settings/email'); - case 'openidsettings': - return common_path('settings/openid'); - case 'smssettings': - return common_path('settings/sms'); - case 'twittersettings': - return common_path('settings/twitter'); - case 'othersettings': - return common_path('settings/other'); - case 'deleteprofile': - return common_path('settings/delete'); - case 'newnotice': - if ($args && $args['replyto']) { - return common_path('notice/new?replyto='.$args['replyto']); - } else { - return common_path('notice/new'); - } - case 'shownotice': - return common_path('notice/'.$args['notice']); - case 'deletenotice': - if ($args && $args['notice']) { - return common_path('notice/delete/'.$args['notice']); - } else { - return common_path('notice/delete'); - } - case 'microsummary': - case 'xrds': - case 'foaf': - return common_path($args['nickname'].'/'.$action); - case 'all': - case 'replies': - case 'inbox': - case 'outbox': - if ($args && isset($args['page'])) { - return common_path($args['nickname'].'/'.$action.'?page=' . $args['page']); - } else { - return common_path($args['nickname'].'/'.$action); - } - case 'subscriptions': - case 'subscribers': - $nickname = $args['nickname']; - unset($args['nickname']); - if (isset($args['tag'])) { - $tag = $args['tag']; - unset($args['tag']); - } - $params = http_build_query($args); - if ($params) { - return common_path($nickname.'/'.$action . (($tag) ? '/' . $tag : '') . '?' . $params); - } else { - return common_path($nickname.'/'.$action . (($tag) ? '/' . $tag : '')); - } - case 'allrss': - return common_path($args['nickname'].'/all/rss'); - case 'repliesrss': - return common_path($args['nickname'].'/replies/rss'); - case 'userrss': - if (isset($args['limit'])) - return common_path($args['nickname'].'/rss?limit=' . $args['limit']); - return common_path($args['nickname'].'/rss'); - case 'showstream': - if ($args && isset($args['page'])) { - return common_path($args['nickname'].'?page=' . $args['page']); - } else { - return common_path($args['nickname']); - } - - case 'usertimeline': - return common_path("api/statuses/user_timeline/".$args['nickname'].".atom"); - case 'confirmaddress': - return common_path('main/confirmaddress/'.$args['code']); - case 'userbyid': - return common_path('user/'.$args['id']); - case 'recoverpassword': - $path = 'main/recoverpassword'; - if ($args['code']) { - $path .= '/' . $args['code']; - } - return common_path($path); - case 'imsettings': - return common_path('settings/im'); - case 'avatarsettings': - return common_path('settings/avatar'); - case 'groupsearch': - return common_path('search/group' . (($args) ? ('?' . http_build_query($args)) : '')); - case 'peoplesearch': - return common_path('search/people' . (($args) ? ('?' . http_build_query($args)) : '')); - case 'noticesearch': - return common_path('search/notice' . (($args) ? ('?' . http_build_query($args)) : '')); - case 'noticesearchrss': - return common_path('search/notice/rss' . (($args) ? ('?' . http_build_query($args)) : '')); - case 'avatarbynickname': - return common_path($args['nickname'].'/avatar/'.$args['size']); - case 'tag': - $path = 'tag/' . $args['tag']; - unset($args['tag']); - return common_path($path . (($args) ? ('?' . http_build_query($args)) : '')); - case 'publictagcloud': - return common_path('tags'); - case 'peopletag': - $path = 'peopletag/' . $args['tag']; - unset($args['tag']); - return common_path($path . (($args) ? ('?' . http_build_query($args)) : '')); - case 'tags': - return common_path('tags' . (($args) ? ('?' . http_build_query($args)) : '')); - case 'favor': - return common_path('main/favor'); - case 'disfavor': - return common_path('main/disfavor'); - case 'showfavorites': - if ($args && isset($args['page'])) { - return common_path($args['nickname'].'/favorites?page=' . $args['page']); - } else { - return common_path($args['nickname'].'/favorites'); - } - case 'favoritesrss': - return common_path($args['nickname'].'/favorites/rss'); - case 'showmessage': - return common_path('message/' . $args['message']); - case 'newmessage': - return common_path('message/new' . (($args) ? ('?' . http_build_query($args)) : '')); - case 'api': - // XXX: do fancy URLs for all the API methods - switch (strtolower($args['apiaction'])) { - case 'statuses': - switch (strtolower($args['method'])) { - case 'user_timeline.rss': - return common_path('api/statuses/user_timeline/'.$args['argument'].'.rss'); - case 'user_timeline.atom': - return common_path('api/statuses/user_timeline/'.$args['argument'].'.atom'); - case 'user_timeline.json': - return common_path('api/statuses/user_timeline/'.$args['argument'].'.json'); - case 'user_timeline.xml': - return common_path('api/statuses/user_timeline/'.$args['argument'].'.xml'); - default: return common_simple_url($action, $args); - } - default: return common_simple_url($action, $args); - } - case 'sup': - if ($args && isset($args['seconds'])) { - return common_path('main/sup?seconds='.$args['seconds']); - } else { - return common_path('main/sup'); - } - case 'newgroup': - return common_path('group/new'); - case 'showgroup': - return common_path('group/'.$args['nickname'] . (($args['page']) ? ('?page=' . $args['page']) : '')); - case 'editgroup': - return common_path('group/'.$args['nickname'].'/edit'); - case 'joingroup': - return common_path('group/'.$args['nickname'].'/join'); - case 'leavegroup': - return common_path('group/'.$args['nickname'].'/leave'); - case 'groupbyid': - return common_path('group/'.$args['id'].'/id'); - case 'grouprss': - return common_path('group/'.$args['nickname'].'/rss'); - case 'groupmembers': - return common_path('group/'.$args['nickname'].'/members' . (($args['page']) ? ('?page=' . $args['page']) : '')); - case 'grouplogo': - return common_path('group/'.$args['nickname'].'/logo'); - case 'usergroups': - $nickname = $args['nickname']; - unset($args['nickname']); - return common_path($nickname.'/groups' . (($args) ? ('?' . http_build_query($args)) : '')); - case 'groups': - return common_path('group' . (($args) ? ('?' . http_build_query($args)) : '')); - default: - return common_simple_url($action, $args); - } -} - -function common_simple_url($action, $args=null) -{ - global $config; - /* XXX: pretty URLs */ - $extra = ''; - if ($args) { - foreach ($args as $key => $value) { - $extra .= "&${key}=${value}"; - } - } - return common_path("index.php?action=${action}${extra}"); -} - function common_path($relative) { global $config; From bba1dbdb403aac067ae97c44531f6886f90fec35 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 00:45:11 -0500 Subject: [PATCH 036/189] Use a router singleton --- index.php | 2 +- lib/router.php | 11 ++++++++++- lib/util.php | 6 +++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/index.php b/index.php index f334e2c345..717b17361d 100644 --- a/index.php +++ b/index.php @@ -51,7 +51,7 @@ function main() { $path = getPath($_REQUEST); - $r = new Router(); + $r = Router::get(); $args = $r->map($path); diff --git a/lib/router.php b/lib/router.php index 6443bf3541..d47ad71183 100644 --- a/lib/router.php +++ b/lib/router.php @@ -48,6 +48,15 @@ require_once 'Net/URL/Mapper.php'; class Router { static $m = null; + static $inst = null; + + static function get() + { + if (!Router::$inst) { + Router::$inst = new Router(); + } + return Router::$inst; + } function __construct() { @@ -344,7 +353,7 @@ class Router $action_arg = array('action' => $action); if ($args) { - $args = array_merge($args, $action_arg); + $args = array_merge($action_arg, $args); } else { $args = $action_arg; } diff --git a/lib/util.php b/lib/util.php index a78af8be96..9b38b55961 100644 --- a/lib/util.php +++ b/lib/util.php @@ -669,8 +669,12 @@ function common_relative_profile($sender, $nickname, $dt=null) function common_local_url($action, $args=null, $fragment=null) { - $r = new Router(); + common_debug("Action = $action, args = " . (($args) ? '(' . implode($args, ',') . ')' : $args) . ", fragment = $fragment"); + $r = Router::get(); + $start = microtime(); $path = $r->build($action, $args, $fragment); + $end = microtime(); + common_debug("Pathbuilding took " . ($end - $start)); if ($path) { } if (common_config('site','fancy')) { From 07c6537898a7c839469b50121b6c6d63be07613c Mon Sep 17 00:00:00 2001 From: Meitar Moscovitz Date: Wed, 11 Feb 2009 23:35:58 +1100 Subject: [PATCH 037/189] Style the notice form and notices to better avoid need to scroll around. --- theme/base/css/mobile.css | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/theme/base/css/mobile.css b/theme/base/css/mobile.css index 6cd717a4df..3d0455a673 100644 --- a/theme/base/css/mobile.css +++ b/theme/base/css/mobile.css @@ -12,31 +12,55 @@ #site_nav_global_primary, #anon_notice, #site_nav_local_views .nav, +#form_notice, +#form_notice .form_data li, #core, #content_inner, #notices_primary, .notice, .notice .entry-title, .notice div.entry-content, +.notice-options, +.notice .notice-options a, .pagination, .pagination .nav, .aside .section { float: none; } +.notice-options .notice_reply, +.notice-options .notice_delete, +.notice-options .form_favor, +.notice-options .form_disfavor { position: static; } + +#form_notice, +#anon_notice, +#content_inner, +#footer { width: auto; } + /* And liquid. */ #wrap { width: 95%; } -body { font-size: 2em; } /* Make things bigger on smaller screens. */ +/* Make things bigger on smaller screens. */ +body { font-size: 2em; } +.notices { font-size: 1.5em; } #site_nav_global_primary, #site_nav_global_secondary { text-align: center; } .notice div.entry-content { margin-left: 0; } address { margin: 0; } -#anon_notice, #footer { clear: left; width: auto; font-size: .5em; } +#anon_notice, #footer { clear: left; font-size: .5em; } + +#form_notice textarea { width: 80%; height: 5em; } +#form_notice .form_note { right: 20%; top: 6em; } +#form_notice .form_actions input.submit { width: auto; } #content { padding: 18px 0; width: 100%; } #content h1, #page_notice, #content_inner { padding: 0 18px; } -#content_inner { width: auto; } +.notices .entry-title, .notices div.entry-content { width: 90%; } +.notice .author .photo { height: 4.5em; width: 4.5em; } /* about double physical size; TODO: do this scaling better */ +.notice-options { position: absolute; top: 0; right: 0; padding-left: 7%; width: 3%; } +.notice-options .notice_delete a { float: left; } /* Works, but feels like it shouldn't. */ +/* TODO: Make the icons of the notice options bigger. Probably with mobile-specific images. */ .pagination .nav { overflow: auto; } #aside_primary { margin: 10px 0 0 0; border: none; padding: 0; width: 100%; } From d345c746b9021266a2ed100329affe49c5ae8fc3 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 08:34:21 -0500 Subject: [PATCH 038/189] add related link to Atom for feeds for some downstream users --- lib/jabber.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/jabber.php b/lib/jabber.php index f41d984d62..b385d3c5cc 100644 --- a/lib/jabber.php +++ b/lib/jabber.php @@ -186,6 +186,11 @@ function jabber_format_entry($profile, $notice) $entry .= "". $notice->uri . "\n"; $entry .= "".common_date_w3dtf($notice->created)."\n"; $entry .= "".common_date_w3dtf($notice->modified)."\n"; + if ($notice->reply_to) { + $replyurl = common_local_url('shownotice', + array('notice' => $notice->reply_to)); + $entry .= "\n"; + } $entry .= "\n"; $html = "\n\n"; From c640b747f7e682ac632397cf32e41ef4c2dca96f Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 08:34:21 -0500 Subject: [PATCH 039/189] add related link to Atom for feeds for some downstream users --- lib/jabber.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/jabber.php b/lib/jabber.php index f41d984d62..b385d3c5cc 100644 --- a/lib/jabber.php +++ b/lib/jabber.php @@ -186,6 +186,11 @@ function jabber_format_entry($profile, $notice) $entry .= "". $notice->uri . "\n"; $entry .= "".common_date_w3dtf($notice->created)."\n"; $entry .= "".common_date_w3dtf($notice->modified)."\n"; + if ($notice->reply_to) { + $replyurl = common_local_url('shownotice', + array('notice' => $notice->reply_to)); + $entry .= "\n"; + } $entry .= "\n"; $html = "\n\n"; From ad65c447d5e32b8ef5681789eca12a3717231311 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 10:35:15 -0500 Subject: [PATCH 040/189] fix Atom link, add Atom and RSS 2.0 to --- actions/public.php | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/actions/public.php b/actions/public.php index cc6537f74f..1137bd8768 100644 --- a/actions/public.php +++ b/actions/public.php @@ -73,9 +73,9 @@ class PublicAction extends Action { parent::prepare($args); $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; - + common_set_returnto($this->selfUrl()); - + return true; } @@ -123,8 +123,20 @@ class PublicAction extends Action { $this->element('link', array('rel' => 'alternate', 'href' => common_local_url('publicrss'), + 'type' => 'application/rdf+xml', + 'title' => _('Public Stream Feed (RSS 1.0)'))); + $this->element('link', array('rel' => 'alternate', + 'href' => common_local_url('api', + array('apiaction' => 'statuses', + 'method' => 'public_timeline.rss')), 'type' => 'application/rss+xml', - 'title' => _('Public Stream Feed'))); + 'title' => _('Public Stream Feed (RSS 2.0)'))); + $this->element('link', array('rel' => 'alternate', + 'href' => common_local_url('api', + array('apiaction' => 'statuses', + 'method' => 'public_timeline.atom')), + 'type' => 'application/atom+xml', + 'title' => _('Public Stream Feed (Atom)'))); } /** @@ -200,7 +212,9 @@ class PublicAction extends Action 'type' => 'rss', 'version' => 'RSS 1.0', 'item' => 'publicrss'), - 1 => array('href' => common_local_url('publicatom'), + 1 => array('href' => common_local_url('api', + array('apiaction' => 'statuses', + 'method' => 'public_timeline.atom')), 'type' => 'atom', 'version' => 'Atom 1.0', 'item' => 'publicatom'))); From 22b10399aaa97061ed940f92f5b15f6aacfb1093 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 11:37:50 -0500 Subject: [PATCH 041/189] Unify feeds definition in actions I got a little sick of trying to keep the export data and links synched in actions, so I made a common method, getFeeds(), which gets the feeds for both. It returns an array of Feed objects, which know about what their mime type is, title, location, all that jazz. I changed the FeedList class so it handles the new Feed objects instead of the old array of data. I changed all the actions that show feeds (I think...) so that they now use getFeeds() for all their feed needs. --- actions/all.php | 36 ++++++------- actions/noticesearch.php | 64 ++++------------------ actions/public.php | 53 +++++------------- actions/replies.php | 28 ++-------- actions/showfavorites.php | 33 ++---------- actions/showgroup.php | 25 ++------- actions/showstream.php | 67 +++++++++-------------- actions/tag.php | 22 +++----- lib/action.php | 38 ++++++++++--- lib/feed.php | 110 ++++++++++++++++++++++++++++++++++++++ lib/feedlist.php | 101 +++++++--------------------------- 11 files changed, 241 insertions(+), 336 deletions(-) create mode 100644 lib/feed.php diff --git a/actions/all.php b/actions/all.php index d75d1b9461..08dcccbddb 100644 --- a/actions/all.php +++ b/actions/all.php @@ -42,9 +42,9 @@ class AllAction extends Action if (!$this->page) { $this->page = 1; } - + common_set_returnto($this->selfUrl()); - + return true; } @@ -69,13 +69,22 @@ class AllAction extends Action } } - function showFeeds() + function getFeeds() { - $this->element('link', array('rel' => 'alternate', - 'href' => common_local_url('allrss', array('nickname' => - $this->user->nickname)), - 'type' => 'application/rss+xml', - 'title' => sprintf(_('Feed for friends of %s'), $this->user->nickname))); + return array(new Feed(Feed::RSS1, + common_local_url('allrss', array('nickname' => + $this->user->nickname)), + sprintf(_('Feed for friends of %s (RSS 1.0)'), $this->user->nickname)), + new Feed(Feed::RSS2, + common_local_url('api', array('apiaction' => 'statuses', + 'method' => 'friends', + 'argument' => $this->user->nickname.'.rss')), + sprintf(_('Feed for friends of %s (RSS 2.0)'), $this->user->nickname)), + new Feed(Feed::ATOM, + common_local_url('api', array('apiaction' => 'statuses', + 'method' => 'friends', + 'argument' => $this->user->nickname.'.atom')), + sprintf(_('Feed for friends of %s (Atom)'), $this->user->nickname))); } function showLocalNav() @@ -84,15 +93,6 @@ class AllAction extends Action $nav->show(); } - function showExportData() - { - $fl = new FeedList($this); - $fl->show(array(0=>array('href'=>common_local_url('allrss', array('nickname' => $this->user->nickname)), - 'type' => 'rss', - 'version' => 'RSS 1.0', - 'item' => 'allrss'))); - } - function showContent() { $notice = $this->user->noticesWithFriends(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1); @@ -110,7 +110,7 @@ class AllAction extends Action $user =& common_current_user(); if ($user && ($user->id == $this->user->id)) { $this->element('h1', NULL, _("You and friends")); - } else { + } else { $this->element('h1', NULL, sprintf(_('%s and friends'), $this->user->nickname)); } } diff --git a/actions/noticesearch.php b/actions/noticesearch.php index 2d94a7906f..dc58d7528a 100644 --- a/actions/noticesearch.php +++ b/actions/noticesearch.php @@ -57,11 +57,11 @@ class NoticesearchAction extends SearchAction return true; } - + /** * Get instructions - * - * @return string instruction text + * + * @return string instruction text */ function getInstructions() { @@ -70,7 +70,7 @@ class NoticesearchAction extends SearchAction /** * Get title - * + * * @return string title */ function title() @@ -78,62 +78,20 @@ class NoticesearchAction extends SearchAction return _('Text search'); } - - function showExportData() + function getFeeds() { $q = $this->trimmed('q'); + if (!$q) { - return; - } - $fl = new FeedList($this); - $fl->show(array(0 => array('href' => common_local_url('noticesearchrss', array('q' => $q)), - 'type' => 'rss', - 'version' => 'RSS 1.0', - 'item' => 'noticesearchrss'))); - } - - - - function showFeeds() - { - $q = $this->trimmed('q'); - if (!$q) { - return; + return null; } - $this->element('link', array('rel' => 'alternate', - 'href' => common_local_url('noticesearchrss', - array('q' => $q)), - 'type' => 'application/rss+xml', - 'title' => _('Search Stream Feed'))); + return array(new Feed(Feed::RSS1, common_local_url('noticesearchrss', + array('q' => $q)), + sprintf(_('Search results for "%s" on %s'), + $q, common_config('site', 'name')))); } - - /** - * Show header - * - * @param array $arr array containing the query - * - * @return void - */ - - function extraHead2() - { - $q = $this->trimmed('q'); - if ($q) { - $this->element('link', array('rel' => 'alternate', - 'href' => common_local_url('noticesearchrss', - array('q' => $q)), - 'type' => 'application/rss+xml', - 'title' => _('Search Stream Feed'))); - } - } - - - - - - /** * Show results * diff --git a/actions/public.php b/actions/public.php index 1137bd8768..a20ae40321 100644 --- a/actions/public.php +++ b/actions/public.php @@ -119,24 +119,20 @@ class PublicAction extends Action * @return void */ - function showFeeds() + function getFeeds() { - $this->element('link', array('rel' => 'alternate', - 'href' => common_local_url('publicrss'), - 'type' => 'application/rdf+xml', - 'title' => _('Public Stream Feed (RSS 1.0)'))); - $this->element('link', array('rel' => 'alternate', - 'href' => common_local_url('api', - array('apiaction' => 'statuses', - 'method' => 'public_timeline.rss')), - 'type' => 'application/rss+xml', - 'title' => _('Public Stream Feed (RSS 2.0)'))); - $this->element('link', array('rel' => 'alternate', - 'href' => common_local_url('api', - array('apiaction' => 'statuses', - 'method' => 'public_timeline.atom')), - 'type' => 'application/atom+xml', - 'title' => _('Public Stream Feed (Atom)'))); + return array(new Feed(Feed::RSS1, common_local_url('publicrss'), + _('Public Stream Feed (RSS 1.0)')), + new Feed(Feed::RSS2, + common_local_url('api', + array('apiaction' => 'statuses', + 'method' => 'public_timeline.rss')), + _('Public Stream Feed (RSS 2.0)')), + new Feed(Feed::ATOM, + common_local_url('api', + array('apiaction' => 'statuses', + 'method' => 'public_timeline.atom')), + _('Public Stream Feed (Atom)'))); } /** @@ -197,29 +193,6 @@ class PublicAction extends Action $this->page, 'public'); } - /** - * Makes a list of exported feeds for this page - * - * @return void - * - * @todo I18N - */ - - function showExportData() - { - $fl = new FeedList($this); - $fl->show(array(0 => array('href' => common_local_url('publicrss'), - 'type' => 'rss', - 'version' => 'RSS 1.0', - 'item' => 'publicrss'), - 1 => array('href' => common_local_url('api', - array('apiaction' => 'statuses', - 'method' => 'public_timeline.atom')), - 'type' => 'atom', - 'version' => 'Atom 1.0', - 'item' => 'publicatom'))); - } - function showSections() { // $top = new TopPostersSection($this); diff --git a/actions/replies.php b/actions/replies.php index 7eff74a669..4ab9b14ed2 100644 --- a/actions/replies.php +++ b/actions/replies.php @@ -84,7 +84,7 @@ class RepliesAction extends Action $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; common_set_returnto($this->selfUrl()); - + return true; } @@ -129,16 +129,13 @@ class RepliesAction extends Action * @return void */ - function showFeeds() + function getFeeds() { $rssurl = common_local_url('repliesrss', array('nickname' => $this->user->nickname)); $rsstitle = sprintf(_('Feed for replies to %s'), $this->user->nickname); - $this->element('link', array('rel' => 'alternate', - 'href' => $rssurl, - 'type' => 'application/rss+xml', - 'title' => $rsstitle)); + return array(new Feed(Feed::RSS1, $rssurl, $rsstitle)); } /** @@ -153,25 +150,6 @@ class RepliesAction extends Action $nav->show(); } - /** - * Show the replies feed links - * - * @return void - */ - - function showExportData() - { - $fl = new FeedList($this); - - $rssurl = common_local_url('repliesrss', - array('nickname' => $this->user->nickname)); - - $fl->show(array(0=>array('href'=> $rssurl, - 'type' => 'rss', - 'version' => 'RSS 1.0', - 'item' => 'repliesrss'))); - } - /** * Show the content * diff --git a/actions/showfavorites.php b/actions/showfavorites.php index 31479e1a78..d1c9283f0f 100644 --- a/actions/showfavorites.php +++ b/actions/showfavorites.php @@ -113,7 +113,7 @@ class ShowfavoritesAction extends Action } common_set_returnto($this->selfUrl()); - + return true; } @@ -136,10 +136,10 @@ class ShowfavoritesAction extends Action /** * Feeds for the section * - * @return void + * @return array Feed objects to show */ - function showFeeds() + function getFeeds() { $feedurl = common_local_url('favoritesrss', array('nickname' => @@ -147,10 +147,7 @@ class ShowfavoritesAction extends Action $feedtitle = sprintf(_('Feed for favorites of %s'), $this->user->nickname); - $this->element('link', array('rel' => 'alternate', - 'href' => $feedurl, - 'type' => 'application/rss+xml', - 'title' => $feedtitle)); + return array(new Feed(Feed::RSS1, $feedurl, $feedtitle)); } /** @@ -165,28 +162,6 @@ class ShowfavoritesAction extends Action $nav->show(); } - /** - * Show the replies feed links - * - * @return void - */ - - function showExportData() - { - $feedurl = common_local_url('favoritesrss', - array('nickname' => - $this->user->nickname)); - - $fl = new FeedList($this); - - // XXX: I18N - - $fl->show(array(0=>array('href'=> $feedurl, - 'type' => 'rss', - 'version' => 'RSS 1.0', - 'item' => 'Favorites'))); - } - /** * Show the content * diff --git a/actions/showgroup.php b/actions/showgroup.php index 7bc68fbc64..340e183330 100644 --- a/actions/showgroup.php +++ b/actions/showgroup.php @@ -292,37 +292,18 @@ class ShowgroupAction extends Action } /** - * Show a list of links to feeds this page produces + * Get a list of the feeds for this page * * @return void */ - function showExportData() - { - $fl = new FeedList($this); - $fl->show(array(0=>array('href'=>common_local_url('grouprss', - array('nickname' => $this->group->nickname)), - 'type' => 'rss', - 'version' => 'RSS 1.0', - 'item' => 'notices'))); - } - - /** - * Show a list of links to feeds this page produces - * - * @return void - */ - - function showFeeds() + function getFeeds() { $url = common_local_url('grouprss', array('nickname' => $this->group->nickname)); - $this->element('link', array('rel' => 'alternate', - 'href' => $url, - 'type' => 'application/rss+xml', - 'title' => sprintf(_('Notice feed for %s group'), + return array(new Feed(Feed::RSS1, $url, sprintf(_('Notice feed for %s group'), $this->group->nickname))); } diff --git a/actions/showstream.php b/actions/showstream.php index 962f4b4524..0ee5d769e1 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -155,54 +155,35 @@ class ShowstreamAction extends Action return; } - function showExportData() + function getFeeds() { - $fl = new FeedList($this); - $fl->show(array(0=>array('href'=>common_local_url('userrss', - array('nickname' => $this->user->nickname)), - 'type' => 'rss', - 'version' => 'RSS 1.0', - 'item' => 'notices'), - 1=>array('href'=>common_local_url('usertimeline', - array('nickname' => $this->user->nickname)), - 'type' => 'atom', - 'version' => 'Atom 1.0', - 'item' => 'usertimeline'), - 2=>array('href'=>common_local_url('foaf', - array('nickname' => $this->user->nickname)), - 'type' => 'rdf', - 'version' => 'FOAF', - 'item' => 'foaf'))); - } - - function showFeeds() - { - $this->element('link', array('rel' => 'alternate', - 'type' => 'application/rss+xml', - 'href' => common_local_url('userrss', - array('nickname' => $this->user->nickname)), - 'title' => sprintf(_('Notice feed for %s (RSS)'), - $this->user->nickname))); - - $this->element('link', - array('rel' => 'alternate', - 'href' => common_local_url('api', - array('apiaction' => 'statuses', - 'method' => 'user_timeline.atom', - 'argument' => $this->user->nickname)), - 'type' => 'application/atom+xml', - 'title' => sprintf(_('Notice feed for %s (Atom)'), - $this->user->nickname))); + return array(new Feed(Feed::RSS1, + common_local_url('userrss', + array('nickname' => $this->user->nickname)), + sprintf(_('Notice feed for %s (RSS 1.0)'), + $this->user->nickname)), + new Feed(Feed::RSS2, + common_local_url('api', + array('apiaction' => 'statuses', + 'method' => 'user_timeline', + 'argument' => $this->user->nickname.'.rss')), + sprintf(_('Notice feed for %s (RSS 2.0)'), + $this->user->nickname)), + new Feed(Feed::ATOM, + common_local_url('api', + array('apiaction' => 'statuses', + 'method' => 'user_timeline', + 'argument' => $this->user->nickname.'.atom')), + sprintf(_('Notice feed for %s (Atom)'), + $this->user->nickname)), + new Feed(Feed::FOAF, + common_local_url('foaf', array('nickname' => + $this->user->nickname)), + sprintf(_('FOAF for %s'), $this->user->nickname))); } function extraHead() { - // FOAF - $this->element('link', array('rel' => 'meta', - 'href' => common_local_url('foaf', array('nickname' => - $this->user->nickname)), - 'type' => 'application/rdf+xml', - 'title' => 'FOAF')); // for remote subscriptions etc. $this->element('meta', array('http-equiv' => 'X-XRDS-Location', 'content' => common_local_url('xrds', array('nickname' => diff --git a/actions/tag.php b/actions/tag.php index 4401f892a9..231f2c2992 100644 --- a/actions/tag.php +++ b/actions/tag.php @@ -37,9 +37,9 @@ class TagAction extends Action } $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; - + common_set_returnto($this->selfUrl()); - + return true; } @@ -61,12 +61,11 @@ class TagAction extends Action $this->showPage(); } - function showFeeds() + function getFeeds() { - $this->element('link', array('rel' => 'alternate', - 'href' => common_local_url('tagrss', array('tag' => $this->tag)), - 'type' => 'application/rss+xml', - 'title' => sprintf(_('Feed for tag %s'), $this->tag))); + return array(new Feed(Feed::RSS1, + common_local_url('tagrss', array('tag' => $this->tag)), + sprintf(_('Feed for tag %s'), $this->tag))); } function showPageNotice() @@ -74,15 +73,6 @@ class TagAction extends Action return sprintf(_('Messages tagged "%s", most recent first'), $this->tag); } - function showExportData() - { - $fl = new FeedList($this); - $fl->show(array(0=>array('href'=>common_local_url('tagrss', array('tag' => $this->tag)), - 'type' => 'rss', - 'version' => 'RSS 1.0', - 'item' => 'tagrss'))); - } - function showContent() { $notice = Notice_tag::getStream($this->tag, (($this->page-1)*NOTICES_PER_PAGE), NOTICES_PER_PAGE + 1); diff --git a/lib/action.php b/lib/action.php index ce92addf5c..bd38bf79cc 100644 --- a/lib/action.php +++ b/lib/action.php @@ -225,9 +225,19 @@ class Action extends HTMLOutputter // lawsuit * * @return nothing */ + function showFeeds() { - // does nothing by default + $feeds = $this->getFeeds(); + + if ($feeds) { + foreach ($feeds as $feed) { + $this->element('link', array('rel' => $feed->rel(), + 'href' => $feed->url, + 'type' => $feed->mimeType(), + 'title' => $feed->title)); + } + } } /** @@ -540,15 +550,16 @@ class Action extends HTMLOutputter // lawsuit /** * Show export data feeds. * - * MAY overload if there are feeds - * - * @return nothing + * @return void */ + function showExportData() { - // is there structure to this? - // list of (visible!) feed links - // can we reuse list of feeds from showFeeds() ? + $feeds = $this->getFeeds(); + if ($feeds) { + $fl = new FeedList($this); + $fl->show($feeds); + } } /** @@ -924,4 +935,17 @@ class Action extends HTMLOutputter // lawsuit $this->elementEnd('div'); } } + + /** + * An array of feeds for this action. + * + * Returns an array of potential feeds for this action. + * + * @return array Feed object to show in head and links + */ + + function getFeeds() + { + return null; + } } diff --git a/lib/feed.php b/lib/feed.php new file mode 100644 index 0000000000..466926844e --- /dev/null +++ b/lib/feed.php @@ -0,0 +1,110 @@ +. + * + * @category Feed + * @package Laconica + * @author Evan Prodromou + * @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); +} + +/** + * Data structure for feeds + * + * This structure is a helpful container for shipping around information about syndication feeds. + * + * @category Feed + * @package Laconica + * @author Evan Prodromou + * @author Sarven Capadisli + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +class Feed +{ + const RSS1 = 1; + const RSS2 = 2; + const ATOM = 3; + const FOAF = 4; + + var $type = null; + var $url = null; + var $title = null; + + function __construct($type, $url, $title) + { + $this->type = $type; + $this->url = $url; + $this->title = $title; + } + + function mimeType() + { + switch ($this->type) { + case Feed::RSS1: + return 'application/rdf+xml'; + case Feed::RSS2: + return 'application/rss+xml'; + case Feed::ATOM: + return 'application/atom+xml'; + case Feed::FOAF: + return 'application/rdf+xml'; + default: + return null; + } + } + + function typeName() + { + switch ($this->type) { + case Feed::RSS1: + return _('RSS 1.0'); + case Feed::RSS2: + return _('RSS 2.0'); + case Feed::ATOM: + return _('Atom'); + case Feed::FOAF: + return _('FOAF'); + default: + return null; + } + } + + function rel() + { + switch ($this->type) { + case Feed::RSS1: + case Feed::RSS2: + case Feed::ATOM: + return 'alternate'; + case Feed::FOAF: + return 'meta'; + default: + return null; + } + } +} diff --git a/lib/feedlist.php b/lib/feedlist.php index 8bfcb9c5ab..927e43c330 100644 --- a/lib/feedlist.php +++ b/lib/feedlist.php @@ -50,7 +50,7 @@ if (!defined('LACONICA')) { class FeedList extends Widget { var $action = null; - + function __construct($action=null) { parent::__construct($action); @@ -64,8 +64,8 @@ class FeedList extends Widget $this->out->element('h2', null, _('Export data')); $this->out->elementStart('ul', array('class' => 'xoxo')); - foreach ($feeds as $key => $value) { - $this->feedItem($feeds[$key]); + foreach ($feeds as $feed) { + $this->feedItem($feed); } $this->out->elementEnd('ul'); @@ -74,92 +74,27 @@ class FeedList extends Widget function feedItem($feed) { - $nickname = $this->action->trimmed('nickname'); + $classname = null; - switch($feed['item']) { - case 'notices': default: - $feed_classname = $feed['type']; - $feed_mimetype = "application/".$feed['type']."+xml"; - $feed_title = "$nickname's ".$feed['version']." notice feed"; - $feed['textContent'] = "RSS"; + switch ($feed->type) { + case Feed::RSS1: + case Feed::RSS2: + $classname = 'rss'; break; - - case 'allrss': - $feed_classname = $feed['type']; - $feed_mimetype = "application/".$feed['type']."+xml"; - $feed_title = $feed['version']." feed for $nickname and friends"; - $feed['textContent'] = "RSS"; + case Feed::ATOM: + $classname = 'atom'; break; - - case 'repliesrss': - $feed_classname = $feed['type']; - $feed_mimetype = "application/".$feed['type']."+xml"; - $feed_title = $feed['version']." feed for replies to $nickname"; - $feed['textContent'] = "RSS"; - break; - - case 'publicrss': - $feed_classname = $feed['type']; - $feed_mimetype = "application/".$feed['type']."+xml"; - $feed_title = "Public timeline ".$feed['version']." feed"; - $feed['textContent'] = "RSS"; - break; - - case 'publicatom': - $feed_classname = "atom"; - $feed_mimetype = "application/".$feed['type']."+xml"; - $feed_title = "Public timeline ".$feed['version']." feed"; - $feed['textContent'] = "Atom"; - break; - - case 'noticesearchrss': - $feed_classname = $feed['type']; - $feed_mimetype = "application/".$feed['type']."+xml"; - $feed_title = $feed['version']." feed for this notice search"; - $feed['textContent'] = "RSS"; - break; - - case 'tagrss': - $feed_classname = $feed['type']; - $feed_mimetype = "application/".$feed['type']."+xml"; - $feed_title = $feed['version']." feed for this tag"; - $feed['textContent'] = "RSS"; - break; - - case 'favoritedrss': - $feed_classname = $feed['type']; - $feed_mimetype = "application/".$feed['type']."+xml"; - $feed_title = "Favorited ".$feed['version']." feed"; - $feed['textContent'] = "RSS"; - break; - - case 'foaf': - $feed_classname = "foaf"; - $feed_mimetype = "application/".$feed['type']."+xml"; - $feed_title = "$nickname's FOAF file"; - $feed['textContent'] = "FOAF"; - break; - - case 'favoritesrss': - $feed_classname = "favorites"; - $feed_mimetype = "application/".$feed['type']."+xml"; - $feed_title = "Feed for favorites of $nickname"; - $feed['textContent'] = "RSS"; - break; - - case 'usertimeline': - $feed_classname = "atom"; - $feed_mimetype = "application/".$feed['type']."+xml"; - $feed_title = "$nickname's ".$feed['version']." notice feed"; - $feed['textContent'] = "Atom"; + case Feed::FOAF: + $classname = 'foaf'; break; } + $this->out->elementStart('li'); - $this->out->element('a', array('href' => $feed['href'], - 'class' => $feed_classname, - 'type' => $feed_mimetype, - 'title' => $feed_title), - $feed['textContent']); + $this->out->element('a', array('href' => $feed->url, + 'class' => $classname, + 'type' => $feed->mimeType(), + 'title' => $feed->title), + $feed->typeName()); $this->out->elementEnd('li'); } } From fc293545be13473d992b512141be233c2963f6da Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 11 Feb 2009 16:50:07 +0000 Subject: [PATCH 042/189] Minor. Changed from @class location to label --- actions/showgroup.php | 2 +- actions/showstream.php | 2 +- actions/tagother.php | 2 +- lib/grouplist.php | 2 +- lib/profilelist.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/actions/showgroup.php b/actions/showgroup.php index 7bc68fbc64..6df8dd3067 100644 --- a/actions/showgroup.php +++ b/actions/showgroup.php @@ -244,7 +244,7 @@ class ShowgroupAction extends Action if ($this->group->location) { $this->elementStart('dl', 'entity_location'); $this->element('dt', null, _('Location')); - $this->element('dd', 'location', $this->group->location); + $this->element('dd', 'label', $this->group->location); $this->elementEnd('dl'); } diff --git a/actions/showstream.php b/actions/showstream.php index 962f4b4524..bd6f4153b2 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -281,7 +281,7 @@ class ShowstreamAction extends Action if ($this->profile->location) { $this->elementStart('dl', 'entity_location'); $this->element('dt', null, _('Location')); - $this->element('dd', 'location', $this->profile->location); + $this->element('dd', 'label', $this->profile->location); $this->elementEnd('dl'); } diff --git a/actions/tagother.php b/actions/tagother.php index 3e8a12fd69..79151c9118 100644 --- a/actions/tagother.php +++ b/actions/tagother.php @@ -110,7 +110,7 @@ class TagotherAction extends Action if ($this->profile->location) { $this->elementStart('dl', 'entity_location'); $this->element('dt', null, _('Location')); - $this->element('dd', 'location', $this->profile->location); + $this->element('dd', 'label', $this->profile->location); $this->elementEnd('dl'); } if ($this->profile->homepage) { diff --git a/lib/grouplist.php b/lib/grouplist.php index 4c448e250d..6801ab4261 100644 --- a/lib/grouplist.php +++ b/lib/grouplist.php @@ -124,7 +124,7 @@ class GroupList extends Widget if ($this->group->location) { $this->out->elementStart('dl', 'entity_location'); $this->out->element('dt', null, _('Location')); - $this->out->elementStart('dd', 'location'); + $this->out->elementStart('dd', 'label'); $this->out->raw($this->highlight($this->group->location)); $this->out->elementEnd('dd'); $this->out->elementEnd('dl'); diff --git a/lib/profilelist.php b/lib/profilelist.php index 4d924b039e..8bef49dcee 100644 --- a/lib/profilelist.php +++ b/lib/profilelist.php @@ -123,7 +123,7 @@ class ProfileList extends Widget if ($this->profile->location) { $this->out->elementStart('dl', 'entity_location'); $this->out->element('dt', null, _('Location')); - $this->out->elementStart('dd', 'location'); + $this->out->elementStart('dd', 'label'); $this->out->raw($this->highlight($this->profile->location)); $this->out->elementEnd('dd'); $this->out->elementEnd('dl'); From b5cc7e4aabeddd08a27e02b2249ce86f92f96fac Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 14:45:06 -0500 Subject: [PATCH 043/189] Handle DB_DataObject errors better We try to handle DB_DataObject errors a little bit better. Previously, they just spit out a cryptic string to the browser with a suggestion to turn on debugging (not a good idea!). So, we catch the error, write the full error message to the log, and then tell users that the can contact the admins if they need to. --- index.php | 25 +++++++++++++-- lib/dberroraction.php | 73 +++++++++++++++++++++++++++++++++++++++++++ lib/htmloutputter.php | 14 ++++++--- 3 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 lib/dberroraction.php diff --git a/index.php b/index.php index 717b17361d..4db0e7555b 100644 --- a/index.php +++ b/index.php @@ -25,7 +25,8 @@ require_once INSTALLDIR . '/lib/common.php'; $user = null; $action = null; -function getPath($req) { +function getPath($req) +{ if (common_config('site', 'fancy')) { return $req['p']; } else if ($_SERVER['PATH_INFO']) { @@ -35,10 +36,30 @@ function getPath($req) { } } -function main() { +function handleError($error) +{ + common_log(LOG_ERR, "PEAR error: " . $error->getMessage()); + $msg = sprintf(_('The database for %s isn\'t responding correctly, '. + 'so the site won\'t work properly. '. + 'The site admins probably know about the problem, '. + 'but you can contact them at %s to make sure. '. + 'Otherwise, wait a few minutes and try again.'), + common_config('site', 'name'), + common_config('site', 'email')); + $dac = new DBErrorAction($msg, 500); + $dac->showPage(); + exit(-1); +} + +function main() +{ global $user, $action; + // For database errors + + PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError'); + // XXX: we need a little more structure in this script // get and cache current user diff --git a/lib/dberroraction.php b/lib/dberroraction.php new file mode 100644 index 0000000000..0dc92490cd --- /dev/null +++ b/lib/dberroraction.php @@ -0,0 +1,73 @@ + + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + * + * Laconica - a distributed open-source microblogging tool + * Copyright (C) 2008, Controlez-Vous, 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 . + */ + +if (!defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/servererroraction.php'; + +/** + * Class for displaying DB Errors + * + * This only occurs if there's been a DB_DataObject_Error that's + * reported through PEAR, so we try to avoid doing anything that connects + * to the DB, so we don't trigger it again. + * + * @category Action + * @package Laconica + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + */ + +class DBErrorAction extends ServerErrorAction +{ + function __construct($message='Error', $code=500) + { + parent::__construct($message, $code); + } + + function title() + { + return _('Database error'); + } + + function getLanguage() + { + // Don't try to figure out user's language; just show the page + return common_config('site', 'language'); + } + + function showPrimaryNav() + { + // don't show primary nav + } +} diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index e2319b1fdc..45e61d2fc6 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -108,22 +108,26 @@ class HTMLOutputter extends XMLOutputter } header('Content-Type: '.$type); - + $this->extraHeaders(); $this->startXML('html', '-//W3C//DTD XHTML 1.0 Strict//EN', 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'); - // FIXME: correct language for interface - - $language = common_language(); + $language = $this->getLanguage(); $this->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml', 'xml:lang' => $language, 'lang' => $language)); } + function getLanguage() + { + // FIXME: correct language for interface + return common_language(); + } + /** * Ends an HTML document * @@ -134,7 +138,7 @@ class HTMLOutputter extends XMLOutputter $this->elementEnd('html'); $this->endXML(); } - + /** * To specify additional HTTP headers for the action * From 21f6c911611fe9debfae9a4c123e91294bc877e3 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 11 Feb 2009 19:56:17 +0000 Subject: [PATCH 044/189] Minor correction: pikiw -> piwik --- plugins/GoogleAnalyticsPlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/GoogleAnalyticsPlugin.php b/plugins/GoogleAnalyticsPlugin.php index 87a70e31ea..1ecbb664e0 100644 --- a/plugins/GoogleAnalyticsPlugin.php +++ b/plugins/GoogleAnalyticsPlugin.php @@ -37,7 +37,7 @@ if (!defined('LACONICA')) { * 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! + * Piwik (http://www.piwik.org/) instead! * * @category Plugin * @package Laconica From 1d5296e596168f6ee8b18e9917e8d8aa62ac254c Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 15:39:49 -0500 Subject: [PATCH 045/189] change htmloutputter to use exception instead of common_user_error --- lib/htmloutputter.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index 45e61d2fc6..06603ac054 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -101,9 +101,8 @@ class HTMLOutputter extends XMLOutputter $type = common_negotiate_type($cp, $sp); if (!$type) { - common_user_error(_('This page is not available in a '. - 'media type you accept'), 406); - exit(0); + throw new ClientException(_('This page is not available in a '. + 'media type you accept'), 406); } } From 5127396325a29d6c7b8f0e1e0ae3e0580ab30dda Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 15:46:29 -0500 Subject: [PATCH 046/189] Move Commands stuff out of classes The classes/ subdir is primarily for the DB_DataObject classes. Stuff in there can get stomped by various generation scripts. I've moved the lurkers there -- related to command-handling -- to lib/. Since auto-loading works fine with lib/, there shouldn't be much of a visible change here. --- classes/Channel.php => lib/channel.php | 0 classes/Command.php => lib/command.php | 0 classes/CommandInterpreter.php => lib/commandinterpreter.php | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename classes/Channel.php => lib/channel.php (100%) rename classes/Command.php => lib/command.php (100%) rename classes/CommandInterpreter.php => lib/commandinterpreter.php (100%) diff --git a/classes/Channel.php b/lib/channel.php similarity index 100% rename from classes/Channel.php rename to lib/channel.php diff --git a/classes/Command.php b/lib/command.php similarity index 100% rename from classes/Command.php rename to lib/command.php diff --git a/classes/CommandInterpreter.php b/lib/commandinterpreter.php similarity index 100% rename from classes/CommandInterpreter.php rename to lib/commandinterpreter.php From 9d07032334043625a5aa3243d911bdc1c77a7a9c Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 15:48:30 -0500 Subject: [PATCH 047/189] fix command classes --- lib/channel.php | 1 - lib/command.php | 74 +++++++++++++++++++------------------- lib/commandinterpreter.php | 3 +- 3 files changed, 38 insertions(+), 40 deletions(-) diff --git a/lib/channel.php b/lib/channel.php index fdeff21fc6..f1e2055466 100644 --- a/lib/channel.php +++ b/lib/channel.php @@ -21,7 +21,6 @@ if (!defined('LACONICA')) { exit(1); } class Channel { - function on($user) { return false; diff --git a/lib/command.php b/lib/command.php index eacbdacb36..507990a0b3 100644 --- a/lib/command.php +++ b/lib/command.php @@ -19,18 +19,18 @@ if (!defined('LACONICA')) { exit(1); } -require_once(INSTALLDIR.'/classes/Channel.php'); +require_once(INSTALLDIR.'/lib/channel.php'); class Command { - + var $user = null; - + function __construct($user=null) { $this->user = $user; } - + function execute($channel) { return false; @@ -109,7 +109,7 @@ class StatsCommand extends Command $notices = new Notice(); $notices->profile_id = $this->user->id; $notice_count = (int) $notices->count(); - + $channel->output($this->user, sprintf(_("Subscriptions: %1\$s\n". "Subscribers: %2\$s\n". "Notices: %3\$s"), @@ -121,21 +121,21 @@ class StatsCommand extends Command class FavCommand extends Command { - + var $other = null; - + function __construct($user, $other) { parent::__construct($user); $this->other = $other; } - + function execute($channel) { - - $recipient = + + $recipient = common_relative_profile($this->user, common_canonical_nickname($this->other)); - + if (!$recipient) { $channel->error($this->user, _('No such user.')); return; @@ -145,7 +145,7 @@ class FavCommand extends Command $channel->error($this->user, _('User has no last notice')); return; } - + $fave = Fave::addNew($this->user, $notice); if (!$fave) { @@ -154,15 +154,15 @@ class FavCommand extends Command } $other = User::staticGet('id', $recipient->id); - + if ($other && $other->id != $user->id) { if ($other->email && $other->emailnotifyfav) { mail_notify_fave($other, $this->user, $notice); } } - + $this->user->blowFavesCache(); - + $channel->output($this->user, _('Notice marked as fave.')); } } @@ -175,17 +175,17 @@ class WhoisCommand extends Command parent::__construct($user); $this->other = $other; } - + function execute($channel) { - $recipient = + $recipient = common_relative_profile($this->user, common_canonical_nickname($this->other)); - + if (!$recipient) { $channel->error($this->user, _('No such user.')); return; } - + $whois = sprintf(_("%1\$s (%2\$s)"), $recipient->nickname, $recipient->profileurl); if ($recipient->fullname) { @@ -214,7 +214,7 @@ class MessageCommand extends Command $this->other = $other; $this->text = $text; } - + function execute($channel) { $other = User::staticGet('nickname', common_canonical_nickname($this->other)); @@ -229,7 +229,7 @@ class MessageCommand extends Command return; } } - + if (!$other) { $channel->error($this->user, _('No such user.')); return; @@ -251,19 +251,19 @@ class MessageCommand extends Command class GetCommand extends Command { - + var $other = null; - + function __construct($user, $other) { parent::__construct($user); $this->other = $other; } - + function execute($channel) { $target_nickname = common_canonical_nickname($this->other); - + $target = common_relative_profile($this->user, $target_nickname); @@ -277,32 +277,32 @@ class GetCommand extends Command return; } $notice_content = $notice->content; - + $channel->output($this->user, $target_nickname . ": " . $notice_content); } } class SubCommand extends Command { - + var $other = null; - + function __construct($user, $other) { parent::__construct($user); $this->other = $other; } - + function execute($channel) { - + if (!$this->other) { $channel->error($this->user, _('Specify the name of the user to subscribe to')); return; } - + $result = subs_subscribe_user($this->user, $this->other); - + if ($result == 'true') { $channel->output($this->user, sprintf(_('Subscribed to %s'), $this->other)); } else { @@ -315,7 +315,7 @@ class UnsubCommand extends Command { var $other = null; - + function __construct($user, $other) { parent::__construct($user); @@ -328,9 +328,9 @@ class UnsubCommand extends Command $channel->error($this->user, _('Specify the name of the user to unsubscribe from')); return; } - + $result=subs_unsubscribe_user($this->user, $this->other); - + if ($result) { $channel->output($this->user, sprintf(_('Unsubscribed from %s'), $this->other)); } else { @@ -369,7 +369,7 @@ class OnCommand extends Command parent::__construct($user); $this->other = $other; } - + function execute($channel) { if ($other) { @@ -406,7 +406,7 @@ class HelpCommand extends Command "unsub - same as 'leave'\n". "last - same as 'get'\n". "on - not yet implemented.\n". - "off - not yet implemented.\n". + "off - not yet implemented.\n". "nudge - not yet implemented.\n". "invite - not yet implemented.\n". "track - not yet implemented.\n". diff --git a/lib/commandinterpreter.php b/lib/commandinterpreter.php index 0679f5462d..49c733c033 100644 --- a/lib/commandinterpreter.php +++ b/lib/commandinterpreter.php @@ -19,11 +19,10 @@ if (!defined('LACONICA')) { exit(1); } -require_once(INSTALLDIR.'/classes/Command.php'); +require_once INSTALLDIR.'/lib/command.php'; class CommandInterpreter { - function handle_command($user, $text) { # XXX: localise From 3c8c0572efef97c25037545e1e971f0ce3cd7cc8 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 11 Feb 2009 23:03:12 +0000 Subject: [PATCH 048/189] Ticket 1172. Capitalizing first character of each word to uppercase instead of putting all characters to uppercase. --- theme/base/css/display.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 3b72d00cee..1ac63927d2 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -22,7 +22,7 @@ line-height:1.65; position:relative; } h1,h2,h3,h4,h5,h6 { -text-transform:uppercase; +text-transform:capitalize; margin-bottom:7px; overflow:hidden; } @@ -43,7 +43,6 @@ font-weight:bold; legend { font-weight:bold; font-size:1.3em; -text-transform:uppercase; } input, textarea, select, option { padding:4px; From 7155cf813de1502e796c0eac1e680289f3552c29 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 11 Feb 2009 17:46:53 -0800 Subject: [PATCH 049/189] Move/reorg Twitter broadcast code to lib/twitter.php in prep for making a twitterqueuehandler. --- actions/twittersettings.php | 5 +- lib/twitter.php | 97 +++++++++++++++++++++++++++++++++++-- lib/util.php | 87 +++------------------------------ 3 files changed, 104 insertions(+), 85 deletions(-) diff --git a/actions/twittersettings.php b/actions/twittersettings.php index 2d41469bba..a79859bbf0 100644 --- a/actions/twittersettings.php +++ b/actions/twittersettings.php @@ -32,6 +32,7 @@ if (!defined('LACONICA')) { } require_once INSTALLDIR.'/lib/connectsettingsaction.php'; +require_once INSTALLDIR.'/lib/twitter.php'; define('SUBSCRIPTIONS', 80); @@ -90,7 +91,7 @@ class TwittersettingsAction extends ConnectSettingsAction $fuser = null; - $flink = Foreign_link::getByUserID($user->id, 1); // 1 == Twitter + $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); if ($flink) { $fuser = $flink->getForeignUser(); @@ -358,7 +359,7 @@ class TwittersettingsAction extends ConnectSettingsAction $flink->user_id = $user->id; $flink->foreign_id = $twit_user->id; - $flink->service = 1; // Twitter + $flink->service = TWITTER_SERVICE; $flink->credentials = $password; $flink->created = common_sql_now(); diff --git a/lib/twitter.php b/lib/twitter.php index 1972985493..72212c1851 100644 --- a/lib/twitter.php +++ b/lib/twitter.php @@ -19,6 +19,8 @@ if (!defined('LACONICA')) { exit(1); } +define("TWITTER_SERVICE", 1); // Twitter is foreign_service ID 1 + function get_twitter_data($uri, $screen_name, $password) { @@ -28,14 +30,13 @@ function get_twitter_data($uri, $screen_name, $password) CURLOPT_FAILONERROR => true, CURLOPT_HEADER => false, CURLOPT_FOLLOWLOCATION => true, - # CURLOPT_USERAGENT => "identi.ca", + CURLOPT_USERAGENT => "Laconica", CURLOPT_CONNECTTIMEOUT => 120, CURLOPT_TIMEOUT => 120, # Twitter is strict about accepting invalid "Expect" headers CURLOPT_HTTPHEADER => array('Expect:') ); - $ch = curl_init($uri); curl_setopt_array($ch, $options); $data = curl_exec($ch); @@ -95,7 +96,7 @@ function add_twitter_user($twitter_id, $screen_name) $fuser->nickname = $screen_name; $fuser->uri = 'http://twitter.com/' . $screen_name; $fuser->id = $twitter_id; - $fuser->service = 1; // Twitter + $fuser->service = TWITTER_SERVICE; // Twitter $fuser->created = common_sql_now(); $result = $fuser->insert(); @@ -206,3 +207,93 @@ function save_twitter_friends($user, $twitter_id, $screen_name, $password) return true; } +function is_twitter_bound($notice, $flink) { + + // Check to see if notice should go to Twitter + if (($flink->noticesync & FOREIGN_NOTICE_SEND)) { + + // If it's not a Twitter-style reply, or if the user WANTS to send replies. + if (!preg_match('/^@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) || + ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) { + return true; + } + } + + return false; +} + +function broadcast_twitter($notice) +{ + global $config; + $success = true; + + $flink = Foreign_link::getByUserID($notice->profile_id, + TWITTER_SERVICE); + + // XXX: Not sure WHERE to check whether a notice should go to + // Twitter. Should we even put in the queue if it's not? --Zach + if (is_twitter_bound($notice, $flink)) { + + $fuser = $flink->getForeignUser(); + $twitter_user = $fuser->nickname; + $twitter_password = $flink->credentials; + $uri = 'http://www.twitter.com/statuses/update.json'; + + // XXX: Hack to get around PHP cURL's use of @ being a a meta character + $statustxt = preg_replace('/^@/', ' @', $notice->content); + + $options = array( + CURLOPT_USERPWD => "$twitter_user:$twitter_password", + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => + array( + 'status' => $statustxt, + 'source' => $config['integration']['source'] + ), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FAILONERROR => true, + CURLOPT_HEADER => false, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_USERAGENT => "Laconica", + CURLOPT_CONNECTTIMEOUT => 120, // XXX: How long should this be? + CURLOPT_TIMEOUT => 120, + + # Twitter is strict about accepting invalid "Expect" headers + CURLOPT_HTTPHEADER => array('Expect:') + ); + + $ch = curl_init($uri); + curl_setopt_array($ch, $options); + $data = curl_exec($ch); + $errmsg = curl_error($ch); + + if ($errmsg) { + common_debug("cURL error: $errmsg - " . + "trying to send notice for $twitter_user.", + __FILE__); + $success = false; + } + + curl_close($ch); + + if (!$data) { + common_debug("No data returned by Twitter's " . + "API trying to send update for $twitter_user", + __FILE__); + $success = false; + } + + // Twitter should return a status + $status = json_decode($data); + + if (!$status->id) { + common_debug("Unexpected data returned by Twitter " . + " API trying to send update for $twitter_user", + __FILE__); + $success = false; + } + } + + return $success; +} + diff --git a/lib/util.php b/lib/util.php index c0c9801116..3f4fae3a59 100644 --- a/lib/util.php +++ b/lib/util.php @@ -796,24 +796,6 @@ function common_redirect($url, $code=307) function common_broadcast_notice($notice, $remote=false) { - - // Check to see if notice should go to Twitter - $flink = Foreign_link::getByUserID($notice->profile_id, 1); // 1 == Twitter - if (($flink->noticesync & FOREIGN_NOTICE_SEND) == FOREIGN_NOTICE_SEND) { - - // If it's not a Twitter-style reply, or if the user WANTS to send replies... - - if (!preg_match('/^@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) || - (($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) == FOREIGN_NOTICE_SEND_REPLY)) { - - $result = common_twitter_broadcast($notice, $flink); - - if (!$result) { - common_debug('Unable to send notice: ' . $notice->id . ' to Twitter.', __FILE__); - } - } - } - if (common_config('queue', 'enabled')) { // Do it later! return common_enqueue_notice($notice); @@ -822,68 +804,6 @@ function common_broadcast_notice($notice, $remote=false) } } -function common_twitter_broadcast($notice, $flink) -{ - global $config; - $success = true; - $fuser = $flink->getForeignUser(); - $twitter_user = $fuser->nickname; - $twitter_password = $flink->credentials; - $uri = 'http://www.twitter.com/statuses/update.json'; - - // XXX: Hack to get around PHP cURL's use of @ being a a meta character - $statustxt = preg_replace('/^@/', ' @', $notice->content); - - $options = array( - CURLOPT_USERPWD => "$twitter_user:$twitter_password", - CURLOPT_POST => true, - CURLOPT_POSTFIELDS => array( - 'status' => $statustxt, - 'source' => $config['integration']['source'] - ), - CURLOPT_RETURNTRANSFER => true, - CURLOPT_FAILONERROR => true, - CURLOPT_HEADER => false, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_USERAGENT => "Laconica", - CURLOPT_CONNECTTIMEOUT => 120, // XXX: Scary!!!! How long should this be? - CURLOPT_TIMEOUT => 120, - - # Twitter is strict about accepting invalid "Expect" headers - CURLOPT_HTTPHEADER => array('Expect:') - ); - - $ch = curl_init($uri); - curl_setopt_array($ch, $options); - $data = curl_exec($ch); - $errmsg = curl_error($ch); - - if ($errmsg) { - common_debug("cURL error: $errmsg - trying to send notice for $twitter_user.", - __FILE__); - $success = false; - } - - curl_close($ch); - - if (!$data) { - common_debug("No data returned by Twitter's API trying to send update for $twitter_user", - __FILE__); - $success = false; - } - - // Twitter should return a status - $status = json_decode($data); - - if (!$status->id) { - common_debug("Unexpected data returned by Twitter API trying to send update for $twitter_user", - __FILE__); - $success = false; - } - - return $success; -} - // Stick the notice on the queue function common_enqueue_notice($notice) @@ -935,6 +855,13 @@ function common_real_broadcast($notice, $remote=false) common_log(LOG_ERR, 'Error in public broadcast for notice ' . $notice->id); } } + if ($success) { + $success = broadcast_twitter($notice); + if (!$success) { + common_log(LOG_ERR, 'Error in Twitter broadcast for notice ' . $notice->id); + } + } + // XXX: broadcast notices to other IM return $success; } From 9f035e2847e0d119ca3d70e02df6f4fa73ca64c3 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 11 Feb 2009 21:41:56 -0800 Subject: [PATCH 050/189] Code to handle PEAR_Errors raised by DB_DataObject that are bubbling up, but are actually expected and can safely be ignored. --- actions/emailsettings.php | 22 ++++++++++++++++++++++ actions/register.php | 23 ++++++++++++++++++++++- lib/action.php | 26 ++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/actions/emailsettings.php b/actions/emailsettings.php index b84acb2141..c6c9834538 100644 --- a/actions/emailsettings.php +++ b/actions/emailsettings.php @@ -487,4 +487,26 @@ class EmailsettingsAction extends AccountSettingsAction return $other->id != $user->id; } } + + /** + * Check old fashioned PEAR_Error msgs coming from DB_DataObject + * + * In this case email don't exist in the DB yet, so DB_DataObject + * throws an error. Overrided from Action. + * + * @param PEAR_Error + * + * @return nothing + */ + + function checkDB_DataObjectError($error) { + if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { + + // Do nothing. + + } else { + parent::checkDB_DataObjectError($error); + } + } + } diff --git a/actions/register.php b/actions/register.php index 5d7a8ce690..853bd0cf66 100644 --- a/actions/register.php +++ b/actions/register.php @@ -223,10 +223,31 @@ class RegisterAction extends Action */ function nicknameExists($nickname) - { + { $user = User::staticGet('nickname', $nickname); return ($user !== false); } + + /** + * Check old fashioned PEAR_Error msgs coming from DB_DataObject + * + * In this case nickname and email don't exist in the DB yet, + * so DB_DataObject throws an error. Overrided from Action. + * + * @param PEAR_Error + * + * @return nothing + */ + + function checkDB_DataObjectError($error) { + if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { + + // Do nothing. + + } else { + parent::checkDB_DataObjectError($error); + } + } /** * Does the given email address already exist? diff --git a/lib/action.php b/lib/action.php index bd38bf79cc..e3a8ef62c7 100644 --- a/lib/action.php +++ b/lib/action.php @@ -82,6 +82,17 @@ class Action extends HTMLOutputter // lawsuit */ function prepare($argarray) { + // This is for checking PEAR_Errors raised by DB_DataObject. + // Setting this to PEAR_ERROR_CALLBACK because setting + // to PEAR_ERROR_EXCEPTION does't work to allow PEAR_Errors + // to be handled as PHP5 exceptions, and PEAR_ERROR_RETURN + // does not cause DB_DataObject to actually return PEAR_Errors + // that can be checked with PEAR::isError() -- instead + // they just disappear into the ether, and can only be checked for + // after the fact. -- Zach + PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, + array($this, "checkDB_DataObjectError")); + $this->args =& common_copy_args($argarray); return true; } @@ -844,6 +855,21 @@ class Action extends HTMLOutputter // lawsuit throw new ClientException($msg, $code); } + /** + * Check old fashioned PEAR_Error msgs coming from DB_DataObject + * + * Logs the DB_DataObject error. Override to do something else. + * + * @param PEAR_Error + * + * @return nothing + */ + + function checkDB_DataObjectError($error) { + common_log(LOG_ERR, $error->getMessage()); + // XXX: throw an exception here? --Zach + } + /** * Returns the current URL * From 616bdd43a921b2554d21b80af28ddb0fb6cb3c16 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 11 Feb 2009 22:08:20 -0800 Subject: [PATCH 051/189] Just discovered the PEAR_Error handling function in index.php. Duh. Renamed the Action functions to throw an exception like it. I still think it probably makes sense to have the callback defined in both places for finer control. --- actions/emailsettings.php | 6 +++--- actions/register.php | 6 +++--- lib/action.php | 31 ++++++++++++++++++------------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/actions/emailsettings.php b/actions/emailsettings.php index c6c9834538..0a86aa66d1 100644 --- a/actions/emailsettings.php +++ b/actions/emailsettings.php @@ -489,7 +489,7 @@ class EmailsettingsAction extends AccountSettingsAction } /** - * Check old fashioned PEAR_Error msgs coming from DB_DataObject + * Handle old fashioned PEAR_Error msgs coming from DB_DataObject * * In this case email don't exist in the DB yet, so DB_DataObject * throws an error. Overrided from Action. @@ -499,13 +499,13 @@ class EmailsettingsAction extends AccountSettingsAction * @return nothing */ - function checkDB_DataObjectError($error) { + function handleError($error) { if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { // Do nothing. } else { - parent::checkDB_DataObjectError($error); + parent::handleError($error); } } diff --git a/actions/register.php b/actions/register.php index 853bd0cf66..aafb54ebbf 100644 --- a/actions/register.php +++ b/actions/register.php @@ -229,7 +229,7 @@ class RegisterAction extends Action } /** - * Check old fashioned PEAR_Error msgs coming from DB_DataObject + * Handle old fashioned PEAR_Error msgs coming from DB_DataObject * * In this case nickname and email don't exist in the DB yet, * so DB_DataObject throws an error. Overrided from Action. @@ -239,13 +239,13 @@ class RegisterAction extends Action * @return nothing */ - function checkDB_DataObjectError($error) { + function handleError($error) { if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { // Do nothing. } else { - parent::checkDB_DataObjectError($error); + parent::handleError($error); } } diff --git a/lib/action.php b/lib/action.php index e3a8ef62c7..926fe93fb7 100644 --- a/lib/action.php +++ b/lib/action.php @@ -82,16 +82,10 @@ class Action extends HTMLOutputter // lawsuit */ function prepare($argarray) { - // This is for checking PEAR_Errors raised by DB_DataObject. - // Setting this to PEAR_ERROR_CALLBACK because setting - // to PEAR_ERROR_EXCEPTION does't work to allow PEAR_Errors - // to be handled as PHP5 exceptions, and PEAR_ERROR_RETURN - // does not cause DB_DataObject to actually return PEAR_Errors - // that can be checked with PEAR::isError() -- instead - // they just disappear into the ether, and can only be checked for - // after the fact. -- Zach + + // For PEAR_Errors comming from DB_DataObject PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, - array($this, "checkDB_DataObjectError")); + array($this, "handleError")); $this->args =& common_copy_args($argarray); return true; @@ -856,7 +850,7 @@ class Action extends HTMLOutputter // lawsuit } /** - * Check old fashioned PEAR_Error msgs coming from DB_DataObject + * Handle old fashioned PEAR_Error msgs coming from DB_DataObject * * Logs the DB_DataObject error. Override to do something else. * @@ -865,9 +859,20 @@ class Action extends HTMLOutputter // lawsuit * @return nothing */ - function checkDB_DataObjectError($error) { - common_log(LOG_ERR, $error->getMessage()); - // XXX: throw an exception here? --Zach + function handleError($error) { + + common_log(LOG_ERR, "PEAR error: " . $error->getMessage()); + $msg = sprintf(_('The database for %s isn\'t responding correctly, '. + 'so the site won\'t work properly. '. + 'The site admins probably know about the problem, '. + 'but you can contact them at %s to make sure. '. + 'Otherwise, wait a few minutes and try again.'), + common_config('site', 'name'), + common_config('site', 'email')); + + $dac = new DBErrorAction($msg, 500); + $dac->showPage(); + exit(-1); } /** From a845d06c77f17a5037e30d41db939f0ed3b5936e Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 06:49:34 -0500 Subject: [PATCH 052/189] ignore no-data error, since we use it all the time --- index.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/index.php b/index.php index 4db0e7555b..b180e2b653 100644 --- a/index.php +++ b/index.php @@ -38,6 +38,10 @@ function getPath($req) function handleError($error) { + if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { + return; + } + common_log(LOG_ERR, "PEAR error: " . $error->getMessage()); $msg = sprintf(_('The database for %s isn\'t responding correctly, '. 'so the site won\'t work properly. '. From 99773e3b5ef0fa395bccb9a9656afe7552d42594 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 07:58:36 -0500 Subject: [PATCH 053/189] wrap multiline regexp in quotes so it doesn't mess up my editor's indenting --- lib/util.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/util.php b/lib/util.php index c0c9801116..03e3618db8 100644 --- a/lib/util.php +++ b/lib/util.php @@ -394,20 +394,20 @@ function common_render_text($text) function common_replace_urls_callback($text, $callback) { // Start off with a regex - $regex = '# - (?: - (?: - (?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|xmpp|irc):// - | - (?:mailto|aim|tel): - ) - [^.\s]+\.[^\s]+ - | - (?:[^.\s/:]+\.)+ - (?:museum|travel|[a-z]{2,4}) - (?:[:/][^\s]*)? - ) - #ix'; + $regex = '#'. + '(?:'. + '(?:'. + '(?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|xmpp|irc)://'. + '|'. + '(?:mailto|aim|tel):'. + ')'. + '[^.\s]+\.[^\s]+'. + '|'. + '(?:[^.\s/:]+\.)+'. + '(?:museum|travel|[a-z]{2,4})'. + '(?:[:/][^\s]*)?'. + ')'. + '#ix'; preg_match_all($regex, $text, $matches); // Then clean up what the regex left behind From eaae4562228bcbf780329d7bbb5f35437b31ebe3 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 08:38:43 -0500 Subject: [PATCH 054/189] Add XMLStringer for building XML strings We had a bunch of --- lib/util.php | 41 ++++++++++++++++++++++----- lib/xmlstringer.php | 68 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 lib/xmlstringer.php diff --git a/lib/util.php b/lib/util.php index 03e3618db8..a6f6a93518 100644 --- a/lib/util.php +++ b/lib/util.php @@ -475,13 +475,14 @@ function common_linkify($url) { $display = $url; $url = (!preg_match('#^([a-z]+://|(mailto|aim|tel):)#i', $url)) ? 'http://'.$url:$url; + $attrs = array('href' => $url, 'rel' => 'external'); + if ($longurl = common_longurl($url)) { $longurl = htmlentities($longurl, ENT_QUOTES, 'UTF-8'); - $title = "title=\"$longurl\""; + $attrs['title'] = $longurl; } - else $title = ''; - return "$display"; + return XMLStringer::estring('a', $attrs, $display); } function common_longurl($short_url) @@ -582,7 +583,13 @@ function common_tag_link($tag) { $canonical = common_canonical_tag($tag); $url = common_local_url('tag', array('tag' => $canonical)); - return ''; + $xs = new XMLStringer(); + $xs->elementStart('span', 'tag'); + $xs->element('a', array('href' => $url, + 'rel' => 'tag'), + $tag); + $xs->elementEnd(); + return $xs->getString(); } function common_canonical_tag($tag) @@ -600,7 +607,14 @@ function common_at_link($sender_id, $nickname) $sender = Profile::staticGet($sender_id); $recipient = common_relative_profile($sender, common_canonical_nickname($nickname)); if ($recipient) { - return ''.$nickname.''; + $xs = new XMLStringer(false); + $xs->elementStart('span', 'vcard'); + $xs->elementStart('a', array('href' => $recipient->profileurl, + 'class' => 'url')); + $xs->element('span', 'fn nickname', $nickname); + $xs->elementEnd('a'); + $xs->elementEnd('span'); + return $xs->getString(); } else { return $nickname; } @@ -611,7 +625,14 @@ function common_group_link($sender_id, $nickname) $sender = Profile::staticGet($sender_id); $group = User_group::staticGet('nickname', common_canonical_nickname($nickname)); if ($group && $sender->isMember($group)) { - return ''.$nickname.''; + $xs = new XMLStringer(); + $xs->elementStart('span', 'vcard'); + $xs->elementStart('a', array('href' => $group->permalink(), + 'class' => 'url')); + $xs->element('span', 'fn nickname', $nickname); + $xs->elementEnd('a'); + $xs->elementEnd('span'); + return $xs->getString(); } else { return $nickname; } @@ -628,7 +649,13 @@ function common_at_hash_link($sender_id, $tag) $url = common_local_url('subscriptions', array('nickname' => $user->nickname, 'tag' => $tag)); - return ''; + $xs = new XMLStringer(); + $xs->elementStart('span', 'tag'); + $xs->element('a', array('href' => $url, + 'rel' => $tag), + $tag); + $xs->elementEnd('span'); + return $xs->getString(); } else { return $tag; } diff --git a/lib/xmlstringer.php b/lib/xmlstringer.php new file mode 100644 index 0000000000..951b13b676 --- /dev/null +++ b/lib/xmlstringer.php @@ -0,0 +1,68 @@ +. + * + * @category Output + * @package Laconica + * @author Evan Prodromou + * @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); +} + +/** + * Create in-memory XML + * + * @category Output + * @package Laconica + * @author Evan Prodromou + * @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 + * @see HTMLOutputter + */ + +class XMLStringer extends XMLOutputter +{ + function __construct($indent=false) + { + $this->xw = new XMLWriter(); + $this->xw->openMemory(); + $this->xw->setIndent($indent); + } + + function getString() + { + return $this->xw->outputMemory(); + } + + // utility for quickly creating XML-strings + + static function estring($tag, $attrs=null, $content=null) + { + $xs = new XMLStringer(); + $xs->element($tag, $attrs, $content); + return $xs->getString(); + } +} \ No newline at end of file From ab8d27b8d1e25bd1058b904d03fedf97148f4b89 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 09:22:45 -0500 Subject: [PATCH 055/189] don't over specialize URLs --- lib/util.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/util.php b/lib/util.php index a6f6a93518..37c941cdb4 100644 --- a/lib/util.php +++ b/lib/util.php @@ -472,13 +472,15 @@ function common_replace_urls_callback($text, $callback) { } function common_linkify($url) { + // It comes in special'd, so we unspecial it before passing to the stringifying + // functions + $url = htmlspecialchars_decode($url); $display = $url; - $url = (!preg_match('#^([a-z]+://|(mailto|aim|tel):)#i', $url)) ? 'http://'.$url:$url; + $url = (!preg_match('#^([a-z]+://|(mailto|aim|tel):)#i', $url)) ? 'http://'.$url : $url; $attrs = array('href' => $url, 'rel' => 'external'); if ($longurl = common_longurl($url)) { - $longurl = htmlentities($longurl, ENT_QUOTES, 'UTF-8'); $attrs['title'] = $longurl; } From 511864369511b2e749d168d717d84237a13a201f Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 13:58:05 -0500 Subject: [PATCH 056/189] move carrier.sql to sms_carrier.sql --- db/{carrier.sql => sms_carrier.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename db/{carrier.sql => sms_carrier.sql} (100%) diff --git a/db/carrier.sql b/db/sms_carrier.sql similarity index 100% rename from db/carrier.sql rename to db/sms_carrier.sql From 654a91cc1c99dcb556bce240e03dbfa1689d0d70 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 14:03:15 -0500 Subject: [PATCH 057/189] Add fixed IDs to SMS carrier list Changed the SMS carrier list to use a fixed ID number for each carrier distributed with the software. We claim IDs > 100000; admins can use IDs < 100000 for local values. I'd be pretty surprised if there were more than 100K wireless carriers in the world, but hey. --- db/sms_carrier.sql | 124 +++++++++++++++++++++++---------------------- 1 file changed, 63 insertions(+), 61 deletions(-) diff --git a/db/sms_carrier.sql b/db/sms_carrier.sql index 932f7c8bb0..6879f20890 100644 --- a/db/sms_carrier.sql +++ b/db/sms_carrier.sql @@ -1,61 +1,63 @@ -insert into sms_carrier - (name, email_pattern, created) -values - ('3 River Wireless', '%s@sms.3rivers.net', now()), - ('7-11 Speakout', '%s@cingularme.com', now()), - ('Airtel (Karnataka, India)', '%s@airtelkk.com', now()), - ('Alaska Communications Systems', '%s@msg.acsalaska.com', now()), - ('Alltel Wireless', '%s@message.alltel.com', now()), - ('AT&T Wireless', '%s@txt.att.net', now()), - ('Bell Mobility (Canada)', '%s@txt.bell.ca', now()), - ('Boost Mobile', '%s@myboostmobile.com', now()), - ('Cellular One (Dobson)', '%s@mobile.celloneusa.com', now()), - ('Cincinnati Bell Wireless', '%s@gocbw.com', now()), - ('Cingular (Postpaid)', '%s@cingularme.com', now()), - ('Centennial Wireless', '%s@cwemail.com', now()), - ('Cingular (GoPhone prepaid)', '%s@cingularme.com', now()), - ('Claro (Nicaragua)', '%s@ideasclaro-ca.com', now()), - ('Comcel', '%s@comcel.com.co', now()), - ('Cricket', '%s@sms.mycricket.com', now()), - ('CTI', '%s@sms.ctimovil.com.ar', now()), - ('Emtel (Mauritius)', '%s@emtelworld.net', now()), - ('Fido (Canada)', '%s@fido.ca', now()), - ('General Communications Inc.', '%s@msg.gci.net', now()), - ('Globalstar', '%s@msg.globalstarusa.com', now()), - ('Helio', '%s@myhelio.com', now()), - ('Illinois Valley Cellular', '%s@ivctext.com', now()), - ('i wireless', '%s.iws@iwspcs.net', now()), - ('Meteor (Ireland)', '%s@sms.mymeteor.ie', now()), - ('Mero Mobile (Nepal)', '%s@sms.spicenepal.com', now()), - ('MetroPCS', '%s@mymetropcs.com', now()), - ('Movicom', '%s@movimensaje.com.ar', now()), - ('Mobitel (Sri Lanka)', '%s@sms.mobitel.lk', now()), - ('Movistar (Colombia)', '%s@movistar.com.co', now()), - ('MTN (South Africa)', '%s@sms.co.za', now()), - ('MTS (Canada)', '%s@text.mtsmobility.com', now()), - ('Nextel (Argentina)', '%s@nextel.net.ar', now()), - ('Orange (Poland)', '%s@orange.pl', now()), - ('Orange (UK)', '%s@orange.net', now()), - ('Personal (Argentina)', '%s@personal-net.com.ar', now()), - ('Plus GSM (Poland)', '%s@text.plusgsm.pl', now()), - ('President''s Choice (Canada)', '%s@txt.bell.ca', now()), - ('Qwest', '%s@qwestmp.com', now()), - ('Rogers (Canada)', '%s@pcs.rogers.com', now()), - ('Sasktel (Canada)', '%s@sms.sasktel.com', now()), - ('Setar Mobile email (Aruba)', '%s@mas.aw', now()), - ('Solo Mobile', '%s@txt.bell.ca', now()), - ('Sprint (PCS)', '%s@messaging.sprintpcs.com', now()), - ('Sprint (Nextel)', '%s@page.nextel.com', now()), - ('Suncom', '%s@tms.suncom.com', now()), - ('T-Mobile', '%s@tmomail.net', now()), - ('T-Mobile (Austria)', '%s@sms.t-mobile.at', now()), - ('Telus Mobility (Canada)', '%s@msg.telus.com', now()), - ('Thumb Cellular', '%s@sms.thumbcellular.com', now()), - ('Tigo (Formerly Ola)', '%s@sms.tigo.com.co', now()), - ('Unicel', '%s@utext.com', now()), - ('US Cellular', '%s@email.uscc.net', now()), - ('Verizon', '%s@vtext.com', now()), - ('Virgin Mobile (Canada)', '%s@vmobile.ca', now()), - ('Virgin Mobile (USA)', '%s@vmobl.com', now()), - ('Vodafone NZ (txt ''R'' to 901 to enable first)', '%s@sms.vodafone.net.nz', now()), - ('YCC', '%s@sms.ycc.ru', now()); +INSERT INTO sms_carrier + (id, name, email_pattern, created) +VALUES + (100056, '3 River Wireless', '%s@sms.3rivers.net', now()), + (100057, '7-11 Speakout', '%s@cingularme.com', now()), + (100058, 'Airtel (Karnataka, India)', '%s@airtelkk.com', now()), + (100059, 'Alaska Communications Systems', '%s@msg.acsalaska.com', now()), + (100060, 'Alltel Wireless', '%s@message.alltel.com', now()), + (100061, 'AT&T Wireless', '%s@txt.att.net', now()), + (100062, 'Bell Mobility (Canada)', '%s@txt.bell.ca', now()), + (100063, 'Boost Mobile', '%s@myboostmobile.com', now()), + (100064, 'Cellular One (Dobson)', '%s@mobile.celloneusa.com', now()), + (100065, 'Cingular (Postpaid)', '%s@cingularme.com', now()), + (100066, 'Centennial Wireless', '%s@cwemail.com', now()), + (100067, 'Cingular (GoPhone prepaid)', '%s@cingularme.com', now()), + (100068, 'Claro (Nicaragua)', '%s@ideasclaro-ca.com', now()), + (100069, 'Comcel', '%s@comcel.com.co', now()), + (100070, 'Cricket', '%s@sms.mycricket.com', now()), + (100071, 'CTI', '%s@sms.ctimovil.com.ar', now()), + (100072, 'Emtel (Mauritius)', '%s@emtelworld.net', now()), + (100073, 'Fido (Canada)', '%s@fido.ca', now()), + (100074, 'General Communications Inc.', '%s@msg.gci.net', now()), + (100075, 'Globalstar', '%s@msg.globalstarusa.com', now()), + (100076, 'Helio', '%s@myhelio.com', now()), + (100077, 'Illinois Valley Cellular', '%s@ivctext.com', now()), + (100078, 'i wireless', '%s.iws@iwspcs.net', now()), + (100079, 'Meteor (Ireland)', '%s@sms.mymeteor.ie', now()), + (100080, 'Mero Mobile (Nepal)', '%s@sms.spicenepal.com', now()), + (100081, 'MetroPCS', '%s@mymetropcs.com', now()), + (100082, 'Movicom', '%s@movimensaje.com.ar', now()), + (100083, 'Mobitel (Sri Lanka)', '%s@sms.mobitel.lk', now()), + (100084, 'Movistar (Colombia)', '%s@movistar.com.co', now()), + (100085, 'MTN (South Africa)', '%s@sms.co.za', now()), + (100086, 'MTS (Canada)', '%s@text.mtsmobility.com', now()), + (100087, 'Nextel (Argentina)', '%s@nextel.net.ar', now()), + (100088, 'Orange (Poland)', '%s@orange.pl', now()), + (100089, 'Personal (Argentina)', '%s@personal-net.com.ar', now()), + (100090, 'Plus GSM (Poland)', '%s@text.plusgsm.pl', now()), + (100091, 'President\'s Choice (Canada)', '%s@txt.bell.ca', now()), + (100092, 'Qwest', '%s@qwestmp.com', now()), + (100093, 'Rogers (Canada)', '%s@pcs.rogers.com', now()), + (100094, 'Sasktel (Canada)', '%s@sms.sasktel.com', now()), + (100095, 'Setar Mobile email (Aruba)', '%s@mas.aw', now()), + (100096, 'Solo Mobile', '%s@txt.bell.ca', now()), + (100097, 'Sprint (PCS)', '%s@messaging.sprintpcs.com', now()), + (100098, 'Sprint (Nextel)', '%s@page.nextel.com', now()), + (100099, 'Suncom', '%s@tms.suncom.com', now()), + (100100, 'T-Mobile', '%s@tmomail.net', now()), + (100101, 'T-Mobile (Austria)', '%s@sms.t-mobile.at', now()), + (100102, 'Telus Mobility (Canada)', '%s@msg.telus.com', now()), + (100103, 'Thumb Cellular', '%s@sms.thumbcellular.com', now()), + (100104, 'Tigo (Formerly Ola)', '%s@sms.tigo.com.co', now()), + (100105, 'Unicel', '%s@utext.com', now()), + (100106, 'US Cellular', '%s@email.uscc.net', now()), + (100107, 'Verizon', '%s@vtext.com', now()), + (100108, 'Virgin Mobile (Canada)', '%s@vmobile.ca', now()), + (100109, 'Virgin Mobile (USA)', '%s@vmobl.com', now()), + (100110, 'YCC', '%s@sms.ycc.ru', now()), + (100111, 'Orange (UK)', '%s@orange.net', now()), + (100112, 'Cincinnati Bell Wireless', '%s@gocbw.com', now()), + (100113, 'T-Mobile Germany', '%s@t-mobile-sms.de', now()), + (100114, 'Vodafone Germany', '%s@vodafone-sms.de', now()), + (100115, 'E-Plus', '%s@smsmail.eplus.de', now()); From e686ef042b0f4b3cd8243fa9536092039798837b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 14:16:58 -0500 Subject: [PATCH 058/189] Make ID of SMS Carrier not autoincrement Since we're doing fixed IDs for SMS Carrier, we change the definition so it's not auto increment. --- classes/laconica.ini | 3 ++- db/laconica.sql | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/classes/laconica.ini b/classes/laconica.ini index 255122a97f..19267f2688 100755 --- a/classes/laconica.ini +++ b/classes/laconica.ini @@ -292,7 +292,8 @@ created = 142 modified = 384 [sms_carrier__keys] -id = N +id = K +name = U [subscription] subscriber = 129 diff --git a/db/laconica.sql b/db/laconica.sql index 16f482134b..15f03a978f 100644 --- a/db/laconica.sql +++ b/db/laconica.sql @@ -31,7 +31,7 @@ create table avatar ( ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; create table sms_carrier ( - id integer auto_increment primary key comment 'primary key for SMS carrier', + id integer primary key comment 'primary key for SMS carrier', name varchar(64) unique key comment 'name of the carrier', email_pattern varchar(255) not null comment 'sprintf pattern for making an email address from a phone number', created datetime not null comment 'date this record was created', From b09eb06dae77be27e56d1573de0de7d393950ce5 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 16:04:43 -0500 Subject: [PATCH 059/189] Revert "Just discovered the PEAR_Error handling function in index.php. Duh." This reverts commit 616bdd43a921b2554d21b80af28ddb0fb6cb3c16. Kind of a long hard way to deal with a simple situation, so I'd prefer to just use the global handler. --- actions/emailsettings.php | 6 +++--- actions/register.php | 6 +++--- lib/action.php | 31 +++++++++++++------------------ 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/actions/emailsettings.php b/actions/emailsettings.php index 0a86aa66d1..c6c9834538 100644 --- a/actions/emailsettings.php +++ b/actions/emailsettings.php @@ -489,7 +489,7 @@ class EmailsettingsAction extends AccountSettingsAction } /** - * Handle old fashioned PEAR_Error msgs coming from DB_DataObject + * Check old fashioned PEAR_Error msgs coming from DB_DataObject * * In this case email don't exist in the DB yet, so DB_DataObject * throws an error. Overrided from Action. @@ -499,13 +499,13 @@ class EmailsettingsAction extends AccountSettingsAction * @return nothing */ - function handleError($error) { + function checkDB_DataObjectError($error) { if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { // Do nothing. } else { - parent::handleError($error); + parent::checkDB_DataObjectError($error); } } diff --git a/actions/register.php b/actions/register.php index aafb54ebbf..853bd0cf66 100644 --- a/actions/register.php +++ b/actions/register.php @@ -229,7 +229,7 @@ class RegisterAction extends Action } /** - * Handle old fashioned PEAR_Error msgs coming from DB_DataObject + * Check old fashioned PEAR_Error msgs coming from DB_DataObject * * In this case nickname and email don't exist in the DB yet, * so DB_DataObject throws an error. Overrided from Action. @@ -239,13 +239,13 @@ class RegisterAction extends Action * @return nothing */ - function handleError($error) { + function checkDB_DataObjectError($error) { if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { // Do nothing. } else { - parent::handleError($error); + parent::checkDB_DataObjectError($error); } } diff --git a/lib/action.php b/lib/action.php index 926fe93fb7..e3a8ef62c7 100644 --- a/lib/action.php +++ b/lib/action.php @@ -82,10 +82,16 @@ class Action extends HTMLOutputter // lawsuit */ function prepare($argarray) { - - // For PEAR_Errors comming from DB_DataObject + // This is for checking PEAR_Errors raised by DB_DataObject. + // Setting this to PEAR_ERROR_CALLBACK because setting + // to PEAR_ERROR_EXCEPTION does't work to allow PEAR_Errors + // to be handled as PHP5 exceptions, and PEAR_ERROR_RETURN + // does not cause DB_DataObject to actually return PEAR_Errors + // that can be checked with PEAR::isError() -- instead + // they just disappear into the ether, and can only be checked for + // after the fact. -- Zach PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, - array($this, "handleError")); + array($this, "checkDB_DataObjectError")); $this->args =& common_copy_args($argarray); return true; @@ -850,7 +856,7 @@ class Action extends HTMLOutputter // lawsuit } /** - * Handle old fashioned PEAR_Error msgs coming from DB_DataObject + * Check old fashioned PEAR_Error msgs coming from DB_DataObject * * Logs the DB_DataObject error. Override to do something else. * @@ -859,20 +865,9 @@ class Action extends HTMLOutputter // lawsuit * @return nothing */ - function handleError($error) { - - common_log(LOG_ERR, "PEAR error: " . $error->getMessage()); - $msg = sprintf(_('The database for %s isn\'t responding correctly, '. - 'so the site won\'t work properly. '. - 'The site admins probably know about the problem, '. - 'but you can contact them at %s to make sure. '. - 'Otherwise, wait a few minutes and try again.'), - common_config('site', 'name'), - common_config('site', 'email')); - - $dac = new DBErrorAction($msg, 500); - $dac->showPage(); - exit(-1); + function checkDB_DataObjectError($error) { + common_log(LOG_ERR, $error->getMessage()); + // XXX: throw an exception here? --Zach } /** From 3b5fd8fb6bbfa95efe3294ea77ae809dab071f99 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 16:05:37 -0500 Subject: [PATCH 060/189] Revert "Code to handle PEAR_Errors raised by DB_DataObject that are bubbling" This reverts commit 9f035e2847e0d119ca3d70e02df6f4fa73ca64c3. It's a lot of complicated stuff, and the global handler probably does fine. --- actions/emailsettings.php | 22 ---------------------- actions/register.php | 23 +---------------------- lib/action.php | 26 -------------------------- 3 files changed, 1 insertion(+), 70 deletions(-) diff --git a/actions/emailsettings.php b/actions/emailsettings.php index c6c9834538..b84acb2141 100644 --- a/actions/emailsettings.php +++ b/actions/emailsettings.php @@ -487,26 +487,4 @@ class EmailsettingsAction extends AccountSettingsAction return $other->id != $user->id; } } - - /** - * Check old fashioned PEAR_Error msgs coming from DB_DataObject - * - * In this case email don't exist in the DB yet, so DB_DataObject - * throws an error. Overrided from Action. - * - * @param PEAR_Error - * - * @return nothing - */ - - function checkDB_DataObjectError($error) { - if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { - - // Do nothing. - - } else { - parent::checkDB_DataObjectError($error); - } - } - } diff --git a/actions/register.php b/actions/register.php index 853bd0cf66..5d7a8ce690 100644 --- a/actions/register.php +++ b/actions/register.php @@ -223,31 +223,10 @@ class RegisterAction extends Action */ function nicknameExists($nickname) - { + { $user = User::staticGet('nickname', $nickname); return ($user !== false); } - - /** - * Check old fashioned PEAR_Error msgs coming from DB_DataObject - * - * In this case nickname and email don't exist in the DB yet, - * so DB_DataObject throws an error. Overrided from Action. - * - * @param PEAR_Error - * - * @return nothing - */ - - function checkDB_DataObjectError($error) { - if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { - - // Do nothing. - - } else { - parent::checkDB_DataObjectError($error); - } - } /** * Does the given email address already exist? diff --git a/lib/action.php b/lib/action.php index e3a8ef62c7..bd38bf79cc 100644 --- a/lib/action.php +++ b/lib/action.php @@ -82,17 +82,6 @@ class Action extends HTMLOutputter // lawsuit */ function prepare($argarray) { - // This is for checking PEAR_Errors raised by DB_DataObject. - // Setting this to PEAR_ERROR_CALLBACK because setting - // to PEAR_ERROR_EXCEPTION does't work to allow PEAR_Errors - // to be handled as PHP5 exceptions, and PEAR_ERROR_RETURN - // does not cause DB_DataObject to actually return PEAR_Errors - // that can be checked with PEAR::isError() -- instead - // they just disappear into the ether, and can only be checked for - // after the fact. -- Zach - PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, - array($this, "checkDB_DataObjectError")); - $this->args =& common_copy_args($argarray); return true; } @@ -855,21 +844,6 @@ class Action extends HTMLOutputter // lawsuit throw new ClientException($msg, $code); } - /** - * Check old fashioned PEAR_Error msgs coming from DB_DataObject - * - * Logs the DB_DataObject error. Override to do something else. - * - * @param PEAR_Error - * - * @return nothing - */ - - function checkDB_DataObjectError($error) { - common_log(LOG_ERR, $error->getMessage()); - // XXX: throw an exception here? --Zach - } - /** * Returns the current URL * From dac8d103e69deb63c7eafe16286f23a66034d967 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 12 Feb 2009 22:16:48 +0000 Subject: [PATCH 061/189] Hooks for: header, contentblock, footer --- EVENTS.txt | 19 +++++++++++++++++++ lib/action.php | 15 ++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index d9634325db..aed3dd9c5a 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -39,3 +39,22 @@ StartShowSections: Start the list of sections in the sidebar EndShowSections: End the list of sections in the sidebar - $action: the current action + +StartShowHeader: Showing before the header container +- $action: the current action + +EndShowHeader: Showing after the header container +- $action: the current action + +StartShowFooter: Showing before the footer container +- $action: the current action + +EndShowFooter: Showing after the footer container +- $action: the current action + +StartShowContentBlock: Showing before the content container +- $action: the current action + +EndShowContentBlock: Showing after the content container +- $action: the current action + diff --git a/lib/action.php b/lib/action.php index bd38bf79cc..602118cdf0 100644 --- a/lib/action.php +++ b/lib/action.php @@ -275,9 +275,15 @@ class Action extends HTMLOutputter // lawsuit { $this->elementStart('body', array('id' => $this->trimmed('action'))); $this->elementStart('div', array('id' => 'wrap')); - $this->showHeader(); + if (Event::handle('StartShowHeader', array($this))) { + $this->showHeader(); + Event::handle('EndShowHeader', array($this)); + } $this->showCore(); - $this->showFooter(); + if (Event::handle('StartShowFooter', array($this))) { + $this->showFooter(); + Event::handle('EndShowFooter', array($this)); + } $this->elementEnd('div'); $this->elementEnd('body'); } @@ -432,7 +438,10 @@ class Action extends HTMLOutputter // lawsuit { $this->elementStart('div', array('id' => 'core')); $this->showLocalNavBlock(); - $this->showContentBlock(); + if (Event::handle('StartShowContentBlock', array($this))) { + $this->showContentBlock(); + Event::handle('EndShowContentBlock', array($this)); + } $this->showAside(); $this->elementEnd('div'); } From f8e2ad0677d6eede93e3008452e36985ea999d74 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 12 Feb 2009 14:39:21 -0800 Subject: [PATCH 062/189] The fabled twitterqueuehandler --- lib/twitter.php | 2 +- lib/util.php | 2 +- scripts/startdaemons.sh | 3 +- scripts/stopdaemons.sh | 2 +- scripts/twitterqueuehandler.php | 71 +++++++++++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 4 deletions(-) create mode 100755 scripts/twitterqueuehandler.php diff --git a/lib/twitter.php b/lib/twitter.php index 72212c1851..deb6fd276b 100644 --- a/lib/twitter.php +++ b/lib/twitter.php @@ -231,7 +231,7 @@ function broadcast_twitter($notice) TWITTER_SERVICE); // XXX: Not sure WHERE to check whether a notice should go to - // Twitter. Should we even put in the queue if it's not? --Zach + // Twitter. Should we even put in the queue if it shouldn't? --Zach if (is_twitter_bound($notice, $flink)) { $fuser = $flink->getForeignUser(); diff --git a/lib/util.php b/lib/util.php index 3f4fae3a59..7923fe28a1 100644 --- a/lib/util.php +++ b/lib/util.php @@ -808,7 +808,7 @@ function common_broadcast_notice($notice, $remote=false) function common_enqueue_notice($notice) { - foreach (array('jabber', 'omb', 'sms', 'public') as $transport) { + foreach (array('jabber', 'omb', 'sms', 'public', 'twitter') as $transport) { $qi = new Queue_item(); $qi->notice_id = $notice->id; $qi->transport = $transport; diff --git a/scripts/startdaemons.sh b/scripts/startdaemons.sh index 685bd938fa..269036393d 100755 --- a/scripts/startdaemons.sh +++ b/scripts/startdaemons.sh @@ -23,7 +23,8 @@ DIR=`dirname $0` for f in xmppdaemon.php jabberqueuehandler.php publicqueuehandler.php \ - xmppconfirmhandler.php smsqueuehandler.php ombqueuehandler.php; do + xmppconfirmhandler.php smsqueuehandler.php ombqueuehandler.php \ + twitterqueuehandler.php; do echo -n "Starting $f..."; php $DIR/$f diff --git a/scripts/stopdaemons.sh b/scripts/stopdaemons.sh index 08e1d4714f..b69d296d34 100755 --- a/scripts/stopdaemons.sh +++ b/scripts/stopdaemons.sh @@ -24,7 +24,7 @@ SDIR=`dirname $0` DIR=`php $SDIR/getpiddir.php` for f in jabberhandler ombhandler publichandler smshandler \ - xmppconfirmhandler xmppdaemon; do + xmppconfirmhandler xmppdaemon twitterhandler ; do FILES="$DIR/$f.*.pid" for ff in "$FILES" ; do diff --git a/scripts/twitterqueuehandler.php b/scripts/twitterqueuehandler.php new file mode 100755 index 0000000000..7da4f1e20a --- /dev/null +++ b/scripts/twitterqueuehandler.php @@ -0,0 +1,71 @@ +#!/usr/bin/env php +. + */ + +# Abort if called from a web server +if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { + print "This script must be run from the command line\n"; + exit(); +} + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); +define('LACONICA', true); + +require_once(INSTALLDIR . '/lib/common.php'); +require_once(INSTALLDIR . '/lib/twitter.php'); +require_once(INSTALLDIR . '/lib/queuehandler.php'); + +set_error_handler('common_error_handler'); + +class TwitterQueueHandler extends QueueHandler +{ + + function transport() + { + return 'twitter'; + } + + function start() + { + $this->log(LOG_INFO, "INITIALIZE"); + return true; + } + + function handle_notice($notice) + { + return broadcast_twitter($notice); + } + + function finish() + { + } + +} + +ini_set("max_execution_time", "0"); +ini_set("max_input_time", "0"); +set_time_limit(0); + +mb_internal_encoding('UTF-8'); + +$id = ($argc > 1) ? $argv[1] : null; + +$handler = new TwitterQueueHandler($id); + +$handler->runOnce(); From 070d1a3f247dd20e855bda1486121545ec667e9a Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 13 Feb 2009 04:33:43 +0000 Subject: [PATCH 063/189] Hooks for: custom, laconica, UA specific stylesheets --- EVENTS.txt | 18 ++++++++++++++++++ lib/action.php | 43 ++++++++++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index aed3dd9c5a..16ca9ea066 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -15,6 +15,24 @@ StartSecondaryNav: Showing the secondary nav menu EndSecondaryNav: At the end of the secondary nav menu - $action: the current action +StartShowStyles: Showing Style links; good place to add UA style resets +- $action: the current action + +EndShowStyles: End showing Style links; good place to add custom styles +- $action: the current action + +StartShowLaconicaStyles: Showing Laconica Style links +- $action: the current action + +EndShowLaconicaStyles: End showing Laconica Style links; good place to add handheld or JavaScript dependant styles +- $action: the current action + +StartShowUAStyles: Showing custom UA Style links +- $action: the current action + +EndShowUAStyles: End showing custom UA Style links; good place to add user-agent (e.g., filter, -webkit, -moz) specific styles +- $action: the current action + StartShowScripts: Showing JavaScript links - $action: the current action diff --git a/lib/action.php b/lib/action.php index 602118cdf0..cd0db53999 100644 --- a/lib/action.php +++ b/lib/action.php @@ -151,25 +151,34 @@ class Action extends HTMLOutputter // lawsuit */ function showStylesheets() { - $this->element('link', array('rel' => 'stylesheet', - 'type' => 'text/css', - 'href' => theme_path('css/display.css', 'base') . '?version=' . LACONICA_VERSION, - 'media' => 'screen, projection, tv')); - $this->element('link', array('rel' => 'stylesheet', - 'type' => 'text/css', - 'href' => theme_path('css/display.css', null) . '?version=' . LACONICA_VERSION, - 'media' => 'screen, projection, tv')); - $this->comment('[if IE]>comment('[if lte IE '.$ver.']>element('link', array('rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => theme_path('css/display.css', 'base') . '?version=' . LACONICA_VERSION, + 'media' => 'screen, projection, tv')); + $this->element('link', array('rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => theme_path('css/display.css', null) . '?version=' . LACONICA_VERSION, + 'media' => 'screen, projection, tv')); + Event::handle('EndShowLaconicaStyles', array($this)); } + if (Event::handle('StartShowUAStyles', array($this))) { + $this->comment('[if IE]>comment('[if lte IE '.$ver.']>comment('[if IE]>comment('[if IE]> Date: Fri, 13 Feb 2009 05:42:00 +0000 Subject: [PATCH 064/189] We have a FacebookQueueHandler now. The update_facebook.php cron script is totally deprecated. --- lib/facebookutil.php | 96 +++++++++++++++++++++++- lib/util.php | 4 +- scripts/startdaemons.sh | 2 +- scripts/stopdaemons.sh | 2 +- scripts/update_facebook.php | 141 ------------------------------------ 5 files changed, 100 insertions(+), 145 deletions(-) delete mode 100755 scripts/update_facebook.php diff --git a/lib/facebookutil.php b/lib/facebookutil.php index beab513660..e2ad20d19e 100644 --- a/lib/facebookutil.php +++ b/lib/facebookutil.php @@ -36,7 +36,7 @@ function getFacebookNotices($since) // XXX: What should the limit be? //static function getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since) { - + return Notice::getStreamDirect($qry, 0, 1000, 0, 0, null, $since); } @@ -52,3 +52,97 @@ function updateProfileBox($facebook, $flink, $notice) { $fbaction->updateProfileBox($notice); } +function isFacebookBound($notice, $flink) { + + // If the user does not want to broadcast to Facebook, move along + if (!($flink->noticesync & FOREIGN_NOTICE_SEND == FOREIGN_NOTICE_SEND)) { + common_log(LOG_INFO, "Skipping notice $notice->id " . + 'because user has FOREIGN_NOTICE_SEND bit off.'); + return false; + } + + $success = false; + + // If it's not a reply, or if the user WANTS to send @-replies... + if (!preg_match('/@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) || + ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) { + + $success = true; + + // The two condition below are deal breakers: + + // Avoid a loop + if ($notice->source == 'Facebook') { + common_log(LOG_INFO, "Skipping notice $notice->id because its " . + 'source is Facebook.'); + $success = false; + } + + $facebook = getFacebook(); + $fbuid = $flink->foreign_id; + + try { + + // Check to see if the user has given the FB app status update perms + $result = $facebook->api_client-> + users_hasAppPermission('status_update', $fbuid); + + if ($result != 1) { + $user = $flink->getUser(); + $msg = "Can't send notice $notice->id to Facebook " . + "because user $user->nickname hasn't given the " . + 'Facebook app \'status_update\' permission.'; + common_log(LOG_INFO, $msg); + $success = false; + } + + } catch(FacebookRestClientException $e){ + common_log(LOG_ERROR, $e->getMessage()); + $success = false; + } + + } + + return $success; + +} + + +function facebookBroadcastNotice($notice) +{ + $facebook = getFacebook(); + $flink = Foreign_link::getByUserID($notice->profile_id, FACEBOOK_SERVICE); + $fbuid = $flink->foreign_id; + + if (isFacebookBound($notice, $flink)) { + + $status = null; + + // Get the status 'verb' (prefix) the user has set + try { + $prefix = $facebook->api_client-> + data_getUserPreference(FACEBOOK_NOTICE_PREFIX, $fbuid); + + $status = "$prefix $notice->content"; + + } catch(FacebookRestClientException $e) { + common_log(LOG_ERROR, $e->getMessage()); + return false; + } + + // Okay, we're good to go! + + try { + $facebook->api_client->users_setStatus($status, $fbuid, false, true); + updateProfileBox($facebook, $flink, $notice); + } catch(FacebookRestClientException $e) { + common_log(LOG_ERROR, $e->getMessage()); + return false; + + // Should we remove flink if this fails? + } + + } + + return true; +} diff --git a/lib/util.php b/lib/util.php index 031fd0da03..3eeb9fde3d 100644 --- a/lib/util.php +++ b/lib/util.php @@ -837,7 +837,7 @@ function common_broadcast_notice($notice, $remote=false) function common_enqueue_notice($notice) { - foreach (array('jabber', 'omb', 'sms', 'public', 'twitter') as $transport) { + foreach (array('jabber', 'omb', 'sms', 'public', 'twitter', 'facebook') as $transport) { $qi = new Queue_item(); $qi->notice_id = $notice->id; $qi->transport = $transport; @@ -891,6 +891,8 @@ function common_real_broadcast($notice, $remote=false) } } + // XXX: Do a real-time FB broadcast here? + // XXX: broadcast notices to other IM return $success; } diff --git a/scripts/startdaemons.sh b/scripts/startdaemons.sh index 269036393d..a3256966d5 100755 --- a/scripts/startdaemons.sh +++ b/scripts/startdaemons.sh @@ -24,7 +24,7 @@ DIR=`dirname $0` for f in xmppdaemon.php jabberqueuehandler.php publicqueuehandler.php \ xmppconfirmhandler.php smsqueuehandler.php ombqueuehandler.php \ - twitterqueuehandler.php; do + twitterqueuehandler.php facebookqueuehandler.php; do echo -n "Starting $f..."; php $DIR/$f diff --git a/scripts/stopdaemons.sh b/scripts/stopdaemons.sh index b69d296d34..fd4406d415 100755 --- a/scripts/stopdaemons.sh +++ b/scripts/stopdaemons.sh @@ -24,7 +24,7 @@ SDIR=`dirname $0` DIR=`php $SDIR/getpiddir.php` for f in jabberhandler ombhandler publichandler smshandler \ - xmppconfirmhandler xmppdaemon twitterhandler ; do + xmppconfirmhandler xmppdaemon twitterhandler facebookhandler ; do FILES="$DIR/$f.*.pid" for ff in "$FILES" ; do diff --git a/scripts/update_facebook.php b/scripts/update_facebook.php deleted file mode 100755 index 60e10417fa..0000000000 --- a/scripts/update_facebook.php +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env php -. - */ - -# Abort if called from a web server -if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { - print "This script must be run from the command line\n"; - exit(); -} - -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); -define('LACONICA', true); - -require_once INSTALLDIR . '/lib/common.php'; -require_once INSTALLDIR . '/lib/facebookutil.php'; - -// For storing the last run date-time -$last_updated_file = INSTALLDIR . '/scripts/facebook_last_updated'; - -// Lock file name -$lock_file = INSTALLDIR . '/scripts/update_facebook.lock'; - -// Make sure only one copy of the script is running at a time -$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?\n"); -} - -$facebook = getFacebook(); -$current_time = time(); -$since = getLastUpdated(); -updateLastUpdated($current_time); -$notice = getFacebookNotices($since); -$cnt = 0; - -while($notice->fetch()) { - - $flink = Foreign_link::getByUserID($notice->profile_id, FACEBOOK_SERVICE); - $user = $flink->getUser(); - $fbuid = $flink->foreign_id; - - if (!userCanUpdate($fbuid)) { - continue; - } - - $prefix = $facebook->api_client->data_getUserPreference(FACEBOOK_NOTICE_PREFIX, $fbuid); - $content = "$prefix $notice->content"; - - if (($flink->noticesync & FOREIGN_NOTICE_SEND) == FOREIGN_NOTICE_SEND) { - - // If it's not a reply, or if the user WANTS to send replies... - if (!preg_match('/@[a-zA-Z0-9_]{1,15}\b/u', $content) || - (($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) == FOREIGN_NOTICE_SEND_REPLY)) { - - // Avoid a Loop - if ($notice->source != 'Facebook') { - - try { - $facebook->api_client->users_setStatus($content, - $fbuid, false, true); - updateProfileBox($facebook, $flink, $notice); - $cnt++; - } catch(FacebookRestClientException $e) { - print "Couldn't sent notice $notice->id!\n"; - print $e->getMessage(); - - // Remove flink? - } - } - } - } -} - -if ($cnt > 0) { - print date('r', $current_time) . - ": Found $cnt new notices for Facebook since last run at " . - date('r', $since) . "\n"; -} - -fclose($lock_file); -exit(0); - - -function userCanUpdate($fbuid) { - - global $facebook; - - $result = false; - - try { - $result = $facebook->api_client->users_hasAppPermission('status_update', $fbuid); - } catch(FacebookRestClientException $e){ - print_r($e); - } - - return $result; -} - -function getLastUpdated(){ - global $last_updated_file, $current_time; - $last = $current_time; - - if (file_exists($last_updated_file) && - ($file = fopen($last_updated_file, 'r'))) { - $last = fgets($file); - } else { - print "$last_updated_file doesn't exit. Trying to create it...\n"; - $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); - return $last; -} - -function updateLastUpdated($time){ - global $last_updated_file; - $file = fopen($last_updated_file, 'w') or - die("Can't open $last_updated_file for writing!"); - fwrite($file, $time); - fclose($file); -} - From 47c5d508b36d4351f2bd94e7b36738fdf040b0aa Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 13 Feb 2009 10:47:22 -0500 Subject: [PATCH 065/189] remove debugging info from local_url --- lib/util.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/util.php b/lib/util.php index 3eeb9fde3d..b065c2d748 100644 --- a/lib/util.php +++ b/lib/util.php @@ -701,12 +701,8 @@ function common_relative_profile($sender, $nickname, $dt=null) function common_local_url($action, $args=null, $fragment=null) { - common_debug("Action = $action, args = " . (($args) ? '(' . implode($args, ',') . ')' : $args) . ", fragment = $fragment"); $r = Router::get(); - $start = microtime(); $path = $r->build($action, $args, $fragment); - $end = microtime(); - common_debug("Pathbuilding took " . ($end - $start)); if ($path) { } if (common_config('site','fancy')) { @@ -890,9 +886,9 @@ function common_real_broadcast($notice, $remote=false) common_log(LOG_ERR, 'Error in Twitter broadcast for notice ' . $notice->id); } } - - // XXX: Do a real-time FB broadcast here? - + + // XXX: Do a real-time FB broadcast here? + // XXX: broadcast notices to other IM return $success; } From 4ad5d55ecf65fb1c9f58211b37f8f111e9ca0c7b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 13 Feb 2009 10:52:26 -0500 Subject: [PATCH 066/189] Add events for filtering and logging new notices --- EVENTS.txt | 8 +++++++- actions/newnotice.php | 17 +++++++++------- classes/Notice.php | 47 ++++++++++++++++++++++++------------------- 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index 16ca9ea066..af0bee587c 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -30,7 +30,7 @@ EndShowLaconicaStyles: End showing Laconica Style links; good place to add hand StartShowUAStyles: Showing custom UA Style links - $action: the current action -EndShowUAStyles: End showing custom UA Style links; good place to add user-agent (e.g., filter, -webkit, -moz) specific styles +EndShowUAStyles: End showing custom UA Style links; good place to add user-agent (e.g., filter, -webkit, -moz) specific styles - $action: the current action StartShowScripts: Showing JavaScript links @@ -76,3 +76,9 @@ StartShowContentBlock: Showing before the content container EndShowContentBlock: Showing after the content container - $action: the current action +StartNoticeSave: before inserting a notice (good place for content filters) +- $notice: notice being saved (no ID or URI) + +EndNoticeSave: after inserting a notice and related code +- $notice: notice that was saved (with ID and URI) + diff --git a/actions/newnotice.php b/actions/newnotice.php index 5e7691f33d..9face96443 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -98,7 +98,12 @@ class NewnoticeAction extends Action return; } - $this->saveNewNotice(); + try { + $this->saveNewNotice(); + } catch (Exception $e) { + $this->showForm($e->getMessage()); + return; + } } else { $this->showForm(); } @@ -123,15 +128,13 @@ class NewnoticeAction extends Action $content = $this->trimmed('status_textarea'); if (!$content) { - $this->showForm(_('No content!')); - return; + $this->clientError(_('No content!')); } else { $content_shortened = common_shorten_links($content); if (mb_strlen($content_shortened) > 140) { - $this->showForm(_('That\'s too long. '. - 'Max notice size is 140 chars.')); - return; + $this->clientError(_('That\'s too long. '. + 'Max notice size is 140 chars.')); } } @@ -154,7 +157,7 @@ class NewnoticeAction extends Action ($replyto == 'false') ? null : $replyto); if (is_string($notice)) { - $this->showForm($notice); + $this->clientError($notice); return; } diff --git a/classes/Notice.php b/classes/Notice.php index 3299883686..6db59c96ef 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -154,33 +154,38 @@ class Notice extends Memcached_DataObject $notice->source = $source; $notice->uri = $uri; - $id = $notice->insert(); + if (Event::handle('StartNoticeSave', array(&$notice))) { - if (!$id) { - common_log_db_error($notice, 'INSERT', __FILE__); - return _('Problem saving notice.'); - } + $id = $notice->insert(); - # Update the URI after the notice is in the database - if (!$uri) { - $orig = clone($notice); - $notice->uri = common_notice_uri($notice); - - if (!$notice->update($orig)) { - common_log_db_error($notice, 'UPDATE', __FILE__); + if (!$id) { + common_log_db_error($notice, 'INSERT', __FILE__); return _('Problem saving notice.'); } + + # Update the URI after the notice is in the database + if (!$uri) { + $orig = clone($notice); + $notice->uri = common_notice_uri($notice); + + if (!$notice->update($orig)) { + common_log_db_error($notice, 'UPDATE', __FILE__); + return _('Problem saving notice.'); + } + } + + # XXX: do we need to change this for remote users? + + $notice->saveReplies(); + $notice->saveTags(); + $notice->saveGroups(); + + $notice->addToInboxes(); + $notice->query('COMMIT'); + + Event::handle('EndNoticeSave', array($notice)); } - # XXX: do we need to change this for remote users? - - $notice->saveReplies(); - $notice->saveTags(); - $notice->saveGroups(); - - $notice->addToInboxes(); - $notice->query('COMMIT'); - # Clear the cache for subscribed users, so they'll update at next request # XXX: someone clever could prepend instead of clearing the cache From 02184df7c8fd40ee9d1b134fd318e0c6f444ae01 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 13 Feb 2009 10:08:11 -0800 Subject: [PATCH 067/189] Fixed arguments to syslog (LOG_ERR, not LOG_ERROR) and removed unused function --- lib/facebookutil.php | 21 +++------------------ lib/jabber.php | 2 +- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/lib/facebookutil.php b/lib/facebookutil.php index e2ad20d19e..ec39872732 100644 --- a/lib/facebookutil.php +++ b/lib/facebookutil.php @@ -25,21 +25,6 @@ define("FACEBOOK_SERVICE", 2); // Facebook is foreign_service ID 2 define("FACEBOOK_NOTICE_PREFIX", 1); define("FACEBOOK_PROMPTED_UPDATE_PREF", 2); -// Gets all the notices from users with a Facebook link since a given ID -function getFacebookNotices($since) -{ - $qry = 'SELECT notice.* ' . - 'FROM notice ' . - 'JOIN foreign_link ' . - 'WHERE notice.profile_id = foreign_link.user_id ' . - 'AND foreign_link.service = 2'; - - // XXX: What should the limit be? - //static function getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since) { - - return Notice::getStreamDirect($qry, 0, 1000, 0, 0, null, $since); -} - function getFacebook() { $apikey = common_config('facebook', 'apikey'); @@ -97,7 +82,7 @@ function isFacebookBound($notice, $flink) { } } catch(FacebookRestClientException $e){ - common_log(LOG_ERROR, $e->getMessage()); + common_log(LOG_ERR, $e->getMessage()); $success = false; } @@ -126,7 +111,7 @@ function facebookBroadcastNotice($notice) $status = "$prefix $notice->content"; } catch(FacebookRestClientException $e) { - common_log(LOG_ERROR, $e->getMessage()); + common_log(LOG_ERR, $e->getMessage()); return false; } @@ -136,7 +121,7 @@ function facebookBroadcastNotice($notice) $facebook->api_client->users_setStatus($status, $fbuid, false, true); updateProfileBox($facebook, $flink, $notice); } catch(FacebookRestClientException $e) { - common_log(LOG_ERROR, $e->getMessage()); + common_log(LOG_ERR, $e->getMessage()); return false; // Should we remove flink if this fails? diff --git a/lib/jabber.php b/lib/jabber.php index b385d3c5cc..3fbb3e1ab9 100644 --- a/lib/jabber.php +++ b/lib/jabber.php @@ -114,7 +114,7 @@ function jabber_connect($resource=null) try { $conn->connect(true); // true = persistent connection } catch (XMPPHP_Exception $e) { - common_log(LOG_ERROR, $e->getMessage()); + common_log(LOG_ERR, $e->getMessage()); return false; } From b9fc7334a8ecfbe1c0ec6c3d5451aa82827b50a1 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 14 Feb 2009 17:48:08 -0500 Subject: [PATCH 068/189] Fix the More... link for popular notices section --- lib/popularnoticesection.php | 5 +++++ lib/section.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/popularnoticesection.php b/lib/popularnoticesection.php index 5734d80018..5380563b9e 100644 --- a/lib/popularnoticesection.php +++ b/lib/popularnoticesection.php @@ -80,4 +80,9 @@ class PopularNoticeSection extends NoticeSection { return 'popular_notices'; } + + function moreUrl() + { + return common_local_url('favorited'); + } } diff --git a/lib/section.php b/lib/section.php index 0c32ddcf84..d145750862 100644 --- a/lib/section.php +++ b/lib/section.php @@ -103,6 +103,6 @@ class Section extends Widget function moreTitle() { - return null; + return _('More...'); } } From 3db9c134a052b906c8c9cff946e63ee28457ce1b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 14 Feb 2009 17:53:11 -0500 Subject: [PATCH 069/189] Fix More... URL for featured user section --- lib/featureduserssection.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/featureduserssection.php b/lib/featureduserssection.php index 2935d83630..aed94b1a55 100644 --- a/lib/featureduserssection.php +++ b/lib/featureduserssection.php @@ -86,4 +86,9 @@ class FeaturedUsersSection extends ProfileSection { return 'featured_users'; } + + function moreUrl() + { + return common_local_url('featured'); + } } From da2348fbbe27facf4cfbf3fad800b3ccf98b4136 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 14 Feb 2009 21:55:25 -0500 Subject: [PATCH 070/189] Optionally ignore some notice sources for public page We optionally ignore some notice sources from the public page. Typically these are automatic notice sources like twitterfeed that don't usually represent the community on the site very well. --- classes/Notice.php | 4 +++- config.php.sample | 8 ++++++++ lib/common.php | 3 ++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 6db59c96ef..b8cd2bd7f2 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -136,10 +136,12 @@ class Notice extends Memcached_DataObject $notice->profile_id = $profile_id; $blacklist = common_config('public', 'blacklist'); + $autosource = common_config('public', 'autosource'); # Blacklisted are non-false, but not 1, either - if ($blacklist && in_array($profile_id, $blacklist)) { + if (($blacklist && in_array($profile_id, $blacklist)) || + ($source && $autosource && in_array($source, $autosource))) { $notice->is_local = -1; } else { $notice->is_local = $is_local; diff --git a/config.php.sample b/config.php.sample index a2c5801f45..3fa898e1be 100644 --- a/config.php.sample +++ b/config.php.sample @@ -107,6 +107,14 @@ $config['sphinx']['port'] = 3312; #$config['public']['blacklist'][] = 123; #$config['public']['blacklist'][] = 2307; +#Mark certain notice sources as automatic and thus not +#appropriate for public feed +#$config['public]['autosource'][] = 'twitterfeed'; +#$config['public]['autosource'][] = 'rssdent'; +#$config['public]['autosource'][] = 'Ping.Fm'; +#$config['public]['autosource'][] = 'HelloTxt'; +#$config['public]['autosource'][] = 'Updating.Me'; + #Do notice broadcasts offline #If you use this, you must run the six offline daemons in the #background. See the README for details. diff --git a/lib/common.php b/lib/common.php index 7bfd14c429..4fc749ca06 100644 --- a/lib/common.php +++ b/lib/common.php @@ -106,7 +106,8 @@ $config = array('server' => null), 'public' => array('localonly' => true, - 'blacklist' => array()), + 'blacklist' => array(), + 'autosource' => array()), 'theme' => array('server' => null), 'throttle' => From ed964ea980be63999860aaa5f39274768eca5d59 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 14 Feb 2009 21:55:25 -0500 Subject: [PATCH 071/189] Optionally ignore some notice sources for public page We optionally ignore some notice sources from the public page. Typically these are automatic notice sources like twitterfeed that don't usually represent the community on the site very well. --- classes/Notice.php | 4 +++- config.php.sample | 8 ++++++++ lib/common.php | 3 ++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 3299883686..cc7de63c20 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -136,10 +136,12 @@ class Notice extends Memcached_DataObject $notice->profile_id = $profile_id; $blacklist = common_config('public', 'blacklist'); + $autosource = common_config('public', 'autosource'); # Blacklisted are non-false, but not 1, either - if ($blacklist && in_array($profile_id, $blacklist)) { + if (($blacklist && in_array($profile_id, $blacklist)) || + ($source && $autosource && in_array($source, $autosource))) { $notice->is_local = -1; } else { $notice->is_local = $is_local; diff --git a/config.php.sample b/config.php.sample index db1a216635..efb5aa4432 100644 --- a/config.php.sample +++ b/config.php.sample @@ -107,6 +107,14 @@ $config['sphinx']['port'] = 3312; #$config['public']['blacklist'][] = 123; #$config['public']['blacklist'][] = 2307; +#Mark certain notice sources as automatic and thus not +#appropriate for public feed +#$config['public]['autosource'][] = 'twitterfeed'; +#$config['public]['autosource'][] = 'rssdent'; +#$config['public]['autosource'][] = 'Ping.Fm'; +#$config['public]['autosource'][] = 'HelloTxt'; +#$config['public]['autosource'][] = 'Updating.Me'; + #Do notice broadcasts offline #If you use this, you must run the six offline daemons in the #background. See the README for details. diff --git a/lib/common.php b/lib/common.php index 5b4e3c40c8..bf49fff6b4 100644 --- a/lib/common.php +++ b/lib/common.php @@ -100,7 +100,8 @@ $config = array('server' => null), 'public' => array('localonly' => true, - 'blacklist' => array()), + 'blacklist' => array(), + 'autosource' => array()), 'theme' => array('server' => null), 'throttle' => From faf82eebfebf24c7a8aa62b517e15c0a1ad71954 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 15 Feb 2009 22:30:39 +0000 Subject: [PATCH 072/189] Added a mention of config setting to use a custom theme --- theme/readme.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/theme/readme.txt b/theme/readme.txt index 335801385d..4998b3c988 100644 --- a/theme/readme.txt +++ b/theme/readme.txt @@ -31,3 +31,6 @@ cp -r ./default ./mytheme nano ./mytheme/css/display.css 3. Search and replace a colour or a path to the background image of your choice. + +4. Set /config.php to load 'mytheme': +$config['site']['theme'] = 'mytheme'; From 9d81cef5cc2a0a197a0223206ba3d9a687065886 Mon Sep 17 00:00:00 2001 From: Meitar Moscovitz Date: Mon, 16 Feb 2009 15:45:18 +1100 Subject: [PATCH 073/189] Add framebusting JavaScript to help avoid clickjacking attacks. --- lib/action.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/action.php b/lib/action.php index cd0db53999..48d5821a17 100644 --- a/lib/action.php +++ b/lib/action.php @@ -205,6 +205,9 @@ class Action extends HTMLOutputter // lawsuit $this->element('script', array('type' => 'text/javascript', 'src' => common_path('js/util.js?version='.LACONICA_VERSION)), ' '); + // Frame-busting code to avoid clickjacking attacks. + $this->element('script', array('type' => 'text/javascript'), + 'if (window.top !== window.self) { window.top.location.href = window.self.location.href; }'); Event::handle('EndShowLaconicaScripts', array($this)); } Event::handle('EndShowScripts', array($this)); From 9c9b6790ce78296c0b182f03b5f6f2c035e43a7c Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Mon, 16 Feb 2009 17:46:24 +0000 Subject: [PATCH 074/189] trac #201 Add flowplayer to enable multimedia playback capability. --- bin/flowplayer-3.0.5.swf | Bin 0 -> 92317 bytes bin/flowplayer.audio-3.0.3.swf | Bin 0 -> 2756 bytes bin/flowplayer.controls-3.0.3.swf | Bin 0 -> 15977 bytes js/flowplayer-3.0.5.min.js | 24 ++++++++++++++++++++++++ js/jquery.simplemodal-1.2.2.pack.js | 8 ++++++++ js/video.js | 9 +++++++++ lib/action.php | 26 ++++++++++++++++++++++++++ lib/util.php | 6 ++++++ theme/base/css/modal.css | 22 ++++++++++++++++++++++ theme/base/css/modal_ie.css | 16 ++++++++++++++++ theme/base/images/x.png | Bin 0 -> 1066 bytes 11 files changed, 111 insertions(+) create mode 100644 bin/flowplayer-3.0.5.swf create mode 100644 bin/flowplayer.audio-3.0.3.swf create mode 100644 bin/flowplayer.controls-3.0.3.swf create mode 100644 js/flowplayer-3.0.5.min.js create mode 100644 js/jquery.simplemodal-1.2.2.pack.js create mode 100644 js/video.js create mode 100644 theme/base/css/modal.css create mode 100644 theme/base/css/modal_ie.css create mode 100644 theme/base/images/x.png diff --git a/bin/flowplayer-3.0.5.swf b/bin/flowplayer-3.0.5.swf new file mode 100644 index 0000000000000000000000000000000000000000..05b64a032b96e85ad7fc694495455710e2a1f1b7 GIT binary patch literal 92317 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 Ay8r+H literal 0 HcmV?d00001 diff --git a/bin/flowplayer.audio-3.0.3.swf b/bin/flowplayer.audio-3.0.3.swf new file mode 100644 index 0000000000000000000000000000000000000000..ef85f1bff0d4f179bc47f9a6404dc8ba75716895 GIT binary patch literal 2756 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 X|LvcrW5=aS{Pk7(YlQzFyXx;XOtq1% literal 0 HcmV?d00001 diff --git a/js/flowplayer-3.0.5.min.js b/js/flowplayer-3.0.5.min.js new file mode 100644 index 0000000000..b1c33150ac --- /dev/null +++ b/js/flowplayer-3.0.5.min.js @@ -0,0 +1,24 @@ +/** + * flowplayer.js 3.0.5. The Flowplayer API + * + * Copyright 2009 Flowplayer Oy + * + * This file is part of Flowplayer. + * + * Flowplayer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Flowplayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Flowplayer. If not, see . + * + * Version: 3.0.5 - Tue Feb 03 2009 13:14:17 GMT-0000 (GMT+00:00) + */ +(function(){function log(args){console.log("$f.fireEvent",[].slice.call(args));}function clone(obj){if(!obj||typeof obj!='object'){return obj;}var temp=new obj.constructor();for(var key in obj){if(obj.hasOwnProperty(key)){temp[key]=clone(obj[key]);}}return temp;}function each(obj,fn){if(!obj){return;}var name,i=0,length=obj.length;if(length===undefined){for(name in obj){if(fn.call(obj[name],name,obj[name])===false){break;}}}else{for(var value=obj[0];i1){var swf=arguments[1];var conf=(arguments.length==3)?arguments[2]:{};if(typeof arg=='string'){if(arg.indexOf(".")!=-1){var instances=[];each(select(arg),function(){instances.push(new Player(this,clone(swf),clone(conf)));});return new Iterator(instances);}else{var node=el(arg);return new Player(node!==null?node:arg,swf,conf);}}else if(arg){return new Player(arg,swf,conf);}}return null;};extend(window.$f,{fireEvent:function(id,evt,a0,a1,a2){var p=$f(id);return p?p._fireEvent(evt,a0,a1,a2):null;},addPlugin:function(name,fn){Player.prototype[name]=fn;return $f;},each:each,extend:extend});if(document.all){window.onbeforeunload=function(){$f("*").each(function(){if(this.isLoaded()){this.close();}});};}if(typeof jQuery=='function'){jQuery.prototype.flowplayer=function(params,conf){if(!arguments.length||typeof arguments[0]=='number'){var arr=[];this.each(function(){var p=$f(this);if(p){arr.push(p);}});return arguments.length?arr[arguments[0]]:new Iterator(arr);}return this.each(function(){$f(this,clone(params),conf?clone(conf):{});});};}})();(function(){var jQ=typeof jQuery=='function';function isDomReady(){if(domReady.done){return false;}var d=document;if(d&&d.getElementsByTagName&&d.getElementById&&d.body){clearInterval(domReady.timer);domReady.timer=null;for(var i=0;i';}var e=extend({},p);e.width=e.height=e.id=e.w3c=e.src=null;for(var k in e){if(e[k]!==null){html+='';}}var vars="";if(c){for(var key in c){if(c[key]!==null){vars+=key+'='+(typeof c[key]=='object'?asString(c[key]):c[key])+'&';}}vars=vars.substring(0,vars.length-1);html+='';}html+="";return html;}function Flash(root,opts,flashvars){var version=flashembed.getVersion();extend(this,{getContainer:function(){return root;},getConf:function(){return conf;},getVersion:function(){return version;},getFlashvars:function(){return flashvars;},getApi:function(){return root.firstChild;},getHTML:function(){return getHTML(opts,flashvars);}});var required=opts.version;var express=opts.expressInstall;var ok=!required||flashembed.isSupported(required);if(ok){opts.onFail=opts.version=opts.expressInstall=null;root.innerHTML=getHTML(opts,flashvars);}else if(required&&express&&flashembed.isSupported([6,65])){extend(opts,{src:express});flashvars={MMredirectURL:location.href,MMplayerType:'PlugIn',MMdoctitle:document.title};root.innerHTML=getHTML(opts,flashvars);}else{if(root.innerHTML.replace(/\s/g,'')!==''){}else{root.innerHTML="

Flash version "+required+" or greater is required

"+"

"+(version[0]>0?"Your version is "+version:"You have no flash plugin installed")+"

"+"
";}}if(!ok&&opts.onFail){var ret=opts.onFail.call(this);if(typeof ret=='string'){root.innerHTML=ret;}}}window.flashembed=function(root,conf,flashvars){if(typeof root=='string'){var el=document.getElementById(root);if(el){root=el;}else{domReady(function(){flashembed(root,conf,flashvars);});return;}}if(!root){return;}var opts={width:'100%',height:'100%',allowfullscreen:true,allowscriptaccess:'always',quality:'high',version:null,onFail:null,expressInstall:null,w3c:false};if(typeof conf=='string'){conf={src:conf};}extend(opts,conf);return new Flash(root,opts,flashvars);};extend(window.flashembed,{getVersion:function(){var version=[0,0];if(navigator.plugins&&typeof navigator.plugins["Shockwave Flash"]=="object"){var _d=navigator.plugins["Shockwave Flash"].description;if(typeof _d!="undefined"){_d=_d.replace(/^.*\s+(\S+\s+\S+$)/,"$1");var _m=parseInt(_d.replace(/^(.*)\..*$/,"$1"),10);var _r=/r/.test(_d)?parseInt(_d.replace(/^.*r(.*)$/,"$1"),10):0;version=[_m,_r];}}else if(window.ActiveXObject){try{var _a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");}catch(e){try{_a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");version=[6,0];_a.AllowScriptAccess="always";}catch(ee){if(version[0]==6){return;}}try{_a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");}catch(eee){}}if(typeof _a=="object"){_d=_a.GetVariable("$version");if(typeof _d!="undefined"){_d=_d.replace(/^\S+\s+(.*)$/,"$1").split(",");version=[parseInt(_d[0],10),parseInt(_d[2],10)];}}}return version;},isSupported:function(version){var now=flashembed.getVersion();var ret=(now[0]>version[0])||(now[0]==version[0]&&now[1]>=version[1]);return ret;},domReady:domReady,asString:asString,getHTML:getHTML});if(jQ){jQuery.prototype.flashembed=function(conf,flashvars){return this.each(function(){flashembed(this,conf,flashvars);});};}})(); \ No newline at end of file diff --git a/js/jquery.simplemodal-1.2.2.pack.js b/js/jquery.simplemodal-1.2.2.pack.js new file mode 100644 index 0000000000..b5ad5c23a3 --- /dev/null +++ b/js/jquery.simplemodal-1.2.2.pack.js @@ -0,0 +1,8 @@ +/* + * SimpleModal 1.2.2 - jQuery Plugin + * http://www.ericmmartin.com/projects/simplemodal/ + * Copyright (c) 2008 Eric Martin + * Dual licensed under the MIT and GPL licenses + * Revision: $Id: jquery.simplemodal.js 181 2008-12-16 16:51:44Z emartin24 $ + */ +eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(g($){m f=$.Q.1Q&&1a($.Q.1D)==6&&!10[\'2g\'],1f=$.Q.1Q&&!$.2a,w=[];$.y=g(a,b){I $.y.12.1n(a,b)};$.y.D=g(){$.y.12.D()};$.1P.y=g(a){I $.y.12.1n(3,a)};$.y.1O={V:29,1J:\'r-H\',1B:{},1z:\'r-n\',20:{},1Z:{},v:2t,D:1o,1T:\'\',X:\'r-D\',l:F,1g:K,1e:F,1d:F,1c:F};$.y.12={7:F,4:{},1n:g(a,b){8(3.4.j){I K}3.7=$.U({},$.y.1O,b);3.v=3.7.v;3.1w=K;8(J a==\'27\'){a=a 25 1A?a:$(a);8(a.1v().1v().23()>0){3.4.T=a.1v();8(!3.7.1g){3.4.21=a.2x(1o)}}}q 8(J a==\'2w\'||J a==\'1r\'){a=$(\'<1q/>\').2s(a)}q{2r(\'2q 2p: 2o j 2l: \'+J a);I K}3.4.j=a.11(\'r-j\').E(3.7.1Z);a=F;3.1S();3.1R();8($.1m(3.7.1d)){3.7.1d.1l(3,[3.4])}I 3},1S:g(){w=3.1k();8(f){3.4.x=$(\'\').E($.U(3.7.2b,{1j:\'1i\',V:0,l:\'1h\',A:w[0],z:w[1],v:3.7.v,L:0,B:0})).O(\'u\')}3.4.H=$(\'<1q/>\').1N(\'1M\',3.7.1J).11(\'r-H\').E($.U(3.7.1B,{1j:\'1i\',V:3.7.V/1b,A:w[0],z:w[1],l:\'1h\',B:0,L:0,v:3.7.v+1})).O(\'u\');3.4.n=$(\'<1q/>\').1N(\'1M\',3.7.1z).11(\'r-n\').E($.U(3.7.20,{1j:\'1i\',l:\'1h\',v:3.7.v+2})).1K(3.7.D?$(3.7.1T).11(3.7.X):\'\').O(\'u\');3.19();8(f||1f){3.18()}3.4.n.1K(3.4.j.1I())},1H:g(){m a=3;$(\'.\'+3.7.X).1G(\'1L.r\',g(e){e.28();a.D()});$(10).1G(\'1F.r\',g(){w=a.1k();a.19();8(f||1f){a.18()}q{a.4.x&&a.4.x.E({A:w[0],z:w[1]});a.4.H.E({A:w[0],z:w[1]})}})},1E:g(){$(\'.\'+3.7.X).1C(\'1L.r\');$(10).1C(\'1F.r\')},18:g(){m p=3.7.l;$.26([3.4.x||F,3.4.H,3.4.n],g(i,e){8(e){m a=\'k.u.17\',N=\'k.u.1W\',16=\'k.u.24\',S=\'k.u.1y\',R=\'k.u.1x\',15=\'k.u.22\',1t=\'k.P.17\',1s=\'k.P.1W\',C=\'k.P.1y\',G=\'k.P.1x\',s=e[0].2v;s.l=\'2u\';8(i<2){s.14(\'A\');s.14(\'z\');s.Z(\'A\',\'\'+16+\' > \'+a+\' ? \'+16+\' : \'+a+\' + "o"\');s.Z(\'z\',\'\'+15+\' > \'+N+\' ? \'+15+\' : \'+N+\' + "o"\')}q{m b,W;8(p&&p.1Y==1X){8(p[0]){m c=J p[0]==\'1r\'?p[0].1V():p[0].13(/o/,\'\');b=c.1U(\'%\')==-1?c+\' + (t = \'+G+\' ? \'+G+\' : \'+R+\') + "o"\':1a(c.13(/%/,\'\'))+\' * ((\'+1t+\' || \'+a+\') / 1b) + (t = \'+G+\' ? \'+G+\' : \'+R+\') + "o"\'}8(p[1]){m d=J p[1]==\'1r\'?p[1].1V():p[1].13(/o/,\'\');W=d.1U(\'%\')==-1?d+\' + (t = \'+C+\' ? \'+C+\' : \'+S+\') + "o"\':1a(d.13(/%/,\'\'))+\' * ((\'+1s+\' || \'+N+\') / 1b) + (t = \'+C+\' ? \'+C+\' : \'+S+\') + "o"\'}}q{b=\'(\'+1t+\' || \'+a+\') / 2 - (3.2n / 2) + (t = \'+G+\' ? \'+G+\' : \'+R+\') + "o"\';W=\'(\'+1s+\' || \'+N+\') / 2 - (3.2m / 2) + (t = \'+C+\' ? \'+C+\' : \'+S+\') + "o"\'}s.14(\'L\');s.14(\'B\');s.Z(\'L\',b);s.Z(\'B\',W)}}})},1k:g(){m a=$(10);m h=$.Q.2k&&$.Q.1D>\'9.5\'&&$.1P.2i<=\'1.2.6\'?k.P[\'17\']:a.A();I[h,a.z()]},19:g(){m a,B,1u=(w[0]/2)-((3.4.n.A()||3.4.j.A())/2),1p=(w[1]/2)-((3.4.n.z()||3.4.j.z())/2);8(3.7.l&&3.7.l.1Y==1X){a=3.7.l[0]||1u;B=3.7.l[1]||1p}q{a=1u;B=1p}3.4.n.E({B:B,L:a})},1R:g(){3.4.x&&3.4.x.Y();8($.1m(3.7.1e)){3.7.1e.1l(3,[3.4])}q{3.4.H.Y();3.4.n.Y();3.4.j.Y()}3.1H()},D:g(){8(!3.4.j){I K}8($.1m(3.7.1c)&&!3.1w){3.1w=1o;3.7.1c.1l(3,[3.4])}q{8(3.4.T){8(3.7.1g){3.4.j.1I().O(3.4.T)}q{3.4.j.M();3.4.21.O(3.4.T)}}q{3.4.j.M()}3.4.n.M();3.4.H.M();3.4.x&&3.4.x.M();3.4={}}3.1E()}}})(1A);',62,158,'|||this|dialog|||opts|if||||||||function|||data|document|position|var|container|px||else|simplemodal|||body|zIndex||iframe|modal|width|height|left|sl|close|css|null|st|overlay|return|typeof|false|top|remove|bcw|appendTo|documentElement|browser|bst|bsl|parentNode|extend|opacity|le|closeClass|show|setExpression|window|addClass|impl|replace|removeExpression|bsw|bsh|clientHeight|fixIE|setPosition|parseInt|100|onClose|onShow|onOpen|ieQuirks|persist|fixed|none|display|getDimensions|apply|isFunction|init|true|vCenter|div|number|cw|ch|hCenter|parent|occb|scrollTop|scrollLeft|containerId|jQuery|overlayCss|unbind|version|unbindEvents|resize|bind|bindEvents|hide|overlayId|append|click|id|attr|defaults|fn|msie|open|create|closeHTML|indexOf|toString|clientWidth|Array|constructor|dataCss|containerCss|orig|scrollWidth|size|scrollHeight|instanceof|each|object|preventDefault|50|boxModel|iframeCss|javascript|src|Close|title|XMLHttpRequest|modalCloseImg|jquery|class|opera|type|offsetWidth|offsetHeight|Unsupported|Error|SimpleModal|alert|html|1000|absolute|style|string|clone'.split('|'),0,{})) \ No newline at end of file diff --git a/js/video.js b/js/video.js new file mode 100644 index 0000000000..936a6312e3 --- /dev/null +++ b/js/video.js @@ -0,0 +1,9 @@ +$('document').ready(function() { + $('a.media, a.mediamp3').append(' [PLAY]'); + $('a.mediamp3').html('').css('display', 'block').css('width', '224px').css('height','24px').flowplayer('../bin/flowplayer-3.0.5.swf'); + $('a.media').click(function() { + $('').attr('href', $(this).attr('href')).flowplayer('../bin/flowplayer-3.0.5.swf').modal({'closeHTML':''}); + return false; + }); +}); + diff --git a/lib/action.php b/lib/action.php index cd0db53999..79e8c95471 100644 --- a/lib/action.php +++ b/lib/action.php @@ -153,10 +153,17 @@ class Action extends HTMLOutputter // lawsuit { if (Event::handle('StartShowStyles', array($this))) { if (Event::handle('StartShowLaconicaStyles', array($this))) { + $this->element('link', array('rel' => 'stylesheet', 'type' => 'text/css', 'href' => theme_path('css/display.css', 'base') . '?version=' . LACONICA_VERSION, 'media' => 'screen, projection, tv')); + + + $this->element('link', array('rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => theme_path('css/modal.css', 'base') . '?version=' . LACONICA_VERSION, + 'media' => 'screen, projection, tv')); $this->element('link', array('rel' => 'stylesheet', 'type' => 'text/css', 'href' => theme_path('css/display.css', null) . '?version=' . LACONICA_VERSION, @@ -196,6 +203,13 @@ class Action extends HTMLOutputter // lawsuit $this->element('script', array('type' => 'text/javascript', 'src' => common_path('js/jquery.form.js')), ' '); + + + $this->element('script', array('type' => 'text/javascript', + 'src' => common_path('js/jquery.simplemodal-1.2.2.pack.js')), + ' '); + + Event::handle('EndShowJQueryScripts', array($this)); } if (Event::handle('StartShowLaconicaScripts', array($this))) { @@ -205,6 +219,18 @@ class Action extends HTMLOutputter // lawsuit $this->element('script', array('type' => 'text/javascript', 'src' => common_path('js/util.js?version='.LACONICA_VERSION)), ' '); + + + $this->element('script', array('type' => 'text/javascript', + 'src' => common_path('js/flowplayer-3.0.5.min.js')), + ' '); + + $this->element('script', array('type' => 'text/javascript', + 'src' => common_path('js/video.js')), + ' '); + + + Event::handle('EndShowLaconicaScripts', array($this)); } Event::handle('EndShowScripts', array($this)); diff --git a/lib/util.php b/lib/util.php index b065c2d748..094b2750ca 100644 --- a/lib/util.php +++ b/lib/util.php @@ -474,11 +474,17 @@ function common_replace_urls_callback($text, $callback) { function common_linkify($url) { // It comes in special'd, so we unspecial it before passing to the stringifying // functions + $ext = pathinfo($url, PATHINFO_EXTENSION); $url = htmlspecialchars_decode($url); + $video_ext = array('mp4', 'flv', 'avi', 'mpg', 'mp3', 'ogg'); $display = $url; $url = (!preg_match('#^([a-z]+://|(mailto|aim|tel):)#i', $url)) ? 'http://'.$url : $url; $attrs = array('href' => $url, 'rel' => 'external'); + + if (in_array($ext, $video_ext)) { + $attrs['class'] = 'media'; + } if ($longurl = common_longurl($url)) { $attrs['title'] = $longurl; diff --git a/theme/base/css/modal.css b/theme/base/css/modal.css new file mode 100644 index 0000000000..985e4adfa5 --- /dev/null +++ b/theme/base/css/modal.css @@ -0,0 +1,22 @@ +/* + * SimpleModal Basic Modal Dialog + * http://www.ericmmartin.com/projects/simplemodal/ + * http://code.google.com/p/simplemodal/ + * + * Copyright (c) 2008 Eric Martin - http://ericmmartin.com + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * Revision: $Id: basic.css 162 2008-12-01 23:36:58Z emartin24 $ + * + */ + + +/* Overlay */ +#simplemodal-overlay {background-color:#000; cursor:wait;} + +/* Container */ +#simplemodal-container {height:240px; width:320px; background-color:#fff; border:3px solid #ccc;} +#simplemodal-container a.modalCloseImg {background:url(../images/x.png) no-repeat; width:25px; height:29px; display:inline; z-index:3200; position:absolute; top:-15px; right:-18px; cursor:pointer;} +#simplemodal-container #basicModalContent {padding:8px;} diff --git a/theme/base/css/modal_ie.css b/theme/base/css/modal_ie.css new file mode 100644 index 0000000000..eab4637c0f --- /dev/null +++ b/theme/base/css/modal_ie.css @@ -0,0 +1,16 @@ +/* + * SimpleModal Basic Modal Dialog + * http://www.ericmmartin.com/projects/simplemodal/ + * http://code.google.com/p/simplemodal/ + * + * Copyright (c) 2008 Eric Martin - http://ericmmartin.com + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * Revision: $Id: basic_ie.css 162 2008-12-01 23:36:58Z emartin24 $ + * + */ + +/* IE 6 hacks*/ +#simplemodal-container a.modalCloseImg {background:none; right:-14px; width:22px; height:26px; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../images/x.png',sizingMethod='scale');} diff --git a/theme/base/images/x.png b/theme/base/images/x.png new file mode 100644 index 0000000000000000000000000000000000000000..c11f7af69fad034c3564a4a455be0d9d569e9878 GIT binary patch literal 1066 zcmV+_1l9YAP)P0012b1^@s6UwFrL0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#%t=H+RCwB)mP<%fQ5eVP&iH858mu%+ z@(~);$`IRRsFdWQg)j?25H#9E5+YcjgqtV`TnUOGY74=Yv`}0503pyMZ4rozbYe7z z$w%s><2+9PZ}`sb+&d4g1HZX*&*S@_?|k1m_h>>0%PC7PDB-6UEP>TijU~?!h0g26K6CE8L!teL1 zLtn=e$y0l^wY4QQZl=Dz-t5usSR!v^>*VBQl#$xnTElxmL4jCYTr|`V4-cy%A7hEQ zWNUeOxxwJ->T0CEva(W-#m>%7q;7CW+W51oizD>jY>#XZ^Ja5$m{(#C6SY}DW3nc-b6NO}E# zC=@!P4IkO{$dclCF;Rmo6MzcUQCp&zxZUo1x(G;zH*VRe4xXN#HW+PbY0-_)e^mg- zjUl^AOG_OxB=mFEm0D@BE7x657g{w zXlO8G*GW6Ry1Lrn#rXI*Wro8b0bQaN3ahfBgrlv;h@9*mZTU%RSQD1Bp zNe6l04wlWOdPGMBj`WzuDF(Dess{%L2fOX+0&>Vr&h#0WN~nh<*Mt(z<2vC7~2J!3(F;`5pmu9~~Wq;u)Wsn(|;{en9_rPzUIU zzsgIMq1o}P*}Y6~nLFn5^HNn+RbE+H*@fifWE&2?5WbkdCMG6ah{q<{-QYX3 z0k(k;1nAU~A{qxMg_D4UvRN#L|7Y?f$m=lp4R-l@L_$A+2kd|Ya7-r)C5V2JU?Cd| zrE&UYa;!3WW7)Y64W@HxpN(!ad+Y%T1UU Date: Mon, 16 Feb 2009 18:55:03 +0000 Subject: [PATCH 075/189] Whoops: forgot to include facebookqueuehandler.php with the my original Facebook queue handler patch set! --- scripts/facebookqueuehandler.php | 71 ++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100755 scripts/facebookqueuehandler.php diff --git a/scripts/facebookqueuehandler.php b/scripts/facebookqueuehandler.php new file mode 100755 index 0000000000..c6859cb219 --- /dev/null +++ b/scripts/facebookqueuehandler.php @@ -0,0 +1,71 @@ +#!/usr/bin/env php +. + */ + +# Abort if called from a web server +if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { + print "This script must be run from the command line\n"; + exit(); +} + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); +define('LACONICA', true); + +require_once(INSTALLDIR . '/lib/common.php'); +require_once(INSTALLDIR . '/lib/facebookutil.php'); +require_once(INSTALLDIR . '/lib/queuehandler.php'); + +set_error_handler('common_error_handler'); + +class FacebookQueueHandler extends QueueHandler +{ + + function transport() + { + return 'facebook'; + } + + function start() + { + $this->log(LOG_INFO, "INITIALIZE"); + return true; + } + + function handle_notice($notice) + { + return facebookBroadcastNotice($notice); + } + + function finish() + { + } + +} + +ini_set("max_execution_time", "0"); +ini_set("max_input_time", "0"); +set_time_limit(0); + +mb_internal_encoding('UTF-8'); + +$id = ($argc > 1) ? $argv[1] : null; + +$handler = new FacebookQueueHandler($id); + +$handler->runOnce(); From cc38809764c63dbed24300cdcf68d9f667f0dfad Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Feb 2009 16:34:07 -0500 Subject: [PATCH 076/189] First version of blogspam.net plugin Added a plugin for blogspam.net. The service is kind of aggressive, so by default I turn a lot of the tests off. But worth a look. --- plugins/BlogspamNetPlugin.php | 144 ++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 plugins/BlogspamNetPlugin.php diff --git a/plugins/BlogspamNetPlugin.php b/plugins/BlogspamNetPlugin.php new file mode 100644 index 0000000000..d9372bcd56 --- /dev/null +++ b/plugins/BlogspamNetPlugin.php @@ -0,0 +1,144 @@ +. + * + * @category Plugin + * @package Laconica + * @author Evan Prodromou + * @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); +} + +define('BLOGSPAMNETPLUGIN_VERSION', '0.1'); + +/** + * Plugin to check submitted notices with blogspam.net + * + * When new notices are saved, we check their text with blogspam.net (or + * a compatible service). + * + * Blogspam.net is supposed to catch blog comment spam, and I found that + * some of its tests (min/max size, bayesian match) gave a lot of false positives. + * So, I've turned those tests off by default. This may not get as many + * hits, but it's better than nothing. + * + * @category Plugin + * @package Laconica + * @author Evan Prodromou + * @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 BlogspamNetPlugin extends Plugin +{ + var $baseUrl = 'http://test.blogspam.net:8888/'; + + function __construct($url=null) + { + parent::__construct(); + if ($url) { + $this->baseUrl = $url; + } + } + + function onStartNoticeSave($notice) + { + $args = $this->testArgs($notice); + common_debug("Blogspamnet args = " . print_r($args, TRUE)); + $request = xmlrpc_encode_request('testComment', array($args)); + $context = stream_context_create(array('http' => array('method' => "POST", + 'header' => + "Content-Type: text/xml\r\n". + "User-Agent: " . $this->userAgent(), + 'content' => $request))); + $file = file_get_contents($this->baseUrl, false, $context); + $response = xmlrpc_decode($file); + if (xmlrpc_is_fault($response)) { + throw new ServerException("$response[faultString] ($response[faultCode])", 500); + } else { + common_debug("Blogspamnet results = " . $response); + if (preg_match('/^ERROR(:(.*))?$/', $response, $match)) { + throw new ServerException(sprintf(_("Error from %s: %s"), $this->baseUrl, $match[2]), 500); + } else if (preg_match('/^SPAM(:(.*))?$/', $response, $match)) { + throw new ClientException(sprintf(_("Spam checker results: %s"), $match[2]), 400); + } else if (preg_match('/^OK$/', $response)) { + // don't do anything + } else { + throw new ServerException(sprintf(_("Unexpected response from %s: %s"), $this->baseUrl, $response), 500); + } + } + return true; + } + + function testArgs($notice) + { + $args = array(); + $args['comment'] = $notice->content; + $args['ip'] = $this->getClientIP(); + + if (isset($_SERVER) && array_key_exists('HTTP_USER_AGENT', $_SERVER)) { + $args['agent'] = $_SERVER['HTTP_USER_AGENT']; + } + + $profile = $notice->getProfile(); + + if ($profile && $profile->homepage) { + $args['link'] = $profile->homepage; + } + + if ($profile && $profile->fullname) { + $args['name'] = $profile->fullname; + } else { + $args['name'] = $profile->nickname; + } + + $args['site'] = common_root_url(); + $args['version'] = $this->userAgent(); + + $args['options'] = "max-size=140,min-size=0,min-words=0,exclude=bayasian"; + + return $args; + } + + function getClientIP() + { + if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { + // Note: order matters here; use proxy-forwarded stuff first + foreach (array('HTTP_X_FORWARDED_FOR', 'CLIENT-IP', 'REMOTE_ADDR') as $k) { + if (isset($_SERVER[$k])) { + return $_SERVER[$k]; + } + } + } + return '127.0.0.1'; + } + + function userAgent() + { + return 'BlogspamNetPlugin/'.BLOGSPAMNETPLUGIN_VERSION . ' Laconica/' . LACONICA_VERSION; + } +} From 6440f4cecdf507d49db015bbf4303e50eaadbe13 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 16 Feb 2009 22:39:57 +0000 Subject: [PATCH 077/189] Removed transformation effects from h1-h6 (except in aside) --- theme/base/css/display.css | 1 - 1 file changed, 1 deletion(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 1ac63927d2..5ce5ac884d 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -22,7 +22,6 @@ line-height:1.65; position:relative; } h1,h2,h3,h4,h5,h6 { -text-transform:capitalize; margin-bottom:7px; overflow:hidden; } From c8e71d359cee093dcb15c1d32684d90f81e10204 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Feb 2009 18:02:04 -0500 Subject: [PATCH 078/189] error in hashtag link generation --- lib/util.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/util.php b/lib/util.php index 094b2750ca..46aa7b9df9 100644 --- a/lib/util.php +++ b/lib/util.php @@ -481,7 +481,7 @@ function common_linkify($url) { $url = (!preg_match('#^([a-z]+://|(mailto|aim|tel):)#i', $url)) ? 'http://'.$url : $url; $attrs = array('href' => $url, 'rel' => 'external'); - + if (in_array($ext, $video_ext)) { $attrs['class'] = 'media'; } @@ -596,7 +596,7 @@ function common_tag_link($tag) $xs->element('a', array('href' => $url, 'rel' => 'tag'), $tag); - $xs->elementEnd(); + $xs->elementEnd('span'); return $xs->getString(); } From ca90d790aa9d93a4e88602330f91d4703c29b75d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Feb 2009 18:02:31 -0500 Subject: [PATCH 079/189] Automatically add a tag for every group messages If you post to a group !foo, it's automatically listed as being tagged "foo". This is to keep users from having to do !foo #foo in all their messages. --- classes/Notice.php | 40 +++++++++++++++++++++++++++------------- classes/Notice_tag.php | 13 +++++++++---- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index b8cd2bd7f2..8e08ad503d 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -94,23 +94,28 @@ class Notice extends Memcached_DataObject /* Add them to the database */ foreach(array_unique($match[1]) as $hashtag) { /* elide characters we don't want in the tag */ - $hashtag = common_canonical_tag($hashtag); - - $tag = DB_DataObject::factory('Notice_tag'); - $tag->notice_id = $this->id; - $tag->tag = $hashtag; - $tag->created = $this->created; - $id = $tag->insert(); - if (!$id) { - $last_error = PEAR::getStaticProperty('DB_DataObject','lastError'); - common_log(LOG_ERR, 'DB error inserting hashtag: ' . $last_error->message); - common_server_error(sprintf(_('DB error inserting hashtag: %s'), $last_error->message)); - return; - } + $this->saveTag($hashtag); } return true; } + function saveTag($hashtag) + { + $hashtag = common_canonical_tag($hashtag); + + $tag = new Notice_tag(); + $tag->notice_id = $this->id; + $tag->tag = $hashtag; + $tag->created = $this->created; + $id = $tag->insert(); + + if (!$id) { + throw new ServerException(sprintf(_('DB error inserting hashtag: %s'), + $last_error->message)); + return; + } + } + static function saveNew($profile_id, $content, $source=null, $is_local=1, $reply_to=null, $uri=null) { $profile = Profile::staticGet($profile_id); @@ -621,6 +626,15 @@ class Notice extends Memcached_DataObject continue; } + // we automatically add a tag for every group name, too + + $tag = Notice_tag::pkeyGet(array('tag' => common_canonical_tag($nickname), + 'notice_id' => $this->id)); + + if (is_null($tag)) { + $this->saveTag($nickname); + } + if ($profile->isMember($group)) { $gi = new Group_inbox(); diff --git a/classes/Notice_tag.php b/classes/Notice_tag.php index 94f9296d60..0365973f56 100644 --- a/classes/Notice_tag.php +++ b/classes/Notice_tag.php @@ -19,7 +19,7 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; -class Notice_tag extends Memcached_DataObject +class Notice_tag extends Memcached_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -35,9 +35,9 @@ class Notice_tag extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - + static function getStream($tag, $offset=0, $limit=20) { - $qry = + $qry = 'SELECT notice.* ' . 'FROM notice JOIN notice_tag ON notice.id = notice_tag.notice_id ' . 'WHERE notice_tag.tag = "%s" '; @@ -46,7 +46,7 @@ class Notice_tag extends Memcached_DataObject 'notice_tag:notice_stream:' . common_keyize($tag), $offset, $limit); } - + function blowCache() { $cache = common_memcache(); @@ -54,4 +54,9 @@ class Notice_tag extends Memcached_DataObject $cache->delete(common_cache_key('notice_tag:notice_stream:' . $this->tag)); } } + + function &pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('Notice_tag', $kv); + } } From affb2f9359ee498c2c20240b592a0f62a770e8c8 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Feb 2009 18:24:43 -0500 Subject: [PATCH 080/189] add email notify flag for @-replies --- classes/User.php | 7 +++++-- classes/laconica.ini | 1 + db/laconica.sql | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/classes/User.php b/classes/User.php index a6a1b11b9f..495a982360 100644 --- a/classes/User.php +++ b/classes/User.php @@ -40,6 +40,7 @@ class User extends Memcached_DataObject public $emailnotifyfav; // tinyint(1) default_1 public $emailnotifynudge; // tinyint(1) default_1 public $emailnotifymsg; // tinyint(1) default_1 + public $emailnotifyattn; // tinyint(1) default_1 public $emailmicroid; // tinyint(1) default_1 public $language; // varchar(50) public $timezone; // varchar(50) @@ -62,8 +63,10 @@ class User extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=null) - { return Memcached_DataObject::staticGet('User',$k,$v); } + function staticGet($k,$v=NULL) + { + return Memcached_DataObject::staticGet('User',$k,$v); + } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE diff --git a/classes/laconica.ini b/classes/laconica.ini index 19267f2688..5fd2cd1f86 100755 --- a/classes/laconica.ini +++ b/classes/laconica.ini @@ -332,6 +332,7 @@ emailnotifysub = 17 emailnotifyfav = 17 emailnotifynudge = 17 emailnotifymsg = 17 +emailnotifyattn = 17 emailmicroid = 17 language = 2 timezone = 2 diff --git a/db/laconica.sql b/db/laconica.sql index 15f03a978f..dd93a727b7 100644 --- a/db/laconica.sql +++ b/db/laconica.sql @@ -50,6 +50,7 @@ create table user ( emailnotifyfav tinyint default 1 comment 'Notify by email of favorites', emailnotifynudge tinyint default 1 comment 'Notify by email of nudges', emailnotifymsg tinyint default 1 comment 'Notify by email of direct messages', + emailnotifyattn tinyint default 1 comment 'Notify by email of @-replies', emailmicroid tinyint default 1 comment 'whether to publish email microid', language varchar(50) comment 'preferred language', timezone varchar(50) comment 'timezone', From 175c4665cc33c07bbbc42bbf96dd07cf90ba7bbb Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Feb 2009 18:26:15 -0500 Subject: [PATCH 081/189] send mail when @-replies are received --- classes/Notice.php | 32 +++++++++++++++++++---------- lib/mail.php | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 8e08ad503d..570d76f618 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -34,22 +34,23 @@ class Notice extends Memcached_DataObject ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ - public $__table = 'notice'; // table name - public $id; // int(4) primary_key not_null - public $profile_id; // int(4) not_null + public $__table = 'notice'; // table name + public $id; // int(4) primary_key not_null + public $profile_id; // int(4) not_null public $uri; // varchar(255) unique_key public $content; // varchar(140) - public $rendered; // text() + public $rendered; // text() public $url; // varchar(255) - public $created; // datetime() not_null - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - public $reply_to; // int(4) - public $is_local; // tinyint(1) - public $source; // varchar(32) + public $created; // datetime() not_null + public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + public $reply_to; // int(4) + public $is_local; // tinyint(1) + public $source; // varchar(32) /* Static get */ - function staticGet($k,$v=null) - { return Memcached_DataObject::staticGet('Notice',$k,$v); } + function staticGet($k,$v=NULL) { + return Memcached_DataObject::staticGet('Notice',$k,$v); + } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE @@ -746,10 +747,19 @@ class Notice extends Memcached_DataObject if (!$id) { common_log_db_error($reply, 'INSERT', __FILE__); return; + } else { + $replied[$recipient->id] = 1; } } } } } + + foreach (array_keys($replied) as $recipient) { + $user = User::staticGet('id', $recipient); + if ($user) { + mail_attn_notify($user, $notice); + } + } } } diff --git a/lib/mail.php b/lib/mail.php index a1faefc806..9fa86de5cc 100644 --- a/lib/mail.php +++ b/lib/mail.php @@ -573,3 +573,53 @@ function mail_notify_fave($other, $user, $notice) common_init_locale(); mail_to_user($other, $subject, $body); } + +/** + * notify a user that they have received an "attn:" message AKA "@-reply" + * + * @param User $user The user who recevied the notice + * @param Notice $notice The notice that was sent + * + * @return void + */ + +function mail_notify_attn($user, $notice) +{ + if (!$user->email || !$user->emailnotifyattn) { + return; + } + + $sender = $notice->getProfile(); + + $bestname = $sender->getBestName(); + + common_init_locale($user->language); + + $subject = sprintf(_('%s sent a notice to your attention'), $bestname); + + $body = sprintf(_("%1\$s just sent a notice to your attention (an '@-reply') on %2\$s.\n\n". + "The notice is here:\n\n". + "\t%3\$s\n\n" . + "It reads:\n\n". + "\t%4\$s\n\n" . + "You can reply back here:\n\n". + "\t%5\$s\n\n" . + "The list of all @-replies for you here:\n\n" . + "%6\$s\n\n" . + "Faithfully yours,\n" . + "%2\$s\n\n" . + "P.S. You can turn off these email notifications here: %7\$s\n"), + $bestname, + common_config('site', 'name'), + common_local_url('shownotice', + array('notice' => $notice->id)), + $notice->content, + common_local_url('newnotice', + array('replyto' => $sender->nickname)), + common_local_url('replies', + array('nickname' => $user->nickname)), + common_local_url('emailsettings')); + + common_init_locale(); + mail_to_user($user, $subject, $body); +} From b727e8be33f5f5aaf0d319cdd11d422556e6167a Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Feb 2009 18:45:05 -0500 Subject: [PATCH 082/189] Set @-message notification options in email settings Set the @-message notification options in email settings. --- actions/emailsettings.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/actions/emailsettings.php b/actions/emailsettings.php index b84acb2141..634388fddd 100644 --- a/actions/emailsettings.php +++ b/actions/emailsettings.php @@ -164,6 +164,11 @@ class EmailsettingsAction extends AccountSettingsAction $user->emailnotifymsg); $this->elementEnd('li'); $this->elementStart('li'); + $this->checkbox('emailnotifyattn', + _('Send me email when someone sends me an "@-reply".'), + $user->emailnotifyattn); + $this->elementEnd('li'); + $this->elementStart('li'); $this->checkbox('emailnotifynudge', _('Allow friends to nudge me and send me an email.'), $user->emailnotifynudge); @@ -255,6 +260,7 @@ class EmailsettingsAction extends AccountSettingsAction $emailnotifyfav = $this->boolean('emailnotifyfav'); $emailnotifymsg = $this->boolean('emailnotifymsg'); $emailnotifynudge = $this->boolean('emailnotifynudge'); + $emailnotifyattn = $this->boolean('emailnotifyattn'); $emailmicroid = $this->boolean('emailmicroid'); $emailpost = $this->boolean('emailpost'); @@ -270,6 +276,7 @@ class EmailsettingsAction extends AccountSettingsAction $user->emailnotifyfav = $emailnotifyfav; $user->emailnotifymsg = $emailnotifymsg; $user->emailnotifynudge = $emailnotifynudge; + $user->emailnotifyattn = $emailnotifyattn; $user->emailmicroid = $emailmicroid; $user->emailpost = $emailpost; From 419960fd19c21007958973af5151a84e00b164f8 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Feb 2009 18:45:59 -0500 Subject: [PATCH 083/189] wrong name for attn function --- classes/Notice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/Notice.php b/classes/Notice.php index 570d76f618..8300667fa4 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -758,7 +758,7 @@ class Notice extends Memcached_DataObject foreach (array_keys($replied) as $recipient) { $user = User::staticGet('id', $recipient); if ($user) { - mail_attn_notify($user, $notice); + mail_notify_attn($user, $this); } } } From ab3c5d6f2d93843c199ebc4183f9ba307db1377d Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 16 Feb 2009 17:58:24 -0800 Subject: [PATCH 084/189] Updated README with section about installing the Facebook app, and added info about twitterqueuehandler.php and facebookqueuehandler.php. --- README | 53 ++++++++++++++++++++++++++++++++++++++++++++++- config.php.sample | 2 +- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/README b/README index 989fcb7f0a..67dc9a66b3 100644 --- a/README +++ b/README @@ -507,7 +507,7 @@ server is probably a good idea for high-volume sites. needs as a parameter the install path; if you run it from the Laconica dir, "." should suffice. -This will run six (for now) queue handlers: +This will run eight (for now) queue handlers: * xmppdaemon.php - listens for new XMPP messages from users and stores them as notices in the database. @@ -521,6 +521,10 @@ This will run six (for now) queue handlers: of registered users. * xmppconfirmhandler.php - sends confirmation messages to registered users. +* twitterqueuehandler.php - sends queued notices to Twitter for user + who have opted to set up Twitter bridging. +* facebookqueuehandler.php - sends queued notices to Facebook for users + of the built-in Facebook application. Note that these queue daemons are pretty raw, and need your care. In particular, they leak memory, and you may want to restart them on a @@ -553,6 +557,53 @@ Sample cron job: # Update Twitter friends subscriptions every half hour 0,30 * * * * /path/to/php /path/to/laconica/scripts/synctwitterfriends.php>&/dev/null +Built-in Facebook Application +----------------------------- + +Laconica's Facebook application allows your users to automatically +update their Facebook statuses with their latest notices, invite +their friends to use the app (and thus your site), view their notice +timelines, and post notices -- all from within Facebook. The application +is built into Laconica and runs on your host. For automatic Facebook +status updating to work you will need to enable queuing and run the +facebookqueuehandler.php daemon (see the "Queues and daemons" section +above). + +Quick setup instructions*: + +Install the Facebook Developer application on Facebook: + + http://www.facebook.com/developers/ + +Use it to create a new application and generate an API key and secret. +Uncomment the Facebook app section of your config.php and copy in the +key and secret, e.g.: + + # Config section for the built-in Facebook application + $config['facebook']['apikey'] = 'APIKEY'; + $config['facebook']['secret'] = 'SECRET'; + +In Facebook's application editor, specify the following URLs for your app: + +- Callback URL: http://example.net/mublog/facebook/ +- Post-Remove URL: http://example.net/mublog/facebook/remove +- Post-Add Redirect URL: http://apps.facebook.com/yourapp/ +- Canvas URL: http://apps.facebook.com/yourapp/ + +(Replace 'example.net' with your host's URL, 'mublog' with the path +to your Laconica installation, and 'yourapp' with the name of the +Facebook application you created.) + +Additionally, Choose "Web" for Application type in the Advanced tab. +In the "Canvas setting" section, choose the "FBML" for Render Method, +"Smart Size" for IFrame size, and "Full width (760px)" for Canvas Width. +Everything else can be left with default values. + +*For more detailed instructions please see the installation guide on the +Laconica wiki: + + http://laconi.ca/trac/wiki/FacebookApplication + Sitemaps -------- diff --git a/config.php.sample b/config.php.sample index 3fa898e1be..da31545074 100644 --- a/config.php.sample +++ b/config.php.sample @@ -147,7 +147,7 @@ $config['sphinx']['port'] = 3312; #$config['profile']['banned'][] = 'hacker'; #$config['profile']['banned'][] = 12345; -# config section for the built-in Facebook application +# Config section for the built-in Facebook application #$config['facebook']['apikey'] = 'APIKEY'; #$config['facebook']['secret'] = 'SECRET'; From 4327ae19d49384ca2f4b65b04926fb2fbea7d28d Mon Sep 17 00:00:00 2001 From: Meitar Moscovitz Date: Tue, 17 Feb 2009 18:37:02 +1100 Subject: [PATCH 085/189] Fixes ticket:1053; removes spacing between hash and tag on profile page. --- actions/showstream.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/actions/showstream.php b/actions/showstream.php index c736c99b5d..65482167e1 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -292,11 +292,11 @@ class ShowstreamAction extends Action $this->elementStart('ul', 'tags xoxo'); foreach ($tags as $tag) { $this->elementStart('li'); - $this->element('span', 'mark_hash', '#'); - $this->element('a', array('rel' => 'tag', - 'href' => common_local_url('peopletag', - array('tag' => $tag))), - $tag); + // Avoid space by using raw output. + $pt = '#'; + $this->raw($pt); $this->elementEnd('li'); } $this->elementEnd('ul'); From 75ebd45ebdcf26849e47d3c11a2cc2aa6e946c01 Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Mon, 9 Feb 2009 17:29:27 -0500 Subject: [PATCH 086/189] Fixed #1170: Auto-linking bug when URL cotains special chars. --- lib/util.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/util.php b/lib/util.php index 7ce4e229eb..5204693bc5 100644 --- a/lib/util.php +++ b/lib/util.php @@ -412,8 +412,8 @@ function common_replace_urls_callback($text, $callback) { // Then clean up what the regex left behind $offset = 0; - foreach($matches[0] as $url) { - $url = htmlspecialchars_decode($url); + foreach($matches[0] as $orig_url) { + $url = htmlspecialchars_decode($orig_url); // Make sure we didn't pick up an email address if (preg_match('#^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$#i', $url)) continue; @@ -456,6 +456,9 @@ function common_replace_urls_callback($text, $callback) { if (!in_array($url_parts[2], $tlds)) continue; + // Put the url back the way we found it. + $url = (mb_strpos($orig_url, htmlspecialchars($url)) === FALSE) ? $url:htmlspecialchars($url); + // Call user specified func $modified_url = $callback($url); From 7bcfc9f6434e903936cbd6d47a2c25081f75eb04 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 17 Feb 2009 12:08:53 -0500 Subject: [PATCH 087/189] another system message --- scripts/xmppdaemon.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/xmppdaemon.php b/scripts/xmppdaemon.php index 01fe8914f1..ef3f8c63d8 100755 --- a/scripts/xmppdaemon.php +++ b/scripts/xmppdaemon.php @@ -208,6 +208,8 @@ class XMPPDaemon extends Daemon { if (preg_match('/[\[\(]?[Aa]uto[-\s]?[Rr]e(ply|sponse)[\]\)]/', $txt)) { return true; + } else if (preg_match('/^System: Message wasn\'t delivered. Offline storage size was exceeded.$/', $txt)) { + return true; } else { return false; } From d8b6762e6dcb776571797f51d7c7d4cb99c9ef31 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 18 Feb 2009 09:35:59 -0500 Subject: [PATCH 088/189] move peoplesearchresults to its own module --- actions/peoplesearch.php | 26 +------------ lib/peoplesearchresults.php | 75 +++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 25 deletions(-) create mode 100644 lib/peoplesearchresults.php diff --git a/actions/peoplesearch.php b/actions/peoplesearch.php index 615201c461..14177fcf0d 100644 --- a/actions/peoplesearch.php +++ b/actions/peoplesearch.php @@ -86,33 +86,9 @@ class PeoplesearchAction extends SearchAction } $profile->free(); - + $this->pagination($page > 1, $cnt > PROFILES_PER_PAGE, $page, 'peoplesearch', array('q' => $q)); } } -class PeopleSearchResults extends ProfileList -{ - var $terms = null; - var $pattern = null; - - function __construct($profile, $terms, $action) - { - parent::__construct($profile, $terms, $action); - $this->terms = array_map('preg_quote', - array_map('htmlspecialchars', $terms)); - $this->pattern = '/('.implode('|',$terms).')/i'; - } - - function highlight($text) - { - return preg_replace($this->pattern, '\\1', htmlspecialchars($text)); - } - - function isReadOnly() - { - return true; - } -} - diff --git a/lib/peoplesearchresults.php b/lib/peoplesearchresults.php new file mode 100644 index 0000000000..f8ab7cf3b6 --- /dev/null +++ b/lib/peoplesearchresults.php @@ -0,0 +1,75 @@ + + * @author Robin Millette + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + * + * Laconica - a distributed open-source microblogging tool + * Copyright (C) 2008, Controlez-Vous, 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 . + */ + +if (!defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/profilelist.php'; + +/** + * People search results class + * + * Derivative of ProfileList with specialization for highlighting search terms. + * + * @category Widget + * @package Laconica + * @author Evan Prodromou + * @author Robin Millette + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + * + * @see PeoplesearchAction + */ + +class PeopleSearchResults extends ProfileList +{ + var $terms = null; + var $pattern = null; + + function __construct($profile, $terms, $action) + { + parent::__construct($profile, $terms, $action); + $this->terms = array_map('preg_quote', + array_map('htmlspecialchars', $terms)); + $this->pattern = '/('.implode('|',$terms).')/i'; + } + + function highlight($text) + { + return preg_replace($this->pattern, '\\1', htmlspecialchars($text)); + } + + function isReadOnly() + { + return true; + } +} + From 28e1c163e3e14b646851d7641c1c8a6a00de8fdc Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 18 Feb 2009 15:33:52 -0800 Subject: [PATCH 089/189] Open the /api/laconica/config.format API method so clients can determine whether a site is "private". --- actions/api.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/actions/api.php b/actions/api.php index 21fe4eea32..a27d244929 100644 --- a/actions/api.php +++ b/actions/api.php @@ -131,13 +131,13 @@ class ApiAction extends Action 'statuses/followers', 'favorites/favorites'); - # If the site is "private", all API methods need authentication - - if (common_config('site', 'private')) { - return true; - } - $fullname = "$this->api_action/$this->api_method"; + + // If the site is "private", all API methods except laconica/config + // need authentication + if (common_config('site', 'private')) { + return $fullname != 'laconica/config' || false; + } if (in_array($fullname, $bareauth)) { # bareauth: only needs auth if without an argument From c9def4a8768239093823fbe34367d97f9e30d320 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 18 Feb 2009 23:43:26 +0000 Subject: [PATCH 090/189] more correct handling of etags and last-modified --- lib/action.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/action.php b/lib/action.php index e2d09ace2b..b1e700b670 100644 --- a/lib/action.php +++ b/lib/action.php @@ -209,12 +209,10 @@ class Action extends HTMLOutputter // lawsuit 'src' => common_path('js/jquery.form.js')), ' '); - $this->element('script', array('type' => 'text/javascript', 'src' => common_path('js/jquery.simplemodal-1.2.2.pack.js')), ' '); - Event::handle('EndShowJQueryScripts', array($this)); } if (Event::handle('StartShowLaconicaScripts', array($this))) { @@ -813,8 +811,10 @@ class Action extends HTMLOutputter // lawsuit if ($if_modified_since) { $ims = strtotime($if_modified_since); if ($lm <= $ims) { - if (!$etag || - $this->_hasEtag($etag, $_SERVER['HTTP_IF_NONE_MATCH'])) { + $if_none_match = $_SERVER['HTTP_IF_NONE_MATCH']; + if (!$if_none_match || + !$etag || + $this->_hasEtag($etag, $if_none_match)) { header('HTTP/1.1 304 Not Modified'); // Better way to do this? exit(0); @@ -832,9 +832,11 @@ class Action extends HTMLOutputter // lawsuit * * @return boolean */ + function _hasEtag($etag, $if_none_match) { - return ($if_none_match) && in_array($etag, explode(',', $if_none_match)); + $etags = explode(',', $if_none_match); + return in_array($etag, $etags) || in_array('*', $etags); } /** From 8fc7f5204e7ed07b9450b7650bce6317ea8e79ae Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 19 Feb 2009 02:55:04 +0000 Subject: [PATCH 091/189] Minor CSS cleanup. --- theme/base/css/display.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 5ce5ac884d..b5796374ec 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -27,7 +27,6 @@ overflow:hidden; } h1 { font-size:1.4em; -line-height:1; margin-bottom:18px; } h2 { font-size:1.3em; } @@ -365,7 +364,6 @@ margin-right:4px; #wrap { margin:0 auto; -width:71.714em; width:1003px; overflow:hidden; } From 1abeaf931e2e22806cbf747690df3a3350b401f8 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 14:58:38 +0000 Subject: [PATCH 092/189] handle if-modified-since in RSS feeds --- lib/rssaction.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/rssaction.php b/lib/rssaction.php index 131e8ac65a..66c2d9e8cd 100644 --- a/lib/rssaction.php +++ b/lib/rssaction.php @@ -38,6 +38,7 @@ class Rss10Action extends Action var $creators = array(); var $limit = DEFAULT_RSS_LIMIT; + var $notices = null; /** * Constructor @@ -93,6 +94,9 @@ class Rss10Action extends Action function handle($args) { + // Get the list of notices + $this->notices = $this->getNotices(); + // Parent handling, including cache check parent::handle($args); $this->showRss($this->limit); } @@ -258,5 +262,25 @@ class Rss10Action extends Action { $this->elementEnd('rdf:RDF'); } + + /** + * When was this page last modified? + * + */ + + function lastModified() + { + if (empty($this->notices)) { + return null; + } + + if (count($this->notices) == 0) { + return null; + } + + // FIXME: doesn't handle modified profiles, avatars, deleted notices + + return strtotime($this->notices[0]->created); + } } From 5ec5a22dc75cb8f4d896b41ca24067c68d3e1de6 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 10:04:28 -0500 Subject: [PATCH 093/189] make tagother work with router --- actions/tagother.php | 3 ++- lib/router.php | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/actions/tagother.php b/actions/tagother.php index 79151c9118..0d18945a09 100644 --- a/actions/tagother.php +++ b/actions/tagother.php @@ -135,7 +135,8 @@ class TagotherAction extends Action 'id' => 'form_tag_user', 'class' => 'form_settings', 'name' => 'tagother', - 'action' => $this->selfUrl())); + 'action' => common_local_url('tagother', array('id' => $this->profile->id)))); + $this->elementStart('fieldset'); $this->element('legend', null, _('Tag user')); $this->hidden('token', common_session_token()); diff --git a/lib/router.php b/lib/router.php index d47ad71183..e55b597f21 100644 --- a/lib/router.php +++ b/lib/router.php @@ -98,12 +98,14 @@ class Router $main = array('login', 'logout', 'register', 'subscribe', 'unsubscribe', 'confirmaddress', 'recoverpassword', 'invite', 'favor', 'disfavor', 'sup', - 'tagother', 'block'); + 'block'); foreach ($main as $a) { $m->connect('main/'.$a, array('action' => $a)); } + $m->connect('main/tagother/:id', array('action' => 'tagother')); + // these take a code foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) { From 4da4c94678fecb0fbf06eaba0b71d3206eb89377 Mon Sep 17 00:00:00 2001 From: Leslie Michael Orchard Date: Tue, 17 Feb 2009 23:24:10 -0500 Subject: [PATCH 094/189] Ignoring VIM swap files, log files, and httpd.conf --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index f2e96d3eb6..83a53dfa3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ avatar/* files/* _darcs/* +logs/* config.php .htaccess +httpd.conf *.tmproj dataobject.ini *~ @@ -10,3 +12,4 @@ dataobject.ini *.orig *.rej .#* +*.swp From 17a6e6603058d412f3c3a7c6800f5d47fcf0def0 Mon Sep 17 00:00:00 2001 From: Leslie Michael Orchard Date: Tue, 17 Feb 2009 23:25:00 -0500 Subject: [PATCH 095/189] PROFILES_PER_PAGE already defined in lib/common.php --- lib/profilelist.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/profilelist.php b/lib/profilelist.php index 8bef49dcee..c2040fbc23 100644 --- a/lib/profilelist.php +++ b/lib/profilelist.php @@ -34,8 +34,6 @@ if (!defined('LACONICA')) { require_once INSTALLDIR.'/lib/widget.php'; -define('PROFILES_PER_PAGE', 20); - /** * Widget to show a list of profiles * From 9a0e71f9bf3329004949a0cef61abb5a354c7688 Mon Sep 17 00:00:00 2001 From: Leslie Michael Orchard Date: Tue, 17 Feb 2009 23:22:56 -0500 Subject: [PATCH 096/189] Fixing a bunch of undefined variable warnings in OpenID signup process --- actions/finishopenidlogin.php | 17 +++++++++++------ classes/User.php | 18 +++++++++--------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/actions/finishopenidlogin.php b/actions/finishopenidlogin.php index 1e7b73a7f3..6d92cb9aae 100644 --- a/actions/finishopenidlogin.php +++ b/actions/finishopenidlogin.php @@ -83,7 +83,7 @@ class FinishopenidloginAction extends Action function showContent() { - if ($this->message_text) { + if (!empty($this->message_text)) { $this->element('p', null, $this->message); return; } @@ -232,7 +232,8 @@ class FinishopenidloginAction extends Action return; } - if ($sreg['country']) { + $location = ''; + if (!empty($sreg['country'])) { if ($sreg['postcode']) { # XXX: use postcode to get city and region # XXX: also, store postcode somewhere -- it's valuable! @@ -242,12 +243,16 @@ class FinishopenidloginAction extends Action } } - if ($sreg['fullname'] && mb_strlen($sreg['fullname']) <= 255) { + if (!empty($sreg['fullname']) && mb_strlen($sreg['fullname']) <= 255) { $fullname = $sreg['fullname']; + } else { + $fullname = ''; } - if ($sreg['email'] && Validate::email($sreg['email'], true)) { + if (!empty($sreg['email']) && Validate::email($sreg['email'], true)) { $email = $sreg['email']; + } else { + $email = ''; } # XXX: add language @@ -328,7 +333,7 @@ class FinishopenidloginAction extends Action # Try the passed-in nickname - if ($sreg['nickname']) { + if (!empty($sreg['nickname'])) { $nickname = $this->nicknamize($sreg['nickname']); if ($this->isNewNickname($nickname)) { return $nickname; @@ -337,7 +342,7 @@ class FinishopenidloginAction extends Action # Try the full name - if ($sreg['fullname']) { + if (!empty($sreg['fullname'])) { $fullname = $this->nicknamize($sreg['fullname']); if ($this->isNewNickname($fullname)) { return $fullname; diff --git a/classes/User.php b/classes/User.php index 495a982360..40cf18df67 100644 --- a/classes/User.php +++ b/classes/User.php @@ -183,16 +183,16 @@ class User extends Memcached_DataObject $profile->nickname = $nickname; $profile->profileurl = common_profile_url($nickname); - if ($fullname) { + if (!empty($fullname)) { $profile->fullname = $fullname; } - if ($homepage) { + if (!empty($homepage)) { $profile->homepage = $homepage; } - if ($bio) { + if (!empty($bio)) { $profile->bio = $bio; } - if ($location) { + if (!empty($location)) { $profile->location = $location; } @@ -200,7 +200,7 @@ class User extends Memcached_DataObject $id = $profile->insert(); - if (!$id) { + if (empty($id)) { common_log_db_error($profile, 'INSERT', __FILE__); return false; } @@ -210,13 +210,13 @@ class User extends Memcached_DataObject $user->id = $id; $user->nickname = $nickname; - if ($password) { # may not have a password for OpenID users + if (!empty($password)) { # may not have a password for OpenID users $user->password = common_munge_password($password, $id); } # Users who respond to invite email have proven their ownership of that address - if ($code) { + if (!empty($code)) { $invite = Invitation::staticGet($code); if ($invite && $invite->address && $invite->address_type == 'email' && $invite->address == $email) { $user->email = $invite->address; @@ -253,7 +253,7 @@ class User extends Memcached_DataObject return false; } - if ($email && !$user->email) { + if (!empty($email) && !$user->email) { $confirm = new Confirm_address(); $confirm->code = common_confirmation_code(128); @@ -268,7 +268,7 @@ class User extends Memcached_DataObject } } - if ($code && $user->email) { + if (!empty($code) && $user->email) { $user->emailChanged(); } From 5bb32ccfd0d7bb813c8b35cfb0411625418c5402 Mon Sep 17 00:00:00 2001 From: Leslie Michael Orchard Date: Tue, 17 Feb 2009 23:09:57 -0500 Subject: [PATCH 097/189] Attempt to access non-existent OPENID_COOKIE_KEY cookie causing a warning --- lib/openid.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/openid.php b/lib/openid.php index 8605737026..5c3d460daf 100644 --- a/lib/openid.php +++ b/lib/openid.php @@ -64,6 +64,9 @@ function oid_set_last($openid_url) function oid_get_last() { + if (empty($_COOKIE[OPENID_COOKIE_KEY])) { + return null; + } $openid_url = $_COOKIE[OPENID_COOKIE_KEY]; if ($openid_url && strlen($openid_url) > 0) { return $openid_url; From 76d506cf1644390a073e5178774675c60e1c3332 Mon Sep 17 00:00:00 2001 From: Leslie Michael Orchard Date: Tue, 17 Feb 2009 22:59:48 -0500 Subject: [PATCH 098/189] NOTICES_PER_SECTION already defined in lib/noticesection.php, causing a warning --- lib/popularnoticesection.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/popularnoticesection.php b/lib/popularnoticesection.php index 5380563b9e..c7c7f02150 100644 --- a/lib/popularnoticesection.php +++ b/lib/popularnoticesection.php @@ -31,8 +31,6 @@ if (!defined('LACONICA')) { exit(1); } -define('NOTICES_PER_SECTION', 5); - /** * Base class for sections showing lists of notices * From 5c59c0d90c806636fc07ee9f2445407ad277de59 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 16:44:56 -0500 Subject: [PATCH 099/189] avoid notices on undefined array elements --- index.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/index.php b/index.php index b180e2b653..a8890e9a3b 100644 --- a/index.php +++ b/index.php @@ -27,12 +27,13 @@ $action = null; function getPath($req) { - if (common_config('site', 'fancy')) { + if ((common_config('site', 'fancy') || !array_key_exists('PATH_INFO', $_SERVER)) + && array_key_exists('p', $req)) { return $req['p']; - } else if ($_SERVER['PATH_INFO']) { + } else if (array_key_exists('PATH_INFO', $_SERVER)) { return $_SERVER['PATH_INFO']; } else { - return $req['p']; + return null; } } From 424388611a347eeecd35be3a22984826dd2a503e Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 16:46:26 -0500 Subject: [PATCH 100/189] accidentally used as a global in index.php --- index.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.php b/index.php index a8890e9a3b..914ba5bde1 100644 --- a/index.php +++ b/index.php @@ -116,8 +116,8 @@ function main() // XXX: find somewhere for this little block to live - if ($config['db']['mirror'] && $action_obj->isReadOnly()) { - if (is_array($config['db']['mirror'])) { + if (common_config('db', 'mirror') && $action_obj->isReadOnly()) { + if (is_array(common_config('db', 'mirror'))) { // "load balancing", ha ha $k = array_rand($config['db']['mirror']); From 85eb53247d7c0eac767de9fd79ba70183b7f57be Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 16:48:49 -0500 Subject: [PATCH 101/189] change static in router to var --- lib/router.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/router.php b/lib/router.php index e55b597f21..0640a5911f 100644 --- a/lib/router.php +++ b/lib/router.php @@ -47,7 +47,7 @@ require_once 'Net/URL/Mapper.php'; class Router { - static $m = null; + var $m = null; static $inst = null; static function get() From a76099c59b616004886e3c7add06db1de53e4acf Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 16:50:05 -0500 Subject: [PATCH 102/189] make check for ->value better --- lib/noticesection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/noticesection.php b/lib/noticesection.php index 97b5175296..b31f187445 100644 --- a/lib/noticesection.php +++ b/lib/noticesection.php @@ -96,7 +96,7 @@ class NoticeSection extends Section $this->out->elementStart('p', 'entry-content'); $this->out->raw($notice->rendered); $this->out->elementEnd('p'); - if ($notice->value) { + if (!empty($notice->value)) { $this->out->elementStart('p'); $this->out->text($notice->value); $this->out->elementEnd('p'); From d5bf7e5cfb4b4de335cafec69d93c565e0c9d2f4 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 16:51:39 -0500 Subject: [PATCH 103/189] fix notice in searchaction --- lib/searchaction.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/searchaction.php b/lib/searchaction.php index fdfb8dc5ad..df68764459 100644 --- a/lib/searchaction.php +++ b/lib/searchaction.php @@ -79,10 +79,11 @@ class SearchAction extends Action function showTop($arr=null) { + $error = null; if ($arr) { $error = $arr[1]; } - if ($error) { + if (!empty($error)) { $this->element('p', 'error', $error); } else { $instr = $this->getInstructions(); From 4aa9b95f51216edaffcd229d9c31a5f905a76b13 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 16:58:19 -0500 Subject: [PATCH 104/189] use return value of common_check_user() in login.php --- actions/login.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/actions/login.php b/actions/login.php index 71e4679292..b049791fb1 100644 --- a/actions/login.php +++ b/actions/login.php @@ -108,13 +108,15 @@ class LoginAction extends Action $nickname = common_canonical_nickname($this->trimmed('nickname')); $password = $this->arg('password'); - if (!common_check_user($nickname, $password)) { + $user = common_check_user($nickname, $password); + + if (!$user) { $this->showForm(_('Incorrect username or password.')); return; } // success! - if (!common_set_user($nickname)) { + if (!common_set_user($user)) { $this->serverError(_('Error setting user.')); return; } From 5845f19b051d440125f4921ad0a15184a993d287 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 17:02:34 -0500 Subject: [PATCH 105/189] fix some notices in omb.php --- lib/omb.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/omb.php b/lib/omb.php index f2dbef5ba9..29e14c75f7 100644 --- a/lib/omb.php +++ b/lib/omb.php @@ -239,7 +239,7 @@ function omb_broadcast_profile($profile) while ($sub->fetch()) { $rp = Remote_profile::staticGet('id', $sub->subscriber); if ($rp) { - if (!$updated[$rp->updateprofileurl]) { + if (!array_key_exists($rp->updateprofileurl, $updated)) { if (omb_update_profile($profile, $rp, $sub)) { $updated[$rp->updateprofileurl] = true; } @@ -295,7 +295,9 @@ function omb_update_profile($profile, $remote_profile, $subscription) common_debug('Got HTTP result "'.print_r($result,true).'"', __FILE__); - if ($result->status == 403) { # not authorized, don't send again + if (empty($result) || $result) { + common_debug("Unable to contact " . $req->get_normalized_http_url()); + } else if ($result->status == 403) { # not authorized, don't send again common_debug('403 result, deleting subscription', __FILE__); $subscription->delete(); return false; From 12636d9fb1c8d6bdcf27e068f3be2ad9c9636ee1 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 17:14:13 -0500 Subject: [PATCH 106/189] don't use SUP in group rss --- actions/grouprss.php | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/actions/grouprss.php b/actions/grouprss.php index 1a7b858b1e..de76a59600 100644 --- a/actions/grouprss.php +++ b/actions/grouprss.php @@ -111,13 +111,13 @@ class groupRssAction extends Rss10Action { $group = $this->group; - + if (is_null($group)) { return null; } - + $notice = $group->getNotices(0, ($limit == 0) ? NOTICES_PER_PAGE : $limit); - + while ($notice->fetch()) { $notices[] = clone($notice); } @@ -141,13 +141,4 @@ class groupRssAction extends Rss10Action { return $this->group->homepage_logo; } - - # override parent to add X-SUP-ID URL - - function initRss($limit=0) - { - $url = common_local_url('sup', null, $this->group->id); - header('X-SUP-ID: '.$url); - parent::initRss($limit); - } } From f39dd40ffa9ae9e52fb0e83c2dfaae5f65ca5f80 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 17:29:40 -0500 Subject: [PATCH 107/189] fix notices in lib/grouplist.php --- lib/grouplist.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/grouplist.php b/lib/grouplist.php index 6801ab4261..1b85474998 100644 --- a/lib/grouplist.php +++ b/lib/grouplist.php @@ -151,7 +151,7 @@ class GroupList extends Widget # If we're on a list with an owner (subscriptions or subscribers)... - if ($user && $user->id == $this->owner->id) { + if (!empty($user) && !empty($this->owner) && $user->id == $this->owner->id) { $this->showOwnerControls(); } From 3a999af4d905d3cd23ea9163f47b6ed5c35f606c Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 17:30:09 -0500 Subject: [PATCH 108/189] Change common_local_url() to take 4 arguments I changed common_local_url() to take an additional optional argument -- for query parameters. Being persnickety, I made it the third of four, and moved the last one ($fragment) down a slot. That required changing a couple of calls. --- actions/twitapistatuses.php | 2 +- actions/userrss.php | 11 +++++------ lib/router.php | 4 ++-- lib/util.php | 4 ++-- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/actions/twitapistatuses.php b/actions/twitapistatuses.php index 18e24c0f58..51c2565892 100644 --- a/actions/twitapistatuses.php +++ b/actions/twitapistatuses.php @@ -204,7 +204,7 @@ class TwitapistatusesAction extends TwitterapiAction # FriendFeed's SUP protocol # Also added RSS and Atom feeds - $suplink = common_local_url('sup', null, $user->id); + $suplink = common_local_url('sup', null, null, $user->id); header('X-SUP-ID: '.$suplink); # XXX: since diff --git a/actions/userrss.php b/actions/userrss.php index 04855cccad..a3e5a3aab7 100644 --- a/actions/userrss.php +++ b/actions/userrss.php @@ -46,13 +46,13 @@ class UserrssAction extends Rss10Action { $user = $this->user; - + if (is_null($user)) { return null; } - + $notice = $user->getNotices(0, ($limit == 0) ? NOTICES_PER_PAGE : $limit); - + while ($notice->fetch()) { $notices[] = clone($notice); } @@ -87,10 +87,10 @@ class UserrssAction extends Rss10Action } # override parent to add X-SUP-ID URL - + function initRss($limit=0) { - $url = common_local_url('sup', null, $this->user->id); + $url = common_local_url('sup', null, null, $this->user->id); header('X-SUP-ID: '.$url); parent::initRss($limit); } @@ -100,4 +100,3 @@ class UserrssAction extends Rss10Action return true; } } - diff --git a/lib/router.php b/lib/router.php index 0640a5911f..85425bed29 100644 --- a/lib/router.php +++ b/lib/router.php @@ -350,7 +350,7 @@ class Router return $this->m->match($path); } - function build($action, $args=null, $fragment=null) + function build($action, $args=null, $params=null, $fragment=null) { $action_arg = array('action' => $action); @@ -360,6 +360,6 @@ class Router $args = $action_arg; } - return $this->m->generate($args, null, $fragment); + return $this->m->generate($args, $params, $fragment); } } \ No newline at end of file diff --git a/lib/util.php b/lib/util.php index 46aa7b9df9..5345a08bba 100644 --- a/lib/util.php +++ b/lib/util.php @@ -705,10 +705,10 @@ function common_relative_profile($sender, $nickname, $dt=null) return null; } -function common_local_url($action, $args=null, $fragment=null) +function common_local_url($action, $args=null, $params=null, $fragment=null) { $r = Router::get(); - $path = $r->build($action, $args, $fragment); + $path = $r->build($action, $args, $params, $fragment); if ($path) { } if (common_config('site','fancy')) { From 5e816d7be208fc24419288234559c78da7391c8b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 20 Feb 2009 15:07:59 -0800 Subject: [PATCH 109/189] Fixed routing for direct messages and favorites in the API --- lib/router.php | 50 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/lib/router.php b/lib/router.php index d47ad71183..9d0d3a3f06 100644 --- a/lib/router.php +++ b/lib/router.php @@ -226,20 +226,31 @@ class Router // direct messages - $m->connect('api/direct_messages/:method', - array('action' => 'api', - 'apiaction' => 'direct_messages'), - array('method' => '(sent|new)(\.(xml|json|atom|rss))?')); + foreach (array('xml', 'json') as $e) { + $m->connect('api/direct_messages/new.'.$e, + array('action' => 'api', + 'apiaction' => 'direct_messages', + 'method' => 'create.'.$e)); + } + + foreach (array('xml', 'json', 'rss', 'atom') as $e) { + $m->connect('api/direct_messages.'.$e, + array('action' => 'api', + 'apiaction' => 'direct_messages', + 'method' => 'direct_messages.'.$e)); + } + + foreach (array('xml', 'json', 'rss', 'atom') as $e) { + $m->connect('api/direct_message/sent.'.$e, + array('action' => 'api', + 'apiaction' => 'direct_messages', + 'method' => 'sent.'.$e)); + } $m->connect('api/direct_messages/destroy/:argument', array('action' => 'api', 'apiaction' => 'direct_messages')); - $m->connect('api/:method', - array('action' => 'api', - 'apiaction' => 'direct_messages'), - array('method' => 'direct_messages(\.(xml|json|atom|rss))?')); - // friendships $m->connect('api/friendships/:method/:argument', @@ -269,10 +280,12 @@ class Router 'apiaction' => 'favorites', 'method' => 'favorites')); - $m->connect('api/:method', - array('action' => 'api', - 'apiaction' => 'favorites'), - array('method' => 'favorites(\.(xml|json|rss|atom))?')); + foreach (array('xml', 'json', 'rss', 'atom') as $e) { + $m->connect('api/favorites.'.$e, + array('action' => 'api', + 'apiaction' => 'favorites', + 'method' => 'favorites.'.$e)); + } // notifications @@ -345,7 +358,16 @@ class Router function map($path) { - return $this->m->match($path); + try { + $match = $this->m->match($path); + } catch (Net_URL_Mapper_InvalidException $e) { + common_log(LOG_ERR, "Problem getting route for $path - " . + $e->getMessage()); + $cac = new ClientErrorAction("Page not found.", 404); + $cac->showPage(); + } + + return $match; } function build($action, $args=null, $fragment=null) From f75c2328ccb87650e107e90b89b4d2a16d7a29cd Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 20 Feb 2009 23:47:24 +0000 Subject: [PATCH 110/189] Hooks for: local navigation --- EVENTS.txt | 6 ++++++ lib/action.php | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/EVENTS.txt b/EVENTS.txt index af0bee587c..37e2203d50 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -82,3 +82,9 @@ StartNoticeSave: before inserting a notice (good place for content filters) EndNoticeSave: after inserting a notice and related code - $notice: notice that was saved (with ID and URI) +StartShowLocalNavBlock: Showing the local nav menu +- $action: the current action + +EndShowLocalNavBlock: At the end of the local nav menu +- $action: the current action + diff --git a/lib/action.php b/lib/action.php index b1e700b670..a468c638cd 100644 --- a/lib/action.php +++ b/lib/action.php @@ -474,7 +474,10 @@ class Action extends HTMLOutputter // lawsuit function showCore() { $this->elementStart('div', array('id' => 'core')); - $this->showLocalNavBlock(); + if (Event::handle('StartShowLocalNavBlock', array($this))) { + $this->showLocalNavBlock(); + Event::handle('EndShowLocalNavBlock', array($this)); + } if (Event::handle('StartShowContentBlock', array($this))) { $this->showContentBlock(); Event::handle('EndShowContentBlock', array($this)); From 85694e3fa6669e3d59155e32f2cc2f8df3b9f89c Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 20 Feb 2009 23:54:17 +0000 Subject: [PATCH 111/189] Minor positioning: using absolute right instead of left --- theme/base/css/display.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index b5796374ec..be124f4330 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -900,7 +900,7 @@ left:0; left:29px; } .notice-options .notice_delete { -left:76px; +right:0; } .notice-options .notice_reply dt { display:none; From 1fdb35bbf105fe462dcc663ea20b6aa56d654001 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 20 Feb 2009 17:17:20 -0800 Subject: [PATCH 112/189] New doc page for Identi.ca badge and minor updates to badge's js --- doc-src/badge | 65 ++++++++++++++++++++++++++++++++++++++++++++ js/identica-badge.js | 3 +- lib/action.php | 2 ++ scripts/sitemap.php | 3 +- 4 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 doc-src/badge diff --git a/doc-src/badge b/doc-src/badge new file mode 100644 index 0000000000..1c368eb690 --- /dev/null +++ b/doc-src/badge @@ -0,0 +1,65 @@ +Install the %%site.name%% badge on you blog or web site to show the latest updates +from you and your friends! + + + + + +Things to try +-------------- + +* Click an avatar and the badge will refresh with that user's timeline +* Click a nickname to open a user's profile in your browser +* Click a notice's timestamp to view the notice in your browser +* @-replies and #tags are live links + +## Installation instructions + +Copy and paste the following JavaScript into an HTML page where +you want the badge to show up. Substitute your own ID in the user +parameter. + +
+	<script type="text/javascript" src="http://identi.ca/js/identica-badge.js">
+	{
+	   "user":"kentbrew",
+	   "server":"identi.ca",
+	   "headerText":" and friends"
+	}
+	</script>
+
+
+ + + +Valid parameters for the badge: +------------------------------- + +* user : defaults to 7000 (@kentbrew) +* headerText : defaults to empty +* height : defaults to 350px +* width : defaults to 300px +* background : defaults to #193441. If you set evenBackground, oddBackground, + and headerBackground, you won't see it at all. +* border : defaults to 1px solid black +* userColor : defaults to whatever link color is set to on your page +* headerBackground : defaults to transparent +* headerColor : defaults to white +* evenBackground : defaults to #fff +* oddBackground : defaults to #eee +* thumbnailBorder : 1px solid black +* thumbnailSize : defaults to 24px +* padding : defaults to 3px +* server : defaults to identi.ca + +Licence +------- + +Identi.ca badge by [Kent Brewster](http://kentbrewster.com/identica-badge/). +Licenced under [CC-BY-SA-3](http://kentbrewster.com/rights-and-permissions/). diff --git a/js/identica-badge.js b/js/identica-badge.js index 5c586b5d6a..869230b7a4 100644 --- a/js/identica-badge.js +++ b/js/identica-badge.js @@ -1,4 +1,5 @@ // identica badge -- updated to work with the native API, 12-4-2008 +// Modified to point to Identi.ca, 2-20-2009 by Zach // copyright Kent Brewster 2008 // see http://kentbrewster.com/identica-badge for info ( function() { @@ -127,7 +128,7 @@ var a = document.createElement('A'); a.innerHTML = 'get this'; a.target = '_blank'; - a.href = 'http://kentbrewster.com/identica-badge'; + a.href = 'http://identica/doc/badge'; $.s.f.appendChild(a); $.s.appendChild($.s.f); $.f.getUser(); diff --git a/lib/action.php b/lib/action.php index b1e700b670..0c4d0181db 100644 --- a/lib/action.php +++ b/lib/action.php @@ -657,6 +657,8 @@ class Action extends HTMLOutputter // lawsuit _('Source')); $this->menuItem(common_local_url('doc', array('title' => 'contact')), _('Contact')); + $this->menuItem(common_local_url('doc', array('title' => 'badge')), + _('Badge')); Event::handle('EndSecondaryNav', array($this)); } $this->elementEnd('ul'); diff --git a/scripts/sitemap.php b/scripts/sitemap.php index 51a9bbd757..39eb859bba 100755 --- a/scripts/sitemap.php +++ b/scripts/sitemap.php @@ -61,7 +61,8 @@ function standard_map() ) ); - $docs = array('about', 'faq', 'contact', 'im', 'openid', 'openmublog', 'privacy', 'source'); + $docs = array('about', 'faq', 'contact', 'im', 'openid', 'openmublog', + 'privacy', 'source', 'badge'); foreach($docs as $title) { $standard_map_urls .= url( From cdab8d55a96b61ce6cfbec697d95e3223751fd3f Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sun, 22 Feb 2009 17:01:41 -0800 Subject: [PATCH 113/189] Ticket #925 - make verify_credentials return 'Authorized' if no return type specified --- actions/twitapiaccount.php | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/actions/twitapiaccount.php b/actions/twitapiaccount.php index b7c09cc9dc..c19cd370d9 100644 --- a/actions/twitapiaccount.php +++ b/actions/twitapiaccount.php @@ -24,20 +24,19 @@ require_once(INSTALLDIR.'/lib/twitterapi.php'); class TwitapiaccountAction extends TwitterapiAction { - function verify_credentials($args, $apidata) + function verify_credentials($args, $apidata) { - - if ($apidata['content-type'] == 'xml') { - header('Content-Type: application/xml; charset=utf-8'); - print 'true'; - } elseif ($apidata['content-type'] == 'json') { - header('Content-Type: application/json; charset=utf-8'); - print '{"authorized":true}'; - } else { - common_user_error(_('API method not found!'), $code=404); - } - - } + if ($apidata['content-type'] == 'xml') { + header('Content-Type: application/xml; charset=utf-8'); + print 'true'; + } elseif ($apidata['content-type'] == 'json') { + header('Content-Type: application/json; charset=utf-8'); + print '{"authorized":true}'; + } else { + header('Content-Type: text/html; charset=utf-8'); + print 'Authorized'; + } + } function end_session($args, $apidata) { From 5e646ead492fc62b52f67af6c3a23295ef502345 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sun, 22 Feb 2009 18:01:55 -0800 Subject: [PATCH 114/189] Minor routing fix for friends_timeline API method --- lib/router.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/router.php b/lib/router.php index e842604e9a..a41d35f22c 100644 --- a/lib/router.php +++ b/lib/router.php @@ -213,7 +213,7 @@ class Router $m->connect('api/statuses/:method/:argument', array('action' => 'api', 'apiaction' => 'statuses'), - array('method' => '(user_timeline|show|destroy|friends|followers)')); + array('method' => '(user_timeline|friends_timeline|show|destroy|friends|followers)')); // users From cab322d21b8c8077192a1396bf13050d734c2aba Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sun, 22 Feb 2009 20:04:47 -0800 Subject: [PATCH 115/189] Ticket #1108 - Added 'social graph' methods to the API --- actions/twitapistatuses.php | 54 ++++++++++++++++++++++++++++++++----- lib/router.php | 27 +++++++++++++++++++ 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/actions/twitapistatuses.php b/actions/twitapistatuses.php index 51c2565892..216835026d 100644 --- a/actions/twitapistatuses.php +++ b/actions/twitapistatuses.php @@ -470,19 +470,28 @@ class TwitapistatusesAction extends TwitterapiAction return $this->subscriptions($apidata, 'subscribed', 'subscriber'); } + function friendsIDs($args, $apidata) + { + parent::handle($args); + return $this->subscriptions($apidata, 'subscribed', 'subscriber', true); + } + function followers($args, $apidata) { parent::handle($args); - return $this->subscriptions($apidata, 'subscriber', 'subscribed'); } - function subscriptions($apidata, $other_attr, $user_attr) + function followersIDs($args, $apidata) + { + parent::handle($args); + return $this->subscriptions($apidata, 'subscriber', 'subscribed', true); + } + + function subscriptions($apidata, $other_attr, $user_attr, $onlyIDs=false) { - # XXX: lite - - $this->auth_user = $apidate['user']; + $this->auth_user = $apidata['user']; $user = $this->get_user($apidata['api_arg'], $apidata); if (!$user) { @@ -514,7 +523,10 @@ class TwitapistatusesAction extends TwitterapiAction } $sub->orderBy('created DESC'); - $sub->limit(($page-1)*100, 100); + + if (!$onlyIDs) { + $sub->limit(($page-1)*100, 100); + } $others = array(); @@ -529,7 +541,13 @@ class TwitapistatusesAction extends TwitterapiAction $type = $apidata['content-type']; $this->init_document($type); - $this->show_profiles($others, $type); + + if ($onlyIDs) { + $this->showIDs($others, $type); + } else { + $this->show_profiles($others, $type); + } + $this->end_document($type); } @@ -555,6 +573,28 @@ class TwitapistatusesAction extends TwitterapiAction } } + function showIDs($profiles, $type) + { + switch ($type) { + case 'xml': + $this->elementStart('ids'); + foreach ($profiles as $profile) { + $this->element('id', null, $profile->id); + } + $this->elementEnd('ids'); + break; + case 'json': + $ids = array(); + foreach ($profiles as $profile) { + $ids[] = (int)$profile->id; + } + print json_encode($ids); + break; + default: + $this->clientError(_('unsupported file type')); + } + } + function featured($args, $apidata) { parent::handle($args); diff --git a/lib/router.php b/lib/router.php index a41d35f22c..b18a5523e9 100644 --- a/lib/router.php +++ b/lib/router.php @@ -265,6 +265,33 @@ class Router 'apiaction' => 'friendships'), array('method' => 'exists(\.(xml|json|rss|atom))')); + + // Social graph + + $m->connect('api/friends/ids/:argument', + array('action' => 'api', + 'apiaction' => 'statuses', + 'method' => 'friendsIDs')); + + foreach (array('xml', 'json') as $e) { + $m->connect('api/friends/ids.'.$e, + array('action' => 'api', + 'apiaction' => 'statuses', + 'method' => 'friendsIDs.'.$e)); + } + + $m->connect('api/followers/ids/:argument', + array('action' => 'api', + 'apiaction' => 'statuses', + 'method' => 'followersIDs')); + + foreach (array('xml', 'json') as $e) { + $m->connect('api/followers/ids.'.$e, + array('action' => 'api', + 'apiaction' => 'statuses', + 'method' => 'followersIDs.'.$e)); + } + // account $m->connect('api/account/:method', From 8c3fe83c645265a11a3fca80cac2ac48ea72cdab Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 23 Feb 2009 13:27:22 -0800 Subject: [PATCH 116/189] Make allrss.php work with phpcs --- actions/allrss.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/actions/allrss.php b/actions/allrss.php index 05787f3f73..0114c43962 100644 --- a/actions/allrss.php +++ b/actions/allrss.php @@ -53,7 +53,9 @@ class AllrssAction extends Rss10Action /** * Initialization. - * + * + * @param array $args Web and URL arguments + * * @return boolean false if user doesn't exist */ function prepare($args) @@ -81,7 +83,7 @@ class AllrssAction extends Rss10Action { $user = $this->user; $notice = $user->noticesWithFriends(0, $limit); - + while ($notice->fetch()) { $notices[] = clone($notice); } @@ -104,7 +106,8 @@ class AllrssAction extends Rss10Action 'link' => common_local_url('all', array('nickname' => $user->nickname)), - 'description' => sprintf(_('Feed for friends of %s'), $user->nickname)); + 'description' => sprintf(_('Feed for friends of %s'), + $user->nickname)); return $c; } @@ -123,10 +126,5 @@ class AllrssAction extends Rss10Action $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); return $avatar ? $avatar->url : null; } - - function isReadOnly() - { - return true; - } } From d30590de23f2b9a138ec6923016c4e9af6b9a989 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 24 Feb 2009 04:31:31 +0000 Subject: [PATCH 117/189] Print stylesheet --- lib/action.php | 4 ++++ theme/base/css/print.css | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 theme/base/css/print.css diff --git a/lib/action.php b/lib/action.php index 455ebeff0b..dd7dd44e7f 100644 --- a/lib/action.php +++ b/lib/action.php @@ -173,6 +173,10 @@ class Action extends HTMLOutputter // lawsuit // TODO: "handheld" CSS for other mobile devices 'media' => 'only screen and (max-device-width: 480px)')); // Mobile WebKit } + $this->element('link', array('rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => theme_path('css/print.css', 'base') . '?version=' . LACONICA_VERSION, + 'media' => 'print')); Event::handle('EndShowLaconicaStyles', array($this)); } if (Event::handle('StartShowUAStyles', array($this))) { diff --git a/theme/base/css/print.css b/theme/base/css/print.css new file mode 100644 index 0000000000..cf3ac0391c --- /dev/null +++ b/theme/base/css/print.css @@ -0,0 +1,28 @@ +body { font-size:12pt; } +a:after { background-color:#fff; } +a:not([href^="#"]):after { content:" ( "attr(href)" ) "; } +a:not([href^="http:"]):after { content: " ( http://identi.ca/"attr(href)" ) "; } +a[href^="/"]:after { content: " ( http://identi.ca" attr(href) " ) "; } + +img { border:none; } +p { orphans: 2; widows: 1; } + +#site_nav_global_primary, +#site_nav_local_views, +#form_notice, +.pagination, +#site_nav_global_secondary, +.entity_actions, +.notice-options, +#aside_primary { +display:none; +} + +.timestamp dt, .timestamp dd, +.device dt, .device dd { +display:inline; +} + +.notices li { +margin-bottom:18px; +} From 240ea969214fe3d18c96d0f9ba1d2b5af63aa54d Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 24 Feb 2009 05:09:23 +0000 Subject: [PATCH 118/189] Print stylesheet - Added license and cleanup --- theme/base/css/print.css | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/theme/base/css/print.css b/theme/base/css/print.css index cf3ac0391c..2da3e5e444 100644 --- a/theme/base/css/print.css +++ b/theme/base/css/print.css @@ -1,8 +1,14 @@ -body { font-size:12pt; } +/** theme: base + * + * @package Laconica + * @author Sarven Capadisli + * @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/ + */ + a:after { background-color:#fff; } a:not([href^="#"]):after { content:" ( "attr(href)" ) "; } -a:not([href^="http:"]):after { content: " ( http://identi.ca/"attr(href)" ) "; } -a[href^="/"]:after { content: " ( http://identi.ca" attr(href) " ) "; } img { border:none; } p { orphans: 2; widows: 1; } @@ -14,7 +20,8 @@ p { orphans: 2; widows: 1; } #site_nav_global_secondary, .entity_actions, .notice-options, -#aside_primary { +#aside_primary, +.form_subcription_edit .submit { display:none; } @@ -23,6 +30,7 @@ display:none; display:inline; } +.profiles li, .notices li { margin-bottom:18px; } From 7af6f5392be2b847c4c026f2632fc1c900338b81 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 26 Feb 2009 19:56:31 +0000 Subject: [PATCH 119/189] Hook for setting document content type, charset, language, DOCTYPE and html element properties --- EVENTS.txt | 6 ++++++ lib/action.php | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/EVENTS.txt b/EVENTS.txt index 37e2203d50..ed461ee9fe 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -88,3 +88,9 @@ StartShowLocalNavBlock: Showing the local nav menu EndShowLocalNavBlock: At the end of the local nav menu - $action: the current action +StartShowHTML: Chance to set document content type, charset, language, DOCTYPE and html element properties +- $action: the current action + +EndShowHTML: Showing after the html element +- $action: the current action + diff --git a/lib/action.php b/lib/action.php index dd7dd44e7f..9c71a153dd 100644 --- a/lib/action.php +++ b/lib/action.php @@ -93,7 +93,10 @@ class Action extends HTMLOutputter // lawsuit */ function showPage() { - $this->startHTML(); + if (Event::handle('StartShowHTML', array($this))) { + $this->startHTML(); + Event::handle('EndShowHTML', array($this)); + } $this->showHead(); $this->showBody(); $this->endHTML(); From 478192fa3b2375a0ff0a5ff03956381e5da7a6ac Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 26 Feb 2009 13:22:41 -0800 Subject: [PATCH 120/189] add conversation id to notice --- db/laconica.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/db/laconica.sql b/db/laconica.sql index 012270b51e..254cf5fabf 100644 --- a/db/laconica.sql +++ b/db/laconica.sql @@ -114,8 +114,10 @@ create table notice ( reply_to integer comment 'notice replied to (usually a guess)' references notice (id), is_local tinyint default 0 comment 'notice was generated by a user', source varchar(32) comment 'source of comment, like "web", "im", or "clientname"', + conversation integer comment 'id of root notice in this conversation' references notice (id), index notice_profile_id_idx (profile_id), + index notice_conversation_idx (conversation), index notice_created_idx (created), FULLTEXT(content) ) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_bin; From 2bf5b23016ab4929a1c9197907d4a621bb888742 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 26 Feb 2009 13:36:10 -0800 Subject: [PATCH 121/189] fix notice in register --- actions/register.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/actions/register.php b/actions/register.php index 5d7a8ce690..5c6fe39d3d 100644 --- a/actions/register.php +++ b/actions/register.php @@ -131,11 +131,13 @@ class RegisterAction extends Action $code = $this->trimmed('code'); + $invite = null; + if ($code) { $invite = Invitation::staticGet($code); } - if (common_config('site', 'inviteonly') && !($code && $invite)) { + if (common_config('site', 'inviteonly') && !($code && !empty($invite))) { $this->clientError(_('Sorry, only invited people can register.')); return; } @@ -341,6 +343,8 @@ class RegisterAction extends Action { $code = $this->trimmed('code'); + $invite = null; + if ($code) { $invite = Invitation::staticGet($code); } @@ -377,7 +381,7 @@ class RegisterAction extends Action _('Same as password above. Required.')); $this->elementEnd('li'); $this->elementStart('li'); - if ($invite && $invite->address_type == 'email') { + if (!empty($invite) && $invite->address_type == 'email') { $this->input('email', _('Email'), $invite->address, _('Used only for updates, announcements, '. 'and password recovery')); From ee92d0b0a8870143ee462b5833b0adf47673a613 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 26 Feb 2009 13:36:27 -0800 Subject: [PATCH 122/189] fix notice in action with caching --- lib/action.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/action.php b/lib/action.php index 455ebeff0b..f19a047cfb 100644 --- a/lib/action.php +++ b/lib/action.php @@ -812,11 +812,12 @@ class Action extends HTMLOutputter // lawsuit } if ($lm) { header('Last-Modified: ' . date(DATE_RFC1123, $lm)); - $if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE']; - if ($if_modified_since) { + if (array_key_exists('HTTP_IF_MODIFIED_SINCE', $_SERVER)) { + $if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE']; $ims = strtotime($if_modified_since); if ($lm <= $ims) { - $if_none_match = $_SERVER['HTTP_IF_NONE_MATCH']; + $if_none_match = (array_key_exists('HTTP_IF_NONE_MATCH', $_SERVER)) ? + $_SERVER['HTTP_IF_NONE_MATCH'] : null; if (!$if_none_match || !$etag || $this->_hasEtag($etag, $if_none_match)) { From 2674d40b62934a579c083f4f4f7f1173d64d777b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 26 Feb 2009 13:36:38 -0800 Subject: [PATCH 123/189] fix notice with twitter broadcast --- lib/twitter.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/twitter.php b/lib/twitter.php index deb6fd276b..8a54afb9c4 100644 --- a/lib/twitter.php +++ b/lib/twitter.php @@ -210,7 +210,7 @@ function save_twitter_friends($user, $twitter_id, $screen_name, $password) function is_twitter_bound($notice, $flink) { // Check to see if notice should go to Twitter - if (($flink->noticesync & FOREIGN_NOTICE_SEND)) { + if (!empty($flink) && ($flink->noticesync & FOREIGN_NOTICE_SEND)) { // If it's not a Twitter-style reply, or if the user WANTS to send replies. if (!preg_match('/^@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) || @@ -218,7 +218,7 @@ function is_twitter_bound($notice, $flink) { return true; } } - + return false; } @@ -227,10 +227,10 @@ function broadcast_twitter($notice) global $config; $success = true; - $flink = Foreign_link::getByUserID($notice->profile_id, + $flink = Foreign_link::getByUserID($notice->profile_id, TWITTER_SERVICE); - - // XXX: Not sure WHERE to check whether a notice should go to + + // XXX: Not sure WHERE to check whether a notice should go to // Twitter. Should we even put in the queue if it shouldn't? --Zach if (is_twitter_bound($notice, $flink)) { @@ -245,7 +245,7 @@ function broadcast_twitter($notice) $options = array( CURLOPT_USERPWD => "$twitter_user:$twitter_password", CURLOPT_POST => true, - CURLOPT_POSTFIELDS => + CURLOPT_POSTFIELDS => array( 'status' => $statustxt, 'source' => $config['integration']['source'] @@ -293,7 +293,7 @@ function broadcast_twitter($notice) $success = false; } } - + return $success; } From d1f46d85521876678152f2d06b61cbf212356f6f Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 26 Feb 2009 13:37:00 -0800 Subject: [PATCH 124/189] add conversation to notice class --- classes/Notice.php | 1 + classes/laconica.ini | 1 + 2 files changed, 2 insertions(+) diff --git a/classes/Notice.php b/classes/Notice.php index 8300667fa4..ce5243841f 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -46,6 +46,7 @@ class Notice extends Memcached_DataObject public $reply_to; // int(4) public $is_local; // tinyint(1) public $source; // varchar(32) + public $conversation; // int(4) /* Static get */ function staticGet($k,$v=NULL) { diff --git a/classes/laconica.ini b/classes/laconica.ini index 5fd2cd1f86..aaa7035a44 100755 --- a/classes/laconica.ini +++ b/classes/laconica.ini @@ -168,6 +168,7 @@ modified = 384 reply_to = 1 is_local = 17 source = 2 +conversation = 1 [notice__keys] id = N From baf5afb26339f410a24a7ae5064d6c7a9029e86c Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 26 Feb 2009 13:45:43 -0800 Subject: [PATCH 125/189] save conversation id when saving a notice --- classes/Notice.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/classes/Notice.php b/classes/Notice.php index ce5243841f..c321311acc 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -156,13 +156,20 @@ class Notice extends Memcached_DataObject $notice->query('BEGIN'); - $notice->reply_to = $reply_to; $notice->created = common_sql_now(); $notice->content = common_shorten_links($content); $notice->rendered = common_render_content($notice->content, $notice); $notice->source = $source; $notice->uri = $uri; + if (!empty($reply_to)) { + $reply_notice = Notice::staticGet('id', $reply_to); + if (!empty($reply_notice)) { + $notice->reply_to = $reply_to; + $notice->conversation = $reply_notice->conversation; + } + } + if (Event::handle('StartNoticeSave', array(&$notice))) { $id = $notice->insert(); @@ -707,6 +714,7 @@ class Notice extends Memcached_DataObject if ($recipient_notice) { $orig = clone($this); $this->reply_to = $recipient_notice->id; + $this->conversation = $recipient_notice->conversation; $this->update($orig); } } @@ -756,6 +764,14 @@ class Notice extends Memcached_DataObject } } + // If it's not a reply, make it the root of a new conversation + + if (empty($this->conversation)) { + $orig = clone($this); + $this->conversation = $this->id; + $this->update($orig); + } + foreach (array_keys($replied) as $recipient) { $user = User::staticGet('id', $recipient); if ($user) { From 42eecfabca67e5caee1f4e5894b711cacd0a9f9d Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Sat, 21 Feb 2009 18:51:56 +0100 Subject: [PATCH 126/189] Adds some missing routes. --- lib/router.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/router.php b/lib/router.php index b18a5523e9..b142022f0a 100644 --- a/lib/router.php +++ b/lib/router.php @@ -117,6 +117,11 @@ class Router $m->connect('main/openid', array('action' => 'openidlogin')); $m->connect('main/remote', array('action' => 'remotesubscribe')); + foreach (array('requesttoken', 'accesstoken', 'userauthorization', + 'postnotice', 'updateprofile') as $action) { + $m->connect('index.php?action=' . $action, array('action' => $action)); + } + // settings foreach (array('profile', 'avatar', 'password', 'openid', 'im', @@ -411,4 +416,4 @@ class Router return $this->m->generate($args, $params, $fragment); } -} \ No newline at end of file +} From 89197210cf706683c8ab22deedc43724fa1fe235 Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Sat, 21 Feb 2009 19:04:27 +0100 Subject: [PATCH 127/189] Fixes #1067: Avatar stretched on authorize remote subscription page. The img tag used a wrong class which had a width: 100% applied. --- actions/userauthorization.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/userauthorization.php b/actions/userauthorization.php index ed17ceec97..0dc1841d4f 100644 --- a/actions/userauthorization.php +++ b/actions/userauthorization.php @@ -105,7 +105,7 @@ class UserauthorizationAction extends Action $this->elementStart('div', 'profile'); if ($avatar) { $this->element('img', array('src' => $avatar, - 'class' => 'avatar profile', + 'class' => 'avatar', 'width' => AVATAR_PROFILE_SIZE, 'height' => AVATAR_PROFILE_SIZE, 'alt' => $nickname)); From 616fd16bc528ce78d7fc1fa8a6ad5a67f10ae5eb Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Sat, 21 Feb 2009 19:43:18 +0100 Subject: [PATCH 128/189] Auth_Yadis_Yadis::PlainHTTPFetcher expects plain arrays, not hashes. --- actions/finishremotesubscribe.php | 2 +- actions/remotesubscribe.php | 3 +-- lib/omb.php | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/actions/finishremotesubscribe.php b/actions/finishremotesubscribe.php index 76db887deb..acfacbdc1c 100644 --- a/actions/finishremotesubscribe.php +++ b/actions/finishremotesubscribe.php @@ -283,7 +283,7 @@ class FinishremotesubscribeAction extends Action $fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); $result = $fetcher->post($req->get_normalized_http_url(), $req->to_postdata(), - array('User-Agent' => 'Laconica/' . LACONICA_VERSION)); + array('User-Agent: Laconica/' . LACONICA_VERSION)); common_debug('got result: "'.print_r($result,true).'"', __FILE__); diff --git a/actions/remotesubscribe.php b/actions/remotesubscribe.php index f727a63b82..7ea7acd6d3 100644 --- a/actions/remotesubscribe.php +++ b/actions/remotesubscribe.php @@ -321,8 +321,7 @@ class RemotesubscribeAction extends Action $result = $fetcher->post($req->get_normalized_http_url(), $req->to_postdata(), - array('User-Agent' => 'Laconica/' . LACONICA_VERSION)); - + array('User-Agent: Laconica/' . LACONICA_VERSION)); if ($result->status != 200) { return null; } diff --git a/lib/omb.php b/lib/omb.php index 29e14c75f7..befcf4666a 100644 --- a/lib/omb.php +++ b/lib/omb.php @@ -206,7 +206,7 @@ function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret) $result = $fetcher->post($req->get_normalized_http_url(), $req->to_postdata(), - array('User-Agent' => 'Laconica/' . LACONICA_VERSION)); + array('User-Agent: Laconica/' . LACONICA_VERSION)); common_debug('Got HTTP result "'.print_r($result,true).'"', __FILE__); @@ -291,7 +291,7 @@ function omb_update_profile($profile, $remote_profile, $subscription) common_debug('postdata = '.$req->to_postdata(), __FILE__); $result = $fetcher->post($req->get_normalized_http_url(), $req->to_postdata(), - array('User-Agent' => 'Laconica/' . LACONICA_VERSION)); + array('User-Agent: Laconica/' . LACONICA_VERSION)); common_debug('Got HTTP result "'.print_r($result,true).'"', __FILE__); From d4b6e7266ad80add5ff29c947cfad4bd5dbd3c8a Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Sat, 21 Feb 2009 22:41:24 +0100 Subject: [PATCH 129/189] Correctly handle avatars at updateProfile --- actions/updateprofile.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/actions/updateprofile.php b/actions/updateprofile.php index 898c535432..4751a04ff3 100644 --- a/actions/updateprofile.php +++ b/actions/updateprofile.php @@ -162,7 +162,13 @@ class UpdateprofileAction extends Action if ($avatar) { $temp_filename = tempnam(sys_get_temp_dir(), 'listenee_avatar'); copy($avatar, $temp_filename); - if (!$profile->setOriginal($temp_filename)) { + $imagefile = new ImageFile($profile->id, $temp_filename); + $filename = Avatar::filename($profile->id, + image_type_to_extension($imagefile->type), + null, + common_timestamp()); + rename($temp_filename, Avatar::path($filename)); + if (!$profile->setOriginal($filename)) { $this->serverError(_('Could not save avatar info'), 500); return false; } From c87349350d0422157575f93ab4dd9abc108cc8d8 Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Sat, 21 Feb 2009 22:48:30 +0100 Subject: [PATCH 130/189] Add finishremotesubscribe to the unrouted actions list. --- lib/router.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/router.php b/lib/router.php index b142022f0a..e5b8e7d236 100644 --- a/lib/router.php +++ b/lib/router.php @@ -118,7 +118,7 @@ class Router $m->connect('main/remote', array('action' => 'remotesubscribe')); foreach (array('requesttoken', 'accesstoken', 'userauthorization', - 'postnotice', 'updateprofile') as $action) { + 'postnotice', 'updateprofile', 'finishremotesubscribe') as $action) { $m->connect('index.php?action=' . $action, array('action' => $action)); } From a90a8da5c3c445b11e1e2bf0d0530eb48fa146c0 Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Sat, 21 Feb 2009 22:50:59 +0100 Subject: [PATCH 131/189] Broadcast profile via OMB after avatar change. --- actions/avatarsettings.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/actions/avatarsettings.php b/actions/avatarsettings.php index f38a44a24a..6545d94893 100644 --- a/actions/avatarsettings.php +++ b/actions/avatarsettings.php @@ -324,13 +324,12 @@ class AvatarsettingsAction extends AccountSettingsAction return; } - // If image is not being cropped assume pos & dimentions of original + // If image is not being cropped assume pos & dimensions of original. $dest_x = $this->arg('avatar_crop_x') ? $this->arg('avatar_crop_x'):0; $dest_y = $this->arg('avatar_crop_y') ? $this->arg('avatar_crop_y'):0; $dest_w = $this->arg('avatar_crop_w') ? $this->arg('avatar_crop_w'):$filedata['width']; $dest_h = $this->arg('avatar_crop_h') ? $this->arg('avatar_crop_h'):$filedata['height']; - $size = min($dest_w, $dest_h); - $size = ($size > MAX_ORIGINAL) ? MAX_ORIGINAL:$size; + $size = min($dest_w, $dest_h, MAX_ORIGINAL); $user = common_current_user(); $profile = $user->getProfile(); @@ -343,6 +342,7 @@ class AvatarsettingsAction extends AccountSettingsAction unset($_SESSION['FILEDATA']); $this->mode = 'upload'; $this->showForm(_('Avatar updated.'), true); + common_broadcast_profile($profile); } else { $this->showForm(_('Failed updating avatar.')); } From fa82722e5abd2e1cbf75050aea594f65a384d8d7 Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Sun, 22 Feb 2009 13:31:50 +0100 Subject: [PATCH 132/189] More routes. --- lib/router.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/router.php b/lib/router.php index e5b8e7d236..f70b67f383 100644 --- a/lib/router.php +++ b/lib/router.php @@ -133,6 +133,7 @@ class Router foreach (array('group', 'people', 'notice') as $s) { $m->connect('search/'.$s, array('action' => $s.'search')); + $m->connect('search/'.$s.'?q=:q', array('action' => $s.'search'), array('q' => '.+')); } $m->connect('search/notice/rss', array('action' => 'noticesearchrss')); @@ -140,6 +141,9 @@ class Router // notice $m->connect('notice/new', array('action' => 'newnotice')); + $m->connect('notice/new?replyto=:replyto', + array('action' => 'newnotice'), + array('replyto' => '[A-Za-z0-9_-]+')); $m->connect('notice/:notice', array('action' => 'shownotice'), array('notice' => '[0-9]+')); From 210647a56c0ee917ec5bb7d4753ad2603d28d7c4 Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Sun, 22 Feb 2009 13:32:14 +0100 Subject: [PATCH 133/189] Fix replyto parameter in newnotice --- actions/newnotice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/newnotice.php b/actions/newnotice.php index 9face96443..9f44d25165 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -253,7 +253,7 @@ class NewnoticeAction extends Action } } - $notice_form = new NoticeForm($this, $content); + $notice_form = new NoticeForm($this, '', $content); $notice_form->show(); } From d005b370712943eed3edae2ff82d38dfa3d42a92 Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Sun, 22 Feb 2009 13:38:16 +0100 Subject: [PATCH 134/189] Fixes #1241: in-reply-to links were broken due to copy and paste error (Variable name was wrong). --- actions/noticesearch.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/noticesearch.php b/actions/noticesearch.php index dc58d7528a..0bbaa02560 100644 --- a/actions/noticesearch.php +++ b/actions/noticesearch.php @@ -201,7 +201,7 @@ class NoticesearchAction extends SearchAction if ($notice->reply_to) { $replyurl = common_local_url('shownotice', - array('notice' => $this->notice->reply_to)); + array('notice' => $notice->reply_to)); $this->elementStart('dl', 'response'); $this->element('dt', null, _('To')); $this->elementStart('dd'); From bdb8c12d975846ce128619e672b8064b8f5f890a Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Sun, 22 Feb 2009 14:02:17 +0100 Subject: [PATCH 135/189] Completely refactored noticesearch list, now using subclassing for highlighting. Fixes #1240 and probably other bugs. --- actions/noticesearch.php | 152 +++++++++++---------------------------- 1 file changed, 41 insertions(+), 111 deletions(-) diff --git a/actions/noticesearch.php b/actions/noticesearch.php index 0bbaa02560..83e59dd9ae 100644 --- a/actions/noticesearch.php +++ b/actions/noticesearch.php @@ -113,123 +113,58 @@ class NoticesearchAction extends SearchAction } else { $cnt = $notice->find(); } - if ($cnt > 0) { - $terms = preg_split('/[\s,]+/', $q); - $this->elementStart('ul', array('class' => 'notices')); - for ($i = 0; $i < min($cnt, NOTICES_PER_PAGE); $i++) { - if ($notice->fetch()) { - $this->showNotice($notice, $terms); - } else { - // shouldn't happen! - break; - } - } - $this->elementEnd('ul'); - } else { + if ($cnt === 0) { $this->element('p', 'error', _('No results')); - } - - $this->pagination($page > 1, $cnt > NOTICES_PER_PAGE, - $page, 'noticesearch', array('q' => $q)); - } - - /** - * Show notice - * - * @param class $notice notice - * @param array $terms terms to highlight - * - * @return void - * - * @todo refactor and combine with StreamAction::showNotice() - */ - function showNotice($notice, $terms) - { - $profile = $notice->getProfile(); - if (!$profile) { - common_log_db_error($notice, 'SELECT', __FILE__); - $this->serverError(_('Notice without matching profile')); return; } - // XXX: RDFa - $this->elementStart('li', array('class' => 'hentry notice', - 'id' => 'notice-' . $notice->id)); + $terms = preg_split('/[\s,]+/', $q); + $nl = new SearchNoticeList($notice, $this, $terms); - $this->elementStart('div', 'entry-title'); - $this->elementStart('span', 'vcard author'); - $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE); - $this->elementStart('a', array('href' => $profile->profileurl, - 'class' => 'url')); - $this->element('img', array('src' => ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_STREAM_SIZE), - 'class' => 'avatar photo', - 'width' => AVATAR_STREAM_SIZE, - 'height' => AVATAR_STREAM_SIZE, - 'alt' => - ($profile->fullname) ? $profile->fullname : - $profile->nickname)); - $this->element('span', 'nickname fn', $profile->nickname); - $this->elementEnd('a'); - $this->elementEnd('span'); + $cnt = $nl->show(); + $this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE, + $this->page, 'noticesearch', array('q' => $q)); + } + function isReadOnly() + { + return true; + } +} + +class SearchNoticeList extends NoticeList { + function __construct($notice, $out=null, $terms) + { + parent::__construct($notice, $out); + $this->terms = $terms; + } + + function newListItem($notice) + { + return new SearchNoticeListItem($notice, $this->out, $this->terms); + } +} + +class SearchNoticeListItem extends NoticeListItem { + function __construct($notice, $out=null, $terms) + { + parent::__construct($notice, $out); + $this->terms = $terms; + } + + function showContent() + { // FIXME: URL, image, video, audio - $this->elementStart('p', array('class' => 'entry-content')); - if ($notice->rendered) { - $this->raw($this->highlight($notice->rendered, $terms)); + $this->out->elementStart('p', array('class' => 'entry-content')); + if ($this->notice->rendered) { + $this->out->raw($this->highlight($this->notice->rendered, $this->terms)); } else { // XXX: may be some uncooked notices in the DB, // we cook them right now. This should probably disappear in future // versions (>> 0.4.x) - $this->raw($this->highlight(common_render_content($notice->content, $notice), $terms)); + $this->out->raw($this->highlight(common_render_content($this->notice->content, $this->notice), $this->terms)); } - $this->elementEnd('p'); - $this->elementEnd('div'); + $this->out->elementEnd('p'); - $noticeurl = common_local_url('shownotice', array('notice' => $notice->id)); - $this->elementStart('div', 'entry-content'); - $this->elementStart('dl', 'timestamp'); - $this->element('dt', null, _('Published')); - $this->elementStart('dd', null); - $this->elementStart('a', array('rel' => 'bookmark', - 'href' => $noticeurl)); - $dt = common_date_iso8601($notice->created); - $this->element('abbr', array('class' => 'published', - 'title' => $dt), - common_date_string($notice->created)); - $this->elementEnd('a'); - $this->elementEnd('dd'); - $this->elementEnd('dl'); - - if ($notice->reply_to) { - $replyurl = common_local_url('shownotice', - array('notice' => $notice->reply_to)); - $this->elementStart('dl', 'response'); - $this->element('dt', null, _('To')); - $this->elementStart('dd'); - $this->element('a', array('href' => $replyurl, - 'rel' => 'in-reply-to'), - _('in reply to')); - $this->elementEnd('dd'); - $this->elementEnd('dl'); - } - $this->elementEnd('div'); - - $this->elementStart('div', 'notice-options'); - - $reply_url = common_local_url('newnotice', - array('replyto' => $profile->nickname)); - - $this->elementStart('dl', 'notice_reply'); - $this->element('dt', null, _('Reply to this notice')); - $this->elementStart('dd'); - $this->elementStart('a', array('href' => $reply_url, - 'title' => _('Reply to this notice'))); - $this->text(_('Reply')); - $this->element('span', 'notice_id', $notice->id); - $this->elementEnd('a'); - $this->elementEnd('dd'); - $this->elementEnd('dl'); - $this->elementEnd('div'); - $this->elementEnd('li'); } /** @@ -242,7 +177,7 @@ class NoticesearchAction extends SearchAction */ function highlight($text, $terms) { - /* Highligh serach terms */ + /* Highligh search terms */ $pattern = '/('.implode('|', array_map('htmlspecialchars', $terms)).')/i'; $result = preg_replace($pattern, '\\1', $text); @@ -253,10 +188,5 @@ class NoticesearchAction extends SearchAction } while ($count); return $result; } - - function isReadOnly() - { - return true; - } } From 32e0fb148312bb2a052111513ed71fc4948a9fb7 Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Sun, 22 Feb 2009 14:27:09 +0100 Subject: [PATCH 136/189] Route for remote subscribe link on profile page. --- lib/router.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/router.php b/lib/router.php index f70b67f383..95ce77e5ef 100644 --- a/lib/router.php +++ b/lib/router.php @@ -116,6 +116,7 @@ class Router $m->connect('main/openid', array('action' => 'openidlogin')); $m->connect('main/remote', array('action' => 'remotesubscribe')); + $m->connect('main/remote?nickname=:nickname', array('action' => 'remotesubscribe'), array('nickname' => '[A-Za-z0-9_-]+')); foreach (array('requesttoken', 'accesstoken', 'userauthorization', 'postnotice', 'updateprofile', 'finishremotesubscribe') as $action) { From 120eb77400843669980850882dfae83ca7f8e7e7 Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Sun, 22 Feb 2009 17:45:26 +0100 Subject: [PATCH 137/189] Fixes #1258: A period in a hashtag leads to the tag being interpreted as url and hence breaking the tag. --- lib/util.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/util.php b/lib/util.php index 5345a08bba..18e4f310ce 100644 --- a/lib/util.php +++ b/lib/util.php @@ -456,6 +456,9 @@ function common_replace_urls_callback($text, $callback) { if (!in_array($url_parts[2], $tlds)) continue; + // Make sure we didn't capture a hash tag + if (strpos($url, '#') === 0) continue; + // Put the url back the way we found it. $url = (mb_strpos($orig_url, htmlspecialchars($url)) === FALSE) ? $url:htmlspecialchars($url); From 3f7d70c5e4c5e91d552cc629a9371109cb859bc3 Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Wed, 25 Feb 2009 01:36:58 +0100 Subject: [PATCH 138/189] Hopefully fixes #1260: Mess with norwegian languages. Moreover corrected ltr/rtl for Italian and Hebrew and added Finnish. --- lib/language.php | 61 +++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/lib/language.php b/lib/language.php index a73b73f280..79e9030ae4 100644 --- a/lib/language.php +++ b/lib/language.php @@ -94,40 +94,43 @@ function get_nice_language_list() * Get a list of all languages that are enabled in the default config * * This should ONLY be called when setting up the default config in common.php. - * Any other attempt to get a list of lanugages should instead call + * Any other attempt to get a list of languages should instead call * common_config('site','languages') * * @return array mapping of language codes to language info */ function get_all_languages() { return array( - 'bg' => array('q' => 0.8, 'lang' => 'bg_BG', 'name' => 'Bulgarian', 'direction' => 'ltr'), - 'ca' => array('q' => 0.5, 'lang' => 'ca_ES', 'name' => 'Catalan', 'direction' => 'ltr'), - 'cs' => array('q' => 0.5, 'lang' => 'cs_CZ', 'name' => 'Czech', 'direction' => 'ltr'), - 'de' => array('q' => 0.5, 'lang' => 'de_DE', 'name' => 'German', 'direction' => 'ltr'), - 'el' => array('q' => 0.1, 'lang' => 'el', 'name' => 'Greek', 'direction' => 'ltr'), - 'en-us' => array('q' => 1, 'lang' => 'en_US', 'name' => 'English (US)', 'direction' => 'ltr'), - 'en-gb' => array('q' => 0.3, 'lang' => 'en_GB', 'name' => 'English (British)', 'direction' => 'ltr'), - 'en' => array('q' => 1, 'lang' => 'en', 'name' => 'English', 'direction' => 'ltr'), - 'es' => array('q' => 0.5, 'lang' => 'es', 'name' => 'Spanish', 'direction' => 'ltr'), - 'fr-fr' => array('q' => 0.2, 'lang' => 'fr_FR', 'name' => 'French', 'direction' => 'ltr'), - 'he' => array('q' => 0.5, 'lang' => 'he_IL', 'name' => 'Hebrew', 'direction' => 'ltr'), - 'it' => array('q' => 0.9, 'lang' => 'it_IT', 'name' => 'Italian', 'direction' => 'rtl'), - 'jp' => array('q' => 0.5, 'lang' => 'ja_JP', 'name' => 'Japanese', 'direction' => 'ltr'), -# 'ko' => array('q' => 0, 'lang' => 'ko', 'name' => 'Korean', 'direction' => 'ltr'), - 'mk' => array('q' => 0.5, 'lang' => 'mk_MK', 'name' => 'Macedonian', 'direction' => 'ltr'), - 'nb' => array('q' => 0.1, 'lang' => 'nb_NO', 'name' => 'Norwegian (bokmal)', 'direction' => 'ltr'), - 'nl' => array('q' => 0.5, 'lang' => 'nl_NL', 'name' => 'Dutch', 'direction' => 'ltr'), - 'pl' => array('q' => 0.5, 'lang' => 'pl_PL', 'name' => 'Polish', 'direction' => 'ltr'), -# 'pt' => array('q' => 0, 'lang' => 'pt', 'name' => 'Portuguese', 'direction' => 'ltr'), - 'pt-br' => array('q' => 0.7, 'lang' => 'pt_BR', 'name' => 'Portuguese Brazil', 'direction' => 'ltr'), - 'ru' => array('q' => 0.1, 'lang' => 'ru_RU', 'name' => 'Russian', 'direction' => 'ltr'), - 'sv' => array('q' => 0.9, 'lang' => 'sv_SE', 'name' => 'Swedish', 'direction' => 'ltr'), - 'te' => array('q' => 0.3, 'lang' => 'te_IN', 'name' => 'Telugu', 'direction' => 'ltr'), - 'tr' => array('q' => 0.5, 'lang' => 'tr_TR', 'name' => 'Turkish', 'direction' => 'ltr'), - 'uk' => array('q' => 0.7, 'lang' => 'uk_UA', 'name' => 'Ukrainian', 'direction' => 'ltr'), - 'vi' => array('q' => 0.7, 'lang' => 'vi_VN', 'name' => 'Vietnamese', 'direction' => 'ltr'), - 'zh-cn' => array('q' => 0.9, 'lang' => 'zh_CN', 'name' => 'Chinese (Simplified)', 'direction' => 'ltr'), - 'zh-hant' => array('q' => 0.2, 'lang' => 'zh_hant', 'name' => 'Chinese (Taiwanese)', 'direction' => 'ltr'), + 'bg' => array('q' => 0.8, 'lang' => 'bg_BG', 'name' => 'Bulgarian', 'direction' => 'ltr'), + 'ca' => array('q' => 0.5, 'lang' => 'ca_ES', 'name' => 'Catalan', 'direction' => 'ltr'), + 'cs' => array('q' => 0.5, 'lang' => 'cs_CZ', 'name' => 'Czech', 'direction' => 'ltr'), + 'de' => array('q' => 0.5, 'lang' => 'de_DE', 'name' => 'German', 'direction' => 'ltr'), + 'el' => array('q' => 0.1, 'lang' => 'el', 'name' => 'Greek', 'direction' => 'ltr'), + 'en-us' => array('q' => 1, 'lang' => 'en_US', 'name' => 'English (US)', 'direction' => 'ltr'), + 'en-gb' => array('q' => 0.3, 'lang' => 'en_GB', 'name' => 'English (British)', 'direction' => 'ltr'), + 'en' => array('q' => 1, 'lang' => 'en', 'name' => 'English', 'direction' => 'ltr'), + 'es' => array('q' => 0.5, 'lang' => 'es', 'name' => 'Spanish', 'direction' => 'ltr'), + 'fi' => array('q' => 0.5, 'lang' => 'fi', 'name' => 'Finnish', 'direction' => 'ltr'), + 'fr-fr' => array('q' => 0.2, 'lang' => 'fr_FR', 'name' => 'French', 'direction' => 'ltr'), + 'he' => array('q' => 0.5, 'lang' => 'he_IL', 'name' => 'Hebrew', 'direction' => 'rtl'), + 'it' => array('q' => 0.9, 'lang' => 'it_IT', 'name' => 'Italian', 'direction' => 'ltr'), + 'jp' => array('q' => 0.5, 'lang' => 'ja_JP', 'name' => 'Japanese', 'direction' => 'ltr'), +# 'ko' => array('q' => 0, 'lang' => 'ko', 'name' => 'Korean', 'direction' => 'ltr'), + 'mk' => array('q' => 0.5, 'lang' => 'mk_MK', 'name' => 'Macedonian', 'direction' => 'ltr'), + 'nb' => array('q' => 0.1, 'lang' => 'nb_NO', 'name' => 'Norwegian (Bokmål)', 'direction' => 'ltr'), + 'no' => array('q' => 0.1, 'lang' => 'nb_NO', 'name' => 'Norwegian (Bokmål)', 'direction' => 'ltr'), + 'nn' => array('q' => 0.1, 'lang' => 'nn_NO', 'name' => 'Norwegian (Nynorsk)', 'direction' => 'ltr'), + 'nl' => array('q' => 0.5, 'lang' => 'nl_NL', 'name' => 'Dutch', 'direction' => 'ltr'), + 'pl' => array('q' => 0.5, 'lang' => 'pl_PL', 'name' => 'Polish', 'direction' => 'ltr'), +# 'pt' => array('q' => 0, 'lang' => 'pt', 'name' => 'Portuguese', 'direction' => 'ltr'), + 'pt-br' => array('q' => 0.7, 'lang' => 'pt_BR', 'name' => 'Portuguese Brazil', 'direction' => 'ltr'), + 'ru' => array('q' => 0.1, 'lang' => 'ru_RU', 'name' => 'Russian', 'direction' => 'ltr'), + 'sv' => array('q' => 0.9, 'lang' => 'sv_SE', 'name' => 'Swedish', 'direction' => 'ltr'), + 'te' => array('q' => 0.3, 'lang' => 'te_IN', 'name' => 'Telugu', 'direction' => 'ltr'), + 'tr' => array('q' => 0.5, 'lang' => 'tr_TR', 'name' => 'Turkish', 'direction' => 'ltr'), + 'uk' => array('q' => 0.7, 'lang' => 'uk_UA', 'name' => 'Ukrainian', 'direction' => 'ltr'), + 'vi' => array('q' => 0.7, 'lang' => 'vi_VN', 'name' => 'Vietnamese', 'direction' => 'ltr'), + 'zh-cn' => array('q' => 0.9, 'lang' => 'zh_CN', 'name' => 'Chinese (Simplified)', 'direction' => 'ltr'), + 'zh-hant' => array('q' => 0.2, 'lang' => 'zh_hant', 'name' => 'Chinese (Taiwanese)', 'direction' => 'ltr'), ); } From d92beda526f1495d16b54880a40ebb3c7d7e5f2e Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Wed, 25 Feb 2009 16:59:32 +0100 Subject: [PATCH 139/189] Add route for new message to user. --- lib/router.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/router.php b/lib/router.php index 95ce77e5ef..4b70c01505 100644 --- a/lib/router.php +++ b/lib/router.php @@ -154,6 +154,7 @@ class Router array('notice' => '[0-9]+')); $m->connect('message/new', array('action' => 'newmessage')); + $m->connect('message/new?to=:to', array('action' => 'newmessage'), array('to' => '[A-Za-z0-9_-]')); $m->connect('message/:message', array('action' => 'showmessage'), array('message' => '[0-9]+')); From 0a96edac9cd553cc0d1fa32defb80582699bcdd2 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 28 Feb 2009 23:10:40 +0000 Subject: [PATCH 140/189] Slightly toned down the text-shadow on navigation --- theme/base/css/display.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index be124f4330..c741ed4cba 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -297,7 +297,7 @@ padding:4px 11px; border-width:1px; border-style:solid; border-bottom:0; -text-shadow: 4px 4px 4px #ddd; +text-shadow: 2px 2px 2px #ddd; font-weight:bold; } #site_nav_local_views .nav { From 02ba71b0f186b406071a97c3267603d4863a4b21 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 28 Feb 2009 15:12:31 -0800 Subject: [PATCH 141/189] start conversation action --- actions/conversation.php | 123 +++++++++++++++++++++++++++++++++++++++ lib/router.php | 11 +++- 2 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 actions/conversation.php diff --git a/actions/conversation.php b/actions/conversation.php new file mode 100644 index 0000000000..0c22fe1238 --- /dev/null +++ b/actions/conversation.php @@ -0,0 +1,123 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + * + * Laconica - a distributed open-source microblogging tool + * Copyright (C) 2008, Controlez-Vous, 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 . + */ + +if (!defined('LACONICA')) { + exit(1); +} + +/** + * Conversation tree in the browser + * + * @category Action + * @package Laconica + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + */ +class ConversationAction extends Action +{ + var $id = null; + var $notices = null; + var $page = null; + + /** + * Initialization. + * + * @param array $args Web and URL arguments + * + * @return boolean false if id not passed in + */ + + function prepare($args) + { + parent::prepare($args); + $this->id = $this->trimmed('id'); + if (!$this->id) { + return false; + } + $this->notices = $this->getNotices(); + $this->page = $this->trimmed('page'); + if (empty($this->page)) { + $this->page = 1; + } + return true; + } + + /** + * Get notices + * + * @param integer $limit max number of notices to return + * + * @return array notices + */ + + function getNotices($limit=0) + { + $qry = 'SELECT notice.*, '. + 'FROM notice WHERE conversation = %d '. + 'ORDER BY created '; + + $offset = 0; + $limit = NOTICES_PER_PAGE + 1; + + if (common_config('db', 'type') == 'pgsql') { + $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; + } else { + $qry .= ' LIMIT ' . $offset . ', ' . $limit; + } + + return Notice::getStream(sprintf($qry, $this->id), + 'notice:conversation:'.$this->id, + $offset, $limit); + } + + function handle($args) + { + $this->showPage(); + } + + function title() + { + return _("Conversation"); + } + + function showContent() + { + // FIXME this needs to be a tree, not a list + + $nl = new NoticeList($this->notices, $this); + + $cnt = $nl->show(); + + $this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE, + $this->page, 'conversation', array('id' => $this->id)); + } + +} + diff --git a/lib/router.php b/lib/router.php index b18a5523e9..4c036e7b24 100644 --- a/lib/router.php +++ b/lib/router.php @@ -143,6 +143,12 @@ class Router array('action' => 'deletenotice'), array('notice' => '[0-9]+')); + // conversation + + $m->connect('conversation/:id', + array('action' => 'conversation'), + array('id' => '[0-9]+')); + $m->connect('message/new', array('action' => 'newmessage')); $m->connect('message/:message', array('action' => 'showmessage'), @@ -265,21 +271,20 @@ class Router 'apiaction' => 'friendships'), array('method' => 'exists(\.(xml|json|rss|atom))')); - // Social graph $m->connect('api/friends/ids/:argument', array('action' => 'api', 'apiaction' => 'statuses', 'method' => 'friendsIDs')); - + foreach (array('xml', 'json') as $e) { $m->connect('api/friends/ids.'.$e, array('action' => 'api', 'apiaction' => 'statuses', 'method' => 'friendsIDs.'.$e)); } - + $m->connect('api/followers/ids/:argument', array('action' => 'api', 'apiaction' => 'statuses', From 0369946b6d1b01a3d90fe641521a1ed117219d89 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 28 Feb 2009 15:17:49 -0800 Subject: [PATCH 142/189] fix problem with dupe tags in profile --- classes/Profile_tag.php | 43 +++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/classes/Profile_tag.php b/classes/Profile_tag.php index cb60cbaec9..0a1ad9cd6b 100644 --- a/classes/Profile_tag.php +++ b/classes/Profile_tag.php @@ -4,7 +4,7 @@ */ require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; -class Profile_tag extends Memcached_DataObject +class Profile_tag extends Memcached_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -23,45 +23,46 @@ class Profile_tag extends Memcached_DataObject ###END_AUTOCODE static function getTags($tagger, $tagged) { - + $tags = array(); # XXX: store this in memcached - + $profile_tag = new Profile_tag(); $profile_tag->tagger = $tagger; $profile_tag->tagged = $tagged; - + $profile_tag->find(); - + while ($profile_tag->fetch()) { $tags[] = $profile_tag->tag; } - + $profile_tag->free(); - + return $tags; } - + static function setTags($tagger, $tagged, $newtags) { - + + $newtags = array_unique($newtags); $oldtags = Profile_tag::getTags($tagger, $tagged); - + # Delete stuff that's old that not in new - + $to_delete = array_diff($oldtags, $newtags); - + # Insert stuff that's in new and not in old - + $to_insert = array_diff($newtags, $oldtags); - + $profile_tag = new Profile_tag(); - + $profile_tag->tagger = $tagger; $profile_tag->tagged = $tagged; - + $profile_tag->query('BEGIN'); - + foreach ($to_delete as $deltag) { $profile_tag->tag = $deltag; $result = $profile_tag->delete(); @@ -70,7 +71,7 @@ class Profile_tag extends Memcached_DataObject return false; } } - + foreach ($to_insert as $instag) { $profile_tag->tag = $instag; $result = $profile_tag->insert(); @@ -79,12 +80,12 @@ class Profile_tag extends Memcached_DataObject return false; } } - + $profile_tag->query('COMMIT'); - + return true; } - + # Return profiles with a given tag static function getTagged($tagger, $tag) { $profile = new Profile(); From 83b084e4ce2dea27c5f0ab3edfe2436fcf71e230 Mon Sep 17 00:00:00 2001 From: "ken.sedgwick" Date: Sat, 28 Feb 2009 17:33:20 -0800 Subject: [PATCH 143/189] Added RPM spec file. --- scripts/laconica.spec | 81 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 scripts/laconica.spec diff --git a/scripts/laconica.spec b/scripts/laconica.spec new file mode 100644 index 0000000000..2e65b45496 --- /dev/null +++ b/scripts/laconica.spec @@ -0,0 +1,81 @@ +BuildRequires: php-pear +BuildRequires: httpd-devel + +Name: laconica +Version: 0.7.1 +Release: 1%{?dist} +License: GAGPL v3 or later +Source: laconica-0.7.1.tar.bz2 +Group: Applications/Internet +Summary: Laconica, the Open Source microblogging platform +BuildArch: noarch + +Requires: httpd +Requires: php >= 5 +Requires: php-pear-Mail-Mime +Requires: php-curl +Requires: php-mysql +Requires: php-mbstring +Requires: php-gettext + +BuildRoot: %{_tmppath}/%{name}-%{version}-build + +%define apache_serverroot %(/usr/sbin/apxs -q DATADIR) +%define apache_sysconfdir %(/usr/sbin/apxs -q SYSCONFDIR) +%define wwwpath %{apache_serverroot}/%{name} +%define confpath %{_sysconfdir}/%{name} + +%description +From the ABOUT file: Laconica (pronounced "luh-KAWN-ih-kuh") is a Free +and Open Source microblogging platform. It helps people in a +community, company or group to exchange short (140 character) messages +over the Web. Users can choose which people to "follow" and receive +only their friends' or colleagues' status messages. It provides a +similar service to sites like Twitter, Jaiku, and Plurk. + + +%prep +%setup -q + +%build + + +%install +mkdir -p %{buildroot}%{wwwpath} +cp -a * %{buildroot}%{wwwpath} + +mkdir -p %{buildroot}%{_datadir}/laconica +cp -a db %{buildroot}%{_datadir}/laconica/db + +mkdir -p %{buildroot}%{_sysconfdir}/httpd/conf.d +cat > %{buildroot}%{_sysconfdir}/httpd/conf.d/laconica.conf <<"EOF" +Alias /laconica/ "/var/www/laconica/" + + + Options Indexes FollowSymLinks + AllowOverride All + Order allow,deny + Allow from all + +EOF + +%clean +rm -rf %buildroot + +%files +%defattr(-,root,root) +%dir %{wwwpath} +%{wwwpath}/* +%{_datadir}/laconica/* +%doc COPYING README doc-src/* +%config(noreplace) %{_sysconfdir}/httpd/conf.d/laconica.conf + +%changelog +* Sat Feb 28 2009 Ken Sedgwick - 0.7.1-1 +- Modified RPM for Fedora. + +* Thu Feb 13 2009 tuukka.pasanen@ilmi.fi +- packaged laconica version 0.7.1 + +* Wed Feb 04 2009 tuukka.pasanen@ilmi.fi +- packaged laconica version 0.7.0 using the buildservice spec file wizard From 3bf485ec95f3da56c779671b7a26f36085d93099 Mon Sep 17 00:00:00 2001 From: "ken.sedgwick" Date: Sat, 28 Feb 2009 17:35:36 -0800 Subject: [PATCH 144/189] Switched tarball ext ".bz2" -> ".gz" in spec file. --- scripts/laconica.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/laconica.spec b/scripts/laconica.spec index 2e65b45496..f3fa61904c 100644 --- a/scripts/laconica.spec +++ b/scripts/laconica.spec @@ -5,7 +5,7 @@ Name: laconica Version: 0.7.1 Release: 1%{?dist} License: GAGPL v3 or later -Source: laconica-0.7.1.tar.bz2 +Source: laconica-0.7.1.tar.gz Group: Applications/Internet Summary: Laconica, the Open Source microblogging platform BuildArch: noarch From f0d3ba2bc2b9a4391a89e343e9dea2622d7d9972 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 28 Feb 2009 17:42:12 -0800 Subject: [PATCH 145/189] Add a flag for if there's no config file --- lib/common.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/common.php b/lib/common.php index 4fc749ca06..2298c5f88f 100644 --- a/lib/common.php +++ b/lib/common.php @@ -178,9 +178,12 @@ if (strlen($_path) > 0) { $_config_files[] = INSTALLDIR.'/config.php'; +$_have_a_config = false; + foreach ($_config_files as $_config_file) { if (file_exists($_config_file)) { include_once($_config_file); + $_have_a_config = true; } } From c77bb62f2d0a958e074ad0d9b1334d533cb33121 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 28 Feb 2009 17:42:27 -0800 Subject: [PATCH 146/189] show a form for installation --- install.php | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 install.php diff --git a/install.php b/install.php new file mode 100644 index 0000000000..5ee6b10b3e --- /dev/null +++ b/install.php @@ -0,0 +1,68 @@ + +

Enter your database connection information below to initialize the database.

+
+
+
    +
  • + + +

    The name of your site

    +
  • +
  • +
  • + + +

    Database hostname

    +
  • +
  • +
  • + + +

    Database username

    +
  • +
  • + + +

    Database password

    +
  • +
+ +
+
+ + + + Install Laconica + + + + + +
+
+
+

Install Laconica

+ +
+
+
+ + \ No newline at end of file From 7fa6bb07d859d917080c6b110084ddedd5a772e8 Mon Sep 17 00:00:00 2001 From: "ken.sedgwick" Date: Sat, 28 Feb 2009 20:13:37 -0800 Subject: [PATCH 147/189] Added some missing php dependencies. Made the avatar directory apache owned. --- scripts/laconica.spec | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/laconica.spec b/scripts/laconica.spec index f3fa61904c..2df41c9323 100644 --- a/scripts/laconica.spec +++ b/scripts/laconica.spec @@ -17,6 +17,8 @@ Requires: php-curl Requires: php-mysql Requires: php-mbstring Requires: php-gettext +Requires: php-xml +Requires: php-gd BuildRoot: %{_tmppath}/%{name}-%{version}-build @@ -67,6 +69,7 @@ rm -rf %buildroot %dir %{wwwpath} %{wwwpath}/* %{_datadir}/laconica/* +%attr(-,apache,apache) %dir %{_datadir}/laconica/avatar %doc COPYING README doc-src/* %config(noreplace) %{_sysconfdir}/httpd/conf.d/laconica.conf From b70218dc437c5decc8b27a2bb70e2e1b6ec6e9e3 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 28 Feb 2009 20:32:31 -0800 Subject: [PATCH 148/189] automatically handle non-laconica-named databases --- lib/common.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/common.php b/lib/common.php index 2298c5f88f..0fff3af2ea 100644 --- a/lib/common.php +++ b/lib/common.php @@ -187,6 +187,16 @@ foreach ($_config_files as $_config_file) { } } +// XXX: Throw a conniption if database not installed + +// Fixup for laconica.ini + +$_db_name = substr($config['db']['database'], strrpos($config['db']['database'], '/') + 1); + +if ($_db_name != 'laconica' && !array_key_exists('ini_'.$_db_name, $config['db'])) { + $config['db']['ini_'.$_db_name] = INSTALLDIR.'/classes/laconica.ini'; +} + // XXX: how many of these could be auto-loaded on use? require_once('Validate.php'); From 1d610d3c6f9bb15a5cea93758ddd8d0ce64099f6 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 28 Feb 2009 20:32:53 -0800 Subject: [PATCH 149/189] don't use semicolons in comments --- db/laconica.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db/laconica.sql b/db/laconica.sql index dd93a727b7..c2cd887dee 100644 --- a/db/laconica.sql +++ b/db/laconica.sql @@ -171,7 +171,7 @@ create table token ( tok char(32) not null comment 'identifying value', secret char(32) not null comment 'secret value', type tinyint not null default 0 comment 'request or access', - state tinyint default 0 comment 'for requests; 0 = initial, 1 = authorized, 2 = used', + state tinyint default 0 comment 'for requests, 0 = initial, 1 = authorized, 2 = used', created datetime not null comment 'date this record was created', modified timestamp comment 'date this record was modified', @@ -344,7 +344,7 @@ create table notice_inbox ( user_id integer not null comment 'user receiving the message' references user (id), notice_id integer not null comment 'notice received' references notice (id), created datetime not null comment 'date the notice was created', - source tinyint default 1 comment 'reason it is in the inbox; 1=subscription', + source tinyint default 1 comment 'reason it is in the inbox, 1=subscription', constraint primary key (user_id, notice_id), index notice_inbox_notice_id_idx (notice_id) From 2ad667f704300dcb825a4822d567cbc8ebfebe02 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 28 Feb 2009 20:33:22 -0800 Subject: [PATCH 150/189] first pass at a working install.php --- install.php | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 2 deletions(-) diff --git a/install.php b/install.php index 5ee6b10b3e..a34214c48a 100644 --- a/install.php +++ b/install.php @@ -1,6 +1,9 @@

Enter your database connection information below to initialize the database.

-
+
  • @@ -22,11 +29,15 @@ function showForm()
  • - +

    Database hostname

  • + + +

    Database name

    +
  • @@ -44,9 +55,90 @@ function showForm() +
  • + +
  • + +
      + +
    + From 458c03786735bd3e3b6619b2d20538bd55acd0c6 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 28 Feb 2009 21:01:33 -0800 Subject: [PATCH 151/189] check some prereqs for installation --- install.php | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/install.php b/install.php index a34214c48a..18fc362b62 100644 --- a/install.php +++ b/install.php @@ -3,7 +3,11 @@ define('INSTALLDIR', dirname(__FILE__)); function main() { - checkPrereqs(); + if (!checkPrereqs()) + { + return; + } + if ($_SERVER['REQUEST_METHOD'] == 'POST') { handlePost(); } else { @@ -13,6 +17,55 @@ function main() function checkPrereqs() { + if (file_exists(INSTALLDIR.'/config.php')) { + ?>

    Config file "config.php" already exists.

    +

    Require PHP version 5 or greater.

    Cannot load required extension "".

    Cannot write config file to "".

    +

    On your server, try this command:

    +
    chmod a+w
    +

    Cannot write avatar directory "/avatar/".

    +

    On your server, try this command:

    +
    chmod a+w /avatar/
    + Date: Sun, 1 Mar 2009 10:12:16 -0800 Subject: [PATCH 152/189] check posted parameters --- install.php | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/install.php b/install.php index 18fc362b62..18a10664dd 100644 --- a/install.php +++ b/install.php @@ -130,6 +130,36 @@ function handlePost() $password = $_POST['password']; $sitename = $_POST['sitename']; + if (empty($host)) { + updateStatus("No hostname specified.", true); + showForm(); + return; + } + + if (empty($database)) { + updateStatus("No database specified.", true); + showForm(); + return; + } + + if (empty($username)) { + updateStatus("No username specified.", true); + showForm(); + return; + } + + if (empty($password)) { + updateStatus("No password specified.", true); + showForm(); + return; + } + + if (empty($sitename)) { + updateStatus("No sitename specified.", true); + showForm(); + return; + } + updateStatus("Starting installation..."); updateStatus("Checking database..."); $conn = mysql_connect($host, $username, $password); From 8b9e559167e27c490648cc55c4f617c2e9122a18 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 2 Mar 2009 00:06:29 +0000 Subject: [PATCH 153/189] Updated typography and layout --- theme/base/css/mobile.css | 144 +++++++++++++++++++++++++++++--------- 1 file changed, 111 insertions(+), 33 deletions(-) diff --git a/theme/base/css/mobile.css b/theme/base/css/mobile.css index 3d0455a673..eee98317cc 100644 --- a/theme/base/css/mobile.css +++ b/theme/base/css/mobile.css @@ -2,14 +2,21 @@ * * @package Laconica * @author Meitar Moscovitz + * @author Sarven Capadisli * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://laconi.ca/ */ -/* Go linear. */ +body { +font-size:2.5em; +} + +#wrap { +width:95%; +} + #header, #header address, -#site_nav_global_primary, #anon_notice, #site_nav_local_views .nav, #form_notice, @@ -24,49 +31,120 @@ .notice .notice-options a, .pagination, .pagination .nav, -.aside .section { float: none; } +.aside .section { +float:none; +} .notice-options .notice_reply, .notice-options .notice_delete, .notice-options .form_favor, -.notice-options .form_disfavor { position: static; } +.notice-options .form_disfavor { +position:static; +} #form_notice, #anon_notice, -#content_inner, -#footer { width: auto; } +#footer, +#form_notice .form_actions input.submit { +width:auto; +} -/* And liquid. */ -#wrap { width: 95%; } +.form_settings label { +width:25%; +} +.form_settings .form_data p.form_guide { +margin-left:26%; +} -/* Make things bigger on smaller screens. */ -body { font-size: 2em; } -.notices { font-size: 1.5em; } +#site_nav_global_primary { +width:75%; +} -#site_nav_global_primary, #site_nav_global_secondary { text-align: center; } +.entity_profile { +width:65%; +} +.entity_actions { +margin-left:0; +} -.notice div.entry-content { margin-left: 0; } -address { margin: 0; } +#form_notice, +#anon_notice { +clear:both; +} -#anon_notice, #footer { clear: left; font-size: .5em; } +#content, +#aside_primary { +width:96%; +padding-left:2%; +padding-right:2%; +} -#form_notice textarea { width: 80%; height: 5em; } -#form_notice .form_note { right: 20%; top: 6em; } -#form_notice .form_actions input.submit { width: auto; } +#site_notice { +position:static; +float:right; +clear:right; +width:75%; +margin-right:0; +margin-bottom:11px; +} -#content { padding: 18px 0; width: 100%; } -#content h1, #page_notice, #content_inner { padding: 0 18px; } -.notices .entry-title, .notices div.entry-content { width: 90%; } -.notice .author .photo { height: 4.5em; width: 4.5em; } /* about double physical size; TODO: do this scaling better */ -.notice-options { position: absolute; top: 0; right: 0; padding-left: 7%; width: 3%; } -.notice-options .notice_delete a { float: left; } /* Works, but feels like it shouldn't. */ -/* TODO: Make the icons of the notice options bigger. Probably with mobile-specific images. */ -.pagination .nav { overflow: auto; } +.notices { +font-size:1.5em; +} -#aside_primary { margin: 10px 0 0 0; border: none; padding: 0; width: 100%; } -#popular_notices { float: none; width: auto; } -/* Columns for supplemental info. */ -.aside .section { clear: none; padding: 9px; width: 45%; } -#top_groups_by_post { float: left; } -#featured_users { float: right; } -#export_data { display: none; } +#form_notice textarea { +width:80%; +height:5em; +} +#form_notice .form_note { +right:20%; +top:6em; +} + + +.vcard .photo, +.section .vcard .photo { +margin-right:18px; +} +.notice, +.profile { +margin-bottom:18px; +} + +.notices .entry-title, +.notices div.entry-content { +width:90%; +} +.notice div.entry-content { +margin-left:0; +} + +.notice .author .photo { +height:4.5em; +width:4.5em; +} +.notice-options { +position:absolute; +top:0; +right:0; +padding-left:7%; +width:3%; +} + +.notice-options .notice_delete a { +float:left; +} +.pagination .nav { +overflow:auto; +} + +#export_data { +display:none; +} + +#site_nav_local_views li { +margin-right:4px; +} +#site_nav_local_views a { +padding:18px 11px; +} From 9a2f3358537566084a29fd421891fbc236185e9f Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 2 Mar 2009 18:25:10 -0800 Subject: [PATCH 154/189] add notice source to default install --- db/notice_source.sql | 46 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 db/notice_source.sql diff --git a/db/notice_source.sql b/db/notice_source.sql new file mode 100644 index 0000000000..ea04862e08 --- /dev/null +++ b/db/notice_source.sql @@ -0,0 +1,46 @@ +INSERT INTO notice_source + (code, name, url, created) +VALUES + ('Do','Gnome Do','http://do.davebsd.com/wiki/index.php?title=Microblog_Plugin', now()), + ('Facebook','Facebook','http://apps.facebook.com/identica/', now()), + ('Gwibber','Gwibber','http://launchpad.net/gwibber', now()), + ('HelloTxt','HelloTxt','http://hellotxt.com/', now()), + ('IdentiFox','IdentiFox','http://www.bitbucket.org/uncryptic/identifox/', now()), + ('LaTwit','LaTwit','http://latwit.mac65.com/', now()), + ('Nambu','Nambu','http://www.nambu.com/', now()), + ('Pikchur','Pikchur','http://www.pikchur.com/', now()), + ('Ping.fm','Ping.fm','http://ping.fm/', now()), + ('Twidge','Twidge','http://software.complete.org/twidge', now()), + ('Updating.Me','Updating.Me','http://updating.me/', now()), + ('betwittered','BeTwittered','http://www.32hours.com/betwitteredinfo/', now()), + ('bti','bti','http://gregkh.github.com/bti/', now()), + ('deskbar','Deskbar-Applet','http://www.gnome.org/projects/deskbar-applet/', now()), + ('identicatools','Laconica Tools','http://bitbucketlabs.net/laconica-tools/', now()), + ('identichat','identichat','http://identichat.prosody.im/', now()), + ('identitwitch','IdentiTwitch','http://richfish.org/identitwitch/', now()), + ('mbpidgin','mbpidgin','http://code.google.com/p/microblog-purple/', now()), + ('moconica','Moconica','http://moconica.com/', now()), + ('pocketwit','PockeTwit','http://code.google.com/p/pocketwit/', now()), + ('posty','Posty','http://spreadingfunkyness.com/posty/', now()), + ('royalewithcheese','Royale With Cheese','http://p.hellyeah.org/', now()), + ('rssdent','rssdent','http://github.com/zcopley/rssdent/tree/master', now()), + ('rygh.no','rygh.no','http://rygh.no/', now()), + ('ryghsms','ryghsms','http://sms.rygh.no/', now()), + ('smob','SMOB','http://smob.sioc-project.org/', now()), + ('spaz','Spaz','http://funkatron.com/spaz', 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()), + ('tweenky','Tweenky','http://beta.tweenky.com/', now()), + ('twhirl','Twhirl','http://www.twhirl.org/', now()), + ('twibble','twibble','http://www.twibble.de/', now()), + ('twidge','Twidge','http://software.complete.org/twidge', now()), + ('twidroid','twidroid','http://www.twidroid.com/', now()), + ('twittelator','Twittelator','http://www.stone.com/iPhone/Twittelator/', now()), + ('twitterfeed','twitterfeed','http://twitterfeed.com/', now()), + ('twitterphoto','TwitterPhoto','http://richfish.org/twitterphoto/', now()), + ('twitterpm','Net::Twitter','http://search.cpan.org/dist/Net-Twitter/', now()), + ('twittertools','Twitter Tools','http://wordpress.org/extend/plugins/twitter-tools/', now()), + ('twitux','Twitux','http://live.gnome.org/DanielMorales/Twitux', now()), + ('twitvim','TwitVim','http://vim.sourceforge.net/scripts/script.php?script_id=2204', now()), + ('urfastr','urfastr','http://urfastr.net/', now()); From 8408e82f928d1b159a6c9f2dd8fabd269590be61 Mon Sep 17 00:00:00 2001 From: CiaranG Date: Tue, 3 Mar 2009 20:18:26 +0000 Subject: [PATCH 155/189] PostgreSQL - added new emailnotifyattn field to user table, to match MySQL version --- db/laconica_pg.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/db/laconica_pg.sql b/db/laconica_pg.sql index 9882d091a5..cacdfa97ed 100644 --- a/db/laconica_pg.sql +++ b/db/laconica_pg.sql @@ -50,6 +50,7 @@ create table "user" ( emailnotifyfav integer default 1 /* comment 'Notify by email of favorites' */, emailnotifynudge integer default 1 /* comment 'Notify by email of nudges' */, emailnotifymsg integer default 1 /* comment 'Notify by email of direct messages' */, + emailnotifyattn integer default 1 /* command 'Notify by email of @-replies' */, emailmicroid integer default 1 /* comment 'whether to publish email microid' */, language varchar(50) /* comment 'preferred language' */, timezone varchar(50) /* comment 'timezone' */, From 3fd877c4cecd80fbe65043dd4612d9688e16ecd8 Mon Sep 17 00:00:00 2001 From: CiaranG Date: Tue, 3 Mar 2009 21:32:47 +0000 Subject: [PATCH 156/189] Use single quotes for data literals on inserts to notice_index, so it works on pgsql as well as mysql --- classes/Notice.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 8300667fa4..907239b084 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -585,7 +585,7 @@ class Notice extends Memcached_DataObject $inbox = new Notice_inbox(); $UT = common_config('db','type')=='pgsql'?'"user"':'user'; $qry = 'INSERT INTO notice_inbox (user_id, notice_id, created) ' . - "SELECT $UT.id, " . $this->id . ', "' . $this->created . '" ' . + "SELECT $UT.id, " . $this->id . ", '" . $this->created . "' " . "FROM $UT JOIN subscription ON $UT.id = subscription.subscriber " . 'WHERE subscription.subscribed = ' . $this->profile_id . ' ' . 'AND NOT EXISTS (SELECT user_id, notice_id ' . @@ -655,7 +655,7 @@ class Notice extends Memcached_DataObject $inbox = new Notice_inbox(); $UT = common_config('db','type')=='pgsql'?'"user"':'user'; $qry = 'INSERT INTO notice_inbox (user_id, notice_id, created, source) ' . - "SELECT $UT.id, " . $this->id . ', "' . $this->created . '", 2 ' . + "SELECT $UT.id, " . $this->id . ", '" . $this->created . "', 2 " . "FROM $UT JOIN group_member ON $UT.id = group_member.profile_id " . 'WHERE group_member.group_id = ' . $group->id . ' ' . 'AND NOT EXISTS (SELECT user_id, notice_id ' . From 35677336de4c01c4f6b02840222075c6ac963988 Mon Sep 17 00:00:00 2001 From: CiaranG Date: Tue, 3 Mar 2009 21:33:52 +0000 Subject: [PATCH 157/189] Catch bad replyto IDs before saving a new notice to avoid a constraint violation. This happens, for example, when posting for the first time on a fresh install --- actions/newnotice.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/actions/newnotice.php b/actions/newnotice.php index 9f44d25165..cbd04c58b2 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -152,6 +152,11 @@ class NewnoticeAction extends Action } $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'; + } $notice = Notice::saveNew($user->id, $content, 'web', 1, ($replyto == 'false') ? null : $replyto); From 7279554681da728deb74a87230de3a1021182f71 Mon Sep 17 00:00:00 2001 From: CiaranG Date: Wed, 4 Mar 2009 00:23:34 +0000 Subject: [PATCH 158/189] Additional (optional, defaults to off) logging of PEAR error details, which allows database issues to be more easily diagnosed. --- config.php.sample | 3 +++ index.php | 6 +++++- lib/common.php | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/config.php.sample b/config.php.sample index 6e55eaffc8..a6cada77a2 100644 --- a/config.php.sample +++ b/config.php.sample @@ -34,6 +34,9 @@ $config['site']['path'] = 'laconica'; # If you want logging sent to a file instead of syslog #$config['site']['logfile'] = '/tmp/laconica.log'; +# Enables extra log information, for example full details of PEAR DB errors +#$config['site']['logdebug'] = true; + # This is a PEAR DB DSN, see http://pear.php.net/manual/en/package.database.db.intro-dsn.php # Set it to match your actual database diff --git a/index.php b/index.php index 914ba5bde1..03c044415d 100644 --- a/index.php +++ b/index.php @@ -43,7 +43,11 @@ function handleError($error) return; } - common_log(LOG_ERR, "PEAR error: " . $error->getMessage()); + $logmsg = "PEAR error: " . $error->getMessage(); + if(common_config('site', 'logdebug')) { + $logmsg .= " : ". $error->getDebugInfo(); + } + common_log(LOG_ERR, $logmsg); $msg = sprintf(_('The database for %s isn\'t responding correctly, '. 'so the site won\'t work properly. '. 'The site admins probably know about the problem, '. diff --git a/lib/common.php b/lib/common.php index 0fff3af2ea..3df68d98a2 100644 --- a/lib/common.php +++ b/lib/common.php @@ -73,6 +73,7 @@ $config = 'theme' => 'default', 'path' => $_path, 'logfile' => null, + 'logdebug' => false, 'fancy' => false, 'locale_path' => INSTALLDIR.'/locale', 'language' => 'en_US', From dcdf47cff2dc62996df042aa0e5d19948b265686 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 4 Mar 2009 05:11:18 +0000 Subject: [PATCH 159/189] Avatar crop fix for shrinking/stretching of images when user doesn't scale it with the Jcrop tool (but hits Crop). This will use the lower value from width/height. --- actions/avatarsettings.php | 8 +++++--- js/jcrop/jquery.Jcrop.go.js | 7 ------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/actions/avatarsettings.php b/actions/avatarsettings.php index 6545d94893..c2bb35a395 100644 --- a/actions/avatarsettings.php +++ b/actions/avatarsettings.php @@ -324,11 +324,13 @@ class AvatarsettingsAction extends AccountSettingsAction return; } - // If image is not being cropped assume pos & dimensions of original. + $file_d = ($filedata['width'] > $filedata['height']) + ? $filedata['height'] : $filedata['width']; + $dest_x = $this->arg('avatar_crop_x') ? $this->arg('avatar_crop_x'):0; $dest_y = $this->arg('avatar_crop_y') ? $this->arg('avatar_crop_y'):0; - $dest_w = $this->arg('avatar_crop_w') ? $this->arg('avatar_crop_w'):$filedata['width']; - $dest_h = $this->arg('avatar_crop_h') ? $this->arg('avatar_crop_h'):$filedata['height']; + $dest_w = $this->arg('avatar_crop_w') ? $this->arg('avatar_crop_w'):$file_d; + $dest_h = $this->arg('avatar_crop_h') ? $this->arg('avatar_crop_h'):$file_d; $size = min($dest_w, $dest_h, MAX_ORIGINAL); $user = common_current_user(); diff --git a/js/jcrop/jquery.Jcrop.go.js b/js/jcrop/jquery.Jcrop.go.js index b2737407bf..a0399d5405 100644 --- a/js/jcrop/jquery.Jcrop.go.js +++ b/js/jcrop/jquery.Jcrop.go.js @@ -37,10 +37,3 @@ $('#avatar_crop_w').val(c.w); $('#avatar_crop_h').val(c.h); }; - - function checkCoords() { - if (parseInt($('#avatar_crop_w').val())) return true; - alert('Please select a crop region then press submit.'); - return false; - }; - From 78a715bc37041a852d9e0eb6dc3e3dc0a0b9e279 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 4 Mar 2009 05:23:41 +0000 Subject: [PATCH 160/189] Hooks for: Public group nav in order to place a list-anchor item at the start or end of the list. --- EVENTS.txt | 7 ++++++- lib/publicgroupnav.php | 30 +++++++++++++++++------------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index ed461ee9fe..2f33b2d5d5 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -88,9 +88,14 @@ StartShowLocalNavBlock: Showing the local nav menu EndShowLocalNavBlock: At the end of the local nav menu - $action: the current action -StartShowHTML: Chance to set document content type, charset, language, DOCTYPE and html element properties +StartShowHTML: Chance to set document headers (e.g., content type, charset, language), DOCTYPE and html element properties - $action: the current action EndShowHTML: Showing after the html element - $action: the current action +StartPublicGroupNav: Showing the public group nav menu +- $action: the current action + +EndPublicGroupNav: At the end of the public group nav menu +- $action: the current action diff --git a/lib/publicgroupnav.php b/lib/publicgroupnav.php index d72475e202..485d25e204 100644 --- a/lib/publicgroupnav.php +++ b/lib/publicgroupnav.php @@ -39,6 +39,7 @@ require_once INSTALLDIR.'/lib/widget.php'; * @category Output * @package Laconica * @author Evan Prodromou + * @author Sarven Capadisli * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://laconi.ca/ * @@ -73,23 +74,26 @@ class PublicGroupNav extends Widget $this->action->elementStart('ul', array('class' => 'nav')); - $this->out->menuItem(common_local_url('public'), _('Public'), - _('Public timeline'), $action_name == 'public', 'nav_timeline_public'); + if (Event::handle('StartPublicGroupNav', array($this))) { + $this->out->menuItem(common_local_url('public'), _('Public'), + _('Public timeline'), $action_name == 'public', 'nav_timeline_public'); - $this->out->menuItem(common_local_url('groups'), _('Groups'), - _('User groups'), $action_name == 'groups', 'nav_groups'); + $this->out->menuItem(common_local_url('groups'), _('Groups'), + _('User groups'), $action_name == 'groups', 'nav_groups'); - $this->out->menuItem(common_local_url('publictagcloud'), _('Recent tags'), - _('Recent tags'), $action_name == 'publictagcloud', 'nav_recent-tags'); + $this->out->menuItem(common_local_url('publictagcloud'), _('Recent tags'), + _('Recent tags'), $action_name == 'publictagcloud', 'nav_recent-tags'); - if (count(common_config('nickname', 'featured')) > 0) { - $this->out->menuItem(common_local_url('featured'), _('Featured'), - _('Featured users'), $action_name == 'featured', 'nav_featured'); + if (count(common_config('nickname', 'featured')) > 0) { + $this->out->menuItem(common_local_url('featured'), _('Featured'), + _('Featured users'), $action_name == 'featured', 'nav_featured'); + } + + $this->out->menuItem(common_local_url('favorited'), _('Popular'), + _("Popular notices"), $action_name == 'favorited', 'nav_timeline_favorited'); + + Event::handle('EndPublicGroupNav', array($this)); } - - $this->out->menuItem(common_local_url('favorited'), _('Popular'), - _("Popular notices"), $action_name == 'favorited', 'nav_timeline_favorited'); - $this->action->elementEnd('ul'); } } From c0115bf3bc8957fd2db2dd135ad885ed1b8d6158 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 4 Mar 2009 05:29:21 -0800 Subject: [PATCH 161/189] fix pagination links with new URL mapper --- lib/action.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/action.php b/lib/action.php index 9c71a153dd..812df635e3 100644 --- a/lib/action.php +++ b/lib/action.php @@ -976,17 +976,17 @@ class Action extends HTMLOutputter // lawsuit } if ($have_before) { $pargs = array('page' => $page-1); - $newargs = $args ? array_merge($args, $pargs) : $pargs; $this->elementStart('li', array('class' => 'nav_prev')); - $this->element('a', array('href' => common_local_url($action, $newargs), 'rel' => 'prev'), + $this->element('a', array('href' => common_local_url($action, $args, $pargs), + 'rel' => 'prev'), _('After')); $this->elementEnd('li'); } if ($have_after) { $pargs = array('page' => $page+1); - $newargs = $args ? array_merge($args, $pargs) : $pargs; $this->elementStart('li', array('class' => 'nav_next')); - $this->element('a', array('href' => common_local_url($action, $newargs), 'rel' => 'next'), + $this->element('a', array('href' => common_local_url($action, $args, $pargs), + 'rel' => 'next'), _('Before')); $this->elementEnd('li'); } From 18f7749995c2fb4281839df8f8aa2b0a0c545260 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 4 Mar 2009 05:42:03 -0800 Subject: [PATCH 162/189] run sms carrier script on install --- install.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/install.php b/install.php index 18fc362b62..3d76dace14 100644 --- a/install.php +++ b/install.php @@ -152,6 +152,13 @@ function handlePost() showForm(); return; } + updateStatus("Adding SMS carrier data to database..."); + $res = runDbScript(INSTALLDIR.'/db/sms_carrier.sql', $conn); + if ($res === false) { + updateStatus("Can't run SMS carrier script.", true); + showForm(); + return; + } updateStatus("Writing config file..."); $sqlUrl = "mysqli://$username:$password@$host/$database"; $res = writeConf($sitename, $sqlUrl); From 6070d98aa6ed53cfafa9f4bcc6ecd24977b8866a Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 1 Mar 2009 10:12:16 -0800 Subject: [PATCH 163/189] check posted parameters --- install.php | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/install.php b/install.php index 3d76dace14..29d9094175 100644 --- a/install.php +++ b/install.php @@ -130,6 +130,36 @@ function handlePost() $password = $_POST['password']; $sitename = $_POST['sitename']; + if (empty($host)) { + updateStatus("No hostname specified.", true); + showForm(); + return; + } + + if (empty($database)) { + updateStatus("No database specified.", true); + showForm(); + return; + } + + if (empty($username)) { + updateStatus("No username specified.", true); + showForm(); + return; + } + + if (empty($password)) { + updateStatus("No password specified.", true); + showForm(); + return; + } + + if (empty($sitename)) { + updateStatus("No sitename specified.", true); + showForm(); + return; + } + updateStatus("Starting installation..."); updateStatus("Checking database..."); $conn = mysql_connect($host, $username, $password); From f53b6470cf6a8da02d44c85ce30214414d866646 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 2 Mar 2009 18:25:10 -0800 Subject: [PATCH 164/189] add notice source to default install --- db/notice_source.sql | 46 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 db/notice_source.sql diff --git a/db/notice_source.sql b/db/notice_source.sql new file mode 100644 index 0000000000..ea04862e08 --- /dev/null +++ b/db/notice_source.sql @@ -0,0 +1,46 @@ +INSERT INTO notice_source + (code, name, url, created) +VALUES + ('Do','Gnome Do','http://do.davebsd.com/wiki/index.php?title=Microblog_Plugin', now()), + ('Facebook','Facebook','http://apps.facebook.com/identica/', now()), + ('Gwibber','Gwibber','http://launchpad.net/gwibber', now()), + ('HelloTxt','HelloTxt','http://hellotxt.com/', now()), + ('IdentiFox','IdentiFox','http://www.bitbucket.org/uncryptic/identifox/', now()), + ('LaTwit','LaTwit','http://latwit.mac65.com/', now()), + ('Nambu','Nambu','http://www.nambu.com/', now()), + ('Pikchur','Pikchur','http://www.pikchur.com/', now()), + ('Ping.fm','Ping.fm','http://ping.fm/', now()), + ('Twidge','Twidge','http://software.complete.org/twidge', now()), + ('Updating.Me','Updating.Me','http://updating.me/', now()), + ('betwittered','BeTwittered','http://www.32hours.com/betwitteredinfo/', now()), + ('bti','bti','http://gregkh.github.com/bti/', now()), + ('deskbar','Deskbar-Applet','http://www.gnome.org/projects/deskbar-applet/', now()), + ('identicatools','Laconica Tools','http://bitbucketlabs.net/laconica-tools/', now()), + ('identichat','identichat','http://identichat.prosody.im/', now()), + ('identitwitch','IdentiTwitch','http://richfish.org/identitwitch/', now()), + ('mbpidgin','mbpidgin','http://code.google.com/p/microblog-purple/', now()), + ('moconica','Moconica','http://moconica.com/', now()), + ('pocketwit','PockeTwit','http://code.google.com/p/pocketwit/', now()), + ('posty','Posty','http://spreadingfunkyness.com/posty/', now()), + ('royalewithcheese','Royale With Cheese','http://p.hellyeah.org/', now()), + ('rssdent','rssdent','http://github.com/zcopley/rssdent/tree/master', now()), + ('rygh.no','rygh.no','http://rygh.no/', now()), + ('ryghsms','ryghsms','http://sms.rygh.no/', now()), + ('smob','SMOB','http://smob.sioc-project.org/', now()), + ('spaz','Spaz','http://funkatron.com/spaz', 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()), + ('tweenky','Tweenky','http://beta.tweenky.com/', now()), + ('twhirl','Twhirl','http://www.twhirl.org/', now()), + ('twibble','twibble','http://www.twibble.de/', now()), + ('twidge','Twidge','http://software.complete.org/twidge', now()), + ('twidroid','twidroid','http://www.twidroid.com/', now()), + ('twittelator','Twittelator','http://www.stone.com/iPhone/Twittelator/', now()), + ('twitterfeed','twitterfeed','http://twitterfeed.com/', now()), + ('twitterphoto','TwitterPhoto','http://richfish.org/twitterphoto/', now()), + ('twitterpm','Net::Twitter','http://search.cpan.org/dist/Net-Twitter/', now()), + ('twittertools','Twitter Tools','http://wordpress.org/extend/plugins/twitter-tools/', now()), + ('twitux','Twitux','http://live.gnome.org/DanielMorales/Twitux', now()), + ('twitvim','TwitVim','http://vim.sourceforge.net/scripts/script.php?script_id=2204', now()), + ('urfastr','urfastr','http://urfastr.net/', now()); From 07efccd7ef274f6d6f2b28781a87ec7271702a7f Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 4 Mar 2009 05:44:29 -0800 Subject: [PATCH 165/189] add notice source data on install --- install.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/install.php b/install.php index 29d9094175..fbfd5e4d11 100644 --- a/install.php +++ b/install.php @@ -189,6 +189,13 @@ function handlePost() showForm(); return; } + updateStatus("Adding notice source data to database..."); + $res = runDbScript(INSTALLDIR.'/db/notice_source.sql', $conn); + if ($res === false) { + updateStatus("Can't run notice source script.", true); + showForm(); + return; + } updateStatus("Writing config file..."); $sqlUrl = "mysqli://$username:$password@$host/$database"; $res = writeConf($sitename, $sqlUrl); From 43ef29ba0c64f19b93a2389ab7cd26158964a20b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 4 Mar 2009 05:47:24 -0800 Subject: [PATCH 166/189] all inserts in one statement for foreign services --- db/foreign_services.sql | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/db/foreign_services.sql b/db/foreign_services.sql index 512d425138..557ede0246 100644 --- a/db/foreign_services.sql +++ b/db/foreign_services.sql @@ -1,8 +1,5 @@ insert into foreign_service (id, name, description, created) values - ('1','Twitter', 'Twitter Micro-blogging service', now()); -insert into foreign_service - (id, name, description, created) -values - ('2','Facebook', 'Facebook', now()); + ('1','Twitter', 'Twitter Micro-blogging service', now()), + ('2','Facebook', 'Facebook', now()); From 8d05768e2cfcc1a4534307ae9a3be333376d4cfe Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 4 Mar 2009 05:47:37 -0800 Subject: [PATCH 167/189] run foreign services script --- install.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/install.php b/install.php index fbfd5e4d11..7aa8836c22 100644 --- a/install.php +++ b/install.php @@ -196,6 +196,13 @@ function handlePost() showForm(); return; } + updateStatus("Adding foreign service data to database..."); + $res = runDbScript(INSTALLDIR.'/db/foreign_services.sql', $conn); + if ($res === false) { + updateStatus("Can't run foreign service script.", true); + showForm(); + return; + } updateStatus("Writing config file..."); $sqlUrl = "mysqli://$username:$password@$host/$database"; $res = writeConf($sitename, $sqlUrl); From aa1bc6216e61faf5d5000f875f65b86bdb097c03 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 4 Mar 2009 05:53:04 -0800 Subject: [PATCH 168/189] Make a loop instead of repeating almost identical text in install --- install.php | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/install.php b/install.php index 7aa8836c22..0240349bb1 100644 --- a/install.php +++ b/install.php @@ -182,26 +182,17 @@ function handlePost() showForm(); return; } - updateStatus("Adding SMS carrier data to database..."); - $res = runDbScript(INSTALLDIR.'/db/sms_carrier.sql', $conn); - if ($res === false) { - updateStatus("Can't run SMS carrier script.", true); - showForm(); - return; - } - updateStatus("Adding notice source data to database..."); - $res = runDbScript(INSTALLDIR.'/db/notice_source.sql', $conn); - if ($res === false) { - updateStatus("Can't run notice source script.", true); - showForm(); - return; - } - updateStatus("Adding foreign service data to database..."); - $res = runDbScript(INSTALLDIR.'/db/foreign_services.sql', $conn); - if ($res === false) { - updateStatus("Can't run foreign service script.", true); - showForm(); - return; + foreach (array('sms_carrier' => 'SMS carrier', + 'notice_source' => 'notice source', + 'foreign_services' => 'foreign service') + as $scr => $name) { + updateStatus(sprintf("Adding %s data to database...", $name)); + $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn); + if ($res === false) { + updateStatus(sprintf("Can't run %d script.", $name), true); + showForm(); + return; + } } updateStatus("Writing config file..."); $sqlUrl = "mysqli://$username:$password@$host/$database"; From 09ebc965d58df9cc087568d99c3f4c5ba41496e2 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 4 Mar 2009 06:13:05 -0800 Subject: [PATCH 169/189] update README with new install instructions --- README | 97 ++++++++++++++++++++++++++++------------------------------ 1 file changed, 46 insertions(+), 51 deletions(-) diff --git a/README b/README index 67dc9a66b3..388d67ed2a 100644 --- a/README +++ b/README @@ -236,21 +236,28 @@ especially if you've previously installed PHP/MySQL packages. configure virtual hosts on your web server, you can try setting up "http://micro.example.net/" or the like. -3. You should also take this moment to make your avatar subdirectory +3. Make your target directory writeable by the Web server. + + chmod a+w /var/www/mublog/ + + On some systems, this will probably work: + + chgrp www-data /var/www/mublog/ + chmod g+w /var/www/mublog/ + + If your Web server runs as another user besides "www-data", try + that user's default group instead. As a last resort, you can create + a new group like "mublog" and add the Web server's user to the group. + +4. You should also take this moment to make your avatar subdirectory writeable by the Web server. An insecure way to do this is: chmod a+w /var/www/mublog/avatar - On some systems, this will probably work: + You can also make the avatar directory writeable by the Web server + group, as noted above. - chgrp www-data /var/www/mublog/avatar - chmod g+w /var/www/mublog/avatar - - If your Web server runs as another user besides "www-data", try - that user's default group instead. As a last resort, you can create - a new group like "avatar" and add the Web server's user to the group. - -4. Create a database to hold your microblog data. Something like this +5. Create a database to hold your microblog data. Something like this should work: mysqladmin -u "username" --password="password" create laconica @@ -263,63 +270,55 @@ especially if you've previously installed PHP/MySQL packages. a tool like PHPAdmin to create a database. Check your hosting service's documentation for how to create a new MySQL database.) -5. Run the laconica.sql SQL script in the db subdirectory to create - the database tables in the database. A typical system would work - like this: - - mysql -u "username" --password="password" laconica < /var/www/mublog/db/laconica.sql - - You may want to test by logging into the database and checking that - the tables were created. Here's an example: - - SHOW TABLES; - 6. Create a new database account that Laconica will use to access the database. If you have shell access, this will probably work from the MySQL shell: - GRANT SELECT,INSERT,DELETE,UPDATE on laconica.* + GRANT ALL on laconica.* TO 'lacuser'@'localhost' IDENTIFIED BY 'lacpassword'; You should change 'lacuser' and 'lacpassword' to your preferred new - username and password. You may want to test logging in as this new - user and testing that you can SELECT from some of the tables in the - DB (use SHOW TABLES to see which ones are there). + username and password. You may want to test logging in to MySQL as + this new user. -7. Copy the config.php.sample in the Laconica directory to config.php. +7. In a browser, navigate to the Laconica install script; something like: -8. Edit config.php to set the basic configuration for your system. - (See descriptions below for basic config options.) Note that there - are lots of options and if you try to do them all at once, you will - have a hard time making sure what's working and what's not. So, - stick with the basics at first. In particular, customizing the - 'site' and 'db' settings will almost definitely be needed. + http://yourserver.example.com/mublog/install.php -9. At this point, you should be able to navigate in a browser to your - microblog's main directory and see the "Public Timeline", which - will be empty. If not, magic has happened! You can now register a - new user, post some notices, edit your profile, etc. However, you - may want to wait to do that stuff if you think you can set up - "fancy URLs" (see below), since some URLs are stored in the database. + Enter the database connection information and your site name. The + install program will configure your site and install the initial, + almost-empty database. + +8. You should now be able to navigate to your microblog's main directory + and see the "Public Timeline", which will be empty. If not, magic + has happened! You can now register a new user, post some notices, + edit your profile, etc. However, you may want to wait to do that stuff + if you think you can set up "fancy URLs" (see below), since some + URLs are stored in the database. Fancy URLs ---------- -By default, Laconica will have big long sloppy URLs that are hard for -people to remember or use. For example, a user's home profile might be +By default, Laconica will use URLs that include the main PHP program's +name in them. For example, a user's home profile might be found at: - http://example.org/mublog/index.php?action=showstream&nickname=fred + http://example.org/mublog/index.php/mublog/fred + +On certain systems that don't support this kind of syntax, they'll +look like this: + + http://example.org/mublog/index.php?p=mublog/fred It's possible to configure the software so it looks like this instead: http://example.org/mublog/fred These "fancy URLs" are more readable and memorable for users. To use -fancy URLs, you must either have Apache 2.2.x with .htaccess enabled -and mod_redirect enabled, -OR- know how to configure "url redirection" -in your server. +fancy URLs, you must either have Apache 2.x with .htaccess enabled and +mod_redirect enabled, -OR- know how to configure "url redirection" in +your server. 1. Copy the htaccess.sample file to .htaccess in your Laconica directory. Note: if you have control of your server's httpd.conf or @@ -344,10 +343,6 @@ like: If you changed your HTTP server configuration, you may need to restart the server first. -If you have problems with the .htaccess file on versions of Apache -earlier than 2.2.x, try changing the regular expressions in the -htaccess.sample file that use "\w" to just use ".". - Sphinx ------ @@ -557,7 +552,7 @@ Sample cron job: # Update Twitter friends subscriptions every half hour 0,30 * * * * /path/to/php /path/to/laconica/scripts/synctwitterfriends.php>&/dev/null -Built-in Facebook Application +Built-in Facebook Application ----------------------------- Laconica's Facebook application allows your users to automatically @@ -571,7 +566,7 @@ above). Quick setup instructions*: -Install the Facebook Developer application on Facebook: +Install the Facebook Developer application on Facebook: http://www.facebook.com/developers/ @@ -644,7 +639,7 @@ to these resources. Themes ------ -There are two themes shipped with this version of Laconica: "stoica", +There are two themes shipped with this version of Laconica: "identica", which is what the Identi.ca site uses, and "default", which is a good basis for other sites. From f9babf6a7d4215e763a8c1766a2e6592fe274953 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 4 Mar 2009 06:24:33 -0800 Subject: [PATCH 170/189] Check for config file when running When running the full system, check for a config file, and throw an error if none is found. --- index.php | 8 ++++++++ lib/common.php | 6 ++++++ lib/util.php | 6 +++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/index.php b/index.php index 914ba5bde1..7f580b8367 100644 --- a/index.php +++ b/index.php @@ -61,6 +61,14 @@ function main() { global $user, $action; + if (!_have_config()) { + $msg = sprintf(_("No configuration file found. Try running ". + "the installation program first.")); + $sac = new ServerErrorAction($msg); + $sac->showPage(); + return; + } + // For database errors PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError'); diff --git a/lib/common.php b/lib/common.php index 0fff3af2ea..ca8dedeefe 100644 --- a/lib/common.php +++ b/lib/common.php @@ -187,6 +187,12 @@ foreach ($_config_files as $_config_file) { } } +function _have_config() +{ + global $_have_a_config; + return $_have_a_config; +} + // XXX: Throw a conniption if database not installed // Fixup for laconica.ini diff --git a/lib/util.php b/lib/util.php index 18e4f310ce..f9a787d473 100644 --- a/lib/util.php +++ b/lib/util.php @@ -81,7 +81,7 @@ function common_language() // If there is a user logged in and they've set a language preference // then return that one... - if (common_logged_in()) { + if (_have_config() && common_logged_in()) { $user = common_current_user(); $user_language = $user->language; if ($user_language) @@ -315,6 +315,10 @@ function common_current_user() { global $_cur; + if (!_have_config()) { + return null; + } + if ($_cur === false) { if (isset($_REQUEST[session_name()]) || (isset($_SESSION['userid']) && $_SESSION['userid'])) { From cf4e1872ab8109ea6a3230e43cc70899a17dd075 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 4 Mar 2009 06:27:30 -0800 Subject: [PATCH 171/189] Error actions use HTTP code name for title Change the title of error actions to the HTTP code name, like 'internal server error'. --- lib/clienterroraction.php | 15 ++++++++++----- lib/servererroraction.php | 20 ++++++++++++-------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/lib/clienterroraction.php b/lib/clienterroraction.php index 5019dc06de..0c48414d55 100644 --- a/lib/clienterroraction.php +++ b/lib/clienterroraction.php @@ -49,7 +49,7 @@ class ClientErrorAction extends ErrorAction function __construct($message='Error', $code=400) { parent::__construct($message, $code); - + $this->status = array(400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', @@ -72,7 +72,7 @@ class ClientErrorAction extends ErrorAction } // XXX: Should these error actions even be invokable via URI? - + function handle($args) { parent::handle($args); @@ -84,11 +84,16 @@ class ClientErrorAction extends ErrorAction } $this->message = $this->trimmed('message'); - + if (!$this->message) { - $this->message = "Client Error $this->code"; - } + $this->message = "Client Error $this->code"; + } $this->showPage(); } + + function title() + { + return $this->status[$this->code]; + } } diff --git a/lib/servererroraction.php b/lib/servererroraction.php index 80a3fdd7b4..595dcf1470 100644 --- a/lib/servererroraction.php +++ b/lib/servererroraction.php @@ -42,7 +42,7 @@ require_once INSTALLDIR.'/lib/error.php'; * says that 500 errors should be treated similarly to 400 errors, and * it's easier to give an HTML response. Maybe we can customize these * to display some funny animal cartoons. If not, we can probably role - * these classes up into a single class. + * these classes up into a single class. * * See: http://tools.ietf.org/html/rfc2616#section-10 * @@ -57,19 +57,19 @@ class ServerErrorAction extends ErrorAction function __construct($message='Error', $code=500) { parent::__construct($message, $code); - + $this->status = array(500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported'); - + $this->default = 500; } // XXX: Should these error actions even be invokable via URI? - + function handle($args) { parent::handle($args); @@ -81,12 +81,16 @@ class ServerErrorAction extends ErrorAction } $this->message = $this->trimmed('message'); - + if (!$this->message) { - $this->message = "Server Error $this->code"; - } + $this->message = "Server Error $this->code"; + } $this->showPage(); } - + + function title() + { + return $this->status[$this->code]; + } } From c02a2f189139bd3588b04e557239e243805aaeac Mon Sep 17 00:00:00 2001 From: CiaranG Date: Wed, 4 Mar 2009 15:30:17 +0000 Subject: [PATCH 172/189] PostgreSQL - added defaults for 'created' fields, so they work the same as the MySQL version, and made sure all 'modified' fields are not null --- db/laconica_pg.sql | 56 +++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/db/laconica_pg.sql b/db/laconica_pg.sql index cacdfa97ed..4ef2330f40 100644 --- a/db/laconica_pg.sql +++ b/db/laconica_pg.sql @@ -8,7 +8,7 @@ create table profile ( homepage varchar(255) /* comment 'identifying URL' */, bio varchar(140) /* comment 'descriptive biography' */, location varchar(255) /* comment 'physical location' */, - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified' */, textsearch tsvector @@ -23,7 +23,7 @@ create table avatar ( mediatype varchar(32) not null /* comment 'file type' */, filename varchar(255) null /* comment 'local filename, if local' */, url varchar(255) unique /* comment 'avatar location' */, - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified' */, primary key(profile_id, width, height) @@ -34,7 +34,7 @@ create table sms_carrier ( id serial primary key /* comment 'primary key for SMS carrier' */, name varchar(64) unique /* comment 'name of the carrier' */, email_pattern varchar(255) not null /* comment 'sprintf pattern for making an email address from a phone number' */, - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified ' */ ); @@ -69,7 +69,7 @@ create table "user" ( autosubscribe integer default 0 /* comment 'automatically subscribe to users who subscribe to us' */, urlshorteningservice varchar(50) default 'ur1.ca' /* comment 'service to use for auto-shortening URLs' */, inboxed integer default 0 /* comment 'has an inbox been created for this user?' */, - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified' */ ); @@ -82,7 +82,7 @@ create table remote_profile ( uri varchar(255) unique /* comment 'universally unique identifier, usually a tag URI' */, postnoticeurl varchar(255) /* comment 'URL we use for posting notices' */, updateprofileurl varchar(255) /* comment 'URL we use for updates to this profile' */, - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified' */ ); @@ -93,7 +93,7 @@ create table subscription ( sms integer default 1 /* comment 'deliver sms messages' */, token varchar(255) /* comment 'authorization token' */, secret varchar(255) /* comment 'token secret' */, - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified' */, primary key (subscriber, subscribed) @@ -109,7 +109,7 @@ create table notice ( content varchar(140) /* comment 'update content' */, rendered text /* comment 'HTML version of the content' */, url varchar(255) /* comment 'URL of any attachment (image, video, bookmark, whatever)' */, - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified' */, reply_to integer /* comment 'notice replied to (usually a guess)' */ references notice (id) , is_local integer default 0 /* comment 'notice was generated by a user' */, @@ -124,7 +124,7 @@ create table notice_source ( code varchar(32) primary key not null /* comment 'source code' */, name varchar(255) not null /* comment 'name of the source' */, url varchar(255) not null /* comment 'url to link to' */, - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified' */ ); @@ -132,7 +132,7 @@ create table reply ( notice_id integer not null /* comment 'notice that is the reply' */ references notice (id) , profile_id integer not null /* comment 'profile replied to' */ references profile (id) , - modified timestamp not null default 'now' /* comment 'date this record was modified' */, + modified timestamp /* comment 'date this record was modified' */, replied_id integer /* comment 'notice replied to (not used, see notice.reply_to)' */, primary key (notice_id, profile_id) @@ -146,7 +146,7 @@ create table fave ( notice_id integer not null /* comment 'notice that is the favorite' */ references notice (id), user_id integer not null /* comment 'user who likes this notice' */ references "user" (id) , - modified timestamp not null /* comment 'date this record was modified' */, + modified timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was modified' */, primary key (notice_id, user_id) ); @@ -160,7 +160,7 @@ create table consumer ( consumer_key varchar(255) primary key /* comment 'unique identifier, root URL' */, seed char(32) not null /* comment 'seed for new tokens by this consumer' */, - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified' */ ); @@ -171,7 +171,7 @@ create table token ( type integer not null default 0 /* comment 'request or access' */, state integer default 0 /* comment 'for requests; 0 = initial, 1 = authorized, 2 = used' */, - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified' */, primary key (consumer_key, tok) @@ -183,7 +183,7 @@ create table nonce ( nonce char(32) not null /* comment 'nonce' */, ts timestamp not null /* comment 'timestamp sent' */, - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified' */, primary key (consumer_key, tok, nonce), @@ -196,7 +196,7 @@ create table user_openid ( canonical varchar(255) primary key /* comment 'Canonical true URL' */, display varchar(255) not null unique /* comment 'URL for viewing, may be different from canonical' */, user_id integer not null /* comment 'user owning this URL' */ references "user" (id) , - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified' */ ); @@ -242,7 +242,7 @@ create table queue_item ( notice_id integer not null /* comment 'notice queued' */ references notice (id) , transport varchar(8) not null /* comment 'queue for what? "email", "jabber", "sms", "irc", ...' */, - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, claimed timestamp /* comment 'date this item was claimed' */, primary key (notice_id, transport) @@ -254,7 +254,7 @@ create index queue_item_created_idx on queue_item using btree(created); create table notice_tag ( tag varchar( 64 ) not null /* comment 'hash tag associated with this notice' */, notice_id integer not null /* comment 'notice tagged' */ references notice (id) , - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, primary key (tag, notice_id) ); @@ -266,7 +266,7 @@ create table foreign_service ( id int not null primary key /* comment 'numeric key for service' */, name varchar(32) not null unique /* comment 'name of the service' */, description varchar(255) /* comment 'description' */, - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified' */ ); @@ -275,7 +275,7 @@ create table foreign_user ( service int not null /* comment 'foreign key to service' */ references foreign_service(id) , uri varchar(255) not null unique /* comment 'identifying URI' */, nickname varchar(255) /* comment 'nickname on foreign service' */, - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified' */, primary key (id, service) @@ -289,8 +289,8 @@ create table foreign_link ( noticesync int not null default 1 /* comment 'notice synchronisation, bit 1 = sync outgoing, bit 2 = sync incoming, bit 3 = filter local replies' */, friendsync int not null default 2 /* comment 'friend synchronisation, bit 1 = sync outgoing, bit 2 = sync incoming */, profilesync int not null default 1 /* comment 'profile synchronization, bit 1 = sync outgoing, bit 2 = sync incoming' */, - created timestamp not null /* comment 'date this record was created' */, - modified timestamp not null /* comment 'date this record was modified' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, + modified timestamp /* comment 'date this record was modified' */, primary key (user_id,foreign_id,service) ); @@ -300,7 +300,7 @@ create table foreign_subscription ( service int not null /* comment 'service where relationship happens' */ references foreign_service(id) , subscriber int not null /* comment 'subscriber on foreign service' */ , subscribed int not null /* comment 'subscribed user' */ , - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, primary key (service, subscriber, subscribed) ); @@ -312,7 +312,7 @@ create table invitation ( user_id int not null /* comment 'who sent the invitation' */ references "user" (id), address varchar(255) not null /* comment 'invitation sent to' */, address_type varchar(8) not null /* comment 'address type ("email", "jabber", "sms") '*/, - created timestamp not null /* comment 'date this record was created' */ + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */ ); create index invitation_address_idx on invitation using btree(address,address_type); @@ -327,7 +327,7 @@ create table message ( content varchar(140) /* comment 'message content' */, rendered text /* comment 'HTML version of the content' */, url varchar(255) /* comment 'URL of any attachment (image, video, bookmark, whatever)' */, - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified' */, source varchar(32) /* comment 'source of comment, like "web", "im", or "clientname"' */ @@ -340,7 +340,7 @@ create table notice_inbox ( user_id integer not null /* comment 'user receiving the message' */ references "user" (id), notice_id integer not null /* comment 'notice received' */ references notice (id), - created timestamp not null /* comment 'date the notice was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date the notice was created' */, source integer default 1 /* comment 'reason it is in the inbox; 1=subscription' */, primary key (user_id, notice_id) @@ -383,7 +383,7 @@ create table user_group ( stream_logo varchar(255) /* comment 'stream-sized logo' */, mini_logo varchar(255) /* comment 'mini logo' */, - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified' */ ); @@ -395,7 +395,7 @@ create table group_member ( profile_id integer not null /* comment 'foreign key to profile table' */ references profile (id), is_admin integer default 0 /* comment 'is this user an admin?' */, - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified' */, primary key (group_id, profile_id) @@ -406,7 +406,7 @@ create table related_group ( group_id integer not null /* comment 'foreign key to user_group' */ references user_group (id) , related_group_id integer not null /* comment 'foreign key to user_group' */ references user_group (id), - created timestamp not null /* comment 'date this record was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, primary key (group_id, related_group_id) @@ -415,7 +415,7 @@ create table related_group ( create table group_inbox ( group_id integer not null /* comment 'group receiving the message' references user_group (id) */, notice_id integer not null /* comment 'notice received' references notice (id) */, - created timestamp not null /* comment 'date the notice was created' */, + created timestamp not null default CURRENT_TIMESTAMP /* comment 'date the notice was created' */, primary key (group_id, notice_id) ); From 115519a5e7e84e57656c653918efb39ab4107fe9 Mon Sep 17 00:00:00 2001 From: CiaranG Date: Wed, 4 Mar 2009 15:32:26 +0000 Subject: [PATCH 173/189] PostgreSQL - made all 'weight' calculating SQL expressions compatible with both databases, and made some GROUP BY queries more explicit about the fields they are selecting, for the same reason. --- actions/favorited.php | 8 +++++++- lib/groupsbymemberssection.php | 2 +- lib/groupsbypostssection.php | 2 +- lib/grouptagcloudsection.php | 8 +++++++- lib/personaltagcloudsection.php | 10 ++++++++-- lib/popularnoticesection.php | 12 +++++++++--- 6 files changed, 33 insertions(+), 9 deletions(-) diff --git a/actions/favorited.php b/actions/favorited.php index fd5ff413cb..5082f4a4eb 100644 --- a/actions/favorited.php +++ b/actions/favorited.php @@ -169,8 +169,14 @@ class FavoritedAction extends Action function showContent() { + if (common_config('db', 'type') == 'pgsql') { + $weightexpr='sum(exp(-extract(epoch from (now() - fave.modified)) / %s))'; + } else { + $weightexpr='sum(exp(-(now() - fave.modified) / %s))'; + } + $qry = 'SELECT notice.*, '. - 'sum(exp(-(now() - fave.modified) / %s)) as weight ' . + $weightexpr . ' as weight ' . 'FROM notice JOIN fave ON notice.id = fave.notice_id ' . 'GROUP BY fave.notice_id ' . 'ORDER BY weight DESC'; diff --git a/lib/groupsbymemberssection.php b/lib/groupsbymemberssection.php index 4fa07a244a..5f26c6626f 100644 --- a/lib/groupsbymemberssection.php +++ b/lib/groupsbymemberssection.php @@ -45,7 +45,7 @@ class GroupsByMembersSection extends GroupSection { function getGroups() { - $qry = 'SELECT user_group.*, count(*) as value ' . + $qry = 'SELECT user_group.id, count(*) as value ' . 'FROM user_group JOIN group_member '. 'ON user_group.id = group_member.group_id ' . 'GROUP BY user_group.id ' . diff --git a/lib/groupsbypostssection.php b/lib/groupsbypostssection.php index a5e33a93de..1a60ddb4fc 100644 --- a/lib/groupsbypostssection.php +++ b/lib/groupsbypostssection.php @@ -45,7 +45,7 @@ class GroupsByPostsSection extends GroupSection { function getGroups() { - $qry = 'SELECT user_group.*, count(*) as value ' . + $qry = 'SELECT user_group.id, count(*) as value ' . 'FROM user_group JOIN group_inbox '. 'ON user_group.id = group_inbox.group_id ' . 'GROUP BY user_group.id ' . diff --git a/lib/grouptagcloudsection.php b/lib/grouptagcloudsection.php index f05be85cbb..5d68af28bf 100644 --- a/lib/grouptagcloudsection.php +++ b/lib/grouptagcloudsection.php @@ -58,8 +58,14 @@ class GroupTagCloudSection extends TagCloudSection function getTags() { + if (common_config('db', 'type') == 'pgsql') { + $weightexpr='sum(exp(-extract(epoch from (now() - notice_tag.created)) / %s))'; + } else { + $weightexpr='sum(exp(-(now() - notice_tag.created) / %s))'; + } + $qry = 'SELECT notice_tag.tag, '. - 'sum(exp(-(now() - notice_tag.created)/%s)) as weight ' . + $weightexpr . ' as weight ' . 'FROM notice_tag JOIN notice ' . 'ON notice_tag.notice_id = notice.id ' . 'JOIN group_inbox on group_inbox.notice_id = notice.id ' . diff --git a/lib/personaltagcloudsection.php b/lib/personaltagcloudsection.php index 0882822db2..978153a84e 100644 --- a/lib/personaltagcloudsection.php +++ b/lib/personaltagcloudsection.php @@ -58,8 +58,14 @@ class PersonalTagCloudSection extends TagCloudSection function getTags() { - $qry = 'SELECT notice_tag.tag, '. - 'sum(exp(-(now() - notice_tag.created)/%s)) as weight ' . + if (common_config('db', 'type') == 'pgsql') { + $weightexpr='sum(exp(-extract(epoch from (now() - notice_tag.created)) / %s))'; + } else { + $weightexpr='sum(exp(-(now() - notice_tag.created) / %s))'; + } + + $qry = 'SELECT notice_tag.tag, '. + $weightexpr . ' as weight ' . 'FROM notice_tag JOIN notice ' . 'ON notice_tag.notice_id = notice.id ' . 'WHERE notice.profile_id = %d ' . diff --git a/lib/popularnoticesection.php b/lib/popularnoticesection.php index c7c7f02150..f7fb935543 100644 --- a/lib/popularnoticesection.php +++ b/lib/popularnoticesection.php @@ -48,10 +48,16 @@ class PopularNoticeSection extends NoticeSection { function getNotices() { - $qry = 'SELECT notice.*, '. - 'sum(exp(-(now() - fave.modified) / %s)) as weight ' . + if (common_config('db', 'type') == 'pgsql') { + $weightexpr='sum(exp(-extract(epoch from (now() - fave.modified)) / %s))'; + } else { + $weightexpr='sum(exp(-(now() - fave.modified) / %s))'; + } + + $qry = 'SELECT notice.id, '. + $weightexpr . ' as weight ' . 'FROM notice JOIN fave ON notice.id = fave.notice_id ' . - 'GROUP BY fave.notice_id ' . + 'GROUP BY notice.id ' . 'ORDER BY weight DESC'; $offset = 0; From a7efd4ff556bbf6bafcfc81db758ab192b8802ad Mon Sep 17 00:00:00 2001 From: CiaranG Date: Wed, 4 Mar 2009 15:34:04 +0000 Subject: [PATCH 174/189] Plugins - added a new event (RouterInitialized) which allows a plugin to register new paths to be routed --- EVENTS.txt | 4 ++++ lib/router.php | 2 ++ 2 files changed, 6 insertions(+) diff --git a/EVENTS.txt b/EVENTS.txt index 2f33b2d5d5..5edf59245a 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -99,3 +99,7 @@ StartPublicGroupNav: Showing the public group nav menu EndPublicGroupNav: At the end of the public group nav menu - $action: the current action + +RouterInitialized: After the router instance has been initialized +- $m: the Net_URL_Mapper that has just been set up + diff --git a/lib/router.php b/lib/router.php index 4b70c01505..d4a4d2ca93 100644 --- a/lib/router.php +++ b/lib/router.php @@ -393,6 +393,8 @@ class Router array('action' => 'showstream'), array('nickname' => '[a-zA-Z0-9]{1,64}')); + Event::handle('RouterInitialized', array($m)); + return $m; } From 00c358956fcece251b8d78f2c6a41098571472c7 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 4 Mar 2009 12:07:53 -0800 Subject: [PATCH 175/189] check for profile record --- lib/noticesection.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/noticesection.php b/lib/noticesection.php index b31f187445..94c2738efd 100644 --- a/lib/noticesection.php +++ b/lib/noticesection.php @@ -73,6 +73,11 @@ class NoticeSection extends Section function showNotice($notice) { $profile = $notice->getProfile(); + if (empty($profile)) { + common_log(LOG_WARNING, sprintf("Notice %d has no profile", + $notice->id)); + return; + } $this->out->elementStart('li', 'hentry notice'); $this->out->elementStart('div', 'entry-title'); $avatar = $profile->getAvatar(AVATAR_MINI_SIZE); From e239a5529abc0958c4f874811ff704f1e5d6ba62 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 4 Mar 2009 21:05:26 +0000 Subject: [PATCH 176/189] Yahoo! SearchMonkey applications: * Displays user profile information http://identi.ca * Displays user's XFN on http://identi.ca --- scripts/SearchMonkey-Om3.0.txt | 1 + scripts/SearchMonkey-yQP.0.txt | 1 + 2 files changed, 2 insertions(+) create mode 100644 scripts/SearchMonkey-Om3.0.txt create mode 100644 scripts/SearchMonkey-yQP.0.txt diff --git a/scripts/SearchMonkey-Om3.0.txt b/scripts/SearchMonkey-Om3.0.txt new file mode 100644 index 0000000000..45b782d0b2 --- /dev/null +++ b/scripts/SearchMonkey-Om3.0.txt @@ -0,0 +1 @@ +BE9KrPPSJAPljm0ykS59yJQmWBFY9RPVJFhZsiF_5Wcf_tkGwf4FP2Ncjs7qGfHFCb2pv.eDr5y0zhKrF4s3ugs89DbLaA.hrbRJwglsNym5TDwDycrGw3TvjfCVmHBx4VzOZ2QzUyXBIs0T30paT6PYQfARKBdMkieKbbQ1tfl.f.ul35_kcpoXZt_lTDWFDaia2RM41uDLHJyVbCcRkqfHCLabgNeOq_MGFXGA6DjnKQx7TNyQe.2N6IyVd1quVapHn6jUOsWNkahehPMHtO72yvPpugS0UHCKBqcd.UCcbIhmtzLnKoBQAH2AJqUrmfg1XRwqFvTo6y9Z5XmDQK2hRnv97InV5he1AMIqNUotAcIrYjq5Tn42whYsznnMYhMY44UqZGoJI_ZwsSvnH6Je.AhKU3hBW9Tsmggpxgnhx_o2vhyNw2QAgPJng0FKxaevCmPnFtLntwluhxLbiTo2IiIotP4VjIVKs76hHzAsmXYuvS01OU.XB43Gmcw5yP9OUIoh5fEA1ANxOb.ba8aVxu2FdvedbOECgO6gvr.kYilrdxwwnVbHwVP6esrOiYE32dRs0_tWdgfvJfyloLjj_M5stZLEBcVfoUSQHsxX6jW5jrs4BTEHhKrxlOzdBt8f8T2E6eA9x2h7B8zK4eRtC2NBrcj1jzYCVWUT7IdbN05NZBjGYvxtmX1KRW91QwdFXgytfOINFkDk0scNCGGt4OYDzKLJ7sRBy0iKhHNfTC3noUhWf1372uXQ_yiWDWB7cTfxF9meAU3TWrTS7DjGTkwLvCVUJ43QyCxBQd1jWU6sp_VvytDeHx7cqrkNsa3JRN9dht3POqq_mVL0c5MX_XRpV8O.tPSGmfUPUrR5qnV8Z739D6PGWkOQzfzTTv6vGkf4jj1xyDEfUqr767_yL7gHeI2VlbdD2ammzwEhRK9f8ME8FbrZTTgX1OD0.v1cFULt1lew.rtCOtKf4F2MBbi90edst.TpOoUh_TvFkgKf0zgteNo2JjzrmCO.uviLCM2weGksARL70mdU6W9N932YWr6E8HbUc02S8ifSnbMpiUTHwNzMErsg7BSyRVJngGLDreBkBEIjXkNoApJR7kMPMaYVwtvjU.8wLmyFLZ.PHqqcrADTr7R50hL3UKj1mLsL19gQL3cP9J7_zJJM4Q1GDj2dPWBLhJMosUVhW8XBFPhW0aEdmgqhpsAJ_qv52xfjBeEPyPmKHu1p.1G2QEmOqFm_swbVAPHnra5zd8x4OOlHGFeL3qPLW9_LXN46chs6cOpUdYaWRzT2YuOUHHE8RDV9Mev22p7WjrbgC8hY3wK55Cpw_fCekaHcJX2jU3FPl09httjI1i5yGU04yD5MpOQF5EadbSlfjDEg8H.GoOFZcyKKDnqj_8SYdrXWq4aTqAnWgvBPh.bznvmevDgEpH.9gtzLcao4Dzmk.K0PnRgvkzyaN5IGk60TiD4Fk0u3f4bqKaPCOIKCTajbNGIasyND0J5ROtAa_IlXljCpzeEYPhSASSFV6fWmlqHFiGa2cKlLNr157MAUVQg52dJRRyid1YDcHK8ZuMc4BX8QA0olb2CDbUHKxxwv0zbIsgUTL8gvKIAPwBfF0cPfV9v6Phs1rPFXnysxdnunXnr6r6ZCN00rZeA7XbH68f.UjW0.ERGvB72kMnB8fBhfEgD22Y0ZFkwxI00UVAm5yiwkjYx86EdCpkMLBVDR1kWictX04pFLzxZK8iRflPBy74Nx2SFO19qJFnQmKEV2IN2wlrH6z14902LEI2Vuh_dVxNphr1k5mJ9x2VNGmvol.vvHmaLCufHIrOMRkAFMxeP37Mnzyk6NpxAQbdV0KwC.YTfoK9Y5knYAherlh7x8NqELX6XqutF2Lm11dikoK8Yu8u3Rkhoaef00PISV0LOb0E.D1LRjFFGzUCt5Ezcq68Tt0rFNEF1gm5Xl9Rygln_67F0KMgacpKTJ2PBIqcteyRZoRohFBRUTLkKn784KacSBsWFkogD5n7blv943wWeZFvUcKsFyYZny1WLvGEQX01kbjyaDCLDxiEQWSvlDuD.2wOra0JO5.Bo2PpPK5tbNexZDVx3k1yb48ev2UC4J4IlD6oK1KYe46Y.7NX9kDYCcdRiQOVnLJ4PSbPhDW0hcE2SNtxmFdpAf2xQOiekXY35lkuNxvebVhKuoFWxUgQmFEfreJQsC5qf_dunxGquD6u3SXnLbTg8aNWZqtR74dr3s7C5kwBK6eBGwJfkAx8Z2JKwLr5QHdISfa0wLl7aIejz5Mg_tfeATLIwW8SiIQdJiZ992Bx1k_50XQVeHOdyEc8J1Tjnkrji_ZY9PFn3AZc94wXD8erpANuJIbDIdLUp1idqF_TDtmqSK2qQgz26IkewcZnSIQnm98qxkHf9DAIbE6_GUP0ZeWqLQrn0CdhejhOPsKsovcDI7zBHMQABquMeBh9YC1.QCO9br6YmYEAV30n5IsZgl5PRiRDjkhXv8VaE.fnRNndSy7BX6kmxSmDWQfY0FvbUBWBQm7tfU3Mrk2Ojm4ALIYUZnJxl.5xj6VvD.2ZX3ug_1zo7mAiAYG_F939DMccfgCVFUcdtbF9Q9YFeC2bVMfgX5gA75nBCu5R8KopeWfTjdqZBtJoT6HxFmCSLQqIdjrxD7X2RapCI0QoDsp9xyzRZJ5NF5ygVBJqtNn2lAwFENasqFvGRSb35B.sjTQoHW5WBoBjFy5tNF1vWYJENfKRjWn5Pr0FvFdQfBKqeiWrLtStAnGrd49frviyK5VtYa.eSuRyX32SGb7.Pxo0Hf0bhER4uvPBktf3ec5UjL_xRI3eQ__F7tRiGu2tRYIUYcUkhLKAGYoDx2_gdMOXcow5W3KwtuVNtZ4UMRmuB3ccyBUzDTRWNLFISq56JXU46_v7ANub44kTLDfGuJExu.0NaOuW6isndAIYslJlcCs6NJ6j_6Ag55G.UhQFfzDKVOEt.L \ No newline at end of file diff --git a/scripts/SearchMonkey-yQP.0.txt b/scripts/SearchMonkey-yQP.0.txt new file mode 100644 index 0000000000..e42a04e160 --- /dev/null +++ b/scripts/SearchMonkey-yQP.0.txt @@ -0,0 +1 @@ +bzN6aQzSJBM8jb6JDpNRvtfEMpxOC6r2ffo5VJf0ah_Q3kXs8gwvDzGy0KGHkwDDijfnJAwhb.BIcFihUZVWe.vAy2xZZN8XUMxHwc16HwCsgDyOC9sH5WzOV0dvJcVZ.9ipAgI7RuXsAvZdJv90N89iOh1WmD6QwWyzyvnu.FjQDrQH1DlGwFs3ZNzY5lsd5uYhtJ3puBXrzSmGcR68_7OEw4QzAV9SyPSHSskXF56cpH8pUey8WiQieM4X_uuKsMjQfWEUdqNb6teXDplvPDRqHwP5rC6X1_oHBiocfgWiBfUfKOXE1g5J.JtqXYIBee8ROyx3sVIc7V2I0eotZCCiCJ1k74ru2OGC9UhX__ZESGrp9b0JZGZFO6w97IL1Y3BTgVXNox8L3FcFjl11s7YcYBhc_3e3WbJA4pZzczPzd0ouZiCjUcBCZXNu6fnM6XerBbsVj584ZbPdjQ6A5TcrED8dTcFdgfMWlMu7bHsE7e0QIJYmc2g5NWeur2ovAOPVxhELrvnJ6X6Z.06vAmwdJcl4hyYRVqA.9QynHlH2dezXS5y0eKXFUzg.tHNqZboEGutLL316mZQYvwZwATZFc8iO3EuiNV3MWbiCrLX.n3nbXp82A4v7Bv.PVJoeOTmiPmq8vuxts8nUEPf7gA3j6.DmlDvfZQNx9.WNmNFps0B08hzDMvKX98.ft9.GJlftPCYCk2qHRtWm3tjB2R9O6mF6XYvOLUK7aQ4rz5oCxHwmrF.n56G07MS5GS1pjV3ByUO88PI1i_rPbim_Up0jIO5q7PqXudyFE.nG0Dc8yaExZk7PryBfSHk2yMQVXcBjy3XQBVmrIwPwKs.YUFC1qLSThfA8UNckik2xuDZKvf29xpGvHlMbH67UV5HhY5CTeqlgvapRThmfrexqaBTTPPqR.Sy3Pvr9vHX.0UC6kNCTlxW3CExrx9EipRKUvRVClNLEG6M9hm5MQkdfgmd2Dmj5DmjcrZkWOqnNfLn_mtYdoD3nK4zCk.NZwXTFlx5FVr5bOzyncalNdb77qLEH9E9R8d2KUA4Axljx29kycILhZCvy3Qz22Vz_M72lVFKQAFnGlERRS7NLu2rB00e.MNeH9aB01uj.y9UHpfjTAwOJSFEBf7yKRCkVzqFpGAQo7txlC3NRgSyI3kQtW0WeWiNzghBv8c_5iXWkXqFyRfVZUCw2qqow3BF6SDtkhXAr8d.JrprGdG67U66ZD3JB10OLmmAaDiiCr81eVOkrsz3ONdigZOlP7RrYtodAORnheCZ10SAYbaru0sKmsJx4sB41QKn79dieWs5oihI5Rr3jUzPjfYgav5jeEfUYz5qHTtmps4vVJjt9s4zueRiT8AYzg.Xv.wdC1dvBr_kpGxwrkMEZJ0QfZkH.TDWBSWyxeG9gtuKd1gy9CGQTXlLvonJWHwK9gwWZwRKIanV4gnMfWz4pvhGixzEaiRU6g9giKYHpVKe.w0cwPfSDIhbBYqrlvuxkSTXfuFNyTpHM0Mgc6PMNnH6GooeLVK3IO2M8dRdDbL7EHykIJWe85pm5BxSqFTcWXn1nGuVWQV.F.J1XYJj1KheT725MVRlrE3sH.MgY6m4hKCUXT7mIgJ6xsfAQ8dWn7V1fCpqoIYub55iw_dg8yfZ7Yfgwj9bqN81lvTWQzBMLKYpG6pOh37JsTikpXa9kTSJfbJm0_GYZv_Bu10D1WqiO0UCvJXrlqkl5F610fkXkhwRXvmIQL2Mv_R71zSKqMr1g5RmnNifUv43jpXoOWmOKscg59pv8eOyb2JG26BtyVS2XOTDw6pHpHKuksqRKt62i1z83uv_ve5rO0zIKeMnWGlvXKHn8ld3fqGyJvqZ7wqYpnV_LTE3p73XCLEeniClXyk5Q_Icu1PgakYh3GIz8RVBjbjgrZIsvvkEp0Kgeo6oFY0wnboIwDlaUVHIue1nPkuAIsVk9i_IJWeqoRV0eO0wUGWgQ5.HT9RzcKdFVe2kl84RKuQkvoibiCfGlfTXpOfLOZtwsfl2TuNgZtP5oG1Wfsgi6g2a1N8B2WHYN8kZuG8pMnc4NQDIoO8BDnz89lDt7Y9JCslIM3O2Y25EGsylNKamKPJZr.ZSvvTVJG5sxftLxvF8vc8h5_pJD9Akj.84CjqQyfsnzFqc_.EqBaRVSy35wHHrdWTBsvjcobtSed8wXs.5En6HdubdATcgthozHWMEMsUdA29XkenPic2cR.8bQ5VdGZ2_YkJIXrxgt_XoEFFB8tgy061cghyL2fc2fHMQm4KBEc1vjkZjwRo9adWbg9dzAl782AwKC.C72bw-- \ No newline at end of file From 36bb33fb1d7b4befe2fb68c2eef0712619359293 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 4 Mar 2009 16:17:40 -0800 Subject: [PATCH 177/189] Made /api/account/verify_credentials.format return an extended user object. Updates to status and user API objects. --- actions/twitapiaccount.php | 20 ++++++++------- actions/twitapiusers.php | 52 +++++++++++++++++++++++++++----------- lib/router.php | 11 ++++---- lib/twitterapi.php | 41 ++++++++++++++---------------- 4 files changed, 73 insertions(+), 51 deletions(-) diff --git a/actions/twitapiaccount.php b/actions/twitapiaccount.php index c19cd370d9..68a18cb57b 100644 --- a/actions/twitapiaccount.php +++ b/actions/twitapiaccount.php @@ -23,22 +23,24 @@ require_once(INSTALLDIR.'/lib/twitterapi.php'); class TwitapiaccountAction extends TwitterapiAction { - function verify_credentials($args, $apidata) { - if ($apidata['content-type'] == 'xml') { - header('Content-Type: application/xml; charset=utf-8'); - print 'true'; - } elseif ($apidata['content-type'] == 'json') { - header('Content-Type: application/json; charset=utf-8'); - print '{"authorized":true}'; - } else { + parent::handle($args); + + switch ($apidata['content-type']) { + case 'xml': + case 'json': + $action_obj = new TwitapiusersAction(); + $action_obj->prepare($args); + call_user_func(array($action_obj, 'show'), $args, $apidata); + break; + default: header('Content-Type: text/html; charset=utf-8'); print 'Authorized'; } } - function end_session($args, $apidata) + function end_session($args, $apidata) { parent::handle($args); $this->serverError(_('API method under construction.'), $code=501); diff --git a/actions/twitapiusers.php b/actions/twitapiusers.php index 8f16e56131..2894b7486d 100644 --- a/actions/twitapiusers.php +++ b/actions/twitapiusers.php @@ -25,25 +25,29 @@ class TwitapiusersAction extends TwitterapiAction { function show($args, $apidata) - { + { parent::handle($args); - if (!in_array($apidata['content-type'], array('xml', 'json'))) { + if (!in_array($apidata['content-type'], array('xml', 'json'))) { $this->clientError(_('API method not found!'), $code = 404); return; } - - $this->auth_user = $apidata['user']; + $user = null; $email = $this->arg('email'); + $user_id = $this->arg('user_id'); if ($email) { $user = User::staticGet('email', $email); + } elseif ($user_id) { + $user = $this->get_user($user_id); } elseif (isset($apidata['api_arg'])) { $user = $this->get_user($apidata['api_arg']); - } - - if (!$user) { + } elseif (isset($apidata['user'])) { + $user = $apidata['user']; + } + + if (!$user) { // XXX: Twitter returns a random(?) user instead of throwing and err! -- Zach $this->client_error(_('Not found.'), 404, $apidata['content-type']); return; @@ -74,9 +78,12 @@ class TwitapiusersAction extends TwitterapiAction // Other fields Twitter sends... $twitter_user['profile_background_color'] = ''; + $twitter_user['profile_background_image_url'] = ''; $twitter_user['profile_text_color'] = ''; $twitter_user['profile_link_color'] = ''; $twitter_user['profile_sidebar_fill_color'] = ''; + $twitter_user['profile_sidebar_border_color'] = ''; + $twitter_user['profile_background_tile'] = 'false'; $faves = DB_DataObject::factory('fave'); $faves->user_id = $user->id; @@ -94,18 +101,27 @@ class TwitapiusersAction extends TwitterapiAction $twitter_user['utc_offset'] = $t->format('Z'); $twitter_user['time_zone'] = $timezone; - if (isset($this->auth_user)) { + if (isset($apidata['user'])) { - if ($this->auth_user->isSubscribed($profile)) { + if ($apidata['user']->isSubscribed($profile)) { $twitter_user['following'] = 'true'; } else { $twitter_user['following'] = 'false'; } - - // Not implemented yet - $twitter_user['notifications'] = 'false'; - } - + + // Notifications on? + $sub = Subscription::pkeyGet(array('subscriber' => + $apidata['user']->id, 'subscribed' => $profile->id)); + + if ($sub) { + if ($sub->jabber || $sub->sms) { + $twitter_user['notifications'] = 'true'; + } else { + $twitter_user['notifications'] = 'false'; + } + } + } + if ($apidata['content-type'] == 'xml') { $this->init_document('xml'); $this->show_twitter_xml_user($twitter_user); @@ -114,7 +130,13 @@ class TwitapiusersAction extends TwitterapiAction $this->init_document('json'); $this->show_json_objects($twitter_user); $this->end_document('json'); - } + } else { + + // This is in case 'show' was called via /account/verify_credentials + // without a format (xml or json). + header('Content-Type: text/html; charset=utf-8'); + print 'Authorized'; + } } } diff --git a/lib/router.php b/lib/router.php index 4b70c01505..a36cd2691e 100644 --- a/lib/router.php +++ b/lib/router.php @@ -228,14 +228,15 @@ class Router // users - $m->connect('api/users/show/:argument', + $m->connect('api/users/:method/:argument', array('action' => 'api', - 'apiaction' => 'users')); + 'apiaction' => 'users'), + array('method' => 'show(\.(xml|json))?')); $m->connect('api/users/:method', array('action' => 'api', 'apiaction' => 'users'), - array('method' => 'show(\.(xml|json|atom|rss))?')); + array('method' => 'show(\.(xml|json))?')); // direct messages @@ -304,11 +305,11 @@ class Router } // account - + $m->connect('api/account/:method', array('action' => 'api', 'apiaction' => 'account')); - + // favorites $m->connect('api/favorites/:method/:argument', diff --git a/lib/twitterapi.php b/lib/twitterapi.php index a4d183fcd0..74f265cbb9 100644 --- a/lib/twitterapi.php +++ b/lib/twitterapi.php @@ -60,20 +60,34 @@ class TwitterapiAction extends Action function twitter_status_array($notice, $include_user=true) { - $profile = $notice->getProfile(); $twitter_status = array(); $twitter_status['text'] = $notice->content; $twitter_status['truncated'] = 'false'; # Not possible on Laconica $twitter_status['created_at'] = $this->date_twitter($notice->created); - $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ? intval($notice->reply_to) : null; + $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ? + intval($notice->reply_to) : null; $twitter_status['source'] = $this->source_link($notice->source); $twitter_status['id'] = intval($notice->id); - $twitter_status['in_reply_to_user_id'] = ($notice->reply_to) ? $this->replier_by_reply(intval($notice->reply_to)) : null; + + $replier_profile = null; + + if ($notice->reply_to) { + $reply = Notice::staticGet(intval($notice->reply_to)); + if ($reply) { + $replier_profile = $reply->getProfile(); + } + } + + $twitter_status['in_reply_to_user_id'] = + ($replier_profile) ? intval($replier_profile->id) : null; + $twitter_status['in_reply_to_screen_name'] = + ($replier_profile) ? $replier_profile->nickname : null; if (isset($this->auth_user)) { - $twitter_status['favorited'] = ($this->auth_user->hasFave($notice)) ? 'true' : 'false'; + $twitter_status['favorited'] = + ($this->auth_user->hasFave($notice)) ? 'true' : 'false'; } else { $twitter_status['favorited'] = 'false'; } @@ -137,7 +151,6 @@ class TwitterapiAction extends Action function twitter_dmsg_array($message) { - $twitter_dm = array(); $from_profile = $message->getFrom(); @@ -386,23 +399,7 @@ class TwitterapiAction extends Action $t = strtotime($dt); return date("D M d G:i:s O Y", $t); } - - function replier_by_reply($reply_id) - { - $notice = Notice::staticGet($reply_id); - if ($notice) { - $profile = $notice->getProfile(); - if ($profile) { - return intval($profile->id); - } else { - common_debug('Can\'t find a profile for notice: ' . $notice->id, __FILE__); - } - } else { - common_debug("Can't get notice: $reply_id", __FILE__); - } - return null; - } - + // XXX: Candidate for a general utility method somewhere? function count_subscriptions($profile) { From 8ba8eaa330550cbb70ba46f81788644f6d7ed168 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 4 Mar 2009 16:55:33 -0800 Subject: [PATCH 178/189] Bumped the version number in laconica.spec in prep for a 0.7.2. RPM --- scripts/laconica.spec | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/laconica.spec b/scripts/laconica.spec index 2df41c9323..afa2766a40 100644 --- a/scripts/laconica.spec +++ b/scripts/laconica.spec @@ -2,10 +2,10 @@ BuildRequires: php-pear BuildRequires: httpd-devel Name: laconica -Version: 0.7.1 +Version: 0.7.2 Release: 1%{?dist} License: GAGPL v3 or later -Source: laconica-0.7.1.tar.gz +Source: laconica-0.7.2.tar.gz Group: Applications/Internet Summary: Laconica, the Open Source microblogging platform BuildArch: noarch @@ -74,6 +74,9 @@ rm -rf %buildroot %config(noreplace) %{_sysconfdir}/httpd/conf.d/laconica.conf %changelog +* Sat Feb 28 2009 Zach Copley - 0.7.2 +- Changed version number to 0.7.2. + * Sat Feb 28 2009 Ken Sedgwick - 0.7.1-1 - Modified RPM for Fedora. From 344f7194f0b7be60c8cfc616db39f748a914d0ed Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 4 Mar 2009 17:03:01 -0800 Subject: [PATCH 179/189] Fixed wrong date on my msg in the laconica.spec changelog section --- scripts/laconica.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/laconica.spec b/scripts/laconica.spec index afa2766a40..5f0ed5fa92 100644 --- a/scripts/laconica.spec +++ b/scripts/laconica.spec @@ -74,7 +74,7 @@ rm -rf %buildroot %config(noreplace) %{_sysconfdir}/httpd/conf.d/laconica.conf %changelog -* Sat Feb 28 2009 Zach Copley - 0.7.2 +* Wed Mar 03 2009 Zach Copley - 0.7.2 - Changed version number to 0.7.2. * Sat Feb 28 2009 Ken Sedgwick - 0.7.1-1 From 38b6946349d39359ce9f4b5ec37967a48e192862 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 4 Mar 2009 18:14:52 -0800 Subject: [PATCH 180/189] Stubs for Twitter-compatible API search methods --- actions/twitapisearch.php | 97 +++++++++++++++++++++++++++++++++++++++ actions/twitapitrends.php | 90 ++++++++++++++++++++++++++++++++++++ lib/router.php | 20 ++++++-- 3 files changed, 202 insertions(+), 5 deletions(-) create mode 100644 actions/twitapisearch.php create mode 100644 actions/twitapitrends.php diff --git a/actions/twitapisearch.php b/actions/twitapisearch.php new file mode 100644 index 0000000000..822ee77e17 --- /dev/null +++ b/actions/twitapisearch.php @@ -0,0 +1,97 @@ +. + * + * @category Search + * @package Laconica + * @author Zach Copley + * @copyright 2008-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); +} + +require_once INSTALLDIR.'/lib/twitterapi.php'; + +/** + * Action handler for Twitter-compatible API search + * + * @category Search + * @package Laconica + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + * @see TwitterapiAction + */ + +class TwitapisearchAction extends TwitterapiAction +{ + + var $query; + var $limit; + var $callback; + + /** + * Initialization. + * + * @param array $args Web and URL arguments + * + * @return boolean false if user doesn't exist + */ + + function prepare($args) + { + parent::prepare($args); + $qeury = $this->trimmed('query'); + + return true; + } + + /** + * Handle a request + * + * @param array $args Arguments from $_REQUEST + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + $this->showResults($this->limit); + } + + /** + * Show search results + * + * @param int $limit Number of notices to show + * + * @return void + */ + + function showResults($limit) + { + $this->serverError(_('API method under construction.'), $code = 501); + } + +} diff --git a/actions/twitapitrends.php b/actions/twitapitrends.php new file mode 100644 index 0000000000..c73d894460 --- /dev/null +++ b/actions/twitapitrends.php @@ -0,0 +1,90 @@ +. + * + * @category Search + * @package Laconica + * @author Zach Copley + * @copyright 2008-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); +} + +require_once INSTALLDIR.'/lib/twitterapi.php'; + +/** + * Returns the top ten queries that are currently trending + * + * @category Search + * @package Laconica + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + * + * @see TwitterapiAction + */ + +class TwitapitrendsAction extends TwitterapiAction +{ + + var $callback; + + /** + * Initialization. + * + * @param array $args Web and URL arguments + * + * @return boolean false if user doesn't exist + */ + function prepare($args) + { + parent::prepare($args); + return true; + } + + /** + * Handle a request + * + * @param array $args Arguments from $_REQUEST + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + $this->showTrends(); + } + + /** + * Output the trends + * + * @return void + */ + function showTrends() + { + $this->serverError(_('API method under construction.'), $code = 501); + } + +} \ No newline at end of file diff --git a/lib/router.php b/lib/router.php index da57f84170..41c376a725 100644 --- a/lib/router.php +++ b/lib/router.php @@ -230,7 +230,7 @@ class Router $m->connect('api/users/:method/:argument', array('action' => 'api', - 'apiaction' => 'users'), + 'apiaction' => 'users'), array('method' => 'show(\.(xml|json))?')); $m->connect('api/users/:method', @@ -284,14 +284,14 @@ class Router array('action' => 'api', 'apiaction' => 'statuses', 'method' => 'friendsIDs')); - + foreach (array('xml', 'json') as $e) { $m->connect('api/friends/ids.'.$e, array('action' => 'api', 'apiaction' => 'statuses', 'method' => 'friendsIDs.'.$e)); } - + $m->connect('api/followers/ids/:argument', array('action' => 'api', 'apiaction' => 'statuses', @@ -305,11 +305,11 @@ class Router } // account - + $m->connect('api/account/:method', array('action' => 'api', 'apiaction' => 'account')); - + // favorites $m->connect('api/favorites/:method/:argument', @@ -352,6 +352,16 @@ class Router array('action' => 'api', 'apiaction' => 'laconica')); + + // search + + foreach (array('json', 'atom') as $e) { + $m->connect('api/search.'.$e, + array('action' => 'twitapisearch')); + } + + $m->connect('api/trends.json', array('action' => 'twitapitrends')); + // user stuff foreach (array('subscriptions', 'subscribers', From 0c066db428843a6ca969c8523f0be2bcdfa278f7 Mon Sep 17 00:00:00 2001 From: CiaranG Date: Thu, 5 Mar 2009 14:35:50 +0000 Subject: [PATCH 181/189] Undo my previous change that breaks the Popular Notices section on the public timeline under MySQL --- lib/popularnoticesection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/popularnoticesection.php b/lib/popularnoticesection.php index f7fb935543..cbf458c34f 100644 --- a/lib/popularnoticesection.php +++ b/lib/popularnoticesection.php @@ -54,7 +54,7 @@ class PopularNoticeSection extends NoticeSection $weightexpr='sum(exp(-(now() - fave.modified) / %s))'; } - $qry = 'SELECT notice.id, '. + $qry = 'SELECT notice.*, '. $weightexpr . ' as weight ' . 'FROM notice JOIN fave ON notice.id = fave.notice_id ' . 'GROUP BY notice.id ' . From ea0c5f565c9ca4b34c1071a51333f0f842a954b9 Mon Sep 17 00:00:00 2001 From: CiaranG Date: Thu, 5 Mar 2009 14:52:35 +0000 Subject: [PATCH 182/189] The correct version of the bad fix I undid in the previous commit. Must explicitly specify all relevant columns in the GROUP BY. --- lib/popularnoticesection.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/popularnoticesection.php b/lib/popularnoticesection.php index cbf458c34f..0505f0fa9a 100644 --- a/lib/popularnoticesection.php +++ b/lib/popularnoticesection.php @@ -57,7 +57,9 @@ class PopularNoticeSection extends NoticeSection $qry = 'SELECT notice.*, '. $weightexpr . ' as weight ' . 'FROM notice JOIN fave ON notice.id = fave.notice_id ' . - 'GROUP BY notice.id ' . + 'GROUP BY notice.id,notice.profile_id,notice.content,notice.uri,' . + 'notice.rendered,notice.url,notice.created,notice.modified,' . + 'notice.reply_to,notice.is_local,notice.source ' . 'ORDER BY weight DESC'; $offset = 0; From 97bc187e3132663ceadd2a779ce43ca67a7f3a02 Mon Sep 17 00:00:00 2001 From: CiaranG Date: Thu, 5 Mar 2009 16:15:29 +0000 Subject: [PATCH 183/189] PostgreSQL - the ts field in the OAuth nonce table needed to be an integer. (fix submitted by oxygene) --- db/laconica_pg.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/laconica_pg.sql b/db/laconica_pg.sql index 4ef2330f40..2d83f784a2 100644 --- a/db/laconica_pg.sql +++ b/db/laconica_pg.sql @@ -181,7 +181,7 @@ create table nonce ( consumer_key varchar(255) not null /* comment 'unique identifier, root URL' */, tok char(32) not null /* comment 'identifying value' */, nonce char(32) not null /* comment 'nonce' */, - ts timestamp not null /* comment 'timestamp sent' */, + ts integer not null /* comment 'timestamp sent' values are epoch, and only used internally */, created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, modified timestamp /* comment 'date this record was modified' */, From e5345d8d7a5b519cfb24c9e2a971b485b3c1c872 Mon Sep 17 00:00:00 2001 From: CiaranG Date: Thu, 5 Mar 2009 16:18:31 +0000 Subject: [PATCH 184/189] PostgreSQL - fixed a couple more quoting issues --- classes/Notice_tag.php | 2 +- classes/User.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/classes/Notice_tag.php b/classes/Notice_tag.php index 0365973f56..f2247299a4 100644 --- a/classes/Notice_tag.php +++ b/classes/Notice_tag.php @@ -40,7 +40,7 @@ class Notice_tag extends Memcached_DataObject $qry = 'SELECT notice.* ' . 'FROM notice JOIN notice_tag ON notice.id = notice_tag.notice_id ' . - 'WHERE notice_tag.tag = "%s" '; + "WHERE notice_tag.tag = '%s' "; return Notice::getStream(sprintf($qry, $tag), 'notice_tag:notice_stream:' . common_keyize($tag), diff --git a/classes/User.php b/classes/User.php index 40cf18df67..8b0b9acd50 100644 --- a/classes/User.php +++ b/classes/User.php @@ -589,7 +589,7 @@ class User extends Memcached_DataObject 'JOIN profile_tag ON (profile_tag.tagged = subscription.subscriber ' . 'AND profile_tag.tagger = subscription.subscribed) ' . 'WHERE subscription.subscribed = %d ' . - 'AND profile_tag.tag = "%s" ' . + "AND profile_tag.tag = '%s' " . 'AND subscription.subscribed != subscription.subscriber ' . 'ORDER BY subscription.created DESC '; @@ -617,7 +617,7 @@ class User extends Memcached_DataObject 'JOIN profile_tag on (profile_tag.tagged = subscription.subscribed ' . 'AND profile_tag.tagger = subscription.subscriber) ' . 'WHERE subscription.subscriber = %d ' . - 'AND profile_tag.tag = "%s" ' . + "AND profile_tag.tag = '%s' " . 'AND subscription.subscribed != subscription.subscriber ' . 'ORDER BY subscription.created DESC '; From 3087e4ad5dfbccb7218c12ca747e1dbcf3a6415c Mon Sep 17 00:00:00 2001 From: CiaranG Date: Thu, 5 Mar 2009 16:23:39 +0000 Subject: [PATCH 185/189] Fixed bad field name in oauthstore. (fix submitted by oxygene) --- lib/oauthstore.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/oauthstore.php b/lib/oauthstore.php index 7ad3be20e2..9af05ea2de 100644 --- a/lib/oauthstore.php +++ b/lib/oauthstore.php @@ -63,7 +63,7 @@ class LaconicaOAuthDataStore extends OAuthDataStore if ($n->find(true)) { return true; } else { - $n->timestamp = $timestamp; + $n->ts = $timestamp; $n->created = DB_DataObject_Cast::dateTime(); $n->insert(); return false; From b9781258bbfaac0e7fc91af4f77f1f340274a88e Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 5 Mar 2009 11:03:42 -0800 Subject: [PATCH 186/189] @-links go to permalinks for local users --- lib/util.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/util.php b/lib/util.php index f9a787d473..167508d2b3 100644 --- a/lib/util.php +++ b/lib/util.php @@ -622,9 +622,15 @@ function common_at_link($sender_id, $nickname) $sender = Profile::staticGet($sender_id); $recipient = common_relative_profile($sender, common_canonical_nickname($nickname)); if ($recipient) { + $user = User::staticGet('id', $recipient->id); + if ($user) { + $url = common_local_url('userbyid', array('id' => $user->id)); + } else { + $url = $recipient->profileurl; + } $xs = new XMLStringer(false); $xs->elementStart('span', 'vcard'); - $xs->elementStart('a', array('href' => $recipient->profileurl, + $xs->elementStart('a', array('href' => $url, 'class' => 'url')); $xs->element('span', 'fn nickname', $nickname); $xs->elementEnd('a'); From 87e5badcb6e8d89e2c85e2bf4be3748b8cd5f940 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 5 Mar 2009 14:37:27 -0800 Subject: [PATCH 187/189] add ping daemon to daemons --- scripts/startdaemons.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/startdaemons.sh b/scripts/startdaemons.sh index a3256966d5..c3729761d0 100755 --- a/scripts/startdaemons.sh +++ b/scripts/startdaemons.sh @@ -24,7 +24,7 @@ DIR=`dirname $0` for f in xmppdaemon.php jabberqueuehandler.php publicqueuehandler.php \ xmppconfirmhandler.php smsqueuehandler.php ombqueuehandler.php \ - twitterqueuehandler.php facebookqueuehandler.php; do + twitterqueuehandler.php facebookqueuehandler.php pingqueuehandler.php; do echo -n "Starting $f..."; php $DIR/$f From 896f0340bf3710b79b117236e940b487a4b56460 Mon Sep 17 00:00:00 2001 From: CiaranG Date: Fri, 6 Mar 2009 19:28:15 +0000 Subject: [PATCH 188/189] Fixed problem with group lists not display all details - broken (by me) in 115519a5e7e84e57656c653918efb39ab4107fe9 --- lib/groupsbymemberssection.php | 4 ++-- lib/groupsbypostssection.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/groupsbymemberssection.php b/lib/groupsbymemberssection.php index 5f26c6626f..963e21f156 100644 --- a/lib/groupsbymemberssection.php +++ b/lib/groupsbymemberssection.php @@ -45,10 +45,10 @@ class GroupsByMembersSection extends GroupSection { function getGroups() { - $qry = 'SELECT user_group.id, count(*) as value ' . + $qry = 'SELECT user_group.*, count(*) as value ' . 'FROM user_group JOIN group_member '. 'ON user_group.id = group_member.group_id ' . - 'GROUP BY user_group.id ' . + 'GROUP BY user_group.id,user_group.nickname,user_group.fullname,user_group.homepage,user_group.description,user_group.location,user_group.original_logo,user_group.homepage_logo,user_group.stream_logo,user_group.mini_logo,user_group.created,user_group.modified ' . 'ORDER BY value DESC '; $limit = GROUPS_PER_SECTION; diff --git a/lib/groupsbypostssection.php b/lib/groupsbypostssection.php index 1a60ddb4fc..325b4033f4 100644 --- a/lib/groupsbypostssection.php +++ b/lib/groupsbypostssection.php @@ -45,10 +45,10 @@ class GroupsByPostsSection extends GroupSection { function getGroups() { - $qry = 'SELECT user_group.id, count(*) as value ' . + $qry = 'SELECT user_group.*, count(*) as value ' . 'FROM user_group JOIN group_inbox '. 'ON user_group.id = group_inbox.group_id ' . - 'GROUP BY user_group.id ' . + 'GROUP BY user_group.id,user_group.nickname,user_group.fullname,user_group.homepage,user_group.description,user_group.location,user_group.original_logo,user_group.homepage_logo,user_group.stream_logo,user_group.mini_logo,user_group.created,user_group.modified ' . 'ORDER BY value DESC '; $limit = GROUPS_PER_SECTION; From b1f337fe018dd3910c68589a98b0242470743a4c Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 6 Mar 2009 13:33:47 -0800 Subject: [PATCH 189/189] First crack at Twitter-like JSON search results for the API --- ...witapisearch.php => twitapisearchjson.php} | 88 ++++-- lib/jsonsearchresultslist.php | 259 ++++++++++++++++++ lib/router.php | 10 +- lib/twitterapi.php | 28 +- 4 files changed, 357 insertions(+), 28 deletions(-) rename actions/{twitapisearch.php => twitapisearchjson.php} (54%) create mode 100644 lib/jsonsearchresultslist.php diff --git a/actions/twitapisearch.php b/actions/twitapisearchjson.php similarity index 54% rename from actions/twitapisearch.php rename to actions/twitapisearchjson.php index 822ee77e17..b50aa86b7a 100644 --- a/actions/twitapisearch.php +++ b/actions/twitapisearchjson.php @@ -27,11 +27,12 @@ * @link http://laconi.ca/ */ -if (!defined('LACONICA')) { +if (!defined('LACONICA')) { exit(1); } require_once INSTALLDIR.'/lib/twitterapi.php'; +require_once INSTALLDIR.'/lib/jsonsearchresultslist.php'; /** * Action handler for Twitter-compatible API search @@ -44,29 +45,52 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; * @see TwitterapiAction */ -class TwitapisearchAction extends TwitterapiAction +class TwitapisearchjsonAction extends TwitterapiAction { - var $query; + var $lang; + var $rpp; + var $page; + var $since_id; var $limit; - var $callback; - + var $geocode; + /** * Initialization. * * @param array $args Web and URL arguments * - * @return boolean false if user doesn't exist + * @return boolean true if nothing goes wrong */ - + function prepare($args) { parent::prepare($args); - $qeury = $this->trimmed('query'); + + $this->query = $this->trimmed('q'); + $this->lang = $this->trimmed('lang'); + $this->rpp = $this->trimmed('rpp'); + + if (!$this->rpp) { + $this->rpp = 15; + } + + if ($this->rpp > 100) { + $this->rpp = 100; + } + + $this->page = $this->trimmed('page'); + + if (!$this->page) { + $this->page = 1; + } + + $this->since_id = $this->trimmed('since_id'); + $this->geocode = $this->trimmed('geocode'); return true; } - + /** * Handle a request * @@ -78,20 +102,48 @@ class TwitapisearchAction extends TwitterapiAction function handle($args) { parent::handle($args); - $this->showResults($this->limit); + $this->showResults(); } - + /** * Show search results * - * @param int $limit Number of notices to show - * * @return void */ - - function showResults($limit) + + function showResults() { - $this->serverError(_('API method under construction.'), $code = 501); + + // TODO: Support search operators like from: and to: + + $notice = new Notice(); + + // lcase it for comparison + $q = strtolower($this->query); + + $search_engine = $notice->getSearchEngine('identica_notices'); + $search_engine->set_sort_mode('chron'); + $search_engine->limit(($this->page - 1) * $this->rpp, $this->rpp + 1, true); + $search_engine->query($q); + $cnt = $notice->find(); + + // TODO: since_id, lang, geocode + + $results = new JSONSearchResultsList($notice, $q, $this->rpp, $this->page); + + $this->init_document('json'); + $results->show(); + $this->end_document('json'); } - -} + + /** + * This is a read-only action + * + * @return boolean true + */ + + function isReadOnly() + { + return true; + } +} \ No newline at end of file diff --git a/lib/jsonsearchresultslist.php b/lib/jsonsearchresultslist.php new file mode 100644 index 0000000000..171e1db4d6 --- /dev/null +++ b/lib/jsonsearchresultslist.php @@ -0,0 +1,259 @@ +. + * + * @category Search + * @package Laconica + * @author Zach Copley + * @copyright 2008-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); +} + +/** + * widget-like class for showing JSON search results + * + * @category Search + * @package Laconica + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + * + */ + +class JSONSearchResultsList +{ + protected $notice; // protected attrs invisible to json_encode() + protected $rpp; + + // The below attributes are carefully named so the JSON output from + // this obj matches the output from search.twitter.com + + var $results; + var $since_id; + var $max_id; + var $refresh_url; + var $results_per_page; + var $completed_in; + var $page; + var $query; + + /** + * constructor + * + * @param Notice $notice stream of notices from DB_DataObject + */ + + function __construct($notice, $query, $rpp, $page, $since_id = 0) + { + $this->notice = $notice; + $this->query = urlencode($query); + $this->results_per_page = $this->rpp = $rpp; + $this->page = $page; + $this->since_id = $since_id; + $this->results = array(); + } + + /** + * show the list of search results + * + * @return int count of the search results listed. + */ + + function show() + { + $cnt = 0; + + $time_start = microtime(true); + + while ($this->notice->fetch() && $cnt <= $this->rpp) { + $cnt++; + + // XXX: Hmmm. this depends on desc sort order + if (!$this->max_id) { + $this->max_id = (int)$this->notice->id; + } + + if ($cnt > $this->rpp) { + break; + } + + $item = new ResultItem($this->notice); + array_push($this->results, $item); + } + + $time_end = microtime(true); + $this->completed_in = $time_end - $time_start; + + // Set other attrs + + $this->refresh_url = '?since_id=' . $this->max_id . + '&q=' . $this->query; + + // pagination stuff + + if ($cnt > $this->rpp) { + $this->next_page = '?page=' . ($this->page + 1) . + '&max_id=' . $this->max_id; + if ($this->rpp != 15) { + $this->next_page .= '&rpp=' . $this->rpp; + } + $this->next_page .= '&q=' . $this->query; + } + + if ($this->page > 1) { + $this->previous_page = '?page=' . ($this->page - 1) . + '&max_id=' . $this->max_id; + if ($this->rpp != 15) { + $this->previous_page .= '&rpp=' . $this->rpp; + } + $this->previous_page .= '&q=' . $this->query; + } + + print json_encode($this); + + return $cnt; + } +} + +/** + * widget for displaying a single JSON search result + * + * @category UI + * @package Laconica + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + * @see JSONSearchResultsList + */ + +class ResultItem +{ + /** The notice this item is based on. */ + + protected $notice; // protected attrs invisible to json_encode() + + /** The profile associated with the notice. */ + + protected $profile; + + // The below attributes are carefully named so the JSON output from + // this obj matches the output from search.twitter.com + + var $text; + var $to_user_id; + var $to_user; + var $from_user; + var $id; + var $from_user_id; + var $iso_language_code; + var $source; + var $profile_image_url; + var $created_at; + + /** + * constructor + * + * Also initializes the profile attribute. + * + * @param Notice $notice The notice we'll display + */ + + function __construct($notice) + { + $this->notice = $notice; + $this->profile = $notice->getProfile(); + $this->buildResult(); + } + + /** + * Build a search result object + * + * This populates the the result in preparation for JSON encoding. + * + * @return void + */ + + function buildResult() + { + $this->text = $this->notice->content; + $replier_profile = null; + + if ($this->notice->reply_to) { + $reply = Notice::staticGet(intval($notice->reply_to)); + if ($reply) { + $replier_profile = $reply->getProfile(); + } + } + + $this->to_user_id = ($replier_profile) ? + intval($replier_profile->id) : null; + $this->to_user = ($replier_profile) ? + $replier_profile->nickname : null; + $this->from_user = $this->profile->nickname; + $this->id = $this->notice->id; + $this->from_user_id = $this->profile->id; + + $user = User::staticGet('id', $this->profile->id); + $this->iso_language_code = $this->user->language; + + $this->source = $this->getSourceLink($this->notice->source); + + $avatar = $this->profile->getAvatar(AVATAR_STREAM_SIZE); + $this->profile_image_url = ($avatar) ? + $avatar->displayUrl() : Avatar::defaultImage(AVATAR_STREAM_SIZE); + + $this->created_at = date('r', $this->notice->created); + } + + /** + * Show the source of the notice + * + * Either the name (and link) of the API client that posted the notice, + * or one of other other channels. + * + * @return string the source of the Notice + */ + + function getSourceLink($source) + { + $source_name = _($source); + switch ($source) { + case 'web': + case 'xmpp': + case 'mail': + case 'omb': + case 'api': + break; + default: + $ns = Notice_source::staticGet($source); + if ($ns) { + $source_name = '' . $ns->name . ''; + } + break; + } + return $source_name; + } + +} diff --git a/lib/router.php b/lib/router.php index 41c376a725..516b481227 100644 --- a/lib/router.php +++ b/lib/router.php @@ -354,17 +354,13 @@ class Router // search - - foreach (array('json', 'atom') as $e) { - $m->connect('api/search.'.$e, - array('action' => 'twitapisearch')); - } - + $m->connect('api/search.atom', array('action' => 'twitapisearchatom')); + $m->connect('api/search.json', array('action' => 'twitapisearchjson')); $m->connect('api/trends.json', array('action' => 'twitapitrends')); // user stuff - foreach (array('subscriptions', 'subscribers', + foreach (array('subscriptions', 'subscribers', 'nudge', 'xrds', 'all', 'foaf', 'replies', 'inbox', 'outbox', 'microsummary') as $a) { $m->connect(':nickname/'.$a, diff --git a/lib/twitterapi.php b/lib/twitterapi.php index 74f265cbb9..1de169a0b1 100644 --- a/lib/twitterapi.php +++ b/lib/twitterapi.php @@ -24,11 +24,33 @@ class TwitterapiAction extends Action var $auth_user; + /** + * Initialization. + * + * @param array $args Web and URL arguments + * + * @return boolean false if user doesn't exist + */ + + function prepare($args) + { + parent::prepare($args); + return true; + } + + /** + * Handle a request + * + * @param array $args Arguments from $_REQUEST + * + * @return void + */ + function handle($args) { parent::handle($args); } - + function twitter_user_array($profile, $get_notice=false) { @@ -86,7 +108,7 @@ class TwitterapiAction extends Action ($replier_profile) ? $replier_profile->nickname : null; if (isset($this->auth_user)) { - $twitter_status['favorited'] = + $twitter_status['favorited'] = ($this->auth_user->hasFave($notice)) ? 'true' : 'false'; } else { $twitter_status['favorited'] = 'false'; @@ -399,7 +421,7 @@ class TwitterapiAction extends Action $t = strtotime($dt); return date("D M d G:i:s O Y", $t); } - + // XXX: Candidate for a general utility method somewhere? function count_subscriptions($profile) {

Download latest version from here