From d6245dca6347a670445701e9c726b0719ea945c8 Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Fri, 6 Feb 2009 18:36:34 -0500 Subject: 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 eab1fc0a2..3b4c97865 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)), -- cgit v1.2.3-54-g00ecf From d90089314944ed1696f66cabbb6935ea61e4b2e6 Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Sat, 7 Feb 2009 10:01:08 -0500 Subject: 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 74c3d14f0..faec7f0ff 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; } -- cgit v1.2.3-54-g00ecf From 805560677bc66a58c270551fa54b613642b3af97 Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Sat, 7 Feb 2009 11:10:46 -0500 Subject: 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 a8e963370..ea24029a3 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; } -- cgit v1.2.3-54-g00ecf From 43888b523919f816e45a956a2e9f7d10416067df Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Mon, 9 Feb 2009 15:35:38 +0000 Subject: 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 f83015a37..82276ff34 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 7780b1c19..e2319b1fd 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); -- cgit v1.2.3-54-g00ecf From 9e23b5c5d706f4573261d6530688a44a8b80bcf4 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 11:46:26 -0500 Subject: 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 0a79b9731..e62d9469a 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 041459cf3..7bfd14c42 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'); } } -- cgit v1.2.3-54-g00ecf From c1bc77efd968aecd465fb6173d20bd386016eae2 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 12:06:06 -0500 Subject: 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 ea24029a3..0c93b257e 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; -- cgit v1.2.3-54-g00ecf From 9b7c57d094ef5fc52a8a2c4ac6b7a89b1c221c54 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 12:07:21 -0500 Subject: whitespace and formatting in showstream.php --- actions/showstream.php | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/actions/showstream.php b/actions/showstream.php index 6bed700c2..962f4b452 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))); - - $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))); + '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))); } 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'); -- cgit v1.2.3-54-g00ecf From 42e3c928dd4784813528826fd31e16cce60c0b34 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 12:29:10 -0500 Subject: ignore .#* files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f5a3e0212..f2e96d3eb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ dataobject.ini *.bak *.orig *.rej +.#* -- cgit v1.2.3-54-g00ecf From e5bad1526422f346041978a0812b0b29dc7f78bc Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 12:39:03 -0500 Subject: upgrade jQuery from 1.3 to 1.3.1 --- js/jquery.js | 224 +++++++++++++++++++++++++++---------------------------- js/jquery.min.js | 12 +-- 2 files changed, 118 insertions(+), 118 deletions(-) diff --git a/js/jquery.js b/js/jquery.js index fc06ace27..94e9c1755 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 ); - - // Otherwise, we inject the element directly into the jQuery object - var ret = jQuery( elem ); - ret.context = document; - ret.selector = selector; - return ret; - } - 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; } // 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 396646c84..c327fae81 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 -- cgit v1.2.3-54-g00ecf From 20bfa6daed290378231a2a4fd8b7d9fd98fed1ce Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 12:39:03 -0500 Subject: upgrade jQuery from 1.3 to 1.3.1 --- js/jquery.js | 224 +++++++++++++++++++++++++++---------------------------- js/jquery.min.js | 12 +-- 2 files changed, 118 insertions(+), 118 deletions(-) diff --git a/js/jquery.js b/js/jquery.js index fc06ace27..94e9c1755 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 ); - - // Otherwise, we inject the element directly into the jQuery object - var ret = jQuery( elem ); - ret.context = document; - ret.selector = selector; - return ret; - } - 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; } // 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 396646c84..c327fae81 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 -- cgit v1.2.3-54-g00ecf From 28ef2ccf427683837dde29f8b89ea8d5378f287b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sun, 8 Feb 2009 23:07:56 +0000 Subject: 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 3c872f94b..0d1cb8c4c 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'); -- cgit v1.2.3-54-g00ecf From bb13d896423691237c7598e8223c9bfcc94af996 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sun, 8 Feb 2009 21:58:25 +0000 Subject: 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 0d1cb8c4c..1302064ad 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'); -- cgit v1.2.3-54-g00ecf From ddb67cda1799f38f214574e838434b36d40a36e9 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sun, 8 Feb 2009 13:25:48 -0800 Subject: 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 3578c53fd..6b1e34f1a 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); } - + } -- cgit v1.2.3-54-g00ecf From 37a1a6b9d8c50c490d4b7fe42b40e2af7d8c00b3 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sat, 7 Feb 2009 18:16:34 -0800 Subject: Safer, better script for automatically updating Facebook statuses --- scripts/update_facebook.php | 92 +++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 49 deletions(-) diff --git a/scripts/update_facebook.php b/scripts/update_facebook.php index 141bcfe0c..60e10417f 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; - - $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; + 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); + 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); } + -- cgit v1.2.3-54-g00ecf From 09d8a73eceba11ed021a697b3b8e283c5bc2dc6a Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Sat, 7 Feb 2009 19:33:18 +0000 Subject: 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 98ebcb87a..e7e79040a 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 bc9151120..1e7b73a7f 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 42fd380df..cbd8dfeec 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 82e6c3c82..60f7c0796 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 01d94f488..5d7a8ce69 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 dc8e2e798..b7c09cc9d 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 c79112dac..898c53543 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 58fc96c0e..7455a41a6 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'); -- cgit v1.2.3-54-g00ecf From 3e005f2d1bf01c295c132b6ca52e1139ef60b733 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 6 Feb 2009 21:17:45 -0800 Subject: "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 b424d579f..a1faefc80 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, -- cgit v1.2.3-54-g00ecf From 2393fbec6062da4e681b4315e89b27c934a641f1 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 14:47:23 -0500 Subject: 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 012270b51..16f482134 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; -- cgit v1.2.3-54-g00ecf From 32744124bcb8aa0683490a56defd4a79f072d278 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 16:56:38 -0500 Subject: 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 4b8260b3c..d9634325d 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 0628dc70d..ce92addf5 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'); } -- cgit v1.2.3-54-g00ecf From f6705f06c0a8251c0f3eb0fe88532e75645f7705 Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Mon, 9 Feb 2009 17:29:27 -0500 Subject: 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 7ce4e229e..5204693bc 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); -- cgit v1.2.3-54-g00ecf From 0d586524874560c51277b90f33bcbed164748f6d Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Mon, 9 Feb 2009 15:35:38 +0000 Subject: 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 f83015a37..82276ff34 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 7780b1c19..e2319b1fd 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); -- cgit v1.2.3-54-g00ecf From 47a5d2b7f06cd1612734c47ad21fc397bbff5276 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 23:13:11 +0000 Subject: 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 f9094a50c..76db887de 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 7455a41a6..ed17ceec9 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) -- cgit v1.2.3-54-g00ecf From 80429ca70c5ec3f057197914ea9817a25c4a86c7 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Feb 2009 23:13:11 +0000 Subject: 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 f9094a50c..76db887de 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 7455a41a6..ed17ceec9 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) -- cgit v1.2.3-54-g00ecf From cf29ef2bc484c1d9719e852d1d3b6eeaff74e094 Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Mon, 9 Feb 2009 19:15:30 -0500 Subject: 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 be92f422f..75d1e21a4 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 -- cgit v1.2.3-54-g00ecf From 55fcc150fe90156ad33f8627128c7ae555662528 Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Mon, 9 Feb 2009 20:17:56 -0500 Subject: 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 7dd53f6eb..f38a44a24 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 -- cgit v1.2.3-54-g00ecf From c9e8b1e5c380904e479927e2f24754d8709f590e Mon Sep 17 00:00:00 2001 From: Meitar Moscovitz Date: Wed, 11 Feb 2009 03:03:16 +1100 Subject: 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 a2c5801f4..d1191ea01 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 ce92addf5..ce37f4760 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 000000000..6cd717a4d --- /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; } -- cgit v1.2.3-54-g00ecf From beddf906634054b115d41046ac112cd0264dbfe1 Mon Sep 17 00:00:00 2001 From: Meitar Moscovitz Date: Wed, 11 Feb 2009 03:12:14 +1100 Subject: 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 ce37f4760..3e236d714 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 } } -- cgit v1.2.3-54-g00ecf From 3f859026e6bba0dfa55ba5bf034eadc7bd64a875 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 10 Feb 2009 15:54:13 -0500 Subject: 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 000000000..65e38818b --- /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 000000000..ac3ad172b --- /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 000000000..2f15b2c16 --- /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 000000000..349d87338 --- /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 000000000..b315b442d --- /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 000000000..6085ff648 --- /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 000000000..b541002c7 --- /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 -- cgit v1.2.3-54-g00ecf From e21f2cf29ea6495b74ce79f71e3ea0af013590fd Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 10 Feb 2009 15:55:37 -0500 Subject: Move Mapper to correct spot in the hierarchy --- 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 -------------------------------- extlib/Net/URL/Mapper.php | 324 ++++++++++++++++++++++++ extlib/Net/URL/Mapper/Exception.php | 104 ++++++++ extlib/Net/URL/Mapper/Part.php | 142 +++++++++++ extlib/Net/URL/Mapper/Part/Dynamic.php | 81 ++++++ extlib/Net/URL/Mapper/Part/Fixed.php | 70 ++++++ extlib/Net/URL/Mapper/Part/Wildcard.php | 80 ++++++ extlib/Net/URL/Mapper/Path.php | 430 ++++++++++++++++++++++++++++++++ 14 files changed, 1231 insertions(+), 1231 deletions(-) delete mode 100644 extlib/Mapper.php delete mode 100644 extlib/Mapper/Exception.php delete mode 100644 extlib/Mapper/Part.php delete mode 100644 extlib/Mapper/Part/Dynamic.php delete mode 100644 extlib/Mapper/Part/Fixed.php delete mode 100644 extlib/Mapper/Part/Wildcard.php delete mode 100644 extlib/Mapper/Path.php create mode 100644 extlib/Net/URL/Mapper.php create mode 100644 extlib/Net/URL/Mapper/Exception.php create mode 100644 extlib/Net/URL/Mapper/Part.php create mode 100644 extlib/Net/URL/Mapper/Part/Dynamic.php create mode 100644 extlib/Net/URL/Mapper/Part/Fixed.php create mode 100644 extlib/Net/URL/Mapper/Part/Wildcard.php create mode 100644 extlib/Net/URL/Mapper/Path.php diff --git a/extlib/Mapper.php b/extlib/Mapper.php deleted file mode 100644 index 65e38818b..000000000 --- a/extlib/Mapper.php +++ /dev/null @@ -1,324 +0,0 @@ - - * 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 deleted file mode 100644 index ac3ad172b..000000000 --- a/extlib/Mapper/Exception.php +++ /dev/null @@ -1,104 +0,0 @@ - - * 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 deleted file mode 100644 index 2f15b2c16..000000000 --- a/extlib/Mapper/Part.php +++ /dev/null @@ -1,142 +0,0 @@ - - * 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 deleted file mode 100644 index 349d87338..000000000 --- a/extlib/Mapper/Part/Dynamic.php +++ /dev/null @@ -1,81 +0,0 @@ - - * 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 deleted file mode 100644 index b315b442d..000000000 --- a/extlib/Mapper/Part/Fixed.php +++ /dev/null @@ -1,70 +0,0 @@ - - * 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 deleted file mode 100644 index 6085ff648..000000000 --- a/extlib/Mapper/Part/Wildcard.php +++ /dev/null @@ -1,80 +0,0 @@ - - * 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 deleted file mode 100644 index b541002c7..000000000 --- a/extlib/Mapper/Path.php +++ /dev/null @@ -1,430 +0,0 @@ - - * 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 diff --git a/extlib/Net/URL/Mapper.php b/extlib/Net/URL/Mapper.php new file mode 100644 index 000000000..65e38818b --- /dev/null +++ b/extlib/Net/URL/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/Net/URL/Mapper/Exception.php b/extlib/Net/URL/Mapper/Exception.php new file mode 100644 index 000000000..ac3ad172b --- /dev/null +++ b/extlib/Net/URL/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/Net/URL/Mapper/Part.php b/extlib/Net/URL/Mapper/Part.php new file mode 100644 index 000000000..2f15b2c16 --- /dev/null +++ b/extlib/Net/URL/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/Net/URL/Mapper/Part/Dynamic.php b/extlib/Net/URL/Mapper/Part/Dynamic.php new file mode 100644 index 000000000..349d87338 --- /dev/null +++ b/extlib/Net/URL/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/Net/URL/Mapper/Part/Fixed.php b/extlib/Net/URL/Mapper/Part/Fixed.php new file mode 100644 index 000000000..b315b442d --- /dev/null +++ b/extlib/Net/URL/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/Net/URL/Mapper/Part/Wildcard.php b/extlib/Net/URL/Mapper/Part/Wildcard.php new file mode 100644 index 000000000..6085ff648 --- /dev/null +++ b/extlib/Net/URL/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/Net/URL/Mapper/Path.php b/extlib/Net/URL/Mapper/Path.php new file mode 100644 index 000000000..b541002c7 --- /dev/null +++ b/extlib/Net/URL/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 -- cgit v1.2.3-54-g00ecf From b45ea01dab70d538aaec8359b1e6e25dec0975e6 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 10 Feb 2009 16:19:45 -0500 Subject: 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 000000000..3dcfef60d --- /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]; + } + +} +?> -- cgit v1.2.3-54-g00ecf From 7763f804cafa6f41316e30512c4ceab9b78f2c08 Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Tue, 10 Feb 2009 22:04:47 +0000 Subject: 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 a5f01350c..2d94a7906 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 47d909e96..8bfcb9c5a 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"; -- cgit v1.2.3-54-g00ecf From 646fdea1bf8a93bbfb60212982a218994ffa8bbe Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Tue, 10 Feb 2009 17:42:58 -0500 Subject: 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 75d1e21a4..a130c7d49 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); -- cgit v1.2.3-54-g00ecf From 7b9e69eb8923ef3018f0ce3e6042d9901ed16abd Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 10 Feb 2009 22:32:38 -0500 Subject: integrate URL routing into core code --- htaccess.sample | 162 +-------------------------- index.php | 124 +++++++++++++-------- lib/router.php | 340 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 423 insertions(+), 203 deletions(-) create mode 100644 lib/router.php diff --git a/htaccess.sample b/htaccess.sample index 5f9827f96..634900dbf 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 e62d9469a..f334e2c34 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; + +function getPath($req) { + if (common_config('site', 'fancy')) { + return $req['p']; + } else if ($_SERVER['PATH_INFO']) { + return $_SERVER['PATH_INFO']; + } else { + return $req['p']; + } +} -// get and cache current user +function main() { -$user = common_current_user(); + global $user, $action; -// initialize language env + // XXX: we need a little more structure in this script -common_init_language(); + // get and cache current user -$action = $_REQUEST['action']; + $user = common_current_user(); -if (!$action || !preg_match('/^[a-zA-Z0-9_-]*$/', $action)) { - common_redirect(common_local_url('public')); -} + // initialize language env -// If the site is private, and they're not on one of the "public" -// parts of the site, redirect to login + common_init_language(); -if (!$user && common_config('site', 'private') && - !in_array($action, array('login', 'openidlogin', 'finishopenidlogin', - 'recoverpassword', 'api', 'doc', 'register'))) { - common_redirect(common_local_url('login')); -} + $path = getPath($_REQUEST); + + $r = new Router(); -$action_class = ucfirst($action).'Action'; + $args = $r->map($path); -if (!class_exists($action_class)) { - $cac = new ClientErrorAction(_('Unknown action'), 404); - $cac->showPage(); -} else { - $action_obj = new $action_class(); + if (!$args) { + $cac = new ClientErrorAction(_('Unknown page'), 404); + $cac->showPage(); + return; + } - // XXX: find somewhere for this little block to live + $args = array_merge($args, $_REQUEST); - if ($config['db']['mirror'] && $action_obj->isReadOnly()) { - if (is_array($config['db']['mirror'])) { - // "load balancing", ha ha - $k = array_rand($config['db']['mirror']); + $action = $args['action']; - $mirror = $config['db']['mirror'][$k]; - } else { - $mirror = $config['db']['mirror']; - } - $config['db']['database'] = $mirror; + if (!$action || !preg_match('/^[a-zA-Z0-9_-]*$/', $action)) { + common_redirect(common_local_url('public')); + return; } - try { - if ($action_obj->prepare($_REQUEST)) { - $action_obj->handle($_REQUEST); - } - } catch (ClientException $cex) { - $cac = new ClientErrorAction($cex->getMessage(), $cex->getCode()); + // 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(); - } catch (ServerException $sex) { // snort snort guffaw - $sac = new ServerErrorAction($sex->getMessage(), $sex->getCode()); - $sac->showPage(); - } catch (Exception $ex) { - $sac = new ServerErrorAction($ex->getMessage()); - $sac->showPage(); + } else { + $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 000000000..6781322a2 --- /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 -- cgit v1.2.3-54-g00ecf From 1a7337f2fdc106b80a48692628d0b749ee090c03 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 10 Feb 2009 22:33:57 -0500 Subject: 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-src/about | 10 ++++++++ doc-src/contact | 24 +++++++++++++++++++ doc-src/faq | 42 +++++++++++++++++++++++++++++++++ doc-src/groups | 42 +++++++++++++++++++++++++++++++++ doc-src/help | 32 +++++++++++++++++++++++++ doc-src/im | 35 ++++++++++++++++++++++++++++ doc-src/openid | 11 +++++++++ doc-src/openmublog | 25 ++++++++++++++++++++ doc-src/privacy | 45 ++++++++++++++++++++++++++++++++++++ doc-src/sms | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ doc-src/source | 12 ++++++++++ doc-src/tags | 40 ++++++++++++++++++++++++++++++++ doc/about | 10 -------- doc/contact | 24 ------------------- doc/faq | 42 --------------------------------- doc/groups | 42 --------------------------------- doc/help | 32 ------------------------- doc/im | 35 ---------------------------- doc/openid | 11 --------- doc/openmublog | 25 -------------------- doc/privacy | 45 ------------------------------------ doc/sms | 68 ------------------------------------------------------ doc/source | 12 ---------- doc/tags | 40 -------------------------------- 25 files changed, 392 insertions(+), 392 deletions(-) create mode 100644 doc-src/about create mode 100644 doc-src/contact create mode 100644 doc-src/faq create mode 100644 doc-src/groups create mode 100644 doc-src/help create mode 100644 doc-src/im create mode 100644 doc-src/openid create mode 100644 doc-src/openmublog create mode 100644 doc-src/privacy create mode 100644 doc-src/sms create mode 100644 doc-src/source create mode 100644 doc-src/tags delete mode 100644 doc/about delete mode 100644 doc/contact delete mode 100644 doc/faq delete mode 100644 doc/groups delete mode 100644 doc/help delete mode 100644 doc/im delete mode 100644 doc/openid delete mode 100644 doc/openmublog delete mode 100644 doc/privacy delete mode 100644 doc/sms delete mode 100644 doc/source delete mode 100644 doc/tags diff --git a/actions/doc.php b/actions/doc.php index 6957659ad..ebffb7c15 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-src/about b/doc-src/about new file mode 100644 index 000000000..3036a51b9 --- /dev/null +++ b/doc-src/about @@ -0,0 +1,10 @@ +%%site.name%% is a +[micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service +based on the Free Software [Laconica](http://laconi.ca/) tool. + +If you [register](%%action.register%%) for an account, +you can post small (140 chars or less) text notices +about yourself, where you are, what you're doing, or practically +anything you want. You can also subscribe to the notices of your +friends, or other people you're interested in, and follow them on the +Web or in an [RSS](http://en.wikipedia.org/wiki/RSS) feed. diff --git a/doc-src/contact b/doc-src/contact new file mode 100644 index 000000000..a8efc456a --- /dev/null +++ b/doc-src/contact @@ -0,0 +1,24 @@ +There are a number of options for getting in contact with responsible +people for %%site.name%%. + +Post a notice +------------- + +If you have a question about how to do something, just post a notice +with your question. People here like to answer messages. Watch the +[public timeline](%%action.public%%) for answers; they'll usually start +with "@" plus your user name. + +Bugs +---- + +If you think you've found a bug in the [Laconica](http://laconi.ca/) software, +or if there's a new feature you'd like to see, add it into the [Laconica bug database](http://laconi.ca/PITS/HomePage). Don't forget to check the list of +existing bugs to make sure it hasn't already been reported! + +Email +----- + +You can reach the responsible party for this server at [%%site.email%%](mailto:%%site.email%%). + + diff --git a/doc-src/faq b/doc-src/faq new file mode 100644 index 000000000..31582b9f0 --- /dev/null +++ b/doc-src/faq @@ -0,0 +1,42 @@ +These are some *Frequently Asked Questions* about this service, with +some answers. + +What is %%site.name%%? +---------------------- + +%%site.name%% is a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service. +You can use it to write short notices about yourself, where you are, +and what you're doing, and those notices will be sent to all your friends +and fans. + +How is %%site.name%% different from Twitter, Jaiku, Pownce, Plurk, others? +-------------------------------------------------------------------------- + +%%site.name%% is an [Open Network Service](http://opendefinition.org/ossd). Our main +goal is to provide a fair and transparent service that preserves users' autonomy. In +particular, all the software used for %%site.name%% is [Free Software](http://en.wikipedia.org/wiki/Free_Software), and all the data is available +under the [%%license.title%%](%%license.url%%) license, making it Open Data. + +The software also implements the [OpenMicroBlogging](http://openmicroblogging.org/) protocol, meaning that you can have friends on other microblogging services +that can receive your notices. + +The goal here is *autonomy* -- you deserve the right to manage your own on-line +presence. If you don't like how %%site.name%% works, you can take your data and the source code and set up your own server (or move your account to another one). + +Where is feature X? +------------------- + +The software we run, [Laconica](http://laconi.ca/), is still in its early stages, +and many features people expect from microblogging sites are not yet implemented. Some important ones that are expected "soon": + +* More [AJAX](http://en.wikipedia.org/wiki/AJAX)-y interface +* Maps +* Cross-post to Pownce, Jaiku, etc. +* Pull messages from Twitter, Pownce, Jaiku, etc. +* [Facebook](http://www.facebook.com/) integration +* Image, video, audio notices + +There is [a list of bugs and features](http://laconi.ca/trac/) that you may find +interesting. New ideas or complaints are very welcome. + + diff --git a/doc-src/groups b/doc-src/groups new file mode 100644 index 000000000..645390e0c --- /dev/null +++ b/doc-src/groups @@ -0,0 +1,42 @@ +Users on %%site.name%% can create *groups* that other users can join. +Groups can be a great way to share information and entertainment with +a group of people who have a common interest or background. + +You can find out about groups on the server on the +[Groups](%%action.groups%%) page. You can join a group by clicking on +the "Join" button either in the group list or on the group's home page. + +Starting a new group +-------------------- + +If you want, you can start a new group for friends and people with +common interests. Note that all groups are free for anyone to join. + +To start a new group, use the [new group](%%action.newgroup%%) tool +and fill out the form. Describe your group as best you can if you want +people to be able to find it. + +When choosing the nickname for your group, try to keep it short. The +nickname is included in every message to and from the group, so the +less chars the better. Try using acronyms for organizations, or +airport codes for places (like 'pdx' instead of 'portland'). + +Sending messages to a group +--------------------------- + +You can send a message to a group using the syntax "!groupname" +anywhere in the message. If you have more than one group named, the +notice will go to each group. Only members can send notices to a +group, and groups do not respond to direct messages (DMs). + +Receiving messages +------------------ + +New group messages will appear in your inbox, and will also come to +your phone or IM client if you've set them up to receive notices. + +Remote groups +------------- + +While it's technically possible, this version of Laconica does not +support remote group membership. diff --git a/doc-src/help b/doc-src/help new file mode 100644 index 000000000..a8cfccd2b --- /dev/null +++ b/doc-src/help @@ -0,0 +1,32 @@ +%%site.name%% is a **microblogging service**. Users post short (140 +character) notices which are broadcast to their friends and fans using +the Web, RSS, or instant messages. + +If you'd like to try it out, first [register](%%action.register%%) a new account. +Then, on the [public timeline](%%action.public%%), enter your message into +the textbox at the top of the page, and click "Send". It will go out on the +public timeline and to anyone who is subscribed to your notices (probably nobody, +at first). + +To subscribe to other people's notifications, go to their profile page +and click the "subscribe" button. They'll get a notice that you're now +subscribed to their notifications, and, who knows?, they might subscribe +back. + +More help +--------- + +Here are some documents that you might find helpful in understanding +%%site.name%% and how to use it. + +* [About](%%doc.about%%) - an overview of the service +* [FAQ](%%doc.faq%%) - frequently-asked questions about %%site.name%% +* [Contact](%%doc.contact%%) - who to contact with questions about the service +* [IM](%%doc.im%%) - using the instant-message (IM) features of %%site.name%% +* [SMS](%%doc.sms%%) - tying your cellphone to %%site.name%% +* [tags](%%doc.tags%%) - different ways to use tagging +* [Groups](%%doc.groups%%) - joining together in groups +* [OpenID](%%doc.openid%%) - what OpenID is and how to use it with this service +* [OpenMicroBlogging](%%doc.openmublog%%) - subscribing to remote users +* [Privacy](%%doc.privacy%%) - %%site.name%%'s privacy policy +* [Source](%%doc.source%%) - How to get the Laconica source code diff --git a/doc-src/im b/doc-src/im new file mode 100644 index 000000000..da07f9fe7 --- /dev/null +++ b/doc-src/im @@ -0,0 +1,35 @@ +You can post messages to %%site.name%% using a [Jabber](http://jabber.org/) client +on your computer, mobile phone, or other platform. ([GTalk](http://talk.google.com/), +Google's Jabber program, will also work.) This can be a convenient way to keep +up with your friends on %%site.name%%. + +If you don't already have a Jabber account, you can use GTalk or one of the other +[public Jabber services](http://www.jabber.org/im-services). You'll probably also +need an IM client like [Pidgin](http://www.pidgin.im/). + +Managing your IM settings +------------------------- + +Use the [IM settings](%%action.imsettings%%) page to set your IM preferences. You can add or change your Jabber address and set the flags for Jabber update. + +When you add or change your address, you'll receive a message from **%%xmpp.user%%@%%xmpp.server%%** asking you to confirm the change. (You may need to +add %%xmpp.user%%@%%xmpp.server%% to your buddy list *before* changing your IM +settings; this is definitely true for GTalk.) + +Sending updates +--------------- + +You send updates by sending messages to %%xmpp.user%%@%%xmpp.server%%. Messages +should be less than 140 characters; longer messages will be truncated. + +Commands +-------- + +You can do some minor management of your account through Jabber. These are the +currently-implemented commands: + +* **on**: Turn on notifications. You'll receive copies of messages by people + you subscribe to. +* **off**: Turn off notifications. You'll no longer receive Jabber + notifications. + diff --git a/doc-src/openid b/doc-src/openid new file mode 100644 index 000000000..c741e3674 --- /dev/null +++ b/doc-src/openid @@ -0,0 +1,11 @@ +%%site.name%% supports the [OpenID](http://openid.net/) standard for single signon between Web sites. OpenID lets you log into many different Web sites without using a different password for each. (See [Wikipedia's OpenID article](http://en.wikipedia.org/wiki/OpenID) for more information.) + +If you already have an account on %%site.name%%, you can [login](%%action.login%%) with your username and password as usual. +To use OpenID in the future, you can [add an OpenID to your account](%%action.openidsettings%%) after you have logged in normally. + +There are many [Public OpenID providers](http://wiki.openid.net/Public_OpenID_providers), and you may already have an OpenID-enabled account on another service. + +* On wikis: If you have an account on an OpenID-enabled wiki, like [Wikitravel](http://wikitravel.org/), [wikiHow](http://www.wikihow.com/), [Vinismo](http://vinismo.com/), [AboutUs](http://aboutus.org/) or [Keiki](http://kei.ki/), you can log in to %%site.name%% by entering the **full URL** of your user page on that other wiki in the box above. For example, *http://kei.ki/en/User:Evan*. +* [Yahoo!](http://openid.yahoo.com/) : If you have an account with Yahoo!, you can log in to this site by entering your Yahoo!-provided OpenID in the box above. Yahoo! OpenID URLs have the form *https://me.yahoo.com/yourusername*. +* [AOL](http://dev.aol.com/aol-and-63-million-openids) : If you have an account with [AOL](http://www.aol.com/), like an [AIM](http://www.aim.com/) account, you can log in to %%site.name%% by entering your AOL-provided OpenID in the box above. AOL OpenID URLs have the form *http://openid.aol.com/yourusername*. Your username should be all lowercase, no spaces. +* [Blogger](http://bloggerindraft.blogspot.com/2008/01/new-feature-blogger-as-openid-provider.html), [Wordpress.com](http://faq.wordpress.com/2007/03/06/what-is-openid/), [LiveJournal](http://www.livejournal.com/openid/about.bml), [Vox](http://bradfitz.vox.com/library/post/openid-for-vox.html) : If you have a blog on any of these services, enter your blog URL in the box above. For example, *http://yourusername.blogspot.com/*, *http://yourusername.wordpress.com/*, *http://yourusername.livejournal.com/*, or *http://yourusername.vox.com/*. diff --git a/doc-src/openmublog b/doc-src/openmublog new file mode 100644 index 000000000..6e3abee42 --- /dev/null +++ b/doc-src/openmublog @@ -0,0 +1,25 @@ +[OpenMicroBlogging](http://openmicroblogging.org/) is a protocol that +lets users of one [microblogging](http://en.wikipedia.org/wiki/microblogging) service +subscribe to notices by users of another service. The protocol, based on +[OAuth](http://oauth.net/), is open and free, and doesn't depend on any +central authority to maintain the federated microblogs. + +The [Laconica](http://laconi.ca/) software that runs %%site.name%% supports +OpenMicroBlogging 0.1. Anyone can make a new installation of Laconica on their +own servers, and users of that new installation can subscribe to notices from +%%site.name%%. + +Remote subscription +------------------- + +If you have an account on a remote site that supports OpenMicroBlogging, and you +want to subscribe to the notices of a user on this site, click on the "Subscribe" +link under their avatar on their profile page. This should take you to the +[remote subscription](%%action.remotesubscribe%%) page. Make sure that you've got the +right nickname registered, and enter your profile URL on the other microblogging +service. + +You'll be taken to your microblogging service, where you'll be asked to confirm the +subscription. When you confirm, your service will receive new notifications from +the user on %%site.name%%, and your service will forward them to you (using IM, SMS, +the Web, or whatever else). diff --git a/doc-src/privacy b/doc-src/privacy new file mode 100644 index 000000000..90c7b3c7f --- /dev/null +++ b/doc-src/privacy @@ -0,0 +1,45 @@ +This document outlines this service's respect for your personal +privacy as a user of the service. + +- Almost all the text and files that users upload to this site is + available under the site license (see the license block at the bottom + of this page). Users agree to the license when they register to use + the site for the first time. Typically that means that the data can + be copied far and wide, for commercial and non-commercial purposes, + and in modified or unmodified form. If you're not OK with that, + don't use the service. +- The following data items are considered *private data* that won't be + shared with other users, business partners, or the public at large: + * your password + * your email address + * your IM address (AIM, Jabber, or other instant messaging address) + * your phone number + * your "private messages" + * your login credentials (username and password) for other services (Twitter, Facebook, etc.) +- Some private data may be published in aggregate, e.g. "30% of our + users are registered with Hotmail addresses." +- Your notices (including files) can be downloaded and re-used by + other services, either one-by-one or in bulk as + [RSS](http://en.wikipedia.org/wiki/RSS) files. +- Your profile information (including subscriptions and avatars) can be + downloaded and re-used by other services, either scraped from the HTML + interface or in bulk as [FOAF](http://en.wikipedia.org/wiki/FOAF) files. +- Your notices will be forwarded to users who subscribe to them, + including users on another microblogging service. +- Your profile information will be sent to microblogging services for + users who subscribe to you or to whom you subscribe. +- Based on your email preferences, you may receive automated email + messages for important system events, such as when others subscribe + to your notices. +- Based on your email preferences, you may receive an email + newsletter. You can opt out of the newsletter if you don't want to + receive it. +- In urgent situations, administrators may send you email directly to + your registered email address, even if you've requested no notices + or newsletter. *Administrators will use digitally-signed email.* +- This service will comply with court orders to turn over your private + information. + + + + diff --git a/doc-src/sms b/doc-src/sms new file mode 100644 index 000000000..1beb49786 --- /dev/null +++ b/doc-src/sms @@ -0,0 +1,68 @@ +You can post messages to %%site.name%% using a many kinds of cell +phones that support SMS messaging. This site does not support SMS +directly; rather, it uses your carrier's email gateway to send and +receive messages. + +Managing your SMS settings +-------------------------- + +Use the [SMS settings](%%action.smssettings%%) page to set your SMS +preferences. You can add or change your SMS number and set the +flags for SMS updates. + +When you add or change your phone number, you'll receive a message on your +phone with a verification code. Enter it into the SMS settings page to +confirm that the owner of the phone authorizes sending it messages. + +Note that only the carriers listed in the drop down list on the form +are supported by %%site.name%%. They're the only ones we know how to +make email addresses for. + +Receiving messages +------------------ + +Once you've verified your phone number, you can enable sending +messages to your phone. If you have a lot of friends and a typical +phone, it can be hard to keep up. + +Sending messages +---------------- + +To send a message, you must send an email to the incoming email +address visible on your SMS settings page. The method for sending +email from your phone varies from carrier to carrier and from handset +to handet; if in doubt, ask your carrier. + +Keep your incoming email address a secret -- it's the only way we know +you're really you! + +Commands +-------- + +You can use the following commands with %%site.name%%. + +* on - turn on notifications +* off - turn off notifications +* help - show this help +* follow - subscribe to user +* leave - unsubscribe from user +* d - direct message to user +* get - get last notice from user +* whois - get profile info on user +* fav - add user's last notice as a 'fave' +* stats - get your stats +* stop - same as 'off' +* quit - same as 'off' +* sub - same as 'follow' +* unsub - same as 'leave' +* last - same as 'get' +* on - not yet implemented. +* off - not yet implemented. +* nudge - not yet implemented. +* invite - not yet implemented. +* track - not yet implemented. +* untrack - not yet implemented. +* track off - not yet implemented. +* untrack all - not yet implemented. +* tracks - not yet implemented. +* tracking - not yet implemented. diff --git a/doc-src/source b/doc-src/source new file mode 100644 index 000000000..83debbe53 --- /dev/null +++ b/doc-src/source @@ -0,0 +1,12 @@ +This service uses a Free microblogging tool called **Laconica**. +Laconica is available under the [GNU Affero General Public License +Version 3.0](http://www.fsf.org/licensing/licenses/agpl-3.0.html), a +Free Software license for network services. + +You can get a copy of the software from the +[Laconica](http://laconi.ca/) main site. The version of the software +that runs on *this* site is unmodified from that version. The site +also depends on certain libraries and other software; you can get +those at the Laconica site, too. + + diff --git a/doc-src/tags b/doc-src/tags new file mode 100644 index 000000000..2ed352e70 --- /dev/null +++ b/doc-src/tags @@ -0,0 +1,40 @@ +%%site.name%% supports +[tags](http://en.wikipedia.org/wiki/Tag_(metadata)) to help you +organize your activities here. You can use tags for people and for +notices. + +Tagging a notice +---------------- + +You can tag a notice using a *hashtag*; a # character followed by +letters and numbers as well as '.', '-', and '_'. Note that accented +latin characters are not supported, and non-roman scripts are right out. + +The HTML for the notice will link to a stream of all the other notices +with that tag. This can be a great way to keep track of a conversation. + +The most popular current tags on the site can be found in the [public +tag cloud](%%action.publictagcloud%%). Their size shows their +popularity and recency. + +Tagging yourself +---------------- + +You can also add tags for yourself on your [profile +settings](%%action.profilesettings%%) page. Use single words to +describe yourself, your experiences and your interest. The tags will +become links on your profile page to a list of all the users on the +site who use that same tag. It can be a nice way to find people who +are related to you geographically or who have a common interest. + +Tagging your subscriptions +-------------------------- + +You can also tag your subscriptions, on the subscriptions page. This +makes it easy to organize your subscriptions into groups and sort +through them separately. + +You can also send a notice "to the attention of" everyone you've +marked with a particular tag (note: *not* people who've marked +themselves with that tag). "@#family hello" will send a notice to +everyone you've marked with the tag 'family'. \ No newline at end of file diff --git a/doc/about b/doc/about deleted file mode 100644 index 3036a51b9..000000000 --- a/doc/about +++ /dev/null @@ -1,10 +0,0 @@ -%%site.name%% is a -[micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service -based on the Free Software [Laconica](http://laconi.ca/) tool. - -If you [register](%%action.register%%) for an account, -you can post small (140 chars or less) text notices -about yourself, where you are, what you're doing, or practically -anything you want. You can also subscribe to the notices of your -friends, or other people you're interested in, and follow them on the -Web or in an [RSS](http://en.wikipedia.org/wiki/RSS) feed. diff --git a/doc/contact b/doc/contact deleted file mode 100644 index a8efc456a..000000000 --- a/doc/contact +++ /dev/null @@ -1,24 +0,0 @@ -There are a number of options for getting in contact with responsible -people for %%site.name%%. - -Post a notice -------------- - -If you have a question about how to do something, just post a notice -with your question. People here like to answer messages. Watch the -[public timeline](%%action.public%%) for answers; they'll usually start -with "@" plus your user name. - -Bugs ----- - -If you think you've found a bug in the [Laconica](http://laconi.ca/) software, -or if there's a new feature you'd like to see, add it into the [Laconica bug database](http://laconi.ca/PITS/HomePage). Don't forget to check the list of -existing bugs to make sure it hasn't already been reported! - -Email ------ - -You can reach the responsible party for this server at [%%site.email%%](mailto:%%site.email%%). - - diff --git a/doc/faq b/doc/faq deleted file mode 100644 index 31582b9f0..000000000 --- a/doc/faq +++ /dev/null @@ -1,42 +0,0 @@ -These are some *Frequently Asked Questions* about this service, with -some answers. - -What is %%site.name%%? ----------------------- - -%%site.name%% is a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service. -You can use it to write short notices about yourself, where you are, -and what you're doing, and those notices will be sent to all your friends -and fans. - -How is %%site.name%% different from Twitter, Jaiku, Pownce, Plurk, others? --------------------------------------------------------------------------- - -%%site.name%% is an [Open Network Service](http://opendefinition.org/ossd). Our main -goal is to provide a fair and transparent service that preserves users' autonomy. In -particular, all the software used for %%site.name%% is [Free Software](http://en.wikipedia.org/wiki/Free_Software), and all the data is available -under the [%%license.title%%](%%license.url%%) license, making it Open Data. - -The software also implements the [OpenMicroBlogging](http://openmicroblogging.org/) protocol, meaning that you can have friends on other microblogging services -that can receive your notices. - -The goal here is *autonomy* -- you deserve the right to manage your own on-line -presence. If you don't like how %%site.name%% works, you can take your data and the source code and set up your own server (or move your account to another one). - -Where is feature X? -------------------- - -The software we run, [Laconica](http://laconi.ca/), is still in its early stages, -and many features people expect from microblogging sites are not yet implemented. Some important ones that are expected "soon": - -* More [AJAX](http://en.wikipedia.org/wiki/AJAX)-y interface -* Maps -* Cross-post to Pownce, Jaiku, etc. -* Pull messages from Twitter, Pownce, Jaiku, etc. -* [Facebook](http://www.facebook.com/) integration -* Image, video, audio notices - -There is [a list of bugs and features](http://laconi.ca/trac/) that you may find -interesting. New ideas or complaints are very welcome. - - diff --git a/doc/groups b/doc/groups deleted file mode 100644 index 645390e0c..000000000 --- a/doc/groups +++ /dev/null @@ -1,42 +0,0 @@ -Users on %%site.name%% can create *groups* that other users can join. -Groups can be a great way to share information and entertainment with -a group of people who have a common interest or background. - -You can find out about groups on the server on the -[Groups](%%action.groups%%) page. You can join a group by clicking on -the "Join" button either in the group list or on the group's home page. - -Starting a new group --------------------- - -If you want, you can start a new group for friends and people with -common interests. Note that all groups are free for anyone to join. - -To start a new group, use the [new group](%%action.newgroup%%) tool -and fill out the form. Describe your group as best you can if you want -people to be able to find it. - -When choosing the nickname for your group, try to keep it short. The -nickname is included in every message to and from the group, so the -less chars the better. Try using acronyms for organizations, or -airport codes for places (like 'pdx' instead of 'portland'). - -Sending messages to a group ---------------------------- - -You can send a message to a group using the syntax "!groupname" -anywhere in the message. If you have more than one group named, the -notice will go to each group. Only members can send notices to a -group, and groups do not respond to direct messages (DMs). - -Receiving messages ------------------- - -New group messages will appear in your inbox, and will also come to -your phone or IM client if you've set them up to receive notices. - -Remote groups -------------- - -While it's technically possible, this version of Laconica does not -support remote group membership. diff --git a/doc/help b/doc/help deleted file mode 100644 index a8cfccd2b..000000000 --- a/doc/help +++ /dev/null @@ -1,32 +0,0 @@ -%%site.name%% is a **microblogging service**. Users post short (140 -character) notices which are broadcast to their friends and fans using -the Web, RSS, or instant messages. - -If you'd like to try it out, first [register](%%action.register%%) a new account. -Then, on the [public timeline](%%action.public%%), enter your message into -the textbox at the top of the page, and click "Send". It will go out on the -public timeline and to anyone who is subscribed to your notices (probably nobody, -at first). - -To subscribe to other people's notifications, go to their profile page -and click the "subscribe" button. They'll get a notice that you're now -subscribed to their notifications, and, who knows?, they might subscribe -back. - -More help ---------- - -Here are some documents that you might find helpful in understanding -%%site.name%% and how to use it. - -* [About](%%doc.about%%) - an overview of the service -* [FAQ](%%doc.faq%%) - frequently-asked questions about %%site.name%% -* [Contact](%%doc.contact%%) - who to contact with questions about the service -* [IM](%%doc.im%%) - using the instant-message (IM) features of %%site.name%% -* [SMS](%%doc.sms%%) - tying your cellphone to %%site.name%% -* [tags](%%doc.tags%%) - different ways to use tagging -* [Groups](%%doc.groups%%) - joining together in groups -* [OpenID](%%doc.openid%%) - what OpenID is and how to use it with this service -* [OpenMicroBlogging](%%doc.openmublog%%) - subscribing to remote users -* [Privacy](%%doc.privacy%%) - %%site.name%%'s privacy policy -* [Source](%%doc.source%%) - How to get the Laconica source code diff --git a/doc/im b/doc/im deleted file mode 100644 index da07f9fe7..000000000 --- a/doc/im +++ /dev/null @@ -1,35 +0,0 @@ -You can post messages to %%site.name%% using a [Jabber](http://jabber.org/) client -on your computer, mobile phone, or other platform. ([GTalk](http://talk.google.com/), -Google's Jabber program, will also work.) This can be a convenient way to keep -up with your friends on %%site.name%%. - -If you don't already have a Jabber account, you can use GTalk or one of the other -[public Jabber services](http://www.jabber.org/im-services). You'll probably also -need an IM client like [Pidgin](http://www.pidgin.im/). - -Managing your IM settings -------------------------- - -Use the [IM settings](%%action.imsettings%%) page to set your IM preferences. You can add or change your Jabber address and set the flags for Jabber update. - -When you add or change your address, you'll receive a message from **%%xmpp.user%%@%%xmpp.server%%** asking you to confirm the change. (You may need to -add %%xmpp.user%%@%%xmpp.server%% to your buddy list *before* changing your IM -settings; this is definitely true for GTalk.) - -Sending updates ---------------- - -You send updates by sending messages to %%xmpp.user%%@%%xmpp.server%%. Messages -should be less than 140 characters; longer messages will be truncated. - -Commands --------- - -You can do some minor management of your account through Jabber. These are the -currently-implemented commands: - -* **on**: Turn on notifications. You'll receive copies of messages by people - you subscribe to. -* **off**: Turn off notifications. You'll no longer receive Jabber - notifications. - diff --git a/doc/openid b/doc/openid deleted file mode 100644 index c741e3674..000000000 --- a/doc/openid +++ /dev/null @@ -1,11 +0,0 @@ -%%site.name%% supports the [OpenID](http://openid.net/) standard for single signon between Web sites. OpenID lets you log into many different Web sites without using a different password for each. (See [Wikipedia's OpenID article](http://en.wikipedia.org/wiki/OpenID) for more information.) - -If you already have an account on %%site.name%%, you can [login](%%action.login%%) with your username and password as usual. -To use OpenID in the future, you can [add an OpenID to your account](%%action.openidsettings%%) after you have logged in normally. - -There are many [Public OpenID providers](http://wiki.openid.net/Public_OpenID_providers), and you may already have an OpenID-enabled account on another service. - -* On wikis: If you have an account on an OpenID-enabled wiki, like [Wikitravel](http://wikitravel.org/), [wikiHow](http://www.wikihow.com/), [Vinismo](http://vinismo.com/), [AboutUs](http://aboutus.org/) or [Keiki](http://kei.ki/), you can log in to %%site.name%% by entering the **full URL** of your user page on that other wiki in the box above. For example, *http://kei.ki/en/User:Evan*. -* [Yahoo!](http://openid.yahoo.com/) : If you have an account with Yahoo!, you can log in to this site by entering your Yahoo!-provided OpenID in the box above. Yahoo! OpenID URLs have the form *https://me.yahoo.com/yourusername*. -* [AOL](http://dev.aol.com/aol-and-63-million-openids) : If you have an account with [AOL](http://www.aol.com/), like an [AIM](http://www.aim.com/) account, you can log in to %%site.name%% by entering your AOL-provided OpenID in the box above. AOL OpenID URLs have the form *http://openid.aol.com/yourusername*. Your username should be all lowercase, no spaces. -* [Blogger](http://bloggerindraft.blogspot.com/2008/01/new-feature-blogger-as-openid-provider.html), [Wordpress.com](http://faq.wordpress.com/2007/03/06/what-is-openid/), [LiveJournal](http://www.livejournal.com/openid/about.bml), [Vox](http://bradfitz.vox.com/library/post/openid-for-vox.html) : If you have a blog on any of these services, enter your blog URL in the box above. For example, *http://yourusername.blogspot.com/*, *http://yourusername.wordpress.com/*, *http://yourusername.livejournal.com/*, or *http://yourusername.vox.com/*. diff --git a/doc/openmublog b/doc/openmublog deleted file mode 100644 index 6e3abee42..000000000 --- a/doc/openmublog +++ /dev/null @@ -1,25 +0,0 @@ -[OpenMicroBlogging](http://openmicroblogging.org/) is a protocol that -lets users of one [microblogging](http://en.wikipedia.org/wiki/microblogging) service -subscribe to notices by users of another service. The protocol, based on -[OAuth](http://oauth.net/), is open and free, and doesn't depend on any -central authority to maintain the federated microblogs. - -The [Laconica](http://laconi.ca/) software that runs %%site.name%% supports -OpenMicroBlogging 0.1. Anyone can make a new installation of Laconica on their -own servers, and users of that new installation can subscribe to notices from -%%site.name%%. - -Remote subscription -------------------- - -If you have an account on a remote site that supports OpenMicroBlogging, and you -want to subscribe to the notices of a user on this site, click on the "Subscribe" -link under their avatar on their profile page. This should take you to the -[remote subscription](%%action.remotesubscribe%%) page. Make sure that you've got the -right nickname registered, and enter your profile URL on the other microblogging -service. - -You'll be taken to your microblogging service, where you'll be asked to confirm the -subscription. When you confirm, your service will receive new notifications from -the user on %%site.name%%, and your service will forward them to you (using IM, SMS, -the Web, or whatever else). diff --git a/doc/privacy b/doc/privacy deleted file mode 100644 index 90c7b3c7f..000000000 --- a/doc/privacy +++ /dev/null @@ -1,45 +0,0 @@ -This document outlines this service's respect for your personal -privacy as a user of the service. - -- Almost all the text and files that users upload to this site is - available under the site license (see the license block at the bottom - of this page). Users agree to the license when they register to use - the site for the first time. Typically that means that the data can - be copied far and wide, for commercial and non-commercial purposes, - and in modified or unmodified form. If you're not OK with that, - don't use the service. -- The following data items are considered *private data* that won't be - shared with other users, business partners, or the public at large: - * your password - * your email address - * your IM address (AIM, Jabber, or other instant messaging address) - * your phone number - * your "private messages" - * your login credentials (username and password) for other services (Twitter, Facebook, etc.) -- Some private data may be published in aggregate, e.g. "30% of our - users are registered with Hotmail addresses." -- Your notices (including files) can be downloaded and re-used by - other services, either one-by-one or in bulk as - [RSS](http://en.wikipedia.org/wiki/RSS) files. -- Your profile information (including subscriptions and avatars) can be - downloaded and re-used by other services, either scraped from the HTML - interface or in bulk as [FOAF](http://en.wikipedia.org/wiki/FOAF) files. -- Your notices will be forwarded to users who subscribe to them, - including users on another microblogging service. -- Your profile information will be sent to microblogging services for - users who subscribe to you or to whom you subscribe. -- Based on your email preferences, you may receive automated email - messages for important system events, such as when others subscribe - to your notices. -- Based on your email preferences, you may receive an email - newsletter. You can opt out of the newsletter if you don't want to - receive it. -- In urgent situations, administrators may send you email directly to - your registered email address, even if you've requested no notices - or newsletter. *Administrators will use digitally-signed email.* -- This service will comply with court orders to turn over your private - information. - - - - diff --git a/doc/sms b/doc/sms deleted file mode 100644 index 1beb49786..000000000 --- a/doc/sms +++ /dev/null @@ -1,68 +0,0 @@ -You can post messages to %%site.name%% using a many kinds of cell -phones that support SMS messaging. This site does not support SMS -directly; rather, it uses your carrier's email gateway to send and -receive messages. - -Managing your SMS settings --------------------------- - -Use the [SMS settings](%%action.smssettings%%) page to set your SMS -preferences. You can add or change your SMS number and set the -flags for SMS updates. - -When you add or change your phone number, you'll receive a message on your -phone with a verification code. Enter it into the SMS settings page to -confirm that the owner of the phone authorizes sending it messages. - -Note that only the carriers listed in the drop down list on the form -are supported by %%site.name%%. They're the only ones we know how to -make email addresses for. - -Receiving messages ------------------- - -Once you've verified your phone number, you can enable sending -messages to your phone. If you have a lot of friends and a typical -phone, it can be hard to keep up. - -Sending messages ----------------- - -To send a message, you must send an email to the incoming email -address visible on your SMS settings page. The method for sending -email from your phone varies from carrier to carrier and from handset -to handet; if in doubt, ask your carrier. - -Keep your incoming email address a secret -- it's the only way we know -you're really you! - -Commands --------- - -You can use the following commands with %%site.name%%. - -* on - turn on notifications -* off - turn off notifications -* help - show this help -* follow - subscribe to user -* leave - unsubscribe from user -* d - direct message to user -* get - get last notice from user -* whois - get profile info on user -* fav - add user's last notice as a 'fave' -* stats - get your stats -* stop - same as 'off' -* quit - same as 'off' -* sub - same as 'follow' -* unsub - same as 'leave' -* last - same as 'get' -* on - not yet implemented. -* off - not yet implemented. -* nudge - not yet implemented. -* invite - not yet implemented. -* track - not yet implemented. -* untrack - not yet implemented. -* track off - not yet implemented. -* untrack all - not yet implemented. -* tracks - not yet implemented. -* tracking - not yet implemented. diff --git a/doc/source b/doc/source deleted file mode 100644 index 83debbe53..000000000 --- a/doc/source +++ /dev/null @@ -1,12 +0,0 @@ -This service uses a Free microblogging tool called **Laconica**. -Laconica is available under the [GNU Affero General Public License -Version 3.0](http://www.fsf.org/licensing/licenses/agpl-3.0.html), a -Free Software license for network services. - -You can get a copy of the software from the -[Laconica](http://laconi.ca/) main site. The version of the software -that runs on *this* site is unmodified from that version. The site -also depends on certain libraries and other software; you can get -those at the Laconica site, too. - - diff --git a/doc/tags b/doc/tags deleted file mode 100644 index 2ed352e70..000000000 --- a/doc/tags +++ /dev/null @@ -1,40 +0,0 @@ -%%site.name%% supports -[tags](http://en.wikipedia.org/wiki/Tag_(metadata)) to help you -organize your activities here. You can use tags for people and for -notices. - -Tagging a notice ----------------- - -You can tag a notice using a *hashtag*; a # character followed by -letters and numbers as well as '.', '-', and '_'. Note that accented -latin characters are not supported, and non-roman scripts are right out. - -The HTML for the notice will link to a stream of all the other notices -with that tag. This can be a great way to keep track of a conversation. - -The most popular current tags on the site can be found in the [public -tag cloud](%%action.publictagcloud%%). Their size shows their -popularity and recency. - -Tagging yourself ----------------- - -You can also add tags for yourself on your [profile -settings](%%action.profilesettings%%) page. Use single words to -describe yourself, your experiences and your interest. The tags will -become links on your profile page to a list of all the users on the -site who use that same tag. It can be a nice way to find people who -are related to you geographically or who have a common interest. - -Tagging your subscriptions --------------------------- - -You can also tag your subscriptions, on the subscriptions page. This -makes it easy to organize your subscriptions into groups and sort -through them separately. - -You can also send a notice "to the attention of" everyone you've -marked with a particular tag (note: *not* people who've marked -themselves with that tag). "@#family hello" will send a notice to -everyone you've marked with the tag 'family'. \ No newline at end of file -- cgit v1.2.3-54-g00ecf From fbecbcb693e9d8fc810cf316e8739a22ac501043 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 10 Feb 2009 22:49:25 -0500 Subject: Build urls using Net_URL_Mapper, too --- lib/router.php | 28 ++++-- lib/util.php | 269 ++------------------------------------------------------- 2 files changed, 27 insertions(+), 270 deletions(-) diff --git a/lib/router.php b/lib/router.php index 6781322a2..6443bf354 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 c5a092f63..a78af8be9 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; + $r = new Router(); + $path = $r->build($action, $args, $fragment); + if ($path) { + } if (common_config('site','fancy')) { - $url = common_fancy_url($action, $args); + $url = common_path(mb_substr($path, 1)); } else { - $url = common_simple_url($action, $args); - } - if (!is_null($fragment)) { - $url .= '#'.$fragment; + $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; -- cgit v1.2.3-54-g00ecf From bba1dbdb403aac067ae97c44531f6886f90fec35 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 00:45:11 -0500 Subject: 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 f334e2c34..717b17361 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 6443bf354..d47ad7118 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 a78af8be9..9b38b5596 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')) { -- cgit v1.2.3-54-g00ecf From 07c6537898a7c839469b50121b6c6d63be07613c Mon Sep 17 00:00:00 2001 From: Meitar Moscovitz Date: Wed, 11 Feb 2009 23:35:58 +1100 Subject: 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 6cd717a4d..3d0455a67 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%; } -- cgit v1.2.3-54-g00ecf From d345c746b9021266a2ed100329affe49c5ae8fc3 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 08:34:21 -0500 Subject: 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 f41d984d6..b385d3c5c 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"; -- cgit v1.2.3-54-g00ecf From c640b747f7e682ac632397cf32e41ef4c2dca96f Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 08:34:21 -0500 Subject: 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 f41d984d6..b385d3c5c 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"; -- cgit v1.2.3-54-g00ecf From ad65c447d5e32b8ef5681789eca12a3717231311 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 10:35:15 -0500 Subject: 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 cc6537f74..1137bd876 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'))); -- cgit v1.2.3-54-g00ecf From 22b10399aaa97061ed940f92f5b15f6aacfb1093 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 11:37:50 -0500 Subject: 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 | 62 +++++--------------------- 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, 240 insertions(+), 335 deletions(-) create mode 100644 lib/feed.php diff --git a/actions/all.php b/actions/all.php index d75d1b946..08dcccbdd 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 2d94a7906..dc58d7528 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 1137bd876..a20ae4032 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 7eff74a66..4ab9b14ed 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 31479e1a7..d1c9283f0 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 7bc68fbc6..340e18333 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 962f4b452..0ee5d769e 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 4401f892a..231f2c299 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 ce92addf5..bd38bf79c 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 000000000..466926844 --- /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 8bfcb9c5a..927e43c33 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'); - - 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"; - 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"; - 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; + $classname = null; - case 'favoritedrss': - $feed_classname = $feed['type']; - $feed_mimetype = "application/".$feed['type']."+xml"; - $feed_title = "Favorited ".$feed['version']." feed"; - $feed['textContent'] = "RSS"; + switch ($feed->type) { + case Feed::RSS1: + case Feed::RSS2: + $classname = 'rss'; break; - - case 'foaf': - $feed_classname = "foaf"; - $feed_mimetype = "application/".$feed['type']."+xml"; - $feed_title = "$nickname's FOAF file"; - $feed['textContent'] = "FOAF"; + case Feed::ATOM: + $classname = 'atom'; 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'); } } -- cgit v1.2.3-54-g00ecf From fc293545be13473d992b512141be233c2963f6da Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 11 Feb 2009 16:50:07 +0000 Subject: 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 7bc68fbc6..6df8dd306 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 962f4b452..bd6f4153b 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 3e8a12fd6..79151c911 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 4c448e250..6801ab426 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 4d924b039..8bef49dce 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'); -- cgit v1.2.3-54-g00ecf From b5cc7e4aabeddd08a27e02b2249ce86f92f96fac Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 14:45:06 -0500 Subject: 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 717b17361..4db0e7555 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 000000000..0dc92490c --- /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 e2319b1fd..45e61d2fc 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 * -- cgit v1.2.3-54-g00ecf From 21f6c911611fe9debfae9a4c123e91294bc877e3 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 11 Feb 2009 19:56:17 +0000 Subject: 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 87a70e31e..1ecbb664e 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 -- cgit v1.2.3-54-g00ecf From 1d5296e596168f6ee8b18e9917e8d8aa62ac254c Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 15:39:49 -0500 Subject: 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 45e61d2fc..06603ac05 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); } } -- cgit v1.2.3-54-g00ecf From 5127396325a29d6c7b8f0e1e0ae3e0580ab30dda Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 15:46:29 -0500 Subject: 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 | 238 ----------------------- classes/Command.php | 419 ----------------------------------------- classes/CommandInterpreter.php | 198 ------------------- lib/channel.php | 238 +++++++++++++++++++++++ lib/command.php | 419 +++++++++++++++++++++++++++++++++++++++++ lib/commandinterpreter.php | 198 +++++++++++++++++++ 6 files changed, 855 insertions(+), 855 deletions(-) delete mode 100644 classes/Channel.php delete mode 100644 classes/Command.php delete mode 100644 classes/CommandInterpreter.php create mode 100644 lib/channel.php create mode 100644 lib/command.php create mode 100644 lib/commandinterpreter.php diff --git a/classes/Channel.php b/classes/Channel.php deleted file mode 100644 index fdeff21fc..000000000 --- a/classes/Channel.php +++ /dev/null @@ -1,238 +0,0 @@ -. - */ - -if (!defined('LACONICA')) { exit(1); } - -class Channel -{ - - function on($user) - { - return false; - } - - function off($user) - { - return false; - } - - function output($user, $text) - { - return false; - } - - function error($user, $text) - { - return false; - } - - function source() - { - return null; - } -} - -class XMPPChannel extends Channel -{ - - var $conn = null; - - function source() - { - return 'xmpp'; - } - - function __construct($conn) - { - $this->conn = $conn; - } - - function on($user) - { - return $this->set_notify($user, 1); - } - - function off($user) - { - return $this->set_notify($user, 0); - } - - function output($user, $text) - { - $text = '['.common_config('site', 'name') . '] ' . $text; - jabber_send_message($user->jabber, $text); - } - - function error($user, $text) - { - $text = '['.common_config('site', 'name') . '] ' . $text; - jabber_send_message($user->jabber, $text); - } - - function set_notify(&$user, $notify) - { - $orig = clone($user); - $user->jabbernotify = $notify; - $result = $user->update($orig); - if (!$result) { - $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError'); - common_log(LOG_ERR, - 'Could not set notify flag to ' . $notify . - ' for user ' . common_log_objstring($user) . - ': ' . $last_error->message); - return false; - } else { - common_log(LOG_INFO, - 'User ' . $user->nickname . ' set notify flag to ' . $notify); - return true; - } - } -} - -class WebChannel extends Channel -{ - var $out = null; - - function __construct($out=null) - { - $this->out = $out; - } - - function source() - { - return 'web'; - } - - function on($user) - { - return false; - } - - function off($user) - { - return false; - } - - function output($user, $text) - { - # XXX: buffer all output and send it at the end - # XXX: even better, redirect to appropriate page - # depending on what command was run - $this->out->startHTML(); - $this->out->elementStart('head'); - $this->out->element('title', null, _('Command results')); - $this->out->elementEnd('head'); - $this->out->elementStart('body'); - $this->out->element('p', array('id' => 'command_result'), $text); - $this->out->elementEnd('body'); - $this->out->endHTML(); - } - - function error($user, $text) - { - common_user_error($text); - } -} - -class AjaxWebChannel extends WebChannel -{ - function output($user, $text) - { - $this->out->startHTML('text/xml;charset=utf-8'); - $this->out->elementStart('head'); - $this->out->element('title', null, _('Command results')); - $this->out->elementEnd('head'); - $this->out->elementStart('body'); - $this->out->element('p', array('id' => 'command_result'), $text); - $this->out->elementEnd('body'); - $this->out->endHTML(); - } - - function error($user, $text) - { - $this->out->startHTML('text/xml;charset=utf-8'); - $this->out->elementStart('head'); - $this->out->element('title', null, _('Ajax Error')); - $this->out->elementEnd('head'); - $this->out->elementStart('body'); - $this->out->element('p', array('id' => 'error'), $text); - $this->out->elementEnd('body'); - $this->out->endHTML(); - } -} - -class MailChannel extends Channel -{ - - var $addr = null; - - function source() - { - return 'mail'; - } - - function __construct($addr=null) - { - $this->addr = $addr; - } - - function on($user) - { - return $this->set_notify($user, 1); - } - - function off($user) - { - return $this->set_notify($user, 0); - } - - function output($user, $text) - { - - $headers['From'] = $user->incomingemail; - $headers['To'] = $this->addr; - - $headers['Subject'] = _('Command complete'); - - return mail_send(array($this->addr), $headers, $text); - } - - function error($user, $text) - { - - $headers['From'] = $user->incomingemail; - $headers['To'] = $this->addr; - - $headers['Subject'] = _('Command failed'); - - return mail_send(array($this->addr), $headers, $text); - } - - function set_notify($user, $value) - { - $orig = clone($user); - $user->smsnotify = $value; - $result = $user->update($orig); - if (!$result) { - common_log_db_error($user, 'UPDATE', __FILE__); - return false; - } - return true; - } -} diff --git a/classes/Command.php b/classes/Command.php deleted file mode 100644 index eacbdacb3..000000000 --- a/classes/Command.php +++ /dev/null @@ -1,419 +0,0 @@ -. - */ - -if (!defined('LACONICA')) { exit(1); } - -require_once(INSTALLDIR.'/classes/Channel.php'); - -class Command -{ - - var $user = null; - - function __construct($user=null) - { - $this->user = $user; - } - - function execute($channel) - { - return false; - } -} - -class UnimplementedCommand extends Command -{ - function execute($channel) - { - $channel->error($this->user, _("Sorry, this command is not yet implemented.")); - } -} - -class TrackingCommand extends UnimplementedCommand -{ -} - -class TrackOffCommand extends UnimplementedCommand -{ -} - -class TrackCommand extends UnimplementedCommand -{ - var $word = null; - function __construct($user, $word) - { - parent::__construct($user); - $this->word = $word; - } -} - -class UntrackCommand extends UnimplementedCommand -{ - var $word = null; - function __construct($user, $word) - { - parent::__construct($user); - $this->word = $word; - } -} - -class NudgeCommand extends UnimplementedCommand -{ - var $other = null; - function __construct($user, $other) - { - parent::__construct($user); - $this->other = $other; - } -} - -class InviteCommand extends UnimplementedCommand -{ - var $other = null; - function __construct($user, $other) - { - parent::__construct($user); - $this->other = $other; - } -} - -class StatsCommand extends Command -{ - function execute($channel) - { - - $subs = new Subscription(); - $subs->subscriber = $this->user->id; - $subs_count = (int) $subs->count() - 1; - - $subbed = new Subscription(); - $subbed->subscribed = $this->user->id; - $subbed_count = (int) $subbed->count() - 1; - - $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"), - $subs_count, - $subbed_count, - $notice_count)); - } -} - -class FavCommand extends Command -{ - - var $other = null; - - function __construct($user, $other) - { - parent::__construct($user); - $this->other = $other; - } - - function execute($channel) - { - - $recipient = - common_relative_profile($this->user, common_canonical_nickname($this->other)); - - if (!$recipient) { - $channel->error($this->user, _('No such user.')); - return; - } - $notice = $recipient->getCurrentNotice(); - if (!$notice) { - $channel->error($this->user, _('User has no last notice')); - return; - } - - $fave = Fave::addNew($this->user, $notice); - - if (!$fave) { - $channel->error($this->user, _('Could not create favorite.')); - return; - } - - $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.')); - } -} - -class WhoisCommand extends Command -{ - var $other = null; - function __construct($user, $other) - { - parent::__construct($user); - $this->other = $other; - } - - function execute($channel) - { - $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) { - $whois .= "\n" . sprintf(_('Fullname: %s'), $recipient->fullname); - } - if ($recipient->location) { - $whois .= "\n" . sprintf(_('Location: %s'), $recipient->location); - } - if ($recipient->homepage) { - $whois .= "\n" . sprintf(_('Homepage: %s'), $recipient->homepage); - } - if ($recipient->bio) { - $whois .= "\n" . sprintf(_('About: %s'), $recipient->bio); - } - $channel->output($this->user, $whois); - } -} - -class MessageCommand extends Command -{ - var $other = null; - var $text = null; - function __construct($user, $other, $text) - { - parent::__construct($user); - $this->other = $other; - $this->text = $text; - } - - function execute($channel) - { - $other = User::staticGet('nickname', common_canonical_nickname($this->other)); - $len = mb_strlen($this->text); - if ($len == 0) { - $channel->error($this->user, _('No content!')); - return; - } else if ($len > 140) { - $content = common_shorten_links($content); - if (mb_strlen($content) > 140) { - $channel->error($this->user, sprintf(_('Message too long - maximum is 140 characters, you sent %d'), $len)); - return; - } - } - - if (!$other) { - $channel->error($this->user, _('No such user.')); - return; - } else if (!$this->user->mutuallySubscribed($other)) { - $channel->error($this->user, _('You can\'t send a message to this user.')); - return; - } else if ($this->user->id == $other->id) { - $channel->error($this->user, _('Don\'t send a message to yourself; just say it to yourself quietly instead.')); - return; - } - $message = Message::saveNew($this->user->id, $other->id, $this->text, $channel->source()); - if ($message) { - $channel->output($this->user, sprintf(_('Direct message to %s sent'), $this->other)); - } else { - $channel->error($this->user, _('Error sending direct message.')); - } - } -} - -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); - - if (!$target) { - $channel->error($this->user, _('No such user.')); - return; - } - $notice = $target->getCurrentNotice(); - if (!$notice) { - $channel->error($this->user, _('User has no last notice')); - 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 { - $channel->error($this->user, $result); - } - } -} - -class UnsubCommand 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 unsubscribe from')); - return; - } - - $result=subs_unsubscribe_user($this->user, $this->other); - - if ($result) { - $channel->output($this->user, sprintf(_('Unsubscribed from %s'), $this->other)); - } else { - $channel->error($this->user, $result); - } - } -} - -class OffCommand extends Command -{ - var $other = null; - function __construct($user, $other=null) - { - parent::__construct($user); - $this->other = $other; - } - function execute($channel) - { - if ($other) { - $channel->error($this->user, _("Command not yet implemented.")); - } else { - if ($channel->off($this->user)) { - $channel->output($this->user, _('Notification off.')); - } else { - $channel->error($this->user, _('Can\'t turn off notification.')); - } - } - } -} - -class OnCommand extends Command -{ - var $other = null; - function __construct($user, $other=null) - { - parent::__construct($user); - $this->other = $other; - } - - function execute($channel) - { - if ($other) { - $channel->error($this->user, _("Command not yet implemented.")); - } else { - if ($channel->on($this->user)) { - $channel->output($this->user, _('Notification on.')); - } else { - $channel->error($this->user, _('Can\'t turn on notification.')); - } - } - } -} - -class HelpCommand extends Command -{ - function execute($channel) - { - $channel->output($this->user, - _("Commands:\n". - "on - turn on notifications\n". - "off - turn off notifications\n". - "help - show this help\n". - "follow - subscribe to user\n". - "leave - unsubscribe from user\n". - "d - direct message to user\n". - "get - get last notice from user\n". - "whois - get profile info on user\n". - "fav - add user's last notice as a 'fave'\n". - "stats - get your stats\n". - "stop - same as 'off'\n". - "quit - same as 'off'\n". - "sub - same as 'follow'\n". - "unsub - same as 'leave'\n". - "last - same as 'get'\n". - "on - not yet implemented.\n". - "off - not yet implemented.\n". - "nudge - not yet implemented.\n". - "invite - not yet implemented.\n". - "track - not yet implemented.\n". - "untrack - not yet implemented.\n". - "track off - not yet implemented.\n". - "untrack all - not yet implemented.\n". - "tracks - not yet implemented.\n". - "tracking - not yet implemented.\n")); - } -} diff --git a/classes/CommandInterpreter.php b/classes/CommandInterpreter.php deleted file mode 100644 index 0679f5462..000000000 --- a/classes/CommandInterpreter.php +++ /dev/null @@ -1,198 +0,0 @@ -. - */ - -if (!defined('LACONICA')) { exit(1); } - -require_once(INSTALLDIR.'/classes/Command.php'); - -class CommandInterpreter -{ - - function handle_command($user, $text) - { - # XXX: localise - - $text = preg_replace('/\s+/', ' ', trim($text)); - list($cmd, $arg) = explode(' ', $text, 2); - - # We try to support all the same commands as Twitter, see - # http://getsatisfaction.com/twitter/topics/what_are_the_twitter_commands - # There are a few compatibility commands from earlier versions of - # Laconica - - switch(strtolower($cmd)) { - case 'help': - if ($arg) { - return null; - } - return new HelpCommand($user); - case 'on': - if ($arg) { - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return null; - } else { - return new OnCommand($user, $other); - } - } else { - return new OnCommand($user); - } - case 'off': - if ($arg) { - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return null; - } else { - return new OffCommand($user, $other); - } - } else { - return new OffCommand($user); - } - case 'stop': - case 'quit': - if ($arg) { - return null; - } else { - return new OffCommand($user); - } - case 'follow': - case 'sub': - if (!$arg) { - return null; - } - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return null; - } else { - return new SubCommand($user, $other); - } - case 'leave': - case 'unsub': - if (!$arg) { - return null; - } - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return null; - } else { - return new UnsubCommand($user, $other); - } - case 'get': - case 'last': - if (!$arg) { - return null; - } - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return null; - } else { - return new GetCommand($user, $other); - } - case 'd': - case 'dm': - if (!$arg) { - return null; - } - list($other, $extra) = explode(' ', $arg, 2); - if (!$extra) { - return null; - } else { - return new MessageCommand($user, $other, $extra); - } - case 'whois': - if (!$arg) { - return null; - } - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return null; - } else { - return new WhoisCommand($user, $other); - } - case 'fav': - if (!$arg) { - return null; - } - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return null; - } else { - return new FavCommand($user, $other); - } - case 'nudge': - if (!$arg) { - return null; - } - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return null; - } else { - return new NudgeCommand($user, $other); - } - case 'stats': - if ($arg) { - return null; - } - return new StatsCommand($user); - case 'invite': - if (!$arg) { - return null; - } - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return null; - } else { - return new InviteCommand($user, $other); - } - case 'track': - if (!$arg) { - return null; - } - list($word, $extra) = explode(' ', $arg, 2); - if ($extra) { - return null; - } else if ($word == 'off') { - return new TrackOffCommand($user); - } else { - return new TrackCommand($user, $word); - } - case 'untrack': - if (!$arg) { - return null; - } - list($word, $extra) = explode(' ', $arg, 2); - if ($extra) { - return null; - } else if ($word == 'all') { - return new TrackOffCommand($user); - } else { - return new UntrackCommand($user, $word); - } - case 'tracks': - case 'tracking': - if ($arg) { - return null; - } - return new TrackingCommand($user); - default: - return false; - } - } -} - diff --git a/lib/channel.php b/lib/channel.php new file mode 100644 index 000000000..fdeff21fc --- /dev/null +++ b/lib/channel.php @@ -0,0 +1,238 @@ +. + */ + +if (!defined('LACONICA')) { exit(1); } + +class Channel +{ + + function on($user) + { + return false; + } + + function off($user) + { + return false; + } + + function output($user, $text) + { + return false; + } + + function error($user, $text) + { + return false; + } + + function source() + { + return null; + } +} + +class XMPPChannel extends Channel +{ + + var $conn = null; + + function source() + { + return 'xmpp'; + } + + function __construct($conn) + { + $this->conn = $conn; + } + + function on($user) + { + return $this->set_notify($user, 1); + } + + function off($user) + { + return $this->set_notify($user, 0); + } + + function output($user, $text) + { + $text = '['.common_config('site', 'name') . '] ' . $text; + jabber_send_message($user->jabber, $text); + } + + function error($user, $text) + { + $text = '['.common_config('site', 'name') . '] ' . $text; + jabber_send_message($user->jabber, $text); + } + + function set_notify(&$user, $notify) + { + $orig = clone($user); + $user->jabbernotify = $notify; + $result = $user->update($orig); + if (!$result) { + $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError'); + common_log(LOG_ERR, + 'Could not set notify flag to ' . $notify . + ' for user ' . common_log_objstring($user) . + ': ' . $last_error->message); + return false; + } else { + common_log(LOG_INFO, + 'User ' . $user->nickname . ' set notify flag to ' . $notify); + return true; + } + } +} + +class WebChannel extends Channel +{ + var $out = null; + + function __construct($out=null) + { + $this->out = $out; + } + + function source() + { + return 'web'; + } + + function on($user) + { + return false; + } + + function off($user) + { + return false; + } + + function output($user, $text) + { + # XXX: buffer all output and send it at the end + # XXX: even better, redirect to appropriate page + # depending on what command was run + $this->out->startHTML(); + $this->out->elementStart('head'); + $this->out->element('title', null, _('Command results')); + $this->out->elementEnd('head'); + $this->out->elementStart('body'); + $this->out->element('p', array('id' => 'command_result'), $text); + $this->out->elementEnd('body'); + $this->out->endHTML(); + } + + function error($user, $text) + { + common_user_error($text); + } +} + +class AjaxWebChannel extends WebChannel +{ + function output($user, $text) + { + $this->out->startHTML('text/xml;charset=utf-8'); + $this->out->elementStart('head'); + $this->out->element('title', null, _('Command results')); + $this->out->elementEnd('head'); + $this->out->elementStart('body'); + $this->out->element('p', array('id' => 'command_result'), $text); + $this->out->elementEnd('body'); + $this->out->endHTML(); + } + + function error($user, $text) + { + $this->out->startHTML('text/xml;charset=utf-8'); + $this->out->elementStart('head'); + $this->out->element('title', null, _('Ajax Error')); + $this->out->elementEnd('head'); + $this->out->elementStart('body'); + $this->out->element('p', array('id' => 'error'), $text); + $this->out->elementEnd('body'); + $this->out->endHTML(); + } +} + +class MailChannel extends Channel +{ + + var $addr = null; + + function source() + { + return 'mail'; + } + + function __construct($addr=null) + { + $this->addr = $addr; + } + + function on($user) + { + return $this->set_notify($user, 1); + } + + function off($user) + { + return $this->set_notify($user, 0); + } + + function output($user, $text) + { + + $headers['From'] = $user->incomingemail; + $headers['To'] = $this->addr; + + $headers['Subject'] = _('Command complete'); + + return mail_send(array($this->addr), $headers, $text); + } + + function error($user, $text) + { + + $headers['From'] = $user->incomingemail; + $headers['To'] = $this->addr; + + $headers['Subject'] = _('Command failed'); + + return mail_send(array($this->addr), $headers, $text); + } + + function set_notify($user, $value) + { + $orig = clone($user); + $user->smsnotify = $value; + $result = $user->update($orig); + if (!$result) { + common_log_db_error($user, 'UPDATE', __FILE__); + return false; + } + return true; + } +} diff --git a/lib/command.php b/lib/command.php new file mode 100644 index 000000000..eacbdacb3 --- /dev/null +++ b/lib/command.php @@ -0,0 +1,419 @@ +. + */ + +if (!defined('LACONICA')) { exit(1); } + +require_once(INSTALLDIR.'/classes/Channel.php'); + +class Command +{ + + var $user = null; + + function __construct($user=null) + { + $this->user = $user; + } + + function execute($channel) + { + return false; + } +} + +class UnimplementedCommand extends Command +{ + function execute($channel) + { + $channel->error($this->user, _("Sorry, this command is not yet implemented.")); + } +} + +class TrackingCommand extends UnimplementedCommand +{ +} + +class TrackOffCommand extends UnimplementedCommand +{ +} + +class TrackCommand extends UnimplementedCommand +{ + var $word = null; + function __construct($user, $word) + { + parent::__construct($user); + $this->word = $word; + } +} + +class UntrackCommand extends UnimplementedCommand +{ + var $word = null; + function __construct($user, $word) + { + parent::__construct($user); + $this->word = $word; + } +} + +class NudgeCommand extends UnimplementedCommand +{ + var $other = null; + function __construct($user, $other) + { + parent::__construct($user); + $this->other = $other; + } +} + +class InviteCommand extends UnimplementedCommand +{ + var $other = null; + function __construct($user, $other) + { + parent::__construct($user); + $this->other = $other; + } +} + +class StatsCommand extends Command +{ + function execute($channel) + { + + $subs = new Subscription(); + $subs->subscriber = $this->user->id; + $subs_count = (int) $subs->count() - 1; + + $subbed = new Subscription(); + $subbed->subscribed = $this->user->id; + $subbed_count = (int) $subbed->count() - 1; + + $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"), + $subs_count, + $subbed_count, + $notice_count)); + } +} + +class FavCommand extends Command +{ + + var $other = null; + + function __construct($user, $other) + { + parent::__construct($user); + $this->other = $other; + } + + function execute($channel) + { + + $recipient = + common_relative_profile($this->user, common_canonical_nickname($this->other)); + + if (!$recipient) { + $channel->error($this->user, _('No such user.')); + return; + } + $notice = $recipient->getCurrentNotice(); + if (!$notice) { + $channel->error($this->user, _('User has no last notice')); + return; + } + + $fave = Fave::addNew($this->user, $notice); + + if (!$fave) { + $channel->error($this->user, _('Could not create favorite.')); + return; + } + + $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.')); + } +} + +class WhoisCommand extends Command +{ + var $other = null; + function __construct($user, $other) + { + parent::__construct($user); + $this->other = $other; + } + + function execute($channel) + { + $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) { + $whois .= "\n" . sprintf(_('Fullname: %s'), $recipient->fullname); + } + if ($recipient->location) { + $whois .= "\n" . sprintf(_('Location: %s'), $recipient->location); + } + if ($recipient->homepage) { + $whois .= "\n" . sprintf(_('Homepage: %s'), $recipient->homepage); + } + if ($recipient->bio) { + $whois .= "\n" . sprintf(_('About: %s'), $recipient->bio); + } + $channel->output($this->user, $whois); + } +} + +class MessageCommand extends Command +{ + var $other = null; + var $text = null; + function __construct($user, $other, $text) + { + parent::__construct($user); + $this->other = $other; + $this->text = $text; + } + + function execute($channel) + { + $other = User::staticGet('nickname', common_canonical_nickname($this->other)); + $len = mb_strlen($this->text); + if ($len == 0) { + $channel->error($this->user, _('No content!')); + return; + } else if ($len > 140) { + $content = common_shorten_links($content); + if (mb_strlen($content) > 140) { + $channel->error($this->user, sprintf(_('Message too long - maximum is 140 characters, you sent %d'), $len)); + return; + } + } + + if (!$other) { + $channel->error($this->user, _('No such user.')); + return; + } else if (!$this->user->mutuallySubscribed($other)) { + $channel->error($this->user, _('You can\'t send a message to this user.')); + return; + } else if ($this->user->id == $other->id) { + $channel->error($this->user, _('Don\'t send a message to yourself; just say it to yourself quietly instead.')); + return; + } + $message = Message::saveNew($this->user->id, $other->id, $this->text, $channel->source()); + if ($message) { + $channel->output($this->user, sprintf(_('Direct message to %s sent'), $this->other)); + } else { + $channel->error($this->user, _('Error sending direct message.')); + } + } +} + +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); + + if (!$target) { + $channel->error($this->user, _('No such user.')); + return; + } + $notice = $target->getCurrentNotice(); + if (!$notice) { + $channel->error($this->user, _('User has no last notice')); + 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 { + $channel->error($this->user, $result); + } + } +} + +class UnsubCommand 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 unsubscribe from')); + return; + } + + $result=subs_unsubscribe_user($this->user, $this->other); + + if ($result) { + $channel->output($this->user, sprintf(_('Unsubscribed from %s'), $this->other)); + } else { + $channel->error($this->user, $result); + } + } +} + +class OffCommand extends Command +{ + var $other = null; + function __construct($user, $other=null) + { + parent::__construct($user); + $this->other = $other; + } + function execute($channel) + { + if ($other) { + $channel->error($this->user, _("Command not yet implemented.")); + } else { + if ($channel->off($this->user)) { + $channel->output($this->user, _('Notification off.')); + } else { + $channel->error($this->user, _('Can\'t turn off notification.')); + } + } + } +} + +class OnCommand extends Command +{ + var $other = null; + function __construct($user, $other=null) + { + parent::__construct($user); + $this->other = $other; + } + + function execute($channel) + { + if ($other) { + $channel->error($this->user, _("Command not yet implemented.")); + } else { + if ($channel->on($this->user)) { + $channel->output($this->user, _('Notification on.')); + } else { + $channel->error($this->user, _('Can\'t turn on notification.')); + } + } + } +} + +class HelpCommand extends Command +{ + function execute($channel) + { + $channel->output($this->user, + _("Commands:\n". + "on - turn on notifications\n". + "off - turn off notifications\n". + "help - show this help\n". + "follow - subscribe to user\n". + "leave - unsubscribe from user\n". + "d - direct message to user\n". + "get - get last notice from user\n". + "whois - get profile info on user\n". + "fav - add user's last notice as a 'fave'\n". + "stats - get your stats\n". + "stop - same as 'off'\n". + "quit - same as 'off'\n". + "sub - same as 'follow'\n". + "unsub - same as 'leave'\n". + "last - same as 'get'\n". + "on - not yet implemented.\n". + "off - not yet implemented.\n". + "nudge - not yet implemented.\n". + "invite - not yet implemented.\n". + "track - not yet implemented.\n". + "untrack - not yet implemented.\n". + "track off - not yet implemented.\n". + "untrack all - not yet implemented.\n". + "tracks - not yet implemented.\n". + "tracking - not yet implemented.\n")); + } +} diff --git a/lib/commandinterpreter.php b/lib/commandinterpreter.php new file mode 100644 index 000000000..0679f5462 --- /dev/null +++ b/lib/commandinterpreter.php @@ -0,0 +1,198 @@ +. + */ + +if (!defined('LACONICA')) { exit(1); } + +require_once(INSTALLDIR.'/classes/Command.php'); + +class CommandInterpreter +{ + + function handle_command($user, $text) + { + # XXX: localise + + $text = preg_replace('/\s+/', ' ', trim($text)); + list($cmd, $arg) = explode(' ', $text, 2); + + # We try to support all the same commands as Twitter, see + # http://getsatisfaction.com/twitter/topics/what_are_the_twitter_commands + # There are a few compatibility commands from earlier versions of + # Laconica + + switch(strtolower($cmd)) { + case 'help': + if ($arg) { + return null; + } + return new HelpCommand($user); + case 'on': + if ($arg) { + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new OnCommand($user, $other); + } + } else { + return new OnCommand($user); + } + case 'off': + if ($arg) { + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new OffCommand($user, $other); + } + } else { + return new OffCommand($user); + } + case 'stop': + case 'quit': + if ($arg) { + return null; + } else { + return new OffCommand($user); + } + case 'follow': + case 'sub': + if (!$arg) { + return null; + } + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new SubCommand($user, $other); + } + case 'leave': + case 'unsub': + if (!$arg) { + return null; + } + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new UnsubCommand($user, $other); + } + case 'get': + case 'last': + if (!$arg) { + return null; + } + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new GetCommand($user, $other); + } + case 'd': + case 'dm': + if (!$arg) { + return null; + } + list($other, $extra) = explode(' ', $arg, 2); + if (!$extra) { + return null; + } else { + return new MessageCommand($user, $other, $extra); + } + case 'whois': + if (!$arg) { + return null; + } + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new WhoisCommand($user, $other); + } + case 'fav': + if (!$arg) { + return null; + } + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new FavCommand($user, $other); + } + case 'nudge': + if (!$arg) { + return null; + } + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new NudgeCommand($user, $other); + } + case 'stats': + if ($arg) { + return null; + } + return new StatsCommand($user); + case 'invite': + if (!$arg) { + return null; + } + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new InviteCommand($user, $other); + } + case 'track': + if (!$arg) { + return null; + } + list($word, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else if ($word == 'off') { + return new TrackOffCommand($user); + } else { + return new TrackCommand($user, $word); + } + case 'untrack': + if (!$arg) { + return null; + } + list($word, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else if ($word == 'all') { + return new TrackOffCommand($user); + } else { + return new UntrackCommand($user, $word); + } + case 'tracks': + case 'tracking': + if ($arg) { + return null; + } + return new TrackingCommand($user); + default: + return false; + } + } +} + -- cgit v1.2.3-54-g00ecf From 9d07032334043625a5aa3243d911bdc1c77a7a9c Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 11 Feb 2009 15:48:30 -0500 Subject: 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 fdeff21fc..f1e205546 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 eacbdacb3..507990a0b 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 0679f5462..49c733c03 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 -- cgit v1.2.3-54-g00ecf From 3c8c0572efef97c25037545e1e971f0ce3cd7cc8 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 11 Feb 2009 23:03:12 +0000 Subject: 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 3b72d00ce..1ac63927d 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; -- cgit v1.2.3-54-g00ecf From 7155cf813de1502e796c0eac1e680289f3552c29 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 11 Feb 2009 17:46:53 -0800 Subject: 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 2d41469bb..a79859bbf 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 197298549..72212c185 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 c0c980111..3f4fae3a5 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; } -- cgit v1.2.3-54-g00ecf From 9f035e2847e0d119ca3d70e02df6f4fa73ca64c3 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 11 Feb 2009 21:41:56 -0800 Subject: 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 b84acb214..c6c983453 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 5d7a8ce69..853bd0cf6 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 bd38bf79c..e3a8ef62c 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 * -- cgit v1.2.3-54-g00ecf From 616bdd43a921b2554d21b80af28ddb0fb6cb3c16 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 11 Feb 2009 22:08:20 -0800 Subject: 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 c6c983453..0a86aa66d 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 853bd0cf6..aafb54ebb 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 e3a8ef62c..926fe93fb 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); } /** -- cgit v1.2.3-54-g00ecf From a845d06c77f17a5037e30d41db939f0ed3b5936e Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 06:49:34 -0500 Subject: 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 4db0e7555..b180e2b65 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. '. -- cgit v1.2.3-54-g00ecf From 99773e3b5ef0fa395bccb9a9656afe7552d42594 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 07:58:36 -0500 Subject: 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 c0c980111..03e3618db 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 -- cgit v1.2.3-54-g00ecf From eaae4562228bcbf780329d7bbb5f35437b31ebe3 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 08:38:43 -0500 Subject: 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 03e3618db..a6f6a9351 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 000000000..951b13b67 --- /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 -- cgit v1.2.3-54-g00ecf From ab8d27b8d1e25bd1058b904d03fedf97148f4b89 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 09:22:45 -0500 Subject: 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 a6f6a9351..37c941cdb 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; } -- cgit v1.2.3-54-g00ecf From 511864369511b2e749d168d717d84237a13a201f Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 13:58:05 -0500 Subject: move carrier.sql to sms_carrier.sql --- db/carrier.sql | 61 ------------------------------------------------------ db/sms_carrier.sql | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 61 deletions(-) delete mode 100644 db/carrier.sql create mode 100644 db/sms_carrier.sql diff --git a/db/carrier.sql b/db/carrier.sql deleted file mode 100644 index 932f7c8bb..000000000 --- a/db/carrier.sql +++ /dev/null @@ -1,61 +0,0 @@ -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()); diff --git a/db/sms_carrier.sql b/db/sms_carrier.sql new file mode 100644 index 000000000..932f7c8bb --- /dev/null +++ b/db/sms_carrier.sql @@ -0,0 +1,61 @@ +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()); -- cgit v1.2.3-54-g00ecf From 654a91cc1c99dcb556bce240e03dbfa1689d0d70 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 14:03:15 -0500 Subject: 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 932f7c8bb..6879f2089 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()); -- cgit v1.2.3-54-g00ecf From e686ef042b0f4b3cd8243fa9536092039798837b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 14:16:58 -0500 Subject: 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 255122a97..19267f268 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 16f482134..15f03a978 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', -- cgit v1.2.3-54-g00ecf From b09eb06dae77be27e56d1573de0de7d393950ce5 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 16:04:43 -0500 Subject: 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 0a86aa66d..c6c983453 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 aafb54ebb..853bd0cf6 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 926fe93fb..e3a8ef62c 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 } /** -- cgit v1.2.3-54-g00ecf From 3b5fd8fb6bbfa95efe3294ea77ae809dab071f99 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 12 Feb 2009 16:05:37 -0500 Subject: 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 c6c983453..b84acb214 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 853bd0cf6..5d7a8ce69 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 e3a8ef62c..bd38bf79c 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 * -- cgit v1.2.3-54-g00ecf From dac8d103e69deb63c7eafe16286f23a66034d967 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 12 Feb 2009 22:16:48 +0000 Subject: 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 d9634325d..aed3dd9c5 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 bd38bf79c..602118cdf 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'); } -- cgit v1.2.3-54-g00ecf From f8e2ad0677d6eede93e3008452e36985ea999d74 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 12 Feb 2009 14:39:21 -0800 Subject: 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 72212c185..deb6fd276 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 3f4fae3a5..7923fe28a 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 685bd938f..269036393 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 08e1d4714..b69d296d3 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 000000000..7da4f1e20 --- /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(); -- cgit v1.2.3-54-g00ecf From 070d1a3f247dd20e855bda1486121545ec667e9a Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 13 Feb 2009 04:33:43 +0000 Subject: 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 aed3dd9c5..16ca9ea06 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 602118cdf..cd0db5399 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: 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 beab51366..e2ad20d19 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 031fd0da0..3eeb9fde3 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 269036393..a3256966d 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 b69d296d3..fd4406d41 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 60e10417f..000000000 --- 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); -} - -- cgit v1.2.3-54-g00ecf From 47c5d508b36d4351f2bd94e7b36738fdf040b0aa Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 13 Feb 2009 10:47:22 -0500 Subject: 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 3eeb9fde3..b065c2d74 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; } -- cgit v1.2.3-54-g00ecf From 4ad5d55ecf65fb1c9f58211b37f8f111e9ca0c7b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 13 Feb 2009 10:52:26 -0500 Subject: Add events for filtering and logging new notices --- EVENTS.txt | 8 +++++++- actions/newnotice.php | 17 ++++++++++------- classes/Notice.php | 43 ++++++++++++++++++++++++------------------- 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index 16ca9ea06..af0bee587 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 5e7691f33..9face9644 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 329988368..6db59c96e 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -154,32 +154,37 @@ 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.'); - } - - # Update the URI after the notice is in the database - if (!$uri) { - $orig = clone($notice); - $notice->uri = common_notice_uri($notice); + $id = $notice->insert(); - if (!$notice->update($orig)) { - common_log_db_error($notice, 'UPDATE', __FILE__); + if (!$id) { + common_log_db_error($notice, 'INSERT', __FILE__); return _('Problem saving notice.'); } - } - # XXX: do we need to change this for remote users? + # 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->saveReplies(); + $notice->saveTags(); + $notice->saveGroups(); - $notice->addToInboxes(); - $notice->query('COMMIT'); + $notice->addToInboxes(); + $notice->query('COMMIT'); + + Event::handle('EndNoticeSave', array($notice)); + } # Clear the cache for subscribed users, so they'll update at next request # XXX: someone clever could prepend instead of clearing the cache -- cgit v1.2.3-54-g00ecf From 02184df7c8fd40ee9d1b134fd318e0c6f444ae01 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 13 Feb 2009 10:08:11 -0800 Subject: 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 e2ad20d19..ec3987273 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 b385d3c5c..3fbb3e1ab 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; } -- cgit v1.2.3-54-g00ecf From b9fc7334a8ecfbe1c0ec6c3d5451aa82827b50a1 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 14 Feb 2009 17:48:08 -0500 Subject: 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 5734d8001..5380563b9 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 0c32ddcf8..d14575086 100644 --- a/lib/section.php +++ b/lib/section.php @@ -103,6 +103,6 @@ class Section extends Widget function moreTitle() { - return null; + return _('More...'); } } -- cgit v1.2.3-54-g00ecf From 3db9c134a052b906c8c9cff946e63ee28457ce1b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 14 Feb 2009 17:53:11 -0500 Subject: 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 2935d8363..aed94b1a5 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'); + } } -- cgit v1.2.3-54-g00ecf From da2348fbbe27facf4cfbf3fad800b3ccf98b4136 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 14 Feb 2009 21:55:25 -0500 Subject: 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 6db59c96e..b8cd2bd7f 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 a2c5801f4..3fa898e1b 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 7bfd14c42..4fc749ca0 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' => -- cgit v1.2.3-54-g00ecf From ed964ea980be63999860aaa5f39274768eca5d59 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 14 Feb 2009 21:55:25 -0500 Subject: 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 329988368..cc7de63c2 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 db1a21663..efb5aa443 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 5b4e3c40c..bf49fff6b 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' => -- cgit v1.2.3-54-g00ecf From faf82eebfebf24c7a8aa62b517e15c0a1ad71954 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 15 Feb 2009 22:30:39 +0000 Subject: 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 335801385..4998b3c98 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'; -- cgit v1.2.3-54-g00ecf From 9d81cef5cc2a0a197a0223206ba3d9a687065886 Mon Sep 17 00:00:00 2001 From: Meitar Moscovitz Date: Mon, 16 Feb 2009 15:45:18 +1100 Subject: 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 cd0db5399..48d5821a1 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)); -- cgit v1.2.3-54-g00ecf From 9c9b6790ce78296c0b182f03b5f6f2c035e43a7c Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Mon, 16 Feb 2009 17:46:24 +0000 Subject: 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 000000000..05b64a032 Binary files /dev/null and b/bin/flowplayer-3.0.5.swf differ diff --git a/bin/flowplayer.audio-3.0.3.swf b/bin/flowplayer.audio-3.0.3.swf new file mode 100644 index 000000000..ef85f1bff Binary files /dev/null and b/bin/flowplayer.audio-3.0.3.swf differ diff --git a/bin/flowplayer.controls-3.0.3.swf b/bin/flowplayer.controls-3.0.3.swf new file mode 100644 index 000000000..09a27e8a9 Binary files /dev/null and b/bin/flowplayer.controls-3.0.3.swf differ diff --git a/js/flowplayer-3.0.5.min.js b/js/flowplayer-3.0.5.min.js new file mode 100644 index 000000000..b1c33150a --- /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")+"

"+"

Download latest version from here

";}}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 000000000..b5ad5c23a --- /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 000000000..936a6312e --- /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 cd0db5399..79e8c9547 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 b065c2d74..094b2750c 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 000000000..985e4adfa --- /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 000000000..eab4637c0 --- /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 000000000..c11f7af69 Binary files /dev/null and b/theme/base/images/x.png differ -- cgit v1.2.3-54-g00ecf From 1af6423738ab702b39f81746e0ff2aeeacb4fea4 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 16 Feb 2009 18:55:03 +0000 Subject: 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 000000000..c6859cb21 --- /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(); -- cgit v1.2.3-54-g00ecf From cc38809764c63dbed24300cdcf68d9f667f0dfad Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Feb 2009 16:34:07 -0500 Subject: 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 000000000..d9372bcd5 --- /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; + } +} -- cgit v1.2.3-54-g00ecf From 6440f4cecdf507d49db015bbf4303e50eaadbe13 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 16 Feb 2009 22:39:57 +0000 Subject: 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 1ac63927d..5ce5ac884 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; } -- cgit v1.2.3-54-g00ecf From c8e71d359cee093dcb15c1d32684d90f81e10204 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Feb 2009 18:02:04 -0500 Subject: 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 094b2750c..46aa7b9df 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(); } -- cgit v1.2.3-54-g00ecf From ca90d790aa9d93a4e88602330f91d4703c29b75d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Feb 2009 18:02:31 -0500 Subject: 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 b8cd2bd7f..8e08ad503 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 94f9296d6..0365973f5 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); + } } -- cgit v1.2.3-54-g00ecf From affb2f9359ee498c2c20240b592a0f62a770e8c8 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Feb 2009 18:24:43 -0500 Subject: 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 a6a1b11b9..495a98236 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 19267f268..5fd2cd1f8 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 15f03a978..dd93a727b 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', -- cgit v1.2.3-54-g00ecf From 175c4665cc33c07bbbc42bbf96dd07cf90ba7bbb Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Feb 2009 18:26:15 -0500 Subject: 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 8e08ad503..570d76f61 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 a1faefc80..9fa86de5c 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); +} -- cgit v1.2.3-54-g00ecf From b727e8be33f5f5aaf0d319cdd11d422556e6167a Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Feb 2009 18:45:05 -0500 Subject: 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 b84acb214..634388fdd 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; -- cgit v1.2.3-54-g00ecf From 419960fd19c21007958973af5151a84e00b164f8 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Feb 2009 18:45:59 -0500 Subject: 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 570d76f61..8300667fa 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); } } } -- cgit v1.2.3-54-g00ecf From ab3c5d6f2d93843c199ebc4183f9ba307db1377d Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 16 Feb 2009 17:58:24 -0800 Subject: 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 989fcb7f0..67dc9a66b 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 3fa898e1b..da3154507 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'; -- cgit v1.2.3-54-g00ecf From 4327ae19d49384ca2f4b65b04926fb2fbea7d28d Mon Sep 17 00:00:00 2001 From: Meitar Moscovitz Date: Tue, 17 Feb 2009 18:37:02 +1100 Subject: 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 c736c99b5..65482167e 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'); -- cgit v1.2.3-54-g00ecf From 75ebd45ebdcf26849e47d3c11a2cc2aa6e946c01 Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Mon, 9 Feb 2009 17:29:27 -0500 Subject: 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 7ce4e229e..5204693bc 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); -- cgit v1.2.3-54-g00ecf From 7bcfc9f6434e903936cbd6d47a2c25081f75eb04 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 17 Feb 2009 12:08:53 -0500 Subject: another system message --- scripts/xmppdaemon.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/xmppdaemon.php b/scripts/xmppdaemon.php index 01fe8914f..ef3f8c63d 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; } -- cgit v1.2.3-54-g00ecf From d8b6762e6dcb776571797f51d7c7d4cb99c9ef31 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 18 Feb 2009 09:35:59 -0500 Subject: 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 615201c46..14177fcf0 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 000000000..f8ab7cf3b --- /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; + } +} + -- cgit v1.2.3-54-g00ecf From 28e1c163e3e14b646851d7641c1c8a6a00de8fdc Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 18 Feb 2009 15:33:52 -0800 Subject: Open the /api/laconica/config.format API method so clients can determine whether a site is "private". --- actions/api.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/actions/api.php b/actions/api.php index 21fe4eea3..a27d24492 100644 --- a/actions/api.php +++ b/actions/api.php @@ -131,14 +131,14 @@ class ApiAction extends Action 'statuses/followers', 'favorites/favorites'); - # If the site is "private", all API methods need authentication - + $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 true; + return $fullname != 'laconica/config' || false; } - $fullname = "$this->api_action/$this->api_method"; - if (in_array($fullname, $bareauth)) { # bareauth: only needs auth if without an argument if ($this->api_arg) { -- cgit v1.2.3-54-g00ecf From c9def4a8768239093823fbe34367d97f9e30d320 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 18 Feb 2009 23:43:26 +0000 Subject: 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 e2d09ace2..b1e700b67 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); } /** -- cgit v1.2.3-54-g00ecf From 8fc7f5204e7ed07b9450b7650bce6317ea8e79ae Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 19 Feb 2009 02:55:04 +0000 Subject: 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 5ce5ac884..b5796374e 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; } -- cgit v1.2.3-54-g00ecf From 1abeaf931e2e22806cbf747690df3a3350b401f8 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 14:58:38 +0000 Subject: 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 131e8ac65..66c2d9e8c 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); + } } -- cgit v1.2.3-54-g00ecf From 5ec5a22dc75cb8f4d896b41ca24067c68d3e1de6 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 10:04:28 -0500 Subject: 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 79151c911..0d18945a0 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 d47ad7118..e55b597f2 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) { -- cgit v1.2.3-54-g00ecf From 4da4c94678fecb0fbf06eaba0b71d3206eb89377 Mon Sep 17 00:00:00 2001 From: Leslie Michael Orchard Date: Tue, 17 Feb 2009 23:24:10 -0500 Subject: Ignoring VIM swap files, log files, and httpd.conf --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index f2e96d3eb..83a53dfa3 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 -- cgit v1.2.3-54-g00ecf From 17a6e6603058d412f3c3a7c6800f5d47fcf0def0 Mon Sep 17 00:00:00 2001 From: Leslie Michael Orchard Date: Tue, 17 Feb 2009 23:25:00 -0500 Subject: 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 8bef49dce..c2040fbc2 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 * -- cgit v1.2.3-54-g00ecf From 9a0e71f9bf3329004949a0cef61abb5a354c7688 Mon Sep 17 00:00:00 2001 From: Leslie Michael Orchard Date: Tue, 17 Feb 2009 23:22:56 -0500 Subject: 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 1e7b73a7f..6d92cb9aa 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 495a98236..40cf18df6 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(); } -- cgit v1.2.3-54-g00ecf From 5bb32ccfd0d7bb813c8b35cfb0411625418c5402 Mon Sep 17 00:00:00 2001 From: Leslie Michael Orchard Date: Tue, 17 Feb 2009 23:09:57 -0500 Subject: 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 860573702..5c3d460da 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; -- cgit v1.2.3-54-g00ecf From 76d506cf1644390a073e5178774675c60e1c3332 Mon Sep 17 00:00:00 2001 From: Leslie Michael Orchard Date: Tue, 17 Feb 2009 22:59:48 -0500 Subject: 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 5380563b9..c7c7f0215 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 * -- cgit v1.2.3-54-g00ecf From 5c59c0d90c806636fc07ee9f2445407ad277de59 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 16:44:56 -0500 Subject: 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 b180e2b65..a8890e9a3 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; } } -- cgit v1.2.3-54-g00ecf From 424388611a347eeecd35be3a22984826dd2a503e Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 16:46:26 -0500 Subject: 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 a8890e9a3..914ba5bde 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']); -- cgit v1.2.3-54-g00ecf From 85eb53247d7c0eac767de9fd79ba70183b7f57be Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 16:48:49 -0500 Subject: 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 e55b597f2..0640a5911 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() -- cgit v1.2.3-54-g00ecf From a76099c59b616004886e3c7add06db1de53e4acf Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 16:50:05 -0500 Subject: 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 97b517529..b31f18744 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'); -- cgit v1.2.3-54-g00ecf From d5bf7e5cfb4b4de335cafec69d93c565e0c9d2f4 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 16:51:39 -0500 Subject: 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 fdfb8dc5a..df6876445 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(); -- cgit v1.2.3-54-g00ecf From 4aa9b95f51216edaffcd229d9c31a5f905a76b13 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 16:58:19 -0500 Subject: 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 71e467929..b049791fb 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; } -- cgit v1.2.3-54-g00ecf From 5845f19b051d440125f4921ad0a15184a993d287 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 17:02:34 -0500 Subject: 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 f2dbef5ba..29e14c75f 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; -- cgit v1.2.3-54-g00ecf From 12636d9fb1c8d6bdcf27e068f3be2ad9c9636ee1 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 17:14:13 -0500 Subject: 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 1a7b858b1..de76a5960 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); - } } -- cgit v1.2.3-54-g00ecf From f39dd40ffa9ae9e52fb0e83c2dfaae5f65ca5f80 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 17:29:40 -0500 Subject: 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 6801ab426..1b8547499 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(); } -- cgit v1.2.3-54-g00ecf From 3a999af4d905d3cd23ea9163f47b6ed5c35f606c Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 20 Feb 2009 17:30:09 -0500 Subject: 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 18e24c0f5..51c256589 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 04855ccca..a3e5a3aab 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 0640a5911..85425bed2 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 46aa7b9df..5345a08bb 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')) { -- cgit v1.2.3-54-g00ecf From 5e816d7be208fc24419288234559c78da7391c8b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 20 Feb 2009 15:07:59 -0800 Subject: 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 d47ad7118..9d0d3a3f0 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) -- cgit v1.2.3-54-g00ecf From f75c2328ccb87650e107e90b89b4d2a16d7a29cd Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 20 Feb 2009 23:47:24 +0000 Subject: 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 af0bee587..37e2203d5 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 b1e700b67..a468c638c 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)); -- cgit v1.2.3-54-g00ecf From 85694e3fa6669e3d59155e32f2cc2f8df3b9f89c Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 20 Feb 2009 23:54:17 +0000 Subject: 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 b5796374e..be124f433 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; -- cgit v1.2.3-54-g00ecf From 1fdb35bbf105fe462dcc663ea20b6aa56d654001 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 20 Feb 2009 17:17:20 -0800 Subject: 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 000000000..1c368eb69 --- /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 5c586b5d6..869230b7a 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 b1e700b67..0c4d0181d 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 51a9bbd75..39eb859bb 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( -- cgit v1.2.3-54-g00ecf From cdab8d55a96b61ce6cfbec697d95e3223751fd3f Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sun, 22 Feb 2009 17:01:41 -0800 Subject: 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 b7c09cc9d..c19cd370d 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) { -- cgit v1.2.3-54-g00ecf From 5e646ead492fc62b52f67af6c3a23295ef502345 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sun, 22 Feb 2009 18:01:55 -0800 Subject: 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 e842604e9..a41d35f22 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 -- cgit v1.2.3-54-g00ecf From cab322d21b8c8077192a1396bf13050d734c2aba Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sun, 22 Feb 2009 20:04:47 -0800 Subject: Ticket #1108 - Added 'social graph' methods to the API --- actions/twitapistatuses.php | 52 +++++++++++++++++++++++++++++++++++++++------ lib/router.php | 27 +++++++++++++++++++++++ 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/actions/twitapistatuses.php b/actions/twitapistatuses.php index 51c256589..216835026 100644 --- a/actions/twitapistatuses.php +++ b/actions/twitapistatuses.php @@ -470,19 +470,28 @@ class TwitapistatusesAction extends TwitterapiAction return $this->subscriptions($apidata, 'subscribed', 'subscriber'); } - function followers($args, $apidata) + 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); + } - # XXX: lite + function subscriptions($apidata, $other_attr, $user_attr, $onlyIDs=false) + { - $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 a41d35f22..b18a5523e 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', -- cgit v1.2.3-54-g00ecf From 8c3fe83c645265a11a3fca80cac2ac48ea72cdab Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 23 Feb 2009 13:27:22 -0800 Subject: 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 05787f3f7..0114c4396 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; - } } -- cgit v1.2.3-54-g00ecf