From 92c12898e780ed3685c93bd85cae5772f1547c5e Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Jul 2009 15:07:39 -0400 Subject: [PATCH 01/33] change front page to link to max member groups --- actions/public.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/public.php b/actions/public.php index ef9ef0d1ab..d0317ac706 100644 --- a/actions/public.php +++ b/actions/public.php @@ -229,7 +229,7 @@ class PublicAction extends Action // $top->show(); $pop = new PopularNoticeSection($this); $pop->show(); - $gbp = new GroupsByPostsSection($this); + $gbp = new GroupsByMembersSection($this); $gbp->show(); $feat = new FeaturedUsersSection($this); $feat->show(); From 2cbee8213ae9973c929d8d130cdc54d031e0ccd6 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Jul 2009 13:51:40 -0400 Subject: [PATCH 02/33] better check for existing DB connection runs SET NAMES UTF8 less --- classes/Memcached_DataObject.php | 69 ++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index f7cbb9d5b6..ea070ec849 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -241,10 +241,19 @@ class Memcached_DataObject extends DB_DataObject function _connect() { global $_DB_DATAOBJECT; - $exists = !empty($this->_database_dsn_md5) && - isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]); + + $sum = $this->_getDbDsnMD5(); + + if (!empty($_DB_DATAOBJECT['CONNECTIONS'][$sum]) && + !PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$sum])) { + $exists = true; + } else { + $exists = false; + } + $result = parent::_connect(); - if (!$exists) { + + if ($result && !$exists) { $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; if (common_config('db', 'type') == 'mysql' && common_config('db', 'utf8')) { @@ -258,7 +267,61 @@ class Memcached_DataObject extends DB_DataObject } } } + return $result; } + // XXX: largely cadged from DB_DataObject + + function _getDbDsnMD5() + { + if ($this->_database_dsn_md5) { + return $this->_database_dsn_md5; + } + + $dsn = $this->_getDbDsn(); + + if (is_string($dsn)) { + $sum = md5($dsn); + } else { + /// support array based dsn's + $sum = md5(serialize($dsn)); + } + + return $sum; + } + + function _getDbDsn() + { + global $_DB_DATAOBJECT; + + if (empty($_DB_DATAOBJECT['CONFIG'])) { + DB_DataObject::_loadConfig(); + } + + $options = &$_DB_DATAOBJECT['CONFIG']; + + // if the databse dsn dis defined in the object.. + + $dsn = isset($this->_database_dsn) ? $this->_database_dsn : null; + + if (!$dsn) { + + if (!$this->_database) { + $this->_database = isset($options["table_{$this->__table}"]) ? $options["table_{$this->__table}"] : null; + } + + if ($this->_database && !empty($options["database_{$this->_database}"])) { + $dsn = $options["database_{$this->_database}"]; + } else if (!empty($options['database'])) { + $dsn = $options['database']; + } + } + + if (!$dsn) { + throw new Exception("No database name / dsn found anywhere"); + } + + return $dsn; + } } From 421ee4297ef5055bdd190fe2bd62cdc22b41c82b Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Fri, 17 Jul 2009 22:59:58 -0400 Subject: [PATCH 03/33] Fix RDFS namespace declaration. Thanks tobyink --- lib/rssaction.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/rssaction.php b/lib/rssaction.php index ffa1f9e99f..7686d06460 100644 --- a/lib/rssaction.php +++ b/lib/rssaction.php @@ -320,6 +320,8 @@ class Rss10Action extends Action 'http://rdfs.org/sioc/ns#', 'xmlns:sioct' => 'http://rdfs.org/sioc/types#', + 'xmlns:rdfs' => + 'http://www.w3.org/2000/01/rdf-schema#', 'xmlns:laconica' => 'http://laconi.ca/ont/', 'xmlns' => 'http://purl.org/rss/1.0/')); From cd9748ad563ac8a0cf6f00f9aa3cf825647d394f Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 30 Jul 2009 19:34:32 +0000 Subject: [PATCH 04/33] Attempt to reduce the number of calls to FB to speed things up --- plugins/FBConnect/FBConnectPlugin.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/FBConnect/FBConnectPlugin.php b/plugins/FBConnect/FBConnectPlugin.php index 2e32ad198f..6788793b25 100644 --- a/plugins/FBConnect/FBConnectPlugin.php +++ b/plugins/FBConnect/FBConnectPlugin.php @@ -122,9 +122,7 @@ class FBConnectPlugin extends Plugin FB_RequireFeatures( ["XFBML"], function() { - FB.init("%s", "../xd_receiver.html", - {"doNotUseCachedConnectState":true }); - + FB.init("%s", "../xd_receiver.html"); } ); } @@ -222,7 +220,7 @@ class FBConnectPlugin extends Plugin try { $facebook = getFacebook(); - $fbuid = $facebook->api_client->users_getLoggedInUser(); + $fbuid = $facebook->get_loggedin_user(); } catch (Exception $e) { common_log(LOG_WARNING, From 8371aea9c1320f8902465dea8cdf1d45a789a971 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 30 Jul 2009 16:38:50 -0400 Subject: [PATCH 05/33] remove debugging code about processing a new URL --- classes/File.php | 1 - 1 file changed, 1 deletion(-) diff --git a/classes/File.php b/classes/File.php index 0c4fbf7e69..7f1e7881f8 100644 --- a/classes/File.php +++ b/classes/File.php @@ -93,7 +93,6 @@ class File extends Memcached_DataObject if (empty($file)) { $file_redir = File_redirection::staticGet('url', $given_url); if (empty($file_redir)) { - common_debug("processNew() '$given_url' not a known redirect.\n"); $redir_data = File_redirection::where($given_url); $redir_url = $redir_data['url']; if ($redir_url === $given_url) { From 3af5774769bd1a8193b4061c0b94ed867272c485 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 30 Jul 2009 16:55:09 -0400 Subject: [PATCH 06/33] throw an exception rather than die() --- classes/File.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/classes/File.php b/classes/File.php index 7f1e7881f8..959301edae 100644 --- a/classes/File.php +++ b/classes/File.php @@ -113,7 +113,9 @@ class File extends Memcached_DataObject if (empty($x)) { $x = File::staticGet($file_id); - if (empty($x)) die('Impossible!'); + if (empty($x)) { + throw new ServerException("Robin thinks something is impossible."); + } } File_to_post::processNew($file_id, $notice_id); From 854d24b05a052bffe21f112b705d58c9abf126a9 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 30 Jul 2009 17:05:35 -0400 Subject: [PATCH 07/33] Site-wide design configuration I added some code so that the site-wide design can be set, using the configuration interface. I also moved the configuration option from $config['site']['design']['background'] to just $config['design']['background'], but the old syntax will still work. Conflicts: config.php.sample --- README | 17 ++++-- classes/Design.php | 92 +++++++++++++++++++++++++++------ config.php.sample | 8 +++ lib/action.php | 28 ++++++++++ lib/common.php | 20 ++++--- lib/currentuserdesignaction.php | 37 ++++--------- lib/groupdesignaction.php | 30 +++-------- lib/ownerdesignaction.php | 31 +++-------- 8 files changed, 160 insertions(+), 103 deletions(-) diff --git a/README b/README index 0214e49315..41c015d29b 100644 --- a/README +++ b/README @@ -961,9 +961,6 @@ sslserver: use an alternate server name for SSL URLs, like shorturllength: Length of URL at which URLs in a message exceeding 140 characters will be sent to the user's chosen shortening service. -design: a default design (colors and background) for the site. - Sub-items are: backgroundcolor, contentcolor, sidebarcolor, - textcolor, linkcolor, backgroundimage, disposition. dupelimit: minimum time allowed for one person to say the same thing twice. Default 60s. Anything lower is considered a user or UI error. @@ -1429,6 +1426,20 @@ notify third-party servers of updates. notify: an array of URLs for ping endpoints. Default is the empty array (no notification). +design +------ + +Default design (colors and background) for the site. Actual appearance +depends on the theme. Null values mean to use the theme defaults. + +backgroundcolor: Hex color of the site background. +contentcolor: Hex color of the content area background. +sidebarcolor: Hex color of the sidebar background. +textcolor: Hex color of all non-link text. +linkcolor: Hex color of all links. +backgroundimage: Image to use for the background. +disposition: Flags for whether or not to tile the background image. + Plugins ======= diff --git a/classes/Design.php b/classes/Design.php index 0927fcda70..43544f1c9d 100644 --- a/classes/Design.php +++ b/classes/Design.php @@ -55,26 +55,38 @@ class Design extends Memcached_DataObject function showCSS($out) { - try { + $css = ''; - $bgcolor = new WebColor($this->backgroundcolor); - $ccolor = new WebColor($this->contentcolor); - $sbcolor = new WebColor($this->sidebarcolor); - $tcolor = new WebColor($this->textcolor); - $lcolor = new WebColor($this->linkcolor); + $bgcolor = Design::toWebColor($this->backgroundcolor); - } catch (WebColorException $e) { - // This shouldn't happen - common_log(LOG_ERR, "Unable to create color for design $id.", - __FILE__); + if (!empty($bgcolor)) { + $css .= 'body { background-color: #' . $bgcolor->hexValue() . ' }' . "\n"; } - $css = 'body { background-color: #' . $bgcolor->hexValue() . ' }' . "\n"; - $css .= '#content, #site_nav_local_views .current a { background-color: #'; - $css .= $ccolor->hexValue() . '} '."\n"; - $css .= '#aside_primary { background-color: #'. $sbcolor->hexValue() . ' }' . "\n"; - $css .= 'html body { color: #'. $tcolor->hexValue() . ' }'. "\n"; - $css .= 'a { color: #' . $lcolor->hexValue() . ' }' . "\n"; + $ccolor = Design::toWebColor($this->contentcolor); + + if (!empty($ccolor)) { + $css .= '#content, #site_nav_local_views .current a { background-color: #'; + $css .= $ccolor->hexValue() . '} '."\n"; + } + + $sbcolor = Design::toWebColor($this->sidebarcolor); + + if (!empty($sbcolor)) { + $css .= '#aside_primary { background-color: #'. $sbcolor->hexValue() . ' }' . "\n"; + } + + $tcolor = Design::toWebColor($this->textcolor); + + if (!empty($tcolor)) { + $css .= 'html body { color: #'. $tcolor->hexValue() . ' }'. "\n"; + } + + $lcolor = Design::toWebColor($this->linkcolor); + + if (!empty($lcolor)) { + $css .= 'a { color: #' . $lcolor->hexValue() . ' }' . "\n"; + } if (!empty($this->backgroundimage) && $this->disposition & BACKGROUND_ON) { @@ -88,8 +100,25 @@ class Design extends Memcached_DataObject '); ' . $repeat . ' background-attachment:fixed; }' . "\n"; } - $out->element('style', array('type' => 'text/css'), $css); + if (0 != mb_strlen($css)) { + $out->element('style', array('type' => 'text/css'), $css); + } + } + static function toWebColor($color) + { + if (is_null($color)) { + return null; + } + + try { + return new WebColor($color); + } catch (WebColorException $e) { + // This shouldn't happen + common_log(LOG_ERR, "Unable to create color for design $id.", + __FILE__); + return null; + } } static function filename($id, $extension, $extra=null) @@ -152,4 +181,33 @@ class Design extends Memcached_DataObject } } + /** + * Return a design object based on the configured site design. + * + * @return Design a singleton design object for the site. + */ + + static function siteDesign() + { + static $siteDesign = null; + + if (empty($siteDesign)) { + + $siteDesign = new Design(); + + $attrs = array('backgroundcolor', + 'contentcolor', + 'sidebarcolor', + 'textcolor', + 'linkcolor', + 'backgroundimage', + 'disposition'); + + foreach ($attrs as $attr) { + $siteDesign->$attr = common_config('design', $attr); + } + } + + return $siteDesign; + } } diff --git a/config.php.sample b/config.php.sample index 57aa6a6c8c..c27645ff87 100644 --- a/config.php.sample +++ b/config.php.sample @@ -18,6 +18,14 @@ $config['site']['server'] = 'localhost'; $config['site']['path'] = 'laconica'; // $config['site']['fancy'] = false; // $config['site']['theme'] = 'default'; +// Sets the site's default design values +// $config['design']['backgroundcolor'] = '#F0F2F5'; +// $config['design']['contentcolor'] = '#FFFFFF'; +// $config['design']['sidebarcolor'] = '#CEE1E9'; +// $config['design']['textcolor'] = '#000000'; +// $config['design']['linkcolor'] = '#002E6E'; +// $config['design']['backgroundimage'] = null; +// $config['design']['disposition'] = 1; // To enable the built-in mobile style sheet, defaults to false. // $config['site']['mobile'] = true; // For contact email, defaults to $_SERVER["SERVER_ADMIN"] diff --git a/lib/action.php b/lib/action.php index 95ee10c642..a5244371a5 100644 --- a/lib/action.php +++ b/lib/action.php @@ -191,6 +191,7 @@ class Action extends HTMLOutputter // lawsuit function showStylesheets() { if (Event::handle('StartShowStyles', array($this))) { + if (Event::handle('StartShowLaconicaStyles', array($this))) { $this->element('link', array('rel' => 'stylesheet', 'type' => 'text/css', @@ -209,6 +210,7 @@ class Action extends HTMLOutputter // lawsuit 'media' => 'print')); Event::handle('EndShowLaconicaStyles', array($this)); } + if (Event::handle('StartShowUAStyles', array($this))) { $this->comment('[if IE]>viewdesigns) { + $design = $this->getDesign(); + + if (!empty($design)) { + $design->showCSS($this); + } + } + + Event::handle('EndShowDesign', array($this)); + } Event::handle('EndShowStyles', array($this)); } } @@ -1074,4 +1091,15 @@ class Action extends HTMLOutputter // lawsuit { return null; } + + /** + * A design for this action + * + * @return Design a design object to use + */ + + function getDesign() + { + return Design::siteDesign(); + } } diff --git a/lib/common.php b/lib/common.php index c47702779d..507a2a281f 100644 --- a/lib/common.php +++ b/lib/common.php @@ -94,14 +94,6 @@ $config = array('name' => 'Just another Laconica microblog', 'server' => $_server, 'theme' => 'default', - 'design' => - array('backgroundcolor' => '#CEE1E9', - 'contentcolor' => '#FFFFFF', - 'sidebarcolor' => '#C8D1D5', - 'textcolor' => '#000000', - 'linkcolor' => '#002E6E', - 'backgroundimage' => null, - 'disposition' => 1), 'path' => $_path, 'logfile' => null, 'logo' => null, @@ -261,6 +253,14 @@ $config = 'sessions' => array('handle' => false, // whether to handle sessions ourselves 'debug' => false), // debugging output for sessions + 'design' => + array('backgroundcolor' => null, // null -> 'use theme default' + 'contentcolor' => null, + 'sidebarcolor' => null, + 'textcolor' => null, + 'linkcolor' => null, + 'backgroundimage' => null, + 'disposition' => null), ); $config['db'] = &PEAR::getStaticProperty('DB_DataObject','options'); @@ -277,6 +277,10 @@ $config['db'] = 'quote_identifiers' => false, 'type' => 'mysql' ); +// Backward compatibility + +$config['site']['design'] =& $config['design']; + if (function_exists('date_default_timezone_set')) { /* Work internally in UTC */ date_default_timezone_set('UTC'); diff --git a/lib/currentuserdesignaction.php b/lib/currentuserdesignaction.php index 4c7e15a8b7..52516b624a 100644 --- a/lib/currentuserdesignaction.php +++ b/lib/currentuserdesignaction.php @@ -47,33 +47,10 @@ if (!defined('LACONICA')) { class CurrentUserDesignAction extends Action { - - /** - * Show the user's design stylesheet - * - * @return nothing - */ - - function showStylesheets() - { - parent::showStylesheets(); - - $user = common_current_user(); - - if (empty($user) || $user->viewdesigns) { - $design = $this->getDesign(); - - if (!empty($design)) { - $design->showCSS($this); - } - } - } - /** * A design for this action * - * if the user attribute has been set, returns that user's - * design. + * Returns the design preferences for the current user. * * @return Design a design object to use */ @@ -82,11 +59,15 @@ class CurrentUserDesignAction extends Action { $cur = common_current_user(); - if (empty($cur)) { - return null; + if (!empty($cur)) { + + $design = $cur->getDesign(); + + if (!empty($design)) { + return $design; + } } - return $cur->getDesign(); + return parent::getDesign(); } - } diff --git a/lib/groupdesignaction.php b/lib/groupdesignaction.php index 58777c283a..c7cdff1fe9 100644 --- a/lib/groupdesignaction.php +++ b/lib/groupdesignaction.php @@ -49,26 +49,6 @@ class GroupDesignAction extends Action { /** The group in question */ var $group = null; - /** - * Show the groups's design stylesheet - * - * @return nothing - */ - function showStylesheets() - { - parent::showStylesheets(); - - $user = common_current_user(); - - if (empty($user) || $user->viewdesigns) { - $design = $this->getDesign(); - - if (!empty($design)) { - $design->showCSS($this); - } - } - } - /** * A design for this action * @@ -80,10 +60,12 @@ class GroupDesignAction extends Action { function getDesign() { - if (empty($this->group)) { - return null; + if (!empty($this->group)) { + $design = $this->group->getDesign(); + if (!empty($design)) { + return $design; + } } - - return $this->group->getDesign(); + return parent::getDesign(); } } diff --git a/lib/ownerdesignaction.php b/lib/ownerdesignaction.php index 785b8a93d3..b42df926d0 100644 --- a/lib/ownerdesignaction.php +++ b/lib/ownerdesignaction.php @@ -52,26 +52,6 @@ class OwnerDesignAction extends Action { var $user = null; - /** - * Show the owner's design stylesheet - * - * @return nothing - */ - function showStylesheets() - { - parent::showStylesheets(); - - $user = common_current_user(); - - if (empty($user) || $user->viewdesigns) { - $design = $this->getDesign(); - - if (!empty($design)) { - $design->showCSS($this); - } - } - } - /** * A design for this action * @@ -83,10 +63,15 @@ class OwnerDesignAction extends Action { function getDesign() { - if (empty($this->user)) { - return null; + if (!empty($this->user)) { + + $design = $this->user->getDesign(); + + if (!empty($design)) { + return $design; + } } - return $this->user->getDesign(); + return parent::getDesign(); } } From d5cc1357fdc90c70b0a71a04f9448abf558c3f94 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 30 Jul 2009 17:06:03 -0400 Subject: [PATCH 08/33] don't ignore config.php.sample --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f4c2bba5f7..5394f5eac5 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,4 @@ config-*.php good-config.php lac08.log php.log -config.php.* + From 77c5f9481c5cf1cc548c73c71bd4723001d912f5 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 30 Jul 2009 20:44:51 +0000 Subject: [PATCH 09/33] Removed default values from s. JavaScript will now get the colours from the theme. This approach removes data that was previously available in HTML. It was only necessary if the user wanted to know the site's default values. --- js/userdesign.go.js | 31 ++++++++++++++++++++++++++++++- lib/designsettings.php | 10 +++++----- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/js/userdesign.go.js b/js/userdesign.go.js index dda86294ed..70dd9c7de7 100644 --- a/js/userdesign.go.js +++ b/js/userdesign.go.js @@ -7,6 +7,35 @@ * @link http://laconi.ca/ */ $(document).ready(function() { + function InitColors(i, E) { + switch (parseInt(E.id.slice(-1))) { + case 1: default: + $(E).val(rgb2hex($('body').css('background-color'))); + break; + case 2: + $(E).val(rgb2hex($('#content').css('background-color'))); + break; + case 3: + $(E).val(rgb2hex($('#aside_primary').css('background-color'))); + break; + case 4: + $(E).val(rgb2hex($('html body').css('color'))); + break; + case 5: + $(E).val(rgb2hex($('a').css('color'))); + break; + } + } + + function rgb2hex(rgb) { + rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); + function hex(x) { + hexDigits = new Array("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"); + return isNaN(x) ? "00" : hexDigits[(x - x % 16) / 16] + hexDigits[x % 16]; + } + return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]); + } + function UpdateColors(S) { C = $(S).val(); switch (parseInt(S.id.slice(-1))) { @@ -55,7 +84,7 @@ $(document).ready(function() { f = $.farbtastic('#color-picker', SynchColors); swatches = $('#settings_design_color .swatch'); - + swatches.each(InitColors); swatches .each(SynchColors) .blur(function() { diff --git a/lib/designsettings.php b/lib/designsettings.php index fbffdb208f..1b0e621669 100644 --- a/lib/designsettings.php +++ b/lib/designsettings.php @@ -182,7 +182,7 @@ class DesignSettingsAction extends AccountSettingsAction 'class' => 'swatch', 'maxlength' => '7', 'size' => '7', - 'value' => '#' . $bgcolor->hexValue())); + 'value' => '')); $this->elementEnd('li'); $ccolor = new WebColor($design->contentcolor); @@ -195,7 +195,7 @@ class DesignSettingsAction extends AccountSettingsAction 'class' => 'swatch', 'maxlength' => '7', 'size' => '7', - 'value' => '#' . $ccolor->hexValue())); + 'value' => '')); $this->elementEnd('li'); $sbcolor = new WebColor($design->sidebarcolor); @@ -208,7 +208,7 @@ class DesignSettingsAction extends AccountSettingsAction 'class' => 'swatch', 'maxlength' => '7', 'size' => '7', - 'value' => '#' . $sbcolor->hexValue())); + 'value' => '')); $this->elementEnd('li'); $tcolor = new WebColor($design->textcolor); @@ -221,7 +221,7 @@ class DesignSettingsAction extends AccountSettingsAction 'class' => 'swatch', 'maxlength' => '7', 'size' => '7', - 'value' => '#' . $tcolor->hexValue())); + 'value' => '')); $this->elementEnd('li'); $lcolor = new WebColor($design->linkcolor); @@ -234,7 +234,7 @@ class DesignSettingsAction extends AccountSettingsAction 'class' => 'swatch', 'maxlength' => '7', 'size' => '7', - 'value' => '#' . $lcolor->hexValue())); + 'value' => '')); $this->elementEnd('li'); } catch (WebColorException $e) { From ec103b90e39c20bfdc2f5663e35eae8b1df212e9 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Thu, 30 Jul 2009 22:15:24 -0400 Subject: [PATCH 10/33] Implemented the "show" method of the laconica groups api --- actions/api.php | 1 + actions/twitapigroups.php | 26 +++++++++++++++++++++++ lib/twitterapi.php | 44 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/actions/api.php b/actions/api.php index 8b92889f8a..99ab262ad7 100644 --- a/actions/api.php +++ b/actions/api.php @@ -130,6 +130,7 @@ class ApiAction extends Action 'laconica/wadl', 'tags/timeline', 'oembed/oembed', + 'groups/show', 'groups/timeline'); static $bareauth = array('statuses/user_timeline', diff --git a/actions/twitapigroups.php b/actions/twitapigroups.php index 71a0776f46..f899bc3698 100644 --- a/actions/twitapigroups.php +++ b/actions/twitapigroups.php @@ -51,6 +51,32 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; class TwitapigroupsAction extends TwitterapiAction { + function show($args, $apidata) + { + parent::handle($args); + + common_debug("in groups api action"); + + $this->auth_user = $apidata['user']; + $group = $this->get_group($apidata['api_arg'], $apidata); + + if (empty($group)) { + $this->clientError('Not Found', 404, $apidata['content-type']); + return; + } + + switch($apidata['content-type']) { + case 'xml': + $this->show_single_xml_group($group); + break; + case 'json': + $this->show_single_json_group($group); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + } + } + function timeline($args, $apidata) { parent::handle($args); diff --git a/lib/twitterapi.php b/lib/twitterapi.php index b2602e77ca..2f969f2baf 100644 --- a/lib/twitterapi.php +++ b/lib/twitterapi.php @@ -213,6 +213,25 @@ class TwitterapiAction extends Action return $twitter_status; } + function twitter_group_array($group) + { + $twitter_group=array(); + $twitter_group['id']=$group->id; + $twitter_group['nickname']=$group->nickname; + $twitter_group['fullname']=$group->fullname; + $twitter_group['url']=$group->url; + $twitter_group['original_logo']=$group->original_logo; + $twitter_group['homepage_logo']=$group->homepage_logo; + $twitter_group['stream_logo']=$group->stream_logo; + $twitter_group['mini_logo']=$group->mini_logo; + $twitter_group['homepage']=$group->homepage; + $twitter_group['description']=$group->description; + $twitter_group['location']=$group->location; + $twitter_group['created']=$this->date_twitter($group->created); + $twitter_group['modified']=$this->date_twitter($group->modified); + return $twitter_group; + } + function twitter_rss_entry_array($notice) { $profile = $notice->getProfile(); @@ -413,6 +432,15 @@ class TwitterapiAction extends Action $this->elementEnd('status'); } + function show_twitter_xml_group($twitter_group) + { + $this->elementStart('group'); + foreach($twitter_group as $element => $value) { + $this->element($element, null, $value); + } + $this->elementEnd('group'); + } + function show_twitter_xml_user($twitter_user, $role='user') { $this->elementStart($role); @@ -639,6 +667,22 @@ class TwitterapiAction extends Action $this->end_document('json'); } + function show_single_json_group($group) + { + $this->init_document('json'); + $twitter_group = $this->twitter_group_array($group); + $this->show_json_objects($twitter_group); + $this->end_document('json'); + } + + function show_single_xml_group($group) + { + $this->init_document('xml'); + $twitter_group = $this->twitter_group_array($group); + $this->show_twitter_xml_group($twitter_group); + $this->end_document('xml'); + } + // Anyone know what date format this is? // Twitter's dates look like this: "Mon Jul 14 23:52:38 +0000 2008" -- Zach function date_twitter($dt) From 45ad4cfe7253d1cd2da219378fd714644c906f53 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Thu, 30 Jul 2009 22:43:07 -0400 Subject: [PATCH 11/33] Added a url field to hold the permalink. I believe this field is very useful for api consumers. --- lib/twitterapi.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/twitterapi.php b/lib/twitterapi.php index 2f969f2baf..e6af33e828 100644 --- a/lib/twitterapi.php +++ b/lib/twitterapi.php @@ -217,9 +217,10 @@ class TwitterapiAction extends Action { $twitter_group=array(); $twitter_group['id']=$group->id; + $twitter_group['url']=$group->permalink(); $twitter_group['nickname']=$group->nickname; $twitter_group['fullname']=$group->fullname; - $twitter_group['url']=$group->url; + $twitter_group['homepage_url']=$group->homepage_url; $twitter_group['original_logo']=$group->original_logo; $twitter_group['homepage_logo']=$group->homepage_logo; $twitter_group['stream_logo']=$group->stream_logo; From b2d2b19d3a8fae84e1bc6532661c71ac180eceec Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Sun, 2 Aug 2009 19:36:09 +0800 Subject: [PATCH 12/33] Fixed PHP Notice "Use of undefined constant session_name - assumed 'session_name'" --- lib/util.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util.php b/lib/util.php index d784bb7933..db794181ca 100644 --- a/lib/util.php +++ b/lib/util.php @@ -140,7 +140,7 @@ function common_have_session() function common_ensure_session() { $c = null; - if (array_key_exists(session_name, $_COOKIE)) { + if (array_key_exists(session_name(), $_COOKIE)) { $c = $_COOKIE[session_name()]; } if (!common_have_session()) { From 6c1bd6759127968908a0d7f179447a7a6f653d17 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Sun, 2 Aug 2009 19:38:03 +0800 Subject: [PATCH 13/33] Fixed PHP Notice "Undefined index: enclosures" (and a possible one for 'tags') --- lib/twitterapi.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/twitterapi.php b/lib/twitterapi.php index e6af33e828..4115d9dcb4 100644 --- a/lib/twitterapi.php +++ b/lib/twitterapi.php @@ -479,12 +479,12 @@ class TwitterapiAction extends Action $this->element('link', null, $entry['link']); # RSS only supports 1 enclosure per item - if($entry['enclosures']){ + if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){ $enclosure = $entry['enclosures'][0]; $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null); } - if($entry['tags']){ + if(array_key_exists('tags', $entry)){ foreach($entry['tags'] as $tag){ $this->element('category', null,$tag); } From fe57e2e06b3ae5e7eb9001a0ffe80d9a7d20a5be Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Sun, 2 Aug 2009 19:47:36 +0800 Subject: [PATCH 14/33] Fixed PHP Notice "Undefined variable: suplink" --- actions/twitapigroups.php | 5 ++--- actions/twitapitags.php | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/actions/twitapigroups.php b/actions/twitapigroups.php index f899bc3698..82604ebff2 100644 --- a/actions/twitapigroups.php +++ b/actions/twitapigroups.php @@ -114,8 +114,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; $this->show_xml_timeline($notice); break; case 'rss': - $this->show_rss_timeline($notice, $title, $link, - $subtitle, $suplink); + $this->show_rss_timeline($notice, $title, $link, $subtitle); break; case 'atom': if (isset($apidata['api_arg'])) { @@ -127,7 +126,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; 'api/laconica/groups/timeline.atom'; } $this->show_atom_timeline($notice, $title, $id, $link, - $subtitle, $suplink, $selfuri); + $subtitle, null, $selfuri); break; case 'json': $this->show_json_timeline($notice); diff --git a/actions/twitapitags.php b/actions/twitapitags.php index 5c85275302..e19e1b1ed6 100644 --- a/actions/twitapitags.php +++ b/actions/twitapitags.php @@ -88,8 +88,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; $this->show_xml_timeline($notice); break; case 'rss': - $this->show_rss_timeline($notice, $title, $link, - $subtitle, $suplink); + $this->show_rss_timeline($notice, $title, $link, $subtitle); break; case 'atom': if (isset($apidata['api_arg'])) { @@ -101,7 +100,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; 'api/laconica/tags/timeline.atom'; } $this->show_atom_timeline($notice, $title, $id, $link, - $subtitle, $suplink, $selfuri); + $subtitle, null, $selfuri); break; case 'json': $this->show_json_timeline($notice); From 20c536fdd461c8f39dd6a03751b534a996a94078 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Sun, 2 Aug 2009 19:52:27 +0800 Subject: [PATCH 15/33] Fixed PHP Notice "Undefined variable: cnt" --- actions/conversation.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actions/conversation.php b/actions/conversation.php index c8755ba6ef..6b5d8d54d9 100644 --- a/actions/conversation.php +++ b/actions/conversation.php @@ -167,6 +167,8 @@ class ConversationTree extends NoticeList function _buildTree() { + $cnt = 0; + $this->tree = array(); $this->table = array(); From e670e4306bf3e0e7e90523bcbfa2eb8060f4ed67 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Sun, 2 Aug 2009 20:10:31 +0800 Subject: [PATCH 16/33] Fixed PHP Notices: Undefined index: HTTP_X_FORWARDED_FOR Undefined index: HTTP_CLIENT_IP Undefined variable: proxy Also fixed the return value order to match calls to common_client_ip() in actions/api.php and lib/rssaction.php --- lib/util.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/util.php b/lib/util.php index db794181ca..c8e318efec 100644 --- a/lib/util.php +++ b/lib/util.php @@ -1410,20 +1410,21 @@ function common_client_ip() return null; } - if ($_SERVER['HTTP_X_FORWARDED_FOR']) { - if ($_SERVER['HTTP_CLIENT_IP']) { + if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) { + if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) { $proxy = $_SERVER['HTTP_CLIENT_IP']; } else { $proxy = $_SERVER['REMOTE_ADDR']; } $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; } else { - if ($_SERVER['HTTP_CLIENT_IP']) { + $proxy = null; + if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) { $ip = $_SERVER['HTTP_CLIENT_IP']; } else { $ip = $_SERVER['REMOTE_ADDR']; } } - return array($ip, $proxy); + return array($proxy, $ip); } From 34e8b25ceef890c0d1e41b75696b19e8b53db960 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 2 Aug 2009 10:34:23 -0400 Subject: [PATCH 17/33] GC sessions one by one to make sure memcached gets cleared --- classes/Session.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/classes/Session.php b/classes/Session.php index ac80279c5e..a92ce405b5 100644 --- a/classes/Session.php +++ b/classes/Session.php @@ -110,9 +110,18 @@ class Session extends Memcached_DataObject $session = new Session(); $session->whereAdd('modified < "'.$epoch.'"'); - $result = $session->delete(DB_DATAOBJECT_WHEREADD_ONLY); - self::logdeb("garbage collection result = $result"); + $session->find(); + + while ($session->fetch()) { + $other = new Session(); + $other->id = $session->id; + self::logdeb("Collecting session $other->id"); + $result = $other->delete(); + self::logdeb("garbage collection result = $result"); + } + + $session->free(); } static function setSaveHandler() From 2934099fbd17281ed00055af654aa5b813d535a2 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 2 Aug 2009 10:34:40 -0400 Subject: [PATCH 18/33] A script to GC sessions correctly --- scripts/sessiongc.php | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 scripts/sessiongc.php diff --git a/scripts/sessiongc.php b/scripts/sessiongc.php new file mode 100644 index 0000000000..314b641eb9 --- /dev/null +++ b/scripts/sessiongc.php @@ -0,0 +1,36 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$helptext = << Date: Sun, 2 Aug 2009 10:34:23 -0400 Subject: [PATCH 19/33] GC sessions one by one to make sure memcached gets cleared --- classes/Session.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/classes/Session.php b/classes/Session.php index ac80279c5e..a92ce405b5 100644 --- a/classes/Session.php +++ b/classes/Session.php @@ -110,9 +110,18 @@ class Session extends Memcached_DataObject $session = new Session(); $session->whereAdd('modified < "'.$epoch.'"'); - $result = $session->delete(DB_DATAOBJECT_WHEREADD_ONLY); - self::logdeb("garbage collection result = $result"); + $session->find(); + + while ($session->fetch()) { + $other = new Session(); + $other->id = $session->id; + self::logdeb("Collecting session $other->id"); + $result = $other->delete(); + self::logdeb("garbage collection result = $result"); + } + + $session->free(); } static function setSaveHandler() From f342db586d2d95035c53581bebba66eb7e7f7eec Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 2 Aug 2009 10:34:40 -0400 Subject: [PATCH 20/33] A script to GC sessions correctly --- scripts/sessiongc.php | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 scripts/sessiongc.php diff --git a/scripts/sessiongc.php b/scripts/sessiongc.php new file mode 100644 index 0000000000..314b641eb9 --- /dev/null +++ b/scripts/sessiongc.php @@ -0,0 +1,36 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$helptext = << Date: Sun, 2 Aug 2009 11:18:41 -0400 Subject: [PATCH 21/33] don't delete during select --- classes/Session.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/classes/Session.php b/classes/Session.php index a92ce405b5..5ec509f5f9 100644 --- a/classes/Session.php +++ b/classes/Session.php @@ -108,20 +108,24 @@ class Session extends Memcached_DataObject $epoch = common_sql_date(time() - $maxlifetime); + $ids = array(); + $session = new Session(); $session->whereAdd('modified < "'.$epoch.'"'); + $session->selectAdd(); + $session->selectAdd('id'); $session->find(); while ($session->fetch()) { - $other = new Session(); - $other->id = $session->id; - self::logdeb("Collecting session $other->id"); - $result = $other->delete(); - self::logdeb("garbage collection result = $result"); + $ids[] = $session->id; } $session->free(); + + foreach ($ids as $id) { + self::destroy($id); + } } static function setSaveHandler() From b27af3247d4e0a13a1cd8b29e3938e8d81fa3d22 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 2 Aug 2009 11:18:41 -0400 Subject: [PATCH 22/33] don't delete during select --- classes/Session.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/classes/Session.php b/classes/Session.php index a92ce405b5..5ec509f5f9 100644 --- a/classes/Session.php +++ b/classes/Session.php @@ -108,20 +108,24 @@ class Session extends Memcached_DataObject $epoch = common_sql_date(time() - $maxlifetime); + $ids = array(); + $session = new Session(); $session->whereAdd('modified < "'.$epoch.'"'); + $session->selectAdd(); + $session->selectAdd('id'); $session->find(); while ($session->fetch()) { - $other = new Session(); - $other->id = $session->id; - self::logdeb("Collecting session $other->id"); - $result = $other->delete(); - self::logdeb("garbage collection result = $result"); + $ids[] = $session->id; } $session->free(); + + foreach ($ids as $id) { + self::destroy($id); + } } static function setSaveHandler() From 92ef468fcc5adf6e9445b3b80b87fdc8e55896dd Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Mon, 3 Aug 2009 11:47:58 -0400 Subject: [PATCH 23/33] Use the same favorite notification function in the API as everywhere else http://laconi.ca/trac/ticket/873 --- actions/twitapifavorites.php | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/actions/twitapifavorites.php b/actions/twitapifavorites.php index 8256668f3d..6f93610650 100644 --- a/actions/twitapifavorites.php +++ b/actions/twitapifavorites.php @@ -207,32 +207,10 @@ class TwitapifavoritesAction extends TwitterapiAction $other = User::staticGet('id', $notice->profile_id); if ($other && $other->id != $user->id) { if ($other->email && $other->emailnotifyfav) { - $this->notify_mail($other, $user, $notice); + mail_notify_fave($other, $user, $notice); } # XXX: notify by IM # XXX: notify by SMS } } - - function notify_mail($other, $user, $notice) - { - $profile = $user->getProfile(); - $bestname = $profile->getBestName(); - $subject = sprintf(_('%s added your notice as a favorite'), $bestname); - $body = sprintf(_("%1\$s just added your notice from %2\$s as one of their favorites.\n\n" . - "In case you forgot, you can see the text of your notice here:\n\n" . - "%3\$s\n\n" . - "You can see the list of %1\$s's favorites here:\n\n" . - "%4\$s\n\n" . - "Faithfully yours,\n" . - "%5\$s\n"), - $bestname, - common_exact_date($notice->created), - common_local_url('shownotice', array('notice' => $notice->id)), - common_local_url('showfavorites', array('nickname' => $user->nickname)), - common_config('site', 'name')); - - mail_to_user($other, $subject, $body); - } - -} \ No newline at end of file +} From d0b85d3ad2cb8244a62489742706ad95b78ef4eb Mon Sep 17 00:00:00 2001 From: Brenda Wallace Date: Tue, 4 Aug 2009 08:43:13 +1200 Subject: [PATCH 24/33] Upgrade script --- db/074to080_pg.sql | 108 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 db/074to080_pg.sql diff --git a/db/074to080_pg.sql b/db/074to080_pg.sql new file mode 100644 index 0000000000..0a7171ae56 --- /dev/null +++ b/db/074to080_pg.sql @@ -0,0 +1,108 @@ +BEGIN; +create sequence design_seq; +create table design ( + id bigint default nextval('design_seq') /* comment 'design ID'*/, + backgroundcolor integer /* comment 'main background color'*/ , + contentcolor integer /*comment 'content area background color'*/ , + sidebarcolor integer /*comment 'sidebar background color'*/ , + textcolor integer /*comment 'text color'*/ , + linkcolor integer /*comment 'link color'*/, + backgroundimage varchar(255) /*comment 'background image, if any'*/, + disposition int default 1 /*comment 'bit 1 = hide background image, bit 2 = display background image, bit 4 = tile background image'*/, + primary key (id) +); +alter table "user" + add column design_id integer references design(id); +alter table "user" + add column viewdesigns integer default 1; + +alter table notice add column + conversation integer references notice (id); + +create index notice_conversation_idx on notice(conversation); + +alter table foreign_user + alter column id TYPE bigint; + +alter table foreign_user alter column id set not null; + +alter table foreign_link + alter column foreign_id TYPE bigint; + +alter table user_group + add column design_id integer; + +/*attachments and URLs stuff */ +create sequence file_seq; +create table file ( + id bigint default nextval('file_seq') primary key /* comment 'unique identifier' */, + url varchar(255) unique, + mimetype varchar(50), + size integer, + title varchar(255), + date integer, + protected integer, + filename text /* comment 'if a local file, name of the file' */, + modified timestamp default CURRENT_TIMESTAMP /* comment 'date this record was modified'*/ +); + +create sequence file_oembed_seq; +create table file_oembed ( + file_id bigint default nextval('file_oembed_seq') primary key /* comment 'unique identifier' */, + version varchar(20), + type varchar(20), + provider varchar(50), + provider_url varchar(255), + width integer, + height integer, + html text, + title varchar(255), + author_name varchar(50), + author_url varchar(255), + url varchar(255) +); + +create sequence file_redirection_seq; +create table file_redirection ( + url varchar(255) primary key, + file_id bigint, + redirections integer, + httpcode integer +); + +create sequence file_thumbnail_seq; +create table file_thumbnail ( + file_id bigint primary key, + url varchar(255) unique, + width integer, + height integer +); +create sequence file_to_post_seq; +create table file_to_post ( + file_id bigint, + post_id bigint, + + primary key (file_id, post_id) +); + + +create table group_block ( + group_id integer not null /* comment 'group profile is blocked from' */ references user_group (id), + blocked integer not null /* comment 'profile that is blocked' */references profile (id), + blocker integer not null /* comment 'user making the block'*/ references "user" (id), + modified timestamp /* comment 'date of blocking'*/ , + + primary key (group_id, blocked) +); + +create table group_alias ( + + alias varchar(64) /* comment 'additional nickname for the group'*/ , + group_id integer not null /* comment 'group profile is blocked from'*/ references user_group (id), + modified timestamp /* comment 'date alias was created'*/, + primary key (alias) + +); +create index group_alias_group_id_idx on group_alias (group_id); + +COMMIT; \ No newline at end of file From ff6e976d0315c57fc5b7e31845e9a3bad4f095bc Mon Sep 17 00:00:00 2001 From: Eric Helgeson Date: Mon, 3 Aug 2009 16:39:10 -0500 Subject: [PATCH 25/33] Added the 0.2 recaptcha plugin. Should work in all browsers. Please test. --- plugins/recaptcha/LICENSE | 22 +++ plugins/recaptcha/README | 23 +++ plugins/recaptcha/recaptcha.php | 106 +++++++++++ plugins/recaptcha/recaptchalib.php | 277 +++++++++++++++++++++++++++++ 4 files changed, 428 insertions(+) create mode 100644 plugins/recaptcha/LICENSE create mode 100644 plugins/recaptcha/README create mode 100644 plugins/recaptcha/recaptcha.php create mode 100644 plugins/recaptcha/recaptchalib.php diff --git a/plugins/recaptcha/LICENSE b/plugins/recaptcha/LICENSE new file mode 100644 index 0000000000..b612f71f01 --- /dev/null +++ b/plugins/recaptcha/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2007 reCAPTCHA -- http://recaptcha.net +AUTHORS: + Mike Crawford + Ben Maurer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/plugins/recaptcha/README b/plugins/recaptcha/README new file mode 100644 index 0000000000..3100f697e4 --- /dev/null +++ b/plugins/recaptcha/README @@ -0,0 +1,23 @@ +Laconica reCAPTCHA plugin 0.2 8/3/09 +==================================== +Adds a captcha to your registration page to reduce automated spam bots registering. + +Use: +1. Get an API key from http://recaptcha.net + +2. In config.php add: +include_once('plugins/recaptcha.php'); +$captcha = new recaptcha(publickey, privatekey, showErrors); + +Changelog +========= +0.1 initial release +0.2 Work around for webkit browsers + +reCAPTCHA README +================ + +The reCAPTCHA PHP Lirary helps you use the reCAPTCHA API. Documentation +for this library can be found at + + http://recaptcha.net/plugins/php diff --git a/plugins/recaptcha/recaptcha.php b/plugins/recaptcha/recaptcha.php new file mode 100644 index 0000000000..5ef8352d18 --- /dev/null +++ b/plugins/recaptcha/recaptcha.php @@ -0,0 +1,106 @@ +. + * + * @category Plugin + * @package Laconica + * @author Eric Helgeson + * @copyright 2009 + * @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('RECAPTCHA', '0.2'); + +class recaptcha extends Plugin +{ + var $private_key; + var $public_key; + var $display_errors; + var $failed; + var $ssl; + + function __construct($public_key, $private_key, $display_errors=false) + { + parent::__construct(); + require_once(INSTALLDIR.'/plugins/recaptcha/recaptchalib.php'); + $this->public_key = $public_key; + $this->private_key = $private_key; + $this->display_errors = $display_errors; + } + + function checkssl(){ + if(common_config('site', 'ssl') === 'sometimes' || common_config('site', 'ssl') === 'always') { + return true; + } + return false; + } + + function onStartShowHTML($action) + { + //XXX: Horrible hack to make Safari, FF2, and Chrome work with + //reChapcha. reChapcha beaks xhtml strict + header('Content-Type: text/html'); + + $action->extraHeaders(); + + $action->startXML('html', + '-//W3C//DTD XHTML 1.0 Strict//EN', + 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'); + + $action->raw(''); + return false; + } + + function onEndRegistrationFormData($action) + { + $action->elementStart('li'); + $action->raw(''); + if($this->checkssl() === true){ + $action->raw(recaptcha_get_html($this->public_key), null, true); + } else { + $action->raw(recaptcha_get_html($this->public_key)); + } + $action->elementEnd('li'); + return true; + } + + function onStartRegistrationTry($action) + { + $resp = recaptcha_check_answer ($this->private_key, + $_SERVER["REMOTE_ADDR"], + $action->trimmed('recaptcha_challenge_field'), + $action->trimmed('recaptcha_response_field')); + + if (!$resp->is_valid) + { + if($this->display_errors) + { + $action->showForm ("(reCAPTCHA said: " . $resp->error . ")"); + } + $action->showForm("Captcha does not match!"); + return false; + } + } +} diff --git a/plugins/recaptcha/recaptchalib.php b/plugins/recaptcha/recaptchalib.php new file mode 100644 index 0000000000..897c50981a --- /dev/null +++ b/plugins/recaptcha/recaptchalib.php @@ -0,0 +1,277 @@ + $value ) + $req .= $key . '=' . urlencode( stripslashes($value) ) . '&'; + + // Cut the last '&' + $req=substr($req,0,strlen($req)-1); + return $req; +} + + + +/** + * Submits an HTTP POST to a reCAPTCHA server + * @param string $host + * @param string $path + * @param array $data + * @param int port + * @return array response + */ +function _recaptcha_http_post($host, $path, $data, $port = 80) { + + $req = _recaptcha_qsencode ($data); + + $http_request = "POST $path HTTP/1.0\r\n"; + $http_request .= "Host: $host\r\n"; + $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n"; + $http_request .= "Content-Length: " . strlen($req) . "\r\n"; + $http_request .= "User-Agent: reCAPTCHA/PHP\r\n"; + $http_request .= "\r\n"; + $http_request .= $req; + + $response = ''; + if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) { + die ('Could not open socket'); + } + + fwrite($fs, $http_request); + + while ( !feof($fs) ) + $response .= fgets($fs, 1160); // One TCP-IP packet + fclose($fs); + $response = explode("\r\n\r\n", $response, 2); + + return $response; +} + + + +/** + * Gets the challenge HTML (javascript and non-javascript version). + * This is called from the browser, and the resulting reCAPTCHA HTML widget + * is embedded within the HTML form it was called from. + * @param string $pubkey A public key for reCAPTCHA + * @param string $error The error given by reCAPTCHA (optional, default is null) + * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false) + + * @return string - The HTML to be embedded in the user's form. + */ +function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false) +{ + if ($pubkey == null || $pubkey == '') { + die ("To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey"); + } + + if ($use_ssl) { + $server = RECAPTCHA_API_SECURE_SERVER; + } else { + $server = RECAPTCHA_API_SERVER; + } + + $errorpart = ""; + if ($error) { + $errorpart = "&error=" . $error; + } + return ' + + '; +} + + + + +/** + * A ReCaptchaResponse is returned from recaptcha_check_answer() + */ +class ReCaptchaResponse { + var $is_valid; + var $error; +} + + +/** + * Calls an HTTP POST function to verify if the user's guess was correct + * @param string $privkey + * @param string $remoteip + * @param string $challenge + * @param string $response + * @param array $extra_params an array of extra variables to post to the server + * @return ReCaptchaResponse + */ +function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array()) +{ + if ($privkey == null || $privkey == '') { + die ("To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey"); + } + + if ($remoteip == null || $remoteip == '') { + die ("For security reasons, you must pass the remote ip to reCAPTCHA"); + } + + + + //discard spam submissions + if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) { + $recaptcha_response = new ReCaptchaResponse(); + $recaptcha_response->is_valid = false; + $recaptcha_response->error = 'incorrect-captcha-sol'; + return $recaptcha_response; + } + + $response = _recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/verify", + array ( + 'privatekey' => $privkey, + 'remoteip' => $remoteip, + 'challenge' => $challenge, + 'response' => $response + ) + $extra_params + ); + + $answers = explode ("\n", $response [1]); + $recaptcha_response = new ReCaptchaResponse(); + + if (trim ($answers [0]) == 'true') { + $recaptcha_response->is_valid = true; + } + else { + $recaptcha_response->is_valid = false; + $recaptcha_response->error = $answers [1]; + } + return $recaptcha_response; + +} + +/** + * gets a URL where the user can sign up for reCAPTCHA. If your application + * has a configuration page where you enter a key, you should provide a link + * using this function. + * @param string $domain The domain where the page is hosted + * @param string $appname The name of your application + */ +function recaptcha_get_signup_url ($domain = null, $appname = null) { + return "http://recaptcha.net/api/getkey?" . _recaptcha_qsencode (array ('domain' => $domain, 'app' => $appname)); +} + +function _recaptcha_aes_pad($val) { + $block_size = 16; + $numpad = $block_size - (strlen ($val) % $block_size); + return str_pad($val, strlen ($val) + $numpad, chr($numpad)); +} + +/* Mailhide related code */ + +function _recaptcha_aes_encrypt($val,$ky) { + if (! function_exists ("mcrypt_encrypt")) { + die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed."); + } + $mode=MCRYPT_MODE_CBC; + $enc=MCRYPT_RIJNDAEL_128; + $val=_recaptcha_aes_pad($val); + return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); +} + + +function _recaptcha_mailhide_urlbase64 ($x) { + return strtr(base64_encode ($x), '+/', '-_'); +} + +/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */ +function recaptcha_mailhide_url($pubkey, $privkey, $email) { + if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) { + die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " . + "you can do so at http://mailhide.recaptcha.net/apikey"); + } + + + $ky = pack('H*', $privkey); + $cryptmail = _recaptcha_aes_encrypt ($email, $ky); + + return "http://mailhide.recaptcha.net/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail); +} + +/** + * gets the parts of the email to expose to the user. + * eg, given johndoe@example,com return ["john", "example.com"]. + * the email is then displayed as john...@example.com + */ +function _recaptcha_mailhide_email_parts ($email) { + $arr = preg_split("/@/", $email ); + + if (strlen ($arr[0]) <= 4) { + $arr[0] = substr ($arr[0], 0, 1); + } else if (strlen ($arr[0]) <= 6) { + $arr[0] = substr ($arr[0], 0, 3); + } else { + $arr[0] = substr ($arr[0], 0, 4); + } + return $arr; +} + +/** + * Gets html to display an email address given a public an private key. + * to get a key, go to: + * + * http://mailhide.recaptcha.net/apikey + */ +function recaptcha_mailhide_html($pubkey, $privkey, $email) { + $emailparts = _recaptcha_mailhide_email_parts ($email); + $url = recaptcha_mailhide_url ($pubkey, $privkey, $email); + + return htmlentities($emailparts[0]) . "...@" . htmlentities ($emailparts [1]); + +} + + +?> From 4e9db95bcfe7818f4f937f3e29f31ea64cd73330 Mon Sep 17 00:00:00 2001 From: Tom Adams Date: Thu, 30 Jul 2009 20:38:34 +0100 Subject: [PATCH 26/33] Use -
  • >
  • +
  • >
  • Date: Thu, 30 Jul 2009 19:19:12 +0100 Subject: [PATCH 27/33] Enable 404-based rewrites for lighttpd installations in / --- index.php | 18 ++++++++++++++++++ lighttpd.conf.example | 2 ++ 2 files changed, 20 insertions(+) create mode 100644 lighttpd.conf.example diff --git a/index.php b/index.php index a73983b595..c6776b11bb 100644 --- a/index.php +++ b/index.php @@ -107,6 +107,24 @@ function checkMirror($action_obj) function main() { + // fake HTTP redirects using lighttpd's 404 redirects + if (strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false) { + $_lighty_url = $base_url.$_SERVER['REQUEST_URI']; + $_lighty_url = @parse_url($_lighty_url); + + if ($_lighty_url['path'] != '/index.php' && $_lighty_url['path'] != '/') { + $_SERVER['QUERY_STRING'] = 'p='.substr($_lighty_url['path'], 1); + if ($_lighty_url['query']) + $_SERVER['QUERY_STRING'] .= '&'.$_lighty_url['query']; + parse_str($_lighty_url['query'], $_lighty_query); + foreach ($_lighty_query as $key => $val) { + $_GET[$key] = $_REQUEST[$key] = $val; + } + $_GET['p'] = $_REQUEST['p'] = substr($_lighty_url['path'], 1); + } + } + $_SERVER['REDIRECT_URL'] = preg_replace("/\?.+$/", "", $_SERVER['REQUEST_URI']); + // quick check for fancy URL auto-detection support in installer. if (isset($_SERVER['REDIRECT_URL']) && (preg_replace("/^\/$/","",(dirname($_SERVER['REQUEST_URI']))) . '/check-fancy') === $_SERVER['REDIRECT_URL']) { die("Fancy URL support detection succeeded. We suggest you enable this to get fancy (pretty) URLs."); diff --git a/lighttpd.conf.example b/lighttpd.conf.example new file mode 100644 index 0000000000..b8baafc9e3 --- /dev/null +++ b/lighttpd.conf.example @@ -0,0 +1,2 @@ +# Add this line to lighttpd.conf to enable pseudo-rewrites using 404s +server.error-handler-404 = "/index.php" From 961d2a812f4f463d48385fe78412a116fbfe71f3 Mon Sep 17 00:00:00 2001 From: Tom Adams Date: Thu, 30 Jul 2009 20:55:33 +0100 Subject: [PATCH 28/33] lighttpd rewrites now possible in other directories. --- index.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/index.php b/index.php index c6776b11bb..5f13064dab 100644 --- a/index.php +++ b/index.php @@ -113,14 +113,15 @@ function main() $_lighty_url = @parse_url($_lighty_url); if ($_lighty_url['path'] != '/index.php' && $_lighty_url['path'] != '/') { - $_SERVER['QUERY_STRING'] = 'p='.substr($_lighty_url['path'], 1); + $_lighty_path = preg_replace('/^'.preg_quote(common_config('site','path')).'\//', '', substr($_lighty_url['path'], 1)); + $_SERVER['QUERY_STRING'] = 'p='.$_lighty_path; if ($_lighty_url['query']) $_SERVER['QUERY_STRING'] .= '&'.$_lighty_url['query']; parse_str($_lighty_url['query'], $_lighty_query); foreach ($_lighty_query as $key => $val) { $_GET[$key] = $_REQUEST[$key] = $val; } - $_GET['p'] = $_REQUEST['p'] = substr($_lighty_url['path'], 1); + $_GET['p'] = $_REQUEST['p'] = $_lighty_path; } } $_SERVER['REDIRECT_URL'] = preg_replace("/\?.+$/", "", $_SERVER['REQUEST_URI']); From 20b254077925d3bc2642a6ff623432b3fb5bdd07 Mon Sep 17 00:00:00 2001 From: Tom Adams Date: Tue, 4 Aug 2009 01:22:40 +0100 Subject: [PATCH 29/33] Only warn when chars remaining < 0, not <= 0. --- js/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/util.js b/js/util.js index f3ed918cf2..fd30336b95 100644 --- a/js/util.js +++ b/js/util.js @@ -25,7 +25,7 @@ $(document).ready(function(){ var counter = $("#notice_text-count"); counter.text(remaining); - if (remaining <= 0) { + if (remaining < 0) { $("#form_notice").addClass("warning"); } else { $("#form_notice").removeClass("warning"); From 2b00990d27b55644ff133355628f948ddca6df70 Mon Sep 17 00:00:00 2001 From: Tom Adams Date: Tue, 4 Aug 2009 01:58:45 +0100 Subject: [PATCH 30/33] Prepend replyto string to message, don't destroy user input! --- js/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/util.js b/js/util.js index fd30336b95..d4db8b72a9 100644 --- a/js/util.js +++ b/js/util.js @@ -257,7 +257,7 @@ function NoticeReplySet(nick,id) { if (nick.match(rgx_username)) { replyto = "@" + nick + " "; if ($("#notice_data-text").length) { - $("#notice_data-text").val(replyto); + $("#notice_data-text").val(replyto + $("#notice_data-text").val()); $("#form_notice input#notice_in-reply-to").val(id); $("#notice_data-text").focus(); return false; From 39c420b51fb57f98780d583efaaaebd79de12db9 Mon Sep 17 00:00:00 2001 From: Tom Adams Date: Tue, 4 Aug 2009 09:22:37 +0100 Subject: [PATCH 31/33] Set focus to end of field. --- js/util.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/js/util.js b/js/util.js index d4db8b72a9..0409dc6014 100644 --- a/js/util.js +++ b/js/util.js @@ -256,10 +256,19 @@ function NoticeReplySet(nick,id) { rgx_username = /^[0-9a-zA-Z\-_.]*$/; if (nick.match(rgx_username)) { replyto = "@" + nick + " "; - if ($("#notice_data-text").length) { - $("#notice_data-text").val(replyto + $("#notice_data-text").val()); + var text = $("#notice_data-text"); + if (text.length) { + text.val(replyto + text.val()); $("#form_notice input#notice_in-reply-to").val(id); - $("#notice_data-text").focus(); + if (text.get(0).setSelectionRange) { + var len = text.val().length; + text.get(0).setSelectionRange(len,len); + text.get(0).focus(); + } else if (text.get(0).createTextRange) { + var range = text.createTextRange(); + range.collapse(false); + range.select(); + } return false; } } From 0155d02cecae565e0709a7bd0c8d1b62dd80d9bc Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Tue, 4 Aug 2009 18:45:11 +0800 Subject: [PATCH 32/33] Fixed PHP Notice "Undefined property: Profile::$value" --- lib/profilesection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/profilesection.php b/lib/profilesection.php index 9ff243fb53..d463a07b08 100644 --- a/lib/profilesection.php +++ b/lib/profilesection.php @@ -97,7 +97,7 @@ class ProfileSection extends Section $this->out->elementEnd('a'); $this->out->elementEnd('span'); $this->out->elementEnd('td'); - if ($profile->value) { + if (isset($profile->value)) { $this->out->element('td', 'value', $profile->value); } From ffa1d662a759a729151f2444bdf759749d59045e Mon Sep 17 00:00:00 2001 From: Tom Adams Date: Tue, 4 Aug 2009 16:15:36 +0100 Subject: [PATCH 33/33] Didn't test that JS in IE. Revert a little. --- js/util.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/js/util.js b/js/util.js index 0409dc6014..9d6e52b2d5 100644 --- a/js/util.js +++ b/js/util.js @@ -264,10 +264,6 @@ function NoticeReplySet(nick,id) { var len = text.val().length; text.get(0).setSelectionRange(len,len); text.get(0).focus(); - } else if (text.get(0).createTextRange) { - var range = text.createTextRange(); - range.collapse(false); - range.select(); } return false; }