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