From 698228acb9a6262f2fb4e0c82118249aa94cc0f8 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 26 Apr 2009 13:16:59 -0400 Subject: [PATCH 01/71] Revert "Revert "run 'set names' after each connection to deal with UTF8 correctly"" This reverts commit 86770ccde7914219a0a572ced6dd21fa65566e1d. --- classes/Memcached_DataObject.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 5f71f716b3..877bbf2e0f 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -227,4 +227,20 @@ class Memcached_DataObject extends DB_DataObject $c->set($ckey, $cached, MEMCACHE_COMPRESSED, $expiry); return new ArrayWrapper($cached); } + + // We overload so that 'SET NAMES "utf8"' is called for + // each connection + + function _connect() + { + global $_DB_DATAOBJECT; + $exists = !empty($this->_database_dsn_md5) && + isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]); + $result = parent::_connect(); + if (!$exists) { + $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + $DB->query('SET NAMES "utf8"'); + } + return $result; + } } From 18b130456287a24ac9024b57dc7a38d1dcaa498f Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 25 May 2009 14:23:00 -0700 Subject: [PATCH 02/71] Add EventBox to notice sources --- db/notice_source.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/db/notice_source.sql b/db/notice_source.sql index 7c31a9af49..221b1ee016 100644 --- a/db/notice_source.sql +++ b/db/notice_source.sql @@ -7,6 +7,7 @@ VALUES ('cliqset', 'Cliqset', 'http://www.cliqset.com/', now()), ('deskbar','Deskbar-Applet','http://www.gnome.org/projects/deskbar-applet/', now()), ('Do','Gnome Do','http://do.davebsd.com/wiki/index.php?title=Microblog_Plugin', now()), + ('eventbox','EventBox','http://thecosmicmachine.com/eventbox/ ', now()), ('Facebook','Facebook','http://apps.facebook.com/identica/', now()), ('feed2omb','feed2omb','http://projects.ciarang.com/p/feed2omb/', now()), ('gravity', 'Gravity', 'http://mobileways.de/gravity', now()), From 5f2cf2553cbe97b9983c8c5d0a85abb00b1a8375 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 May 2009 02:23:19 +0000 Subject: [PATCH 03/71] Ticket #1196 - Fixed: Warning on invite tab in Facebook App --- actions/facebookinvite.php | 65 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/actions/facebookinvite.php b/actions/facebookinvite.php index 1302064ad7..2207580f77 100644 --- a/actions/facebookinvite.php +++ b/actions/facebookinvite.php @@ -17,7 +17,9 @@ * along with this program. If not, see . */ -if (!defined('LACONICA')) { exit(1); } +if (!defined('LACONICA')) { + exit(1); +} require_once(INSTALLDIR.'/lib/facebookaction.php'); @@ -67,7 +69,7 @@ class FacebookinviteAction extends FacebookAction function showSuccessContent() { - $this->element('h2', null, sprintf(_('Thanks for inviting your friends to use %s'), + $this->element('h2', null, sprintf(_('Thanks for inviting your friends to use %s'), common_config('site', 'name'))); $this->element('p', null, _('Invitations have been sent to the following users:')); @@ -89,16 +91,6 @@ class FacebookinviteAction extends FacebookAction function showFormContent() { - - // Get a list of users who are already using the app for exclusion - $exclude_ids = $this->facebook->api_client->friends_getAppUsers(); - $exclude_ids_csv = null; - - // fbml needs these as a csv string, not an array - if ($exclude_ids) { - $exclude_ids_csv = implode(',', $exclude_ids); - } - $content = sprintf(_('You have been invited to %s'), common_config('site', 'name')) . htmlentities(''); @@ -109,36 +101,43 @@ class FacebookinviteAction extends FacebookAction 'content' => $content)); $this->hidden('invite', 'true'); $actiontext = sprintf(_('Invite your friends to use %s'), common_config('site', 'name')); - - $multi_params = array('showborder' => 'false'); + + $multi_params = array('showborder' => 'false'); $multi_params['actiontext'] = $actiontext; - - if ($exclude_ids_csv) { + $multi_params['bypass'] = 'cancel'; + + // Get a list of users who are already using the app for exclusion + $exclude_ids = $this->facebook->api_client->friends_getAppUsers(); + $exclude_ids_csv = null; + + // fbml needs these as a csv string, not an array + if ($exclude_ids) { + $exclude_ids_csv = implode(',', $exclude_ids); $multi_params['exclude_ids'] = $exclude_ids_csv; } - $multi_params['bypass'] = 'cancel'; - $this->element('fb:multi-friend-selector', $multi_params); - $this->elementEnd('fb:request-form'); - $this->element('h2', null, sprintf(_('Friends already using %s:'), - common_config('site', 'name'))); - $this->elementStart('ul', array('id' => 'facebook-friends')); - - foreach ($exclude_ids as $friend) { - $this->elementStart('li'); - $this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square')); - $this->element('fb:name', array('uid' => $friend, - 'capitalize' => 'true')); - $this->elementEnd('li'); - } + if ($exclude_ids) { - $this->elementEnd("ul"); + $this->element('h2', null, sprintf(_('Friends already using %s:'), + common_config('site', 'name'))); + $this->elementStart('ul', array('id' => 'facebook-friends')); + + foreach ($exclude_ids as $friend) { + $this->elementStart('li'); + $this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square')); + $this->element('fb:name', array('uid' => $friend, + 'capitalize' => 'true')); + $this->elementEnd('li'); + } + + $this->elementEnd("ul"); + } } - - function title() + + function title() { return sprintf(_('Send invitations')); } From a1f83b293394af34782f74640c3d29f800bdc0bf Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 26 May 2009 15:22:12 -0400 Subject: [PATCH 04/71] added qtwitter --- db/notice_source.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/db/notice_source.sql b/db/notice_source.sql index 7c31a9af49..e7ccadde3a 100644 --- a/db/notice_source.sql +++ b/db/notice_source.sql @@ -28,6 +28,7 @@ VALUES ('pingvine','PingVine','http://pingvine.com/', now()), ('pocketwit','PockeTwit','http://code.google.com/p/pocketwit/', now()), ('posty','Posty','http://spreadingfunkyness.com/posty/', now()), + ('qtwitter','qTwitter','http://qtwitter.ayoy.net/', 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()), From 74b08bff53b614a0d3da56e848940cd3e9ca70ce Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 26 May 2009 15:27:13 -0400 Subject: [PATCH 05/71] Added AgentSolo.com --- db/notice_source.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/db/notice_source.sql b/db/notice_source.sql index a22967b1b1..ac73d3d13b 100644 --- a/db/notice_source.sql +++ b/db/notice_source.sql @@ -2,6 +2,7 @@ INSERT INTO notice_source (code, name, url, created) VALUES ('adium', 'Adium', 'http://www.adiumx.com/', now()), + ('AgentSolo.com','AgentSolo.com','http://www.agentsolo.com/', now()), ('betwittered','BeTwittered','http://www.32hours.com/betwitteredinfo/', now()), ('bti','bti','http://gregkh.github.com/bti/', now()), ('cliqset', 'Cliqset', 'http://www.cliqset.com/', now()), From b2c28faf526e55ddc97d42692048e644b004e1f8 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 May 2009 21:14:42 +0000 Subject: [PATCH 06/71] Removed silly "dented:" status prefix from Facebook App --- actions/facebookhome.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/actions/facebookhome.php b/actions/facebookhome.php index 5946e6c984..4c2b26355c 100644 --- a/actions/facebookhome.php +++ b/actions/facebookhome.php @@ -138,9 +138,6 @@ class FacebookhomeAction extends FacebookAction function setDefaults() { - // A default prefix string for notices - $this->facebook->api_client->data_setUserPreference( - FACEBOOK_NOTICE_PREFIX, 'dented: '); $this->facebook->api_client->data_setUserPreference( FACEBOOK_PROMPTED_UPDATE_PREF, 'false'); } From e1494042105b3842f951acf1e82ed10967fb2c21 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 25 May 2009 14:23:00 -0700 Subject: [PATCH 07/71] Add EventBox to notice sources --- db/notice_source.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/db/notice_source.sql b/db/notice_source.sql index f351bb0668..1508af1ec8 100644 --- a/db/notice_source.sql +++ b/db/notice_source.sql @@ -7,6 +7,7 @@ VALUES ('cliqset', 'Cliqset', 'http://www.cliqset.com/', now()), ('deskbar','Deskbar-Applet','http://www.gnome.org/projects/deskbar-applet/', now()), ('Do','Gnome Do','http://do.davebsd.com/wiki/index.php?title=Microblog_Plugin', now()), + ('eventbox','EventBox','http://thecosmicmachine.com/eventbox/ ', now()), ('Facebook','Facebook','http://apps.facebook.com/identica/', now()), ('feed2omb','feed2omb','http://projects.ciarang.com/p/feed2omb/', now()), ('gravity', 'Gravity', 'http://mobileways.de/gravity', now()), From 0a4ce0ef9938ba91ff3d9286ea2cffb4cebd528b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 May 2009 02:23:19 +0000 Subject: [PATCH 08/71] Ticket #1196 - Fixed: Warning on invite tab in Facebook App --- actions/facebookinvite.php | 65 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/actions/facebookinvite.php b/actions/facebookinvite.php index 1302064ad7..2207580f77 100644 --- a/actions/facebookinvite.php +++ b/actions/facebookinvite.php @@ -17,7 +17,9 @@ * along with this program. If not, see . */ -if (!defined('LACONICA')) { exit(1); } +if (!defined('LACONICA')) { + exit(1); +} require_once(INSTALLDIR.'/lib/facebookaction.php'); @@ -67,7 +69,7 @@ class FacebookinviteAction extends FacebookAction function showSuccessContent() { - $this->element('h2', null, sprintf(_('Thanks for inviting your friends to use %s'), + $this->element('h2', null, sprintf(_('Thanks for inviting your friends to use %s'), common_config('site', 'name'))); $this->element('p', null, _('Invitations have been sent to the following users:')); @@ -89,16 +91,6 @@ class FacebookinviteAction extends FacebookAction function showFormContent() { - - // Get a list of users who are already using the app for exclusion - $exclude_ids = $this->facebook->api_client->friends_getAppUsers(); - $exclude_ids_csv = null; - - // fbml needs these as a csv string, not an array - if ($exclude_ids) { - $exclude_ids_csv = implode(',', $exclude_ids); - } - $content = sprintf(_('You have been invited to %s'), common_config('site', 'name')) . htmlentities(''); @@ -109,36 +101,43 @@ class FacebookinviteAction extends FacebookAction 'content' => $content)); $this->hidden('invite', 'true'); $actiontext = sprintf(_('Invite your friends to use %s'), common_config('site', 'name')); - - $multi_params = array('showborder' => 'false'); + + $multi_params = array('showborder' => 'false'); $multi_params['actiontext'] = $actiontext; - - if ($exclude_ids_csv) { + $multi_params['bypass'] = 'cancel'; + + // Get a list of users who are already using the app for exclusion + $exclude_ids = $this->facebook->api_client->friends_getAppUsers(); + $exclude_ids_csv = null; + + // fbml needs these as a csv string, not an array + if ($exclude_ids) { + $exclude_ids_csv = implode(',', $exclude_ids); $multi_params['exclude_ids'] = $exclude_ids_csv; } - $multi_params['bypass'] = 'cancel'; - $this->element('fb:multi-friend-selector', $multi_params); - $this->elementEnd('fb:request-form'); - $this->element('h2', null, sprintf(_('Friends already using %s:'), - common_config('site', 'name'))); - $this->elementStart('ul', array('id' => 'facebook-friends')); - - foreach ($exclude_ids as $friend) { - $this->elementStart('li'); - $this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square')); - $this->element('fb:name', array('uid' => $friend, - 'capitalize' => 'true')); - $this->elementEnd('li'); - } + if ($exclude_ids) { - $this->elementEnd("ul"); + $this->element('h2', null, sprintf(_('Friends already using %s:'), + common_config('site', 'name'))); + $this->elementStart('ul', array('id' => 'facebook-friends')); + + foreach ($exclude_ids as $friend) { + $this->elementStart('li'); + $this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square')); + $this->element('fb:name', array('uid' => $friend, + 'capitalize' => 'true')); + $this->elementEnd('li'); + } + + $this->elementEnd("ul"); + } } - - function title() + + function title() { return sprintf(_('Send invitations')); } From 0664be924f5f24de0e63d63ea78a56d798a67362 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 May 2009 21:14:42 +0000 Subject: [PATCH 09/71] Removed silly "dented:" status prefix from Facebook App --- actions/facebookhome.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/actions/facebookhome.php b/actions/facebookhome.php index 5946e6c984..4c2b26355c 100644 --- a/actions/facebookhome.php +++ b/actions/facebookhome.php @@ -138,9 +138,6 @@ class FacebookhomeAction extends FacebookAction function setDefaults() { - // A default prefix string for notices - $this->facebook->api_client->data_setUserPreference( - FACEBOOK_NOTICE_PREFIX, 'dented: '); $this->facebook->api_client->data_setUserPreference( FACEBOOK_PROMPTED_UPDATE_PREF, 'false'); } From 69a656af2210acf9211aa4fcb1c05bfd5c5a2e1f Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 May 2009 23:21:10 +0000 Subject: [PATCH 10/71] Some adjustments to allow the Facebook app work with Laconica 0.8 --- actions/facebookhome.php | 2 +- lib/facebookaction.php | 262 ++++++++++++++++----------------------- 2 files changed, 110 insertions(+), 154 deletions(-) diff --git a/actions/facebookhome.php b/actions/facebookhome.php index 4c2b26355c..00b35ef686 100644 --- a/actions/facebookhome.php +++ b/actions/facebookhome.php @@ -115,7 +115,7 @@ class FacebookhomeAction extends FacebookAction $flink->foreign_id = $this->fbuid; $flink->service = FACEBOOK_SERVICE; $flink->created = common_sql_now(); - $flink->set_flags(true, false, false); + $flink->set_flags(true, false, false, false); $flink_id = $flink->insert(); diff --git a/lib/facebookaction.php b/lib/facebookaction.php index 637a6284d9..a445750f7e 100644 --- a/lib/facebookaction.php +++ b/lib/facebookaction.php @@ -38,14 +38,14 @@ require_once INSTALLDIR.'/lib/noticeform.php'; class FacebookAction extends Action { - + var $facebook = null; var $fbuid = null; var $flink = null; var $action = null; var $app_uri = null; var $app_name = null; - + /** * Constructor * @@ -60,71 +60,71 @@ class FacebookAction extends Action function __construct($output='php://output', $indent=true, $facebook=null, $flink=null) { parent::__construct($output, $indent); - + $this->facebook = $facebook; $this->flink = $flink; - + if ($this->flink) { - $this->fbuid = $flink->foreign_id; + $this->fbuid = $flink->foreign_id; $this->user = $flink->getUser(); } - + $this->args = array(); } - + function prepare($argarray) - { + { parent::prepare($argarray); - + $this->facebook = getFacebook(); $this->fbuid = $this->facebook->require_login(); - + $this->action = $this->trimmed('action'); - + $app_props = $this->facebook->api_client->Admin_getAppProperties( array('canvas_name', 'application_name')); - + $this->app_uri = 'http://apps.facebook.com/' . $app_props['canvas_name']; $this->app_name = $app_props['application_name']; $this->flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE); - + return true; - + } - + function showStylesheets() { // Add a timestamp to the file so Facebook cache wont ignore our changes $ts = filemtime(INSTALLDIR.'/theme/base/css/display.css'); - $this->element('link', array('rel' => 'stylesheet', - 'type' => 'text/css', - 'href' => theme_path('css/display.css', 'base') . '?ts=' . $ts)); - + $this->element('link', array('rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => theme_path('css/display.css', 'base') . '?ts=' . $ts)); + $theme = common_config('site', 'theme'); - + $ts = filemtime(INSTALLDIR. '/theme/' . $theme .'/css/display.css'); - + $this->element('link', array('rel' => 'stylesheet', 'type' => 'text/css', 'href' => theme_path('css/display.css', null) . '?ts=' . $ts)); - + $ts = filemtime(INSTALLDIR.'/theme/base/css/facebookapp.css'); - + $this->element('link', array('rel' => 'stylesheet', 'type' => 'text/css', 'href' => theme_path('css/facebookapp.css', 'base') . '?ts=' . $ts)); } - + function showScripts() { // Add a timestamp to the file so Facebook cache wont ignore our changes $ts = filemtime(INSTALLDIR.'/js/facebookapp.js'); - + $this->element('script', array('src' => common_path('js/facebookapp.js') . '?ts=' . $ts)); } - + /** * Start an Facebook ready HTML document * @@ -138,11 +138,11 @@ class FacebookAction extends Action * @return void */ - function startHTML($type=null) - { + function startHTML($type=null) + { $this->showStylesheets(); $this->showScripts(); - + $this->elementStart('div', array('class' => 'facebook-page')); } @@ -177,18 +177,18 @@ class FacebookAction extends Action $this->showFooter(); $this->elementEnd('div'); } - + function showAside() { } function showHead($error, $success) { - + if ($error) { $this->element("h1", null, $error); } - + if ($success) { $this->element("h1", null, $success); } @@ -198,10 +198,10 @@ class FacebookAction extends Action $this->element('fb:add-section-button', array('section' => 'profile')); $this->elementEnd('span'); $this->elementEnd('fb:if-section-not-added'); - + } - + // Make this into a widget later function showLocalNav() { @@ -229,8 +229,8 @@ class FacebookAction extends Action $this->elementEnd('li'); $this->elementEnd('ul'); - } - + } + /** * Show header of the page. * @@ -245,7 +245,7 @@ class FacebookAction extends Action $this->showNoticeForm(); $this->elementEnd('div'); } - + /** * Show page, a template method. * @@ -258,7 +258,7 @@ class FacebookAction extends Action $this->showBody(); $this->endHTML(); } - + function showInstructions() { @@ -278,7 +278,7 @@ class FacebookAction extends Action $this->element('a', array('href' => common_local_url('register')), _('Register')); $this->text($loginmsg_part2); - $this->elementEnd('p'); + $this->elementEnd('p'); $this->elementEnd('dd'); $this->elementEnd('dl'); @@ -317,7 +317,7 @@ class FacebookAction extends Action $this->elementEnd('ul'); $this->submit('submit', _('Login')); - $this->elementEnd('fieldset'); + $this->elementEnd('fieldset'); $this->elementEnd('form'); $this->elementStart('p'); @@ -329,73 +329,73 @@ class FacebookAction extends Action $this->elementEnd('div'); } - - + + function updateProfileBox($notice) { // Need to include inline CSS for styling the Profile box - $app_props = $this->facebook->api_client->Admin_getAppProperties(array('icon_url')); - $icon_url = $app_props['icon_url']; + $app_props = $this->facebook->api_client->Admin_getAppProperties(array('icon_url')); + $icon_url = $app_props['icon_url']; $style = ''; + #facebook_laconica_app { + text-indent:-9999px; + height:16px; + width:16px; + display:block; + background:url('.$icon_url.') no-repeat 0 0; + float:right; + } + '; $this->xw->openMemory(); @@ -407,12 +407,12 @@ class FacebookAction extends Action $fbml_main = "$style " . $this->xw->outputMemory(false) . ""; - $this->facebook->api_client->profile_setFBML(null, $this->fbuid, $fbml, null, null, $fbml_main); + $this->facebook->api_client->profile_setFBML(null, $this->fbuid, $fbml, null, null, $fbml_main); $this->xw->openURI('php://output'); } - - + + /** * Generate pagination links * @@ -457,24 +457,24 @@ class FacebookAction extends Action $this->elementEnd('div'); } } - - function updateFacebookStatus($notice) + + function updateFacebookStatus($notice) { $prefix = $this->facebook->api_client->data_getUserPreference(FACEBOOK_NOTICE_PREFIX, $this->fbuid); $content = "$prefix $notice->content"; - + if ($this->facebook->api_client->users_hasAppPermission('status_update', $this->fbuid)) { $this->facebook->api_client->users_setStatus($content, $this->fbuid, false, true); } } - + function saveNewNotice() { $user = $this->flink->getUser(); $content = $this->trimmed('status_textarea'); - + if (!$content) { $this->showPage(_('No notice content!')); return; @@ -492,9 +492,9 @@ class FacebookAction extends Action $cmd = $inter->handle_command($user, $content_shortened); if ($cmd) { - + // XXX fix this - + $cmd->execute(new WebChannel()); return; } @@ -510,20 +510,20 @@ class FacebookAction extends Action } common_broadcast_notice($notice); - + // Also update the user's Facebook status $this->updateFacebookStatus($notice); $this->updateProfileBox($notice); - + } } -class FacebookNoticeForm extends NoticeForm +class FacebookNoticeForm extends NoticeForm { - + var $post_action = null; - + /** * Constructor * @@ -532,13 +532,13 @@ class FacebookNoticeForm extends NoticeForm * @param string $content content to pre-fill */ - function __construct($out=null, $action=null, $content=null, + function __construct($out=null, $action=null, $content=null, $post_action=null, $user=null) { parent::__construct($out, $action, $content, $user); $this->post_action = $post_action; } - + /** * Action of the form * @@ -554,7 +554,7 @@ class FacebookNoticeForm extends NoticeForm class FacebookNoticeList extends NoticeList { - + /** * constructor * @@ -565,7 +565,7 @@ class FacebookNoticeList extends NoticeList { parent::__construct($notice, $out); } - + /** * show the list of notices * @@ -619,7 +619,7 @@ class FacebookNoticeList extends NoticeList } class FacebookNoticeListItem extends NoticeListItem -{ +{ /** * constructor @@ -646,51 +646,19 @@ class FacebookNoticeListItem extends NoticeListItem function show() { $this->showStart(); + $this->showNotice(); + $this->showNoticeInfo(); - $this->out->elementStart('div', 'entry-title'); - $this->showAuthor(); - $this->showContent(); - $this->out->elementEnd('div'); - - $this->out->elementStart('div', 'entry-content'); - $this->showNoticeLink(); - $this->showNoticeSource(); - $this->showReplyTo(); - $this->out->elementEnd('div'); + // XXX: Need to update to show attachements and controls $this->showEnd(); } - function showNoticeLink() - { - $noticeurl = common_local_url('shownotice', - array('notice' => $this->notice->id)); - // XXX: we need to figure this out better. Is this right? - if (strcmp($this->notice->uri, $noticeurl) != 0 && - preg_match('/^http/', $this->notice->uri)) { - $noticeurl = $this->notice->uri; - } - - $this->out->elementStart('dl', 'timestamp'); - $this->out->element('dt', null, _('Published')); - $this->out->elementStart('dd', null); - $this->out->elementStart('a', array('rel' => 'bookmark', - 'href' => $noticeurl)); - $dt = common_date_iso8601($this->notice->created); - $this->out->element('abbr', array('class' => 'published', - 'title' => $dt), - common_date_string($this->notice->created)); - $this->out->elementEnd('a'); - $this->out->elementEnd('dd'); - $this->out->elementEnd('dl'); - } - } - class FacebookProfileBoxNotice extends FacebookNoticeListItem -{ - +{ + /** * constructor * @@ -703,36 +671,24 @@ class FacebookProfileBoxNotice extends FacebookNoticeListItem { parent::__construct($notice, $out); } - + /** - * Recipe function for displaying a single notice in the - * Facebook App's Profile + * Recipe function for displaying a single notice in the + * Facebook App profile notice box * * @return void */ function show() { - - $this->out->elementStart('div', 'entry-title'); - $this->showAuthor(); - $this->showContent(); - $this->out->elementEnd('div'); - - $this->out->elementStart('div', 'entry-content'); - - $this->showNoticeLink(); - $this->showNoticeSource(); - $this->showReplyTo(); - $this->out->elementEnd('div'); - + $this->showNotice(); + $this->showNoticeInfo(); $this->showAppLink(); - } - function showAppLink() + function showAppLink() { - + $this->facebook = getFacebook(); $app_props = $this->facebook->api_client->Admin_getAppProperties( @@ -740,7 +696,7 @@ class FacebookProfileBoxNotice extends FacebookNoticeListItem $this->app_uri = 'http://apps.facebook.com/' . $app_props['canvas_name']; $this->app_name = $app_props['application_name']; - + $this->out->elementStart('a', array('id' => 'facebook_laconica_app', 'href' => $this->app_uri)); $this->out->text($this->app_name); From 1034bd67b015ae3023042dc3cb0ba684e3ee4c72 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 May 2009 23:59:12 +0000 Subject: [PATCH 11/71] Fixed warning on Facebook app settings page --- actions/facebooksettings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/facebooksettings.php b/actions/facebooksettings.php index 236460c1c9..227e123160 100644 --- a/actions/facebooksettings.php +++ b/actions/facebooksettings.php @@ -55,7 +55,7 @@ class FacebooksettingsAction extends FacebookAction $prefix = $this->trimmed('prefix'); $original = clone($this->flink); - $this->flink->set_flags($noticesync, $replysync, false); + $this->flink->set_flags($noticesync, $replysync, false, false); $result = $this->flink->update($original); $this->facebook->api_client->data_setUserPreference(FACEBOOK_NOTICE_PREFIX, From 2b81dcb25329c496c7c4f2c99334b79ba0421c1e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 15 May 2009 21:44:58 +0000 Subject: [PATCH 12/71] Better err handling when trying to get Facebook client --- lib/facebookutil.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/facebookutil.php b/lib/facebookutil.php index ec39872732..242d2e06f2 100644 --- a/lib/facebookutil.php +++ b/lib/facebookutil.php @@ -27,9 +27,21 @@ define("FACEBOOK_PROMPTED_UPDATE_PREF", 2); function getFacebook() { + static $facebook = null; + $apikey = common_config('facebook', 'apikey'); $secret = common_config('facebook', 'secret'); - return new Facebook($apikey, $secret); + + if ($facebook === null) { + $facebook = new Facebook($apikey, $secret); + } + + if (!$facebook) { + common_log(LOG_ERR, 'Could not make new Facebook client obj!', + __FILE__); + } + + return $facebook; } function updateProfileBox($facebook, $flink, $notice) { @@ -92,7 +104,6 @@ function isFacebookBound($notice, $flink) { } - function facebookBroadcastNotice($notice) { $facebook = getFacebook(); From af700ea27703bbec5aa1078a84f9fd44c0260322 Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Tue, 26 May 2009 21:20:04 -0400 Subject: [PATCH 13/71] Let's you upload a file with a notice and have it shown with other attachments. --- actions/newnotice.php | 37 +++++++++++++++++++++++++++++++++++-- js/jquery.form.js | 4 ++-- js/util.js | 12 ++++++++++++ lib/attachmentlist.php | 6 ++++-- lib/form.php | 16 +++++++++++----- lib/noticeform.php | 12 ++++++++++-- lib/noticelist.php | 11 +++++++++++ lib/router.php | 6 ++++++ 8 files changed, 91 insertions(+), 13 deletions(-) diff --git a/actions/newnotice.php b/actions/newnotice.php index ae0ff96363..d7507c1182 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -109,6 +109,10 @@ class NewnoticeAction extends Action } } + function isFileAttached() { + return $_FILES['attach']['error'] === UPLOAD_ERR_OK; + } + /** * Save a new notice, based on arguments * @@ -158,7 +162,6 @@ class NewnoticeAction extends Action $replyto = 'false'; } -// $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1, $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1, ($replyto == 'false') ? null : $replyto); @@ -167,6 +170,9 @@ class NewnoticeAction extends Action return; } + if ($this->isFileAttached()) { + $this->storeFile($notice); + } $this->saveUrls($notice); common_broadcast_notice($notice); @@ -194,6 +200,33 @@ class NewnoticeAction extends Action } } + function storeFile($notice) { + $filename = basename($_FILES['attach']['name']); + $destination = "file/{$notice->id}-$filename"; + if (move_uploaded_file($_FILES['attach']['tmp_name'], INSTALLDIR . "/$destination")) { + $file = new File; +// $file->url = common_local_url('file', array('notice' => $notice->id)); + $file->url = common_path($destination); + $file->size = filesize(INSTALLDIR . "/$destination"); + $file->date = time(); + $file->mimetype = $_FILES['attach']['type']; + if ($ok = $file->insert()) { + $f2p = new File_to_post; + $f2p->file_id = $ok; + $f2p->post_id = $notice->id; + $f2p->insert(); + } else { + die('inserting file, dying'); + } + } +/* + $url = common_local_url('file', array('notice' => $notice->id)); + echo "$destination
"; + die($url); +*/ + } + + /** save all urls in the notice to the db * * follow redirects and save all available file information @@ -203,7 +236,7 @@ class NewnoticeAction extends Action * * @return void */ - function saveUrls($notice) { + function saveUrls($notice, $uploaded = null) { common_replace_urls_callback($notice->content, array($this, 'saveUrl'), $notice->id); } diff --git a/js/jquery.form.js b/js/jquery.form.js index cb8b5a6609..936b847abe 100644 --- a/js/jquery.form.js +++ b/js/jquery.form.js @@ -157,7 +157,7 @@ $.fn.ajaxSubmit = function(options) { function fileUpload() { var form = $form[0]; - if ($(':input[@name=submit]', form).length) { + if ($(':input[name=submit]', form).length) { alert('Error: Form elements must not be named "submit".'); return; } @@ -570,7 +570,7 @@ $.fn.clearForm = function() { $.fn.clearFields = $.fn.clearInputs = function() { return this.each(function() { var t = this.type, tag = this.tagName.toLowerCase(); - if (t == 'text' || t == 'password' || tag == 'textarea') + if (t == 'file' || t == 'text' || t == 'password' || tag == 'textarea') this.value = ''; else if (t == 'checkbox' || t == 'radio') this.checked = false; diff --git a/js/util.js b/js/util.js index b1b6ec82bd..85ab48b4c0 100644 --- a/js/util.js +++ b/js/util.js @@ -17,6 +17,17 @@ */ $(document).ready(function(){ + $('input#notice_data-attach').toggle(); + $('label[for=notice_data-attach]').text('Upload a file as an attachment?'); + $('label[for=notice_data-attach]').click(function () { + if ('Upload a file as an attachment?' == $(this).text()) { + $(this).text('Upload: '); + $('input#notice_data-attach').slideDown('fast'); + } else { + $('input#notice_data-attach').slideUp('fast', function() {$('label[for=notice_data-attach]').text('Upload a file as an attachment?');}); + } + }); + $('a.attachment').click(function() {$().jOverlay({url: $('address .url')[0].href+'/attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'}); return false; }); $("a.thumbnail").hover( function() { @@ -227,6 +238,7 @@ $(document).ready(function(){ } } $("#notice_data-text").val(""); + $("#notice_data-attach").val(""); counter(); } $("#form_notice").removeClass("processing"); diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index d0478bad35..a58a50f6fe 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -80,13 +80,15 @@ class AttachmentList extends Widget function show() { + $atts = new File; + $att = $atts->getAttachments($this->notice->id); + if (empty($att)) return 0; + $this->out->elementStart('dl', array('id' =>'attachment')); $this->out->element('dt', null, _('Attachments')); $this->out->elementStart('dd'); $this->out->elementStart('ul', array('class' => 'attachments')); - $atts = new File; - $att = $atts->getAttachments($this->notice->id); foreach ($att as $n=>$attachment) { $item = $this->newListItem($attachment); $item->show(); diff --git a/lib/form.php b/lib/form.php index 5317df4715..f872aef0b5 100644 --- a/lib/form.php +++ b/lib/form.php @@ -52,6 +52,8 @@ require_once INSTALLDIR.'/lib/widget.php'; class Form extends Widget { + var $enctype = null; + /** * Show the form * @@ -63,11 +65,15 @@ class Form extends Widget function show() { - $this->out->elementStart('form', - array('id' => $this->id(), - 'class' => $this->formClass(), - 'method' => 'post', - 'action' => $this->action())); + $attributes = array('id' => $this->id(), + 'class' => $this->formClass(), + 'method' => 'post', + 'action' => $this->action()); + + if (!empty($this->enctype)) { + $attributes['enctype'] = $this->enctype; + } + $this->out->elementStart('form', $attributes); $this->out->elementStart('fieldset'); $this->formLegend(); $this->sessionToken(); diff --git a/lib/noticeform.php b/lib/noticeform.php index 606b5d028e..707768cd59 100644 --- a/lib/noticeform.php +++ b/lib/noticeform.php @@ -89,7 +89,8 @@ class NoticeForm extends Form } else { $this->user = common_current_user(); } - + + $this->enctype = 'multipart/form-data'; } /** @@ -136,18 +137,25 @@ class NoticeForm extends Form { $this->out->element('label', array('for' => 'notice_data-text'), sprintf(_('What\'s up, %s?'), $this->user->nickname)); + $this->out->elementStart('span', array('style' => 'float: right; margin-top: 2em;')); +// $this->out->element('a', array('href' => '#attach'), ' [ATTACH]'); + $this->out->elementEnd('span'); // XXX: vary by defined max size $this->out->element('textarea', array('id' => 'notice_data-text', 'cols' => 35, 'rows' => 4, 'name' => 'status_textarea'), ($this->content) ? $this->content : ''); - $this->out->elementStart('dl', 'form_note'); $this->out->element('dt', null, _('Available characters')); $this->out->element('dd', array('id' => 'notice_text-count'), '140'); $this->out->elementEnd('dl'); + $this->out->element('br', array('style' => 'clear:both')); +// $this->out->elementStart('a', array('href' => '#')); + $this->out->element('label', array('for' => 'notice_data-attach'), _('Upload: ')); +// $this->out->elementEnd('a'); + $this->out->element('input', array('id' => 'notice_data-attach', 'type' => 'file', 'name' => 'attach')); if ($this->action) { $this->out->hidden('notice_return-to', $this->action, 'returnto'); diff --git a/lib/noticelist.php b/lib/noticelist.php index 50a95cfcbb..420272515e 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -206,6 +206,7 @@ class NoticeListItem extends Widget return 'shownotice' !== $this->out->args['action']; } +/* function attachmentCount($discriminant = true) { $file_oembed = new File_oembed; $query = "select count(*) as c from file_oembed join file_to_post on file_oembed.file_id = file_to_post.file_id where post_id=" . $this->notice->id; @@ -213,11 +214,16 @@ class NoticeListItem extends Widget $file_oembed->fetch(); return intval($file_oembed->c); } +*/ + + function showWithAttachment() { + } function showNoticeInfo() { $this->out->elementStart('div', 'entry-content'); $this->showNoticeLink(); +// $this->showWithAttachment(); $this->showNoticeSource(); $this->showContext(); $this->out->elementEnd('div'); @@ -388,6 +394,11 @@ class NoticeListItem extends Widget $this->out->element('abbr', array('class' => 'published', 'title' => $dt), common_date_string($this->notice->created)); + + $f2p = File_to_post::staticGet('post_id', $this->notice->id); + if (!empty($f2p)) { + $this->out->text(_(' (with attachments) ')); + } $this->out->elementEnd('a'); $this->out->elementEnd('dd'); $this->out->elementEnd('dl'); diff --git a/lib/router.php b/lib/router.php index fc119821b9..bd482eafab 100644 --- a/lib/router.php +++ b/lib/router.php @@ -164,6 +164,12 @@ class Router array('action' => 'newnotice'), array('replyto' => '[A-Za-z0-9_-]+')); +/* + $m->connect('notice/:notice/file', + array('action' => 'file'), + array('notice' => '[0-9]+')); +*/ + $m->connect('notice/:notice', array('action' => 'shownotice'), array('notice' => '[0-9]+')); From ed1e1d9f440acfbb99c7a6e3d2b5c11fdcfa27ce Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Wed, 27 May 2009 03:47:45 -0400 Subject: [PATCH 14/71] Handles local aliases (redirection) for file uploads attached to notices. --- .gitignore | 6 ++++++ actions/file.php | 40 ++++++++++++++++++++++++++++++++++++ actions/newnotice.php | 15 +++++++++----- classes/File_redirection.php | 2 +- lib/router.php | 2 -- lib/util.php | 5 +++++ 6 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 actions/file.php diff --git a/.gitignore b/.gitignore index da6947bfdc..3418d8ee54 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ avatar/* files/* +file/* _darcs/* logs/* config.php @@ -16,3 +17,8 @@ dataobject.ini .buildpath .project .settings +TODO.rym +config-*.php +good-config.php +lac08.log +php.log diff --git a/actions/file.php b/actions/file.php new file mode 100644 index 0000000000..1179dbe9ac --- /dev/null +++ b/actions/file.php @@ -0,0 +1,40 @@ +. + */ + +if (!defined('LACONICA')) { exit(1); } + +require_once(INSTALLDIR.'/actions/shownotice.php'); + +class FileAction extends ShowNoticeAction +{ + function showPage() { + $source_url = common_local_url('file', array('notice' => $this->notice->id)); + $query = "select file_redirection.url as url from file join file_redirection on file.id = file_redirection.file_id where file.url = '$source_url'"; + $file = new File_redirection; + $file->query($query); + $file->fetch(); + if (empty($file->url)) { + die('nothing attached here'); + } else { + header("Location: {$file->url}"); + die(); + } + } +} + diff --git a/actions/newnotice.php b/actions/newnotice.php index d7507c1182..aebdab3cca 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -110,7 +110,7 @@ class NewnoticeAction extends Action } function isFileAttached() { - return $_FILES['attach']['error'] === UPLOAD_ERR_OK; + return isset($_FILES['attach']['error']) && ($_FILES['attach']['error'] === UPLOAD_ERR_OK); } /** @@ -205,14 +205,19 @@ class NewnoticeAction extends Action $destination = "file/{$notice->id}-$filename"; if (move_uploaded_file($_FILES['attach']['tmp_name'], INSTALLDIR . "/$destination")) { $file = new File; -// $file->url = common_local_url('file', array('notice' => $notice->id)); - $file->url = common_path($destination); + $file->url = common_local_url('file', array('notice' => $notice->id)); +// $file->url = common_path($destination); $file->size = filesize(INSTALLDIR . "/$destination"); $file->date = time(); $file->mimetype = $_FILES['attach']['type']; - if ($ok = $file->insert()) { + if ($file_id = $file->insert()) { + $file_redir = new File_redirection; + $file_redir->url = common_path($destination); + $file_redir->file_id = $file_id; + $file_redir->insert(); + $f2p = new File_to_post; - $f2p->file_id = $ok; + $f2p->file_id = $file_id; $f2p->post_id = $notice->id; $f2p->insert(); } else { diff --git a/classes/File_redirection.php b/classes/File_redirection.php index 0eae681783..212cc36158 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -133,7 +133,7 @@ class File_redirection extends Memcached_DataObject $file->limit(1); $file->orderBy('len'); $file->find(true); - if (!empty($file->id)) { + if (!empty($file->url) && (strlen($file->url) < strlen($long_url))) { return $file->url; } diff --git a/lib/router.php b/lib/router.php index bd482eafab..456d1793e3 100644 --- a/lib/router.php +++ b/lib/router.php @@ -164,11 +164,9 @@ class Router array('action' => 'newnotice'), array('replyto' => '[A-Za-z0-9_-]+')); -/* $m->connect('notice/:notice/file', array('action' => 'file'), array('notice' => '[0-9]+')); -*/ $m->connect('notice/:notice', array('action' => 'shownotice'), diff --git a/lib/util.php b/lib/util.php index d56f44f7b4..ab12723098 100644 --- a/lib/util.php +++ b/lib/util.php @@ -499,6 +499,11 @@ function common_linkify($url) { // if this URL is an attachment, then we set class='attachment' and id='attahcment-ID' // where ID is the id of the attachment for the given URL. +// +// we need a better test telling what can be shown as an attachment +// we're currently picking up oembeds only. +// I think the best option is another file_view table in the db +// and associated dbobject. $query = "select file_oembed.file_id as file_id from file join file_oembed on file.id = file_oembed.file_id where file.url='$longurl'"; $file = new File; $file->query($query); From d068680e1913a32eba0f5b0e1e746ac66ec638d3 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 May 2009 14:57:45 -0400 Subject: [PATCH 15/71] optionally use SET NAMES for utf8 to DB --- README | 4 ++++ classes/Memcached_DataObject.php | 4 +++- lib/common.php | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/README b/README index 7b2dcacc50..c23d4e464a 100644 --- a/README +++ b/README @@ -906,6 +906,10 @@ mirror: you can set this to an array of DSNs, like the above and adding the slaves to this array. Note that if you want some requests to go to the 'database' (master) server, you'll need to include it in this array, too. +utf8: whether to talk to the database in UTF-8 mode. This is the default + with new installations, but older sites may want to turn it off + until they get their databases fixed up. See "UTF-8 database" + above for details. syslog ------ diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 877bbf2e0f..52ad4100fc 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -239,7 +239,9 @@ class Memcached_DataObject extends DB_DataObject $result = parent::_connect(); if (!$exists) { $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - $DB->query('SET NAMES "utf8"'); + if (common_config('db', 'utf8')) { + $DB->query('SET NAMES "utf8"'); + } } return $result; } diff --git a/lib/common.php b/lib/common.php index f983c4d168..8f95c23615 100644 --- a/lib/common.php +++ b/lib/common.php @@ -167,6 +167,7 @@ $config['db'] = 'require_prefix' => 'classes/', 'class_prefix' => '', 'mirror' => null, + 'utf8' => true, 'db_driver' => 'DB', # XXX: JanRain libs only work with DB 'quote_identifiers' => false, 'type' => 'mysql' ); From 60ac9bc6fda74278d98890a2c26f3834acad8222 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 27 May 2009 19:43:43 +0000 Subject: [PATCH 16/71] Bunch of UI fixes/improvements for attachments (hover, overlay, notice view, clip) --- js/util.js | 72 ++++++++++++------ lib/attachmentlist.php | 6 +- theme/base/css/display.css | 67 ++++++++++++---- .../images/icons/twotone/green/clip-02.gif | Bin 0 -> 70 bytes theme/default/css/display.css | 6 ++ theme/identica/css/display.css | 6 ++ 6 files changed, 116 insertions(+), 41 deletions(-) create mode 100644 theme/base/images/icons/twotone/green/clip-02.gif diff --git a/js/util.js b/js/util.js index b1b6ec82bd..08cc1d3705 100644 --- a/js/util.js +++ b/js/util.js @@ -17,30 +17,6 @@ */ $(document).ready(function(){ - $('a.attachment').click(function() {$().jOverlay({url: $('address .url')[0].href+'/attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'}); return false; }); - $("a.thumbnail").hover( - function() { - var anchor = $(this); - $("a.thumbnail").children('img').remove(); - - setTimeout(function() { - anchor.closest(".entry-title").addClass('ov'); - $.get($('address .url')[0].href+'/attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) { - anchor.append(data); - }); - }, 250); - - setTimeout(function() { - anchor.children('img').remove(); - anchor.closest(".entry-title").removeClass('ov'); - }, 3000); - }, - function() { - $(this).children('img').remove(); - $(this).closest(".entry-title").removeClass('ov'); - } - ); - // count character on keyup function counter(event){ var maxLength = 140; @@ -238,6 +214,7 @@ $(document).ready(function(){ $("#form_notice").each(addAjaxHidden); NoticeHover(); NoticeReply(); + NoticeAttachments(); }); @@ -276,3 +253,50 @@ function NoticeReplySet(nick,id) { } return true; } + +function NoticeAttachments() { + $.fn.jOverlay.options = { + method : 'GET', + data : '', + url : '', + color : '#000', + opacity : '0.6', + zIndex : 9999, + center : true, + imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif', + bgClickToClose : true, + success : function() { + $('#jOverlayContent').append(''); + $('#jOverlayContent button').click($.closeOverlay); + }, + timeout : 0 + }; + + $('a.attachment').click(function() { + $().jOverlay({url: $('address .url')[0].href+'/attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'}); + return false; + }); + $("body:not(#shownotice) a.thumbnail").hover( + function() { + var anchor = $(this); + $("a.thumbnail").children('img').remove(); + anchor.closest(".entry-title").addClass('ov'); + + setTimeout(function() { + $.get($('address .url')[0].href+'/attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) { + anchor.append(data); + }); + }, 500); + + setTimeout(function() { + anchor.children('img').remove(); + anchor.closest(".entry-title").removeClass('ov'); + }, 3000); + }, + function() { + $("a.thumbnail").children('img').remove(); + $(this).closest(".entry-title").removeClass('ov'); + } + ); +} + diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index d0478bad35..559962acca 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -80,10 +80,10 @@ class AttachmentList extends Widget function show() { - $this->out->elementStart('dl', array('id' =>'attachment')); + $this->out->elementStart('dl', array('id' =>'attachments')); $this->out->element('dt', null, _('Attachments')); $this->out->elementStart('dd'); - $this->out->elementStart('ul', array('class' => 'attachments')); + $this->out->elementStart('ol', array('class' => 'attachments')); $atts = new File; $att = $atts->getAttachments($this->notice->id); @@ -93,7 +93,7 @@ class AttachmentList extends Widget } $this->out->elementEnd('dd'); - $this->out->elementEnd('ul'); + $this->out->elementEnd('ol'); $this->out->elementEnd('dl'); return count($att); diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 9bc1417b17..16c9e3c009 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -855,20 +855,6 @@ display:inline-block; text-transform:lowercase; } -.notice .attachment { -position:relative; -} -.notice .attachment img { -position:absolute; -top:18px; -left:0; -z-index:99; -} -#shownotice .notice .attachment img { -position:static; -} - - .notice-options { position:relative; font-size:0.95em; @@ -936,6 +922,59 @@ padding:0; } +.notice .attachment { +position:relative; +padding-left:16px; +} +#attachments .attachment { +padding-left:0; +} +.notice .attachment img { +position:absolute; +top:18px; +left:0; +z-index:99; +} +#shownotice .notice .attachment img { +position:static; +} + +#attachments { +clear:both; +float:left; +width:100%; +margin-top:18px; +} +#attachments dt { +font-weight:bold; +font-size:1.3em; +margin-bottom:4px; +} + +#attachments li { +margin-bottom:18px; +list-style-type:decimal; +float:left; +clear:both; +} + +#jOverlayContent, +#jOverlayContent #content { +width: auto !important; +} +#jOverlayContent .external span { +display:block; +margin-bottom:11px; +} +#jOverlayContent button { +position:absolute; +top:0; +right:0; +padding:3px 4px; +font-weight:bold; +} + + #usergroups #new_group { float: left; margin-right: 2em; diff --git a/theme/base/images/icons/twotone/green/clip-02.gif b/theme/base/images/icons/twotone/green/clip-02.gif new file mode 100644 index 0000000000000000000000000000000000000000..77a729799b63901b5818896ff855298931bf9b3c GIT binary patch literal 70 zcmZ?wbhEHb6krfwXkcK7pCSN6ia%Kx85kHDbQpjDB*VZY+ta`D^jo$^+z+mlDK0Z# W&K2bMLi0(6?cF0A-~AS2um%8ElojXz literal 0 HcmV?d00001 diff --git a/theme/default/css/display.css b/theme/default/css/display.css index 737db7ce96..e08a4783bd 100644 --- a/theme/default/css/display.css +++ b/theme/default/css/display.css @@ -175,6 +175,12 @@ background-image:url(../../base/images/icons/twotone/green/shield.gif); } /* NOTICES */ +.notice .attachment { +background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%; +} +#attachments .attachment { +background:none; +} .notice-options .notice_reply a, .notice-options form input.submit { background-color:transparent; diff --git a/theme/identica/css/display.css b/theme/identica/css/display.css index f7abac4823..1f1298737c 100644 --- a/theme/identica/css/display.css +++ b/theme/identica/css/display.css @@ -175,6 +175,12 @@ background-image:url(../../base/images/icons/twotone/green/shield.gif); } /* NOTICES */ +.notice .attachment { +background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%; +} +#attachments .attachment { +background:none; +} .notice-options .notice_reply a, .notice-options form input.submit { background-color:transparent; From 497d1bfe0283598021f2be2bd6664ac8bbab25ce Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 May 2009 19:12:37 -0400 Subject: [PATCH 17/71] Add fixup_utf8 to fixup problems with UTF-8 in a database Fixes up an old database to store strings in UTF-8. Hopefully works! --- scripts/fixup_utf8.php | 131 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 scripts/fixup_utf8.php diff --git a/scripts/fixup_utf8.php b/scripts/fixup_utf8.php new file mode 100644 index 0000000000..0763c72c91 --- /dev/null +++ b/scripts/fixup_utf8.php @@ -0,0 +1,131 @@ +#!/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(1); +} + +ini_set("max_execution_time", "0"); +ini_set("max_input_time", "0"); +set_time_limit(0); +mb_internal_encoding('UTF-8'); + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); +define('LACONICA', true); + +require_once(INSTALLDIR . '/lib/common.php'); +require_once('DB.php'); + +function main() { + + $dbl = doConnect('latin1'); + + if (empty($dbl)) { + return; + } + + $dbu = doConnect('utf8'); + + if (empty($dbu)) { + return; + } + + // Do a separate DB connection + + $sth = $dbu->prepare("UPDATE notice SET content = UNHEX(?), rendered = UNHEX(?) WHERE id = ?"); + + if (PEAR::isError($sth)) { + echo "ERROR: " . $sth->getMessage() . "\n"; + return; + } + + $rn = $dbl->query('SELECT id, content, rendered FROM notice ' . + 'WHERE LENGTH(content) != CHAR_LENGTH(content)'); + + if (PEAR::isError($rn)) { + echo "ERROR: " . $rn->getMessage() . "\n"; + return; + } + + echo "Number of rows: " . $rn->numRows() . "\n"; + + $notice = array(); + + while (DB_OK == $rn->fetchInto($notice)) { + + $id = ($notice[0])+0; + $content = bin2hex($notice[1]); + $rendered = bin2hex($notice[2]); + + echo "$id..."; + + $result =& $dbu->execute($sth, array($content, $rendered, $id)); + + if (PEAR::isError($result)) { + echo "ERROR: " . $result->getMessage() . "\n"; + continue; + } + + $cnt = $dbu->affectedRows(); + + if ($cnt != 1) { + echo "ERROR: 0 rows affected\n"; + continue; + } + + $notice = Notice::staticGet('id', $id); + $notice->decache(); + + echo "OK\n"; + } +} + +function doConnect($charset) +{ + $db = DB::connect(common_config('db', 'database'), + array('persistent' => false)); + + if (PEAR::isError($db)) { + echo "ERROR: " . $db->getMessage() . "\n"; + return NULL; + } + + $result = $db->query("SET NAMES $charset"); + + if (PEAR::isError($result)) { + echo "ERROR: " . $result->getMessage() . "\n"; + $db->disconnect(); + return NULL; + } + + $result = $db->autoCommit(true); + + if (PEAR::isError($result)) { + echo "ERROR: " . $result->getMessage() . "\n"; + $db->disconnect(); + return NULL; + } + + return $db; +} + +main(); From 1e9c5b52b4d816b701ee4e9da1b27c1f1b9cbf31 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 28 May 2009 00:08:00 +0000 Subject: [PATCH 18/71] FB Connect plugin - better workflow for disconnecting and reconnecting Facebook accounts --- plugins/FBConnect/FBConnectAuth.php | 44 +++++++++- plugins/FBConnect/FBConnectPlugin.php | 66 ++++++++------- plugins/FBConnect/FBConnectSettings.php | 103 +++++++++++++----------- 3 files changed, 135 insertions(+), 78 deletions(-) diff --git a/plugins/FBConnect/FBConnectAuth.php b/plugins/FBConnect/FBConnectAuth.php index 233eb83ab3..4699ce636c 100644 --- a/plugins/FBConnect/FBConnectAuth.php +++ b/plugins/FBConnect/FBConnectAuth.php @@ -62,7 +62,28 @@ class FBConnectauthAction extends Action parent::handle($args); if (common_is_real_login()) { - $this->clientError(_('Already logged in.')); + + // User is already logged in. Does she already have a linked Facebook acct? + $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE); + + if ($flink) { + + // User already has a linked Facebook account and shouldn't be here + common_debug('There is already a local user (' . $flink->user_id . + ') linked with this Facebook (' . $this->fbuid . ').'); + + // We don't want these cookies + getFacebook()->clear_cookie_state(); + + $this->clientError(_('There is already a local user linked with this Facebook.')); + + } else { + + // User came from the Facebook connect settings tab, and + // probably just wants to link/relink their Facebook account + $this->connectUser(); + } + } else if ($_SERVER['REQUEST_METHOD'] == 'POST') { $token = $this->trimmed('token'); @@ -78,7 +99,7 @@ class FBConnectauthAction extends Action } $this->createNewUser(); } else if ($this->arg('connect')) { - $this->connectUser(); + $this->connectNewUser(); } else { common_debug(print_r($this->args, true), __FILE__); $this->showForm(_('Something weird happened.'), @@ -259,7 +280,7 @@ class FBConnectauthAction extends Action 303); } - function connectUser() + function connectNewUser() { $nickname = $this->trimmed('nickname'); $password = $this->trimmed('password'); @@ -290,6 +311,23 @@ class FBConnectauthAction extends Action $this->goHome($user->nickname); } + function connectUser() + { + $user = common_current_user(); + + $result = $this->flinkUser($user->id, $this->fbuid); + + if (!$result) { + $this->serverError(_('Error connecting user to Facebook.')); + return; + } + + common_debug("Connected Facebook user $this->fbuid to local user $user->id"); + + // Return to Facebook connection settings tab + common_redirect(common_local_url('FBConnectSettings'), 303); + } + function tryLogin() { common_debug("Trying Facebook Login..."); diff --git a/plugins/FBConnect/FBConnectPlugin.php b/plugins/FBConnect/FBConnectPlugin.php index ad5e47e478..c85ef432d4 100644 --- a/plugins/FBConnect/FBConnectPlugin.php +++ b/plugins/FBConnect/FBConnectPlugin.php @@ -143,23 +143,6 @@ class FBConnectPlugin extends Plugin if ($user) { - $action->menuItem(common_local_url('all', array('nickname' => $user->nickname)), - _('Home'), _('Personal profile and friends timeline'), false, 'nav_home'); - $action->menuItem(common_local_url('profilesettings'), - _('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account'); - if (common_config('xmpp', 'enabled')) { - $action->menuItem(common_local_url('imsettings'), - _('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect'); - } else { - $action->menuItem(common_local_url('smssettings'), - _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect'); - } - $action->menuItem(common_local_url('invite'), - _('Invite'), - sprintf(_('Invite friends and colleagues to join you on %s'), - common_config('site', 'name')), - false, 'nav_invitecontact'); - $flink = Foreign_link::getByUserId($user->id, FACEBOOK_CONNECT_SERVICE); $fbuid = 0; @@ -195,9 +178,25 @@ class FBConnectPlugin extends Plugin } } - // Need to override the Logout link to make it do FB stuff + $action->menuItem(common_local_url('all', array('nickname' => $user->nickname)), + _('Home'), _('Personal profile and friends timeline'), false, 'nav_home'); + $action->menuItem(common_local_url('profilesettings'), + _('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account'); + if (common_config('xmpp', 'enabled')) { + $action->menuItem(common_local_url('imsettings'), + _('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect'); + } else { + $action->menuItem(common_local_url('smssettings'), + _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect'); + } + $action->menuItem(common_local_url('invite'), + _('Invite'), + sprintf(_('Invite friends and colleagues to join you on %s'), + common_config('site', 'name')), + false, 'nav_invitecontact'); - if ($fbuid > 0) { + // Need to override the Logout link to make it do FB stuff + if ($flink && $fbuid > 0) { $logout_url = common_local_url('logout'); $title = _('Logout from the site'); @@ -258,21 +257,32 @@ class FBConnectPlugin extends Plugin return true; } - function onEndLogout($action) + function onStartLogout($action) { - try { + $user = common_current_user(); + + $flink = Foreign_link::getByUserId($user->id, FACEBOOK_CONNECT_SERVICE); + + $action->logout(); + + if ($flink) { $facebook = getFacebook(); - $fbuid = $facebook->get_loggedin_user(); - if ($fbuid > 0) { - $facebook->logout(common_local_url('public')); + try { + $fbuid = $facebook->get_loggedin_user(); + + if ($fbuid > 0) { + $facebook->logout(common_local_url('public')); + } + + } catch (Exception $e) { + common_log(LOG_WARNING, 'Could\'t logout of Facebook: ' . + $e->getMessage()); } - - } catch (Exception $e) { - common_log(LOG_WARNING, 'Could\'t logout of Facebook: ' . - $e->getMessage()); } + + return true; } } diff --git a/plugins/FBConnect/FBConnectSettings.php b/plugins/FBConnect/FBConnectSettings.php index 7e255f43a6..034ecebae2 100644 --- a/plugins/FBConnect/FBConnectSettings.php +++ b/plugins/FBConnect/FBConnectSettings.php @@ -78,63 +78,73 @@ class FBConnectSettingsAction extends ConnectSettingsAction function showContent() { $user = common_current_user(); - $flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE); - if (!$flink) { - - $this->element('p', 'form_note', - _('There is no Facebook user connected to this account.')); - - $this->element('fb:login-button', array('onlogin' => 'goto_login()', - 'length' => 'long')); - - return; - } - - $this->element('p', 'form_note', - _('Connected Facebook user:')); - - $this->elementStart('p', array('class' => 'facebook-user-display')); - $this->elementStart('fb:profile-pic', - array('uid' => $flink->foreign_id, - 'size' => 'square', - 'linked' => 'true', - 'facebook-logo' => 'true')); - $this->elementEnd('fb:profile-pic'); - - $this->elementStart('fb:name', array('uid' => $flink->foreign_id)); - $this->elementEnd('fb:name'); - $this->elementEnd('p'); - $this->elementStart('form', array('method' => 'post', 'id' => 'form_settings_facebook', 'class' => 'form_settings', 'action' => common_local_url('FBConnectSettings'))); - $this->hidden('token', common_session_token()); + if (!$flink) { - $this->elementStart('fieldset'); + $this->element('p', 'instructions', + _('There is no Facebook user connected to this account.')); - $this->element('legend', null, _('Disconnect my account from Facebook')); + $this->element('fb:login-button', array('onlogin' => 'goto_login()', + 'length' => 'long')); - if (!$user->password) { - - $this->elementStart('p', array('class' => 'form_guide')); - $this->text(_('Disconnecting your Faceboook ' . - 'would make it impossible to log in! Please ')); - $this->element('a', - array('href' => common_local_url('passwordsettings')), - _('set a password')); - - $this->text(_(' first.')); - $this->elementEnd('p'); } else { - $this->submit('disconnect', _('Disconnect')); - } - $this->elementEnd('fieldset'); + $this->element('p', 'form_note', + _('Connected Facebook user')); + + $this->elementStart('p', array('class' => 'facebook-user-display')); + $this->elementStart('fb:profile-pic', + array('uid' => $flink->foreign_id, + 'size' => 'small', + 'linked' => 'true', + 'facebook-logo' => 'true')); + $this->elementEnd('fb:profile-pic'); + + $this->elementStart('fb:name', array('uid' => $flink->foreign_id, + 'useyou' => 'false')); + $this->elementEnd('fb:name'); + $this->elementEnd('p'); + + $this->hidden('token', common_session_token()); + + $this->elementStart('fieldset'); + + $this->element('legend', null, _('Disconnect my account from Facebook')); + + if (!$user->password) { + + $this->elementStart('p', array('class' => 'form_guide')); + $this->text(_('Disconnecting your Faceboook ' . + 'would make it impossible to log in! Please ')); + $this->element('a', + array('href' => common_local_url('passwordsettings')), + _('set a password')); + + $this->text(_(' first.')); + $this->elementEnd('p'); + } else { + + $note = 'Keep your %s account but disconnect from Facebook. ' . + 'You\'ll use your %s password to log in.'; + + $site = common_config('site', 'name'); + + $this->element('p', 'instructions', + sprintf($note, $site, $site)); + + $this->submit('disconnect', _('Disconnect')); + } + + $this->elementEnd('fieldset'); + } + $this->elementEnd('form'); } @@ -171,8 +181,7 @@ class FBConnectSettingsAction extends ConnectSettingsAction try { - // XXX: not sure what exactly to do here - + // Clear FB Connect cookies out $facebook = getFacebook(); $facebook->clear_cookie_state(); @@ -182,7 +191,7 @@ class FBConnectSettingsAction extends ConnectSettingsAction $e->getMessage()); } - $this->showForm(_('Facebook user disconnected.'), true); + $this->showForm(_('You have disconnected from Facebook.'), true); } else { $this->showForm(_('Not sure what you\'re trying to do.')); From 3d13a44b66e254f88a7c2aa36190fa8e9df8fec3 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 27 May 2009 17:59:49 -0700 Subject: [PATCH 19/71] Upgraded foreign_id column to handle new 64-bit Facebook user IDs. See: http://developers.facebook.com/news.php?blog=1&story=226 --- classes/Foreign_link.php | 2 +- db/laconica.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/classes/Foreign_link.php b/classes/Foreign_link.php index 6065609512..c0b356eced 100644 --- a/classes/Foreign_link.php +++ b/classes/Foreign_link.php @@ -11,7 +11,7 @@ class Foreign_link extends Memcached_DataObject public $__table = 'foreign_link'; // table name public $user_id; // int(4) primary_key not_null - public $foreign_id; // int(4) primary_key not_null + public $foreign_id; // bigint(8) primary_key not_null unsigned public $service; // int(4) primary_key not_null public $credentials; // varchar(255) public $noticesync; // tinyint(1) not_null default_1 diff --git a/db/laconica.sql b/db/laconica.sql index 0b20bc172c..a11e316925 100644 --- a/db/laconica.sql +++ b/db/laconica.sql @@ -285,7 +285,7 @@ create table foreign_user ( create table foreign_link ( user_id int comment 'link to user on this system, if exists' references user (id), - foreign_id int comment 'link ' references foreign_user(id), + foreign_id bigint unsigned comment 'link to user on foreign service, if exists' references foreign_user(id), service int not null comment 'foreign key to service' references foreign_service(id), credentials varchar(255) comment 'authc credentials, typically a password', noticesync tinyint not null default 1 comment 'notice synchronization, bit 1 = sync outgoing, bit 2 = sync incoming, bit 3 = filter local replies', From 121cb6afb1822517e215eef23a5cb87796ecea35 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 28 May 2009 16:35:34 -0400 Subject: [PATCH 20/71] document utf8 problems and solutions --- README | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README b/README index c23d4e464a..4649eb79ce 100644 --- a/README +++ b/README @@ -690,6 +690,13 @@ to users on a remote site. (Or not... it's not well tested.) The Upgrading ========= +IMPORTANT NOTE: Laconica 0.7.4 introduced a fix for some +incorrectly-stored international characters ("UTF-8"). For new +installations, it will now store non-ASCII characters correctly. +However, older installations will have the incorrect storage, and will +consequently show up "wrong" in browsers. See below for how to deal +with this situation. + If you've been using Laconica 0.6, 0.5 or lower, or if you've been tracking the "git" version of the software, you will probably want to upgrade and keep your existing data. There is no automated upgrade @@ -779,6 +786,29 @@ problem. 3. When fixup_inboxes is finished, you can set the enabled flag to 'true'. +UTF-8 Database +-------------- + +Laconica 0.7.4 introduced a fix for some incorrectly-stored +international characters ("UTF-8"). This fix is not +backwards-compatible; installations from before 0.7.4 will show +non-ASCII characters of old notices incorrectly. This section explains +what to do. + +0. You can disable the new behaviour by setting the 'db''utf8' config + option to "false". You should only do this until you're ready to + convert your DB to the new format. +1. When you're ready to convert, you can run the fixup_utf8.php script + in the scripts/ subdirectory. If you've had the "new behaviour" + enabled (probably a good idea), you can give the ID of the first + "new" notice as a parameter, and only notices before that one will + be converted. Notices are converted in reverse chronological order, + so the most recent (and visible) ones will be converted first. The + script should work whether or not you have the 'db''utf8' config + option enabled. +2. When you're ready, set $config['db']['utf8'] to true, so that + new notices will be stored correctly. + Configuration options ===================== From daf845dbe67a909e2bc9e039d3c2ccc560345b4d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 28 May 2009 16:36:07 -0400 Subject: [PATCH 21/71] take an argument for fixup_utf8 --- scripts/fixup_utf8.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/scripts/fixup_utf8.php b/scripts/fixup_utf8.php index 0763c72c91..e5021ff343 100644 --- a/scripts/fixup_utf8.php +++ b/scripts/fixup_utf8.php @@ -35,7 +35,7 @@ define('LACONICA', true); require_once(INSTALLDIR . '/lib/common.php'); require_once('DB.php'); -function main() { +function fixup_utf8($id) { $dbl = doConnect('latin1'); @@ -58,8 +58,16 @@ function main() { return; } - $rn = $dbl->query('SELECT id, content, rendered FROM notice ' . - 'WHERE LENGTH(content) != CHAR_LENGTH(content)'); + $sql = 'SELECT id, content, rendered FROM notice ' . + 'WHERE LENGTH(content) != CHAR_LENGTH(content)'; + + if (!empty($id)) { + $sql .= ' AND id < ' . $id; + } + + $sql .= ' ORDER BY id DESC'; + + $rn = $dbl->query($sql); if (PEAR::isError($rn)) { echo "ERROR: " . $rn->getMessage() . "\n"; @@ -128,4 +136,6 @@ function doConnect($charset) return $db; } -main(); +$id = ($argc > 1) ? $argv[1] : null; + +fixup_utf8($id); From 047038959c068364f2efa030ba63571a492b9cda Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 28 May 2009 17:37:17 -0400 Subject: [PATCH 22/71] Add any.io to sources --- db/notice_source.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/db/notice_source.sql b/db/notice_source.sql index ac73d3d13b..b1309acd64 100644 --- a/db/notice_source.sql +++ b/db/notice_source.sql @@ -3,6 +3,7 @@ INSERT INTO notice_source VALUES ('adium', 'Adium', 'http://www.adiumx.com/', now()), ('AgentSolo.com','AgentSolo.com','http://www.agentsolo.com/', now()), + ('anyio', 'Any.IO', 'http://any.io/', now()), ('betwittered','BeTwittered','http://www.32hours.com/betwitteredinfo/', now()), ('bti','bti','http://gregkh.github.com/bti/', now()), ('cliqset', 'Cliqset', 'http://www.cliqset.com/', now()), From 3532cd0490a70b54a92108549de6042459fd8469 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 28 May 2009 18:19:22 -0400 Subject: [PATCH 23/71] Only enqueue inbox-dependent transports after inboxes have been filled --- classes/Notice.php | 2 +- lib/util.php | 54 +++++++++++++++++++++++------------ scripts/inboxqueuehandler.php | 10 ++++++- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 771a4e715f..8dd14b3b73 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -195,12 +195,12 @@ class Notice extends Memcached_DataObject $notice->saveReplies(); $notice->saveTags(); - $notice->saveGroups(); if (common_config('queue', 'enabled')) { $notice->addToAuthorInbox(); } else { $notice->addToInboxes(); + $notice->saveGroups(); } $notice->query('COMMIT'); diff --git a/lib/util.php b/lib/util.php index f862d7fcc2..214c332799 100644 --- a/lib/util.php +++ b/lib/util.php @@ -879,7 +879,27 @@ function common_broadcast_notice($notice, $remote=false) function common_enqueue_notice($notice) { - $transports = array('omb', 'sms', 'twitter', 'facebook', 'ping'); + $transports = array('twitter', 'facebook', 'ping'); + + // If inboxes are enabled, wait till inboxes are filled + // before doing inbox-dependent broadcasts + + if (common_config('inboxes', 'enabled') === true || + common_config('inboxes', 'enabled') === 'transitional') { + $transports[] = 'inbox'; + } else { + $transports = array_merge($transports, common_post_inbox_transports()); + } + + foreach ($transports as $transport) { + common_enqueue_notice_transport($notice, $transport); + } + return $result; +} + +function common_post_inbox_transports() +{ + $transports = array('omb', 'sms'); if (common_config('xmpp', 'enabled')) { $transports = array_merge($transports, array('jabber', 'public')); @@ -890,25 +910,23 @@ function common_enqueue_notice($notice) $transports[] = 'memcache'; } - if (common_config('inboxes', 'enabled') === true || - common_config('inboxes', 'enabled') === 'transitional') { - $transports[] = 'inbox'; - } + return $transports; +} - foreach ($transports as $transport) { - $qi = new Queue_item(); - $qi->notice_id = $notice->id; - $qi->transport = $transport; - $qi->created = $notice->created; - $result = $qi->insert(); - if (!$result) { - $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError'); - common_log(LOG_ERR, 'DB error inserting queue item: ' . $last_error->message); - return false; - } - common_log(LOG_DEBUG, 'complete queueing notice ID = ' . $notice->id . ' for ' . $transport); +function common_enqueue_notice_transport($notice, $transport) +{ + $qi = new Queue_item(); + $qi->notice_id = $notice->id; + $qi->transport = $transport; + $qi->created = $notice->created; + $result = $qi->insert(); + if (!$result) { + $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError'); + common_log(LOG_ERR, 'DB error inserting queue item: ' . $last_error->message); + throw new ServerException('DB error inserting queue item: ' . $last_error->message); } - return $result; + common_log(LOG_DEBUG, 'complete queueing notice ID = ' . $notice->id . ' for ' . $transport); + return true; } function common_real_broadcast($notice, $remote=false) diff --git a/scripts/inboxqueuehandler.php b/scripts/inboxqueuehandler.php index 73d31e8542..c7aab4fe16 100755 --- a/scripts/inboxqueuehandler.php +++ b/scripts/inboxqueuehandler.php @@ -41,7 +41,7 @@ class InboxQueueHandler extends QueueHandler } function start() { - $this->log(LOG_INFO, "INITIALIZE"); + $this->log(LOG_INFO, "Initialize inbox queue handler"); return true; } @@ -49,11 +49,19 @@ class InboxQueueHandler extends QueueHandler { $this->log(LOG_INFO, "Distributing notice to inboxes for $notice->id"); $notice->addToInboxes(); + $notice->saveGroups(); $notice->blowSubsCache(); + $transports = common_post_inbox_transports(); + + foreach ($transports as $transport) { + common_enqueue_notice_transport($notice, $transport); + } + return true; } function finish() { + $this->log(LOG_INFO, "Terminating inbox queue handler"); } } From f0dc97e4da16181b0d6a74361734678d0f37fd5e Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 28 May 2009 22:59:22 +0000 Subject: [PATCH 24/71] Smarter call for hover events. It now makes a new GET for attachment thumbnails on mouseover only if it hasn't been requested before on that notice attachment. --- js/util.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/js/util.js b/js/util.js index 08cc1d3705..6cfad3a8e7 100644 --- a/js/util.js +++ b/js/util.js @@ -261,7 +261,7 @@ function NoticeAttachments() { url : '', color : '#000', opacity : '0.6', - zIndex : 9999, + zIndex : '9999', center : true, imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif', bgClickToClose : true, @@ -279,22 +279,22 @@ function NoticeAttachments() { $("body:not(#shownotice) a.thumbnail").hover( function() { var anchor = $(this); - $("a.thumbnail").children('img').remove(); + $("a.thumbnail").children('img').hide(); anchor.closest(".entry-title").addClass('ov'); - setTimeout(function() { - $.get($('address .url')[0].href+'/attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) { - anchor.append(data); - }); - }, 500); - - setTimeout(function() { - anchor.children('img').remove(); - anchor.closest(".entry-title").removeClass('ov'); - }, 3000); + if (anchor.children('img').length == 0) { + setTimeout(function() { + $.get($('address .url')[0].href+'/attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) { + anchor.append(data); + }); + }, 500); + } + else { + anchor.children('img').show(); + } }, function() { - $("a.thumbnail").children('img').remove(); + $("a.thumbnail").children('img').hide(); $(this).closest(".entry-title").removeClass('ov'); } ); From 6612993c1d59051bbb4dafed73e0111eec8ccfd6 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 28 May 2009 23:16:25 +0000 Subject: [PATCH 25/71] Prevents regular hovers over attachment anchors from making a GET request for the thumbnail. There is 500 ms delay while on mouseover state before going ahead with the request. Fun. --- js/util.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/js/util.js b/js/util.js index 6cfad3a8e7..d5697e880d 100644 --- a/js/util.js +++ b/js/util.js @@ -276,6 +276,8 @@ function NoticeAttachments() { $().jOverlay({url: $('address .url')[0].href+'/attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'}); return false; }); + + var t; $("body:not(#shownotice) a.thumbnail").hover( function() { var anchor = $(this); @@ -283,7 +285,7 @@ function NoticeAttachments() { anchor.closest(".entry-title").addClass('ov'); if (anchor.children('img').length == 0) { - setTimeout(function() { + t = setTimeout(function() { $.get($('address .url')[0].href+'/attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) { anchor.append(data); }); @@ -294,6 +296,7 @@ function NoticeAttachments() { } }, function() { + clearTimeout(t); $("a.thumbnail").children('img').hide(); $(this).closest(".entry-title").removeClass('ov'); } From 5e53a97a41038151959c0280d988c609c0834aa2 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 28 May 2009 16:18:58 -0700 Subject: [PATCH 26/71] Warning log msg when http basic auth fails --- actions/api.php | 1 + 1 file changed, 1 insertion(+) diff --git a/actions/api.php b/actions/api.php index 8762b4bcd3..b25ba99f30 100644 --- a/actions/api.php +++ b/actions/api.php @@ -67,6 +67,7 @@ class ApiAction extends Action $this->process_command(); } else { # basic authentication failed + common_log(LOG_WARNING, "Failed API auth attempt, nickname: $nickname."); $this->show_basic_auth_error(); } } From 8e8853a7141b10d01814d68021f24f2c78b2f193 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 28 May 2009 16:18:58 -0700 Subject: [PATCH 27/71] Warning log msg when http basic auth fails --- actions/api.php | 1 + 1 file changed, 1 insertion(+) diff --git a/actions/api.php b/actions/api.php index 8762b4bcd3..b25ba99f30 100644 --- a/actions/api.php +++ b/actions/api.php @@ -67,6 +67,7 @@ class ApiAction extends Action $this->process_command(); } else { # basic authentication failed + common_log(LOG_WARNING, "Failed API auth attempt, nickname: $nickname."); $this->show_basic_auth_error(); } } From b87aa46b3f26909ec538ee675c8d7a51d49cbaf2 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 29 May 2009 00:07:18 +0000 Subject: [PATCH 28/71] Updated max-width for attachment overlay heading --- theme/base/css/display.css | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 16c9e3c009..e3c499a5ed 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -959,7 +959,8 @@ clear:both; } #jOverlayContent, -#jOverlayContent #content { +#jOverlayContent #content, +#jOverlayContent #content_inner { width: auto !important; } #jOverlayContent .external span { @@ -973,7 +974,14 @@ right:0; padding:3px 4px; font-weight:bold; } - +#jOverlayContent h1 { +max-width:475px; +} +#jOverlayContent #content { +border-radius:7px; +-moz-border-radius:7px; +-webkit-border-radius:7px; +} #usergroups #new_group { float: left; From 6885f082ea4191702b525bd5ad693baa13de8879 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 28 May 2009 17:51:45 -0700 Subject: [PATCH 29/71] Added Afficheur to notice sources --- db/notice_source.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/db/notice_source.sql b/db/notice_source.sql index b1309acd64..cf4bd3db44 100644 --- a/db/notice_source.sql +++ b/db/notice_source.sql @@ -2,6 +2,7 @@ INSERT INTO notice_source (code, name, url, created) VALUES ('adium', 'Adium', 'http://www.adiumx.com/', now()), + ('Afficheur', 'Afficheur', 'http://afficheur.sourceforge.jp/', now()), ('AgentSolo.com','AgentSolo.com','http://www.agentsolo.com/', now()), ('anyio', 'Any.IO', 'http://any.io/', now()), ('betwittered','BeTwittered','http://www.32hours.com/betwitteredinfo/', now()), From b8de7935568e631bc99aec6120f2bc4090f50fec Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 29 May 2009 02:38:38 +0000 Subject: [PATCH 30/71] Changed ul to ol and added xoxo for notice lists. --- actions/conversation.php | 10 +++++----- actions/shownotice.php | 4 ++-- lib/noticelist.php | 4 ++-- lib/noticesection.php | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/actions/conversation.php b/actions/conversation.php index ef189016aa..0d7cb9a874 100644 --- a/actions/conversation.php +++ b/actions/conversation.php @@ -179,14 +179,14 @@ class ConversationTree extends NoticeList $this->out->elementStart('div', array('id' =>'notices_primary')); $this->out->element('h2', null, _('Notices')); - $this->out->elementStart('ul', array('class' => 'notices')); + $this->out->elementStart('ol', array('class' => 'notices xoxo')); if (array_key_exists('root', $this->tree)) { $rootid = $this->tree['root'][0]; $this->showNoticePlus($rootid); } - $this->out->elementEnd('ul'); + $this->out->elementEnd('ol'); $this->out->elementEnd('div'); return $cnt; @@ -215,13 +215,13 @@ class ConversationTree extends NoticeList if (array_key_exists($id, $this->tree)) { $children = $this->tree[$id]; - $this->out->elementStart('ul', array('class' => 'notices')); + $this->out->elementStart('ol', array('class' => 'notices')); foreach ($children as $child) { $this->showNoticePlus($child); } - $this->out->elementEnd('ul'); + $this->out->elementEnd('ol'); } $this->out->elementEnd('li'); @@ -295,4 +295,4 @@ class ConversationTreeItem extends NoticeListItem { return; } -} \ No newline at end of file +} diff --git a/actions/shownotice.php b/actions/shownotice.php index 2c469c9dee..34c8a8e946 100644 --- a/actions/shownotice.php +++ b/actions/shownotice.php @@ -208,10 +208,10 @@ class ShownoticeAction extends Action function showContent() { - $this->elementStart('ul', array('class' => 'notices')); + $this->elementStart('ol', array('class' => 'notices xoxo')); $nli = new NoticeListItem($this->notice, $this); $nli->show(); - $this->elementEnd('ul'); + $this->elementEnd('ol'); } /** diff --git a/lib/noticelist.php b/lib/noticelist.php index 50a95cfcbb..0e80a9778e 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -86,7 +86,7 @@ class NoticeList extends Widget { $this->out->elementStart('div', array('id' =>'notices_primary')); $this->out->element('h2', null, _('Notices')); - $this->out->elementStart('ul', array('class' => 'notices')); + $this->out->elementStart('ol', array('class' => 'notices xoxo')); $cnt = 0; @@ -101,7 +101,7 @@ class NoticeList extends Widget $item->show(); } - $this->out->elementEnd('ul'); + $this->out->elementEnd('ol'); $this->out->elementEnd('div'); return $cnt; diff --git a/lib/noticesection.php b/lib/noticesection.php index 37aafdaf6c..ca14326861 100644 --- a/lib/noticesection.php +++ b/lib/noticesection.php @@ -52,12 +52,12 @@ class NoticeSection extends Section { $notices = $this->getNotices(); $cnt = 0; - $this->out->elementStart('ul', 'notices'); + $this->out->elementStart('ol', 'notices xoxo'); while ($notices->fetch() && ++$cnt <= NOTICES_PER_SECTION) { $this->showNotice($notices); } - $this->out->elementEnd('ul'); + $this->out->elementEnd('ol'); return ($cnt > NOTICES_PER_SECTION); } From f66e996073b296ecbcaa76ac2fdb2378508bc2ac Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 29 May 2009 16:28:39 -0400 Subject: [PATCH 31/71] Update version number to 0.7.4 Updated the version number to 0.7.4, added the new song title, and documented the changes since 0.7.3. --- README | 60 ++++++++++++++++++++++++++------------------------ lib/common.php | 2 +- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/README b/README index 4649eb79ce..769c416d32 100644 --- a/README +++ b/README @@ -2,8 +2,8 @@ README ------ -Laconica 0.7.3 ("You Are The Everything") -7 April 2009 +Laconica 0.7.4 ("Can't Get There From Here") +29 May 2009 This is the README file for Laconica, the Open Source microblogging platform. It includes installation instructions, descriptions of @@ -71,29 +71,29 @@ for additional terms. New this version ================ -This is a minor bug-fix and feature release since version 0.7.2.1, -released Mar 11 2009. Notable changes this version: +This is a minor bug-fix and feature release since version 0.7.3, +released Apr 4 2009. Notable changes this version: -- A plugin to allow a templating language for customization -- A plugin for Piwik Analytics engine -- A bookmarklet for posting a notice about a Web page you're reading -- A welcome notice ('welcomebot') and default subscription for new users -- Support for SSL for some or all pages on the site -- Better handling of empty notice lists on many pages -- Major improvements to the Twitter friend-sync offline processing -- subscribers, subscriptions, groups are listed on the Personal page. -- "Invite" link restored to main menu -- Better memory handling in FOAF output -- Fix for SUP support (FriendFeed) -- Correct and intelligent redirect HTTP status codes -- Fix DB collations for search and sort -- Better H1s and Titles using user full names -- Fixes to make the linkback plugin operational -- Better indication that a notice is being published by Ajax (spinner) -- Better and unified Atom output -- Hiding "register" and "join now" messages when site is closed -- ping, twitter and facebook queuehandlers working better -- Updated RPM spec +- Improved handling of UTF-8 characters. The new code is *not* backwards + compatible by default; see "Upgrading" below for instructions on + converting existing databases to the correct character set. +- Unroll joins for large queries. This greatly enhanced database + performance -- up to 50x for some queries -- at the expense of making + an extra DB hit for some queries. +- Added an optional plugin to use WikiHashtags + (http://hashtags.wikia.com/) for the sidebar on hashtag pages. +- Optimized Twitter friend synchronization. +- Better error handling for Ajax posting of notices, including + HTTP errors and timeouts. +- Experimental Comet plugin -- supports the cometd and the Bayeux + protocol. Using this plugin, you can show "real time" updates on the + public and tag pages. However, server configuration is complex. +- If queues are enabled, update inboxes and memcached off-line. Speeds + up posting considerably. +- Correctly shorten links posted through XMPP. +- elements for pagination, supported by some browsers like Opera. +- Corrected date format in search API. +- Made the public XRDS file work correctly. Prerequisites ============= @@ -193,9 +193,9 @@ especially if you've previously installed PHP/MySQL packages. 1. Unpack the tarball you downloaded on your Web server. Usually a command like this will work: - tar zxf laconica-0.7.3.tar.gz + tar zxf laconica-0.7.4.tar.gz - ...which will make a laconica-0.7.3 subdirectory in your current + ...which will make a laconica-0.7.4 subdirectory in your current directory. (If you don't have shell access on your Web server, you may have to unpack the tarball on your local computer and FTP the files to the server.) @@ -203,7 +203,7 @@ especially if you've previously installed PHP/MySQL packages. 2. Move the tarball to a directory of your choosing in your Web root directory. Usually something like this will work: - mv laconica-0.7.3 /var/www/mublog + mv laconica-0.7.4 /var/www/mublog This will make your Laconica instance available in the mublog path of your server, like "http://example.net/mublog". "microblog" or @@ -700,7 +700,7 @@ with this situation. If you've been using Laconica 0.6, 0.5 or lower, or if you've been tracking the "git" version of the software, you will probably want to upgrade and keep your existing data. There is no automated upgrade -procedure in Laconica 0.7.3. Try these step-by-step instructions; read +procedure in Laconica 0.7.4. Try these step-by-step instructions; read to the end first before trying them. 0. Download Laconica and set up all the prerequisites as if you were @@ -1178,7 +1178,7 @@ repository (see below), and you get a compilation error ("unexpected T_STRING") in the browser, check to see that you don't have any conflicts in your code. -If you upgraded to Laconica 0.7.3 without reading the "Notice inboxes" +If you upgraded to Laconica 0.7.4 without reading the "Notice inboxes" section above, and all your users' 'Personal' tabs are empty, read the "Notice inboxes" section above. @@ -1269,6 +1269,8 @@ if anyone's been overlooked in error. * Ken Sedgwick * Brian Hendrickson * Tobias Diekershoff +* Dan Moore +* Fil Thanks also to the developers of our upstream library code and to the thousands of people who have tried out Identi.ca, installed Laconi.ca, diff --git a/lib/common.php b/lib/common.php index 8f95c23615..01d2c78eaa 100644 --- a/lib/common.php +++ b/lib/common.php @@ -19,7 +19,7 @@ if (!defined('LACONICA')) { exit(1); } -define('LACONICA_VERSION', '0.7.3'); +define('LACONICA_VERSION', '0.7.4'); define('AVATAR_PROFILE_SIZE', 96); define('AVATAR_STREAM_SIZE', 48); From eb76a3bbb36e1f73007cb9b602001ec14f6853c6 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 29 May 2009 16:32:55 -0700 Subject: [PATCH 32/71] Ticket #1567 - API: Change before_id parameter to max_id --- actions/twitapidirect_messages.php | 6 ++--- actions/twitapistatuses.php | 36 +++++++++++++----------------- classes/Fave.php | 6 ++--- classes/Notice_inbox.php | 10 ++++----- classes/Notice_tag.php | 6 ++--- classes/Reply.php | 10 ++++----- classes/User_group.php | 6 ++--- 7 files changed, 38 insertions(+), 42 deletions(-) diff --git a/actions/twitapidirect_messages.php b/actions/twitapidirect_messages.php index 7101db8df5..ab8f078106 100644 --- a/actions/twitapidirect_messages.php +++ b/actions/twitapidirect_messages.php @@ -43,7 +43,7 @@ class Twitapidirect_messagesAction extends TwitterapiAction $count = $this->arg('count'); $since = $this->arg('since'); $since_id = $this->arg('since_id'); - $before_id = $this->arg('before_id'); + $max_id = $this->arg('max_id'); $page = $this->arg('page'); @@ -74,8 +74,8 @@ class Twitapidirect_messagesAction extends TwitterapiAction $link = $server . $user->nickname . '/outbox'; } - if ($before_id) { - $message->whereAdd("id < $before_id"); + if ($max_id) { + $message->whereAdd("id < $max_id"); } if ($since_id) { diff --git a/actions/twitapistatuses.php b/actions/twitapistatuses.php index 3abeba3672..1fbde6639f 100644 --- a/actions/twitapistatuses.php +++ b/actions/twitapistatuses.php @@ -45,22 +45,21 @@ class TwitapistatusesAction extends TwitterapiAction $page = $this->arg('page'); $since_id = $this->arg('since_id'); - $before_id = $this->arg('before_id'); + $max_id = $this->arg('max_id'); - // NOTE: page, since_id, and before_id are extensions to Twitter API -- TB if (!$page) { $page = 1; } if (!$since_id) { $since_id = 0; } - if (!$before_id) { - $before_id = 0; + if (!$max_id) { + $max_id = 0; } $since = strtotime($this->arg('since')); - $notice = Notice::publicStream((($page-1)*$MAX_PUBSTATUSES), $MAX_PUBSTATUSES, $since_id, $before_id, $since); + $notice = Notice::publicStream((($page-1)*$MAX_PUBSTATUSES), $MAX_PUBSTATUSES, $since_id, $max_id, $since); if ($notice) { @@ -97,7 +96,7 @@ class TwitapistatusesAction extends TwitterapiAction $since_id = $this->arg('since_id'); $count = $this->arg('count'); $page = $this->arg('page'); - $before_id = $this->arg('before_id'); + $max_id = $this->arg('max_id'); if (!$page) { $page = 1; @@ -111,9 +110,8 @@ class TwitapistatusesAction extends TwitterapiAction $since_id = 0; } - // NOTE: before_id is an extension to Twitter API -- TB - if (!$before_id) { - $before_id = 0; + if (!$max_id) { + $max_id = 0; } $since = strtotime($this->arg('since')); @@ -133,7 +131,7 @@ class TwitapistatusesAction extends TwitterapiAction $link = common_local_url('all', array('nickname' => $user->nickname)); $subtitle = sprintf(_('Updates from %1$s and friends on %2$s!'), $user->nickname, $sitename); - $notice = $user->noticesWithFriends(($page-1)*20, $count, $since_id, $before_id, $since); + $notice = $user->noticesWithFriends(($page-1)*20, $count, $since_id, $max_id, $since); switch($apidata['content-type']) { case 'xml': @@ -184,7 +182,7 @@ class TwitapistatusesAction extends TwitterapiAction $since = $this->arg('since'); $since_id = $this->arg('since_id'); $page = $this->arg('page'); - $before_id = $this->arg('before_id'); + $max_id = $this->arg('max_id'); if (!$page) { $page = 1; @@ -198,9 +196,8 @@ class TwitapistatusesAction extends TwitterapiAction $since_id = 0; } - // NOTE: before_id is an extensions to Twitter API -- TB - if (!$before_id) { - $before_id = 0; + if (!$max_id) { + $max_id = 0; } $since = strtotime($this->arg('since')); @@ -220,7 +217,7 @@ class TwitapistatusesAction extends TwitterapiAction # XXX: since - $notice = $user->getNotices((($page-1)*20), $count, $since_id, $before_id, $since); + $notice = $user->getNotices((($page-1)*20), $count, $since_id, $max_id, $since); switch($apidata['content-type']) { case 'xml': @@ -353,7 +350,7 @@ class TwitapistatusesAction extends TwitterapiAction $count = $this->arg('count'); $page = $this->arg('page'); $since_id = $this->arg('since_id'); - $before_id = $this->arg('before_id'); + $max_id = $this->arg('max_id'); $user = $this->get_user($apidata['api_arg'], $apidata); $this->auth_user = $apidata['user']; @@ -380,15 +377,14 @@ class TwitapistatusesAction extends TwitterapiAction $since_id = 0; } - // NOTE: before_id is an extension to Twitter API -- TB - if (!$before_id) { - $before_id = 0; + if (!$max_id) { + $max_id = 0; } $since = strtotime($this->arg('since')); $notice = $user->getReplies((($page-1)*20), - $count, $since_id, $before_id, $since); + $count, $since_id, $max_id, $since); $notices = array(); while ($notice->fetch()) { diff --git a/classes/Fave.php b/classes/Fave.php index 915b4572ff..cdb4798567 100644 --- a/classes/Fave.php +++ b/classes/Fave.php @@ -46,7 +46,7 @@ class Fave extends Memcached_DataObject return $ids; } - function _streamDirect($user_id, $offset, $limit, $since_id, $before_id, $since) + function _streamDirect($user_id, $offset, $limit, $since_id, $max_id, $since) { $fav = new Fave(); @@ -59,8 +59,8 @@ class Fave extends Memcached_DataObject $fav->whereAdd('notice_id > ' . $since_id); } - if ($before_id != 0) { - $fav->whereAdd('notice_id < ' . $before_id); + if ($max_id != 0) { + $fav->whereAdd('notice_id < ' . $max_id); } if (!is_null($since)) { diff --git a/classes/Notice_inbox.php b/classes/Notice_inbox.php index dec14b0d18..65d7927b7d 100644 --- a/classes/Notice_inbox.php +++ b/classes/Notice_inbox.php @@ -43,15 +43,15 @@ class Notice_inbox extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - function stream($user_id, $offset, $limit, $since_id, $before_id, $since) + function stream($user_id, $offset, $limit, $since_id, $max_id, $since) { return Notice::stream(array('Notice_inbox', '_streamDirect'), array($user_id), 'notice_inbox:by_user:'.$user_id, - $offset, $limit, $since_id, $before_id, $since); + $offset, $limit, $since_id, $max_id, $since); } - function _streamDirect($user_id, $offset, $limit, $since_id, $before_id, $since) + function _streamDirect($user_id, $offset, $limit, $since_id, $max_id, $since) { $inbox = new Notice_inbox(); @@ -61,8 +61,8 @@ class Notice_inbox extends Memcached_DataObject $inbox->whereAdd('notice_id > ' . $since_id); } - if ($before_id != 0) { - $inbox->whereAdd('notice_id < ' . $before_id); + if ($max_id != 0) { + $inbox->whereAdd('notice_id < ' . $max_id); } if (!is_null($since)) { diff --git a/classes/Notice_tag.php b/classes/Notice_tag.php index e5b7722430..758a665947 100644 --- a/classes/Notice_tag.php +++ b/classes/Notice_tag.php @@ -46,7 +46,7 @@ class Notice_tag extends Memcached_DataObject return Notice::getStreamByIds($ids); } - function _streamDirect($tag, $offset, $limit, $since_id, $before_id, $since) + function _streamDirect($tag, $offset, $limit, $since_id, $max_id, $since) { $nt = new Notice_tag(); @@ -59,8 +59,8 @@ class Notice_tag extends Memcached_DataObject $nt->whereAdd('notice_id > ' . $since_id); } - if ($before_id != 0) { - $nt->whereAdd('notice_id < ' . $before_id); + if ($max_id != 0) { + $nt->whereAdd('notice_id < ' . $max_id); } if (!is_null($since)) { diff --git a/classes/Reply.php b/classes/Reply.php index 4439053b44..49b1e05e51 100644 --- a/classes/Reply.php +++ b/classes/Reply.php @@ -22,16 +22,16 @@ class Reply extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null) { $ids = Notice::stream(array('Reply', '_streamDirect'), array($user_id), 'reply:stream:' . $user_id, - $offset, $limit, $since_id, $before_id, $since); + $offset, $limit, $since_id, $max_id, $since); return $ids; } - function _streamDirect($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + function _streamDirect($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null) { $reply = new Reply(); $reply->profile_id = $user_id; @@ -40,8 +40,8 @@ class Reply extends Memcached_DataObject $reply->whereAdd('notice_id > ' . $since_id); } - if ($before_id != 0) { - $reply->whereAdd('notice_id < ' . $before_id); + if ($max_id != 0) { + $reply->whereAdd('notice_id < ' . $max_id); } if (!is_null($since)) { diff --git a/classes/User_group.php b/classes/User_group.php index 7cc31e7026..43591d6559 100755 --- a/classes/User_group.php +++ b/classes/User_group.php @@ -58,7 +58,7 @@ class User_group extends Memcached_DataObject return Notice::getStreamByIds($ids); } - function _streamDirect($offset, $limit, $since_id, $before_id, $since) + function _streamDirect($offset, $limit, $since_id, $max_id, $since) { $inbox = new Group_inbox(); @@ -71,8 +71,8 @@ class User_group extends Memcached_DataObject $inbox->whereAdd('notice_id > ' . $since_id); } - if ($before_id != 0) { - $inbox->whereAdd('notice_id < ' . $before_id); + if ($max_id != 0) { + $inbox->whereAdd('notice_id < ' . $max_id); } if (!is_null($since)) { From 425f9d703c5258920079b268c085b0c7112c3b70 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 29 May 2009 16:54:24 -0700 Subject: [PATCH 33/71] Ticket #1567 - Change max_id to return notices <= ID instead of < ID --- actions/twitapidirect_messages.php | 2 +- classes/Fave.php | 2 +- classes/Notice.php | 32 +++++++++++++++--------------- classes/Notice_inbox.php | 2 +- classes/Profile.php | 10 +++++----- classes/User_group.php | 2 +- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/actions/twitapidirect_messages.php b/actions/twitapidirect_messages.php index ab8f078106..d2dbdb619b 100644 --- a/actions/twitapidirect_messages.php +++ b/actions/twitapidirect_messages.php @@ -75,7 +75,7 @@ class Twitapidirect_messagesAction extends TwitterapiAction } if ($max_id) { - $message->whereAdd("id < $max_id"); + $message->whereAdd("id <= $max_id"); } if ($since_id) { diff --git a/classes/Fave.php b/classes/Fave.php index cdb4798567..572334ce4f 100644 --- a/classes/Fave.php +++ b/classes/Fave.php @@ -60,7 +60,7 @@ class Fave extends Memcached_DataObject } if ($max_id != 0) { - $fav->whereAdd('notice_id < ' . $max_id); + $fav->whereAdd('notice_id <= ' . $max_id); } if (!is_null($since)) { diff --git a/classes/Notice.php b/classes/Notice.php index 8dd14b3b73..3f6c5cebbe 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -426,22 +426,22 @@ class Notice extends Memcached_DataObject # XXX: too many args; we need to move to named params or even a separate # class for notice streams - static function getStream($qry, $cachekey, $offset=0, $limit=20, $since_id=0, $before_id=0, $order=null, $since=null) { + static function getStream($qry, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0, $order=null, $since=null) { if (common_config('memcached', 'enabled')) { - # Skip the cache if this is a since, since_id or before_id qry - if ($since_id > 0 || $before_id > 0 || $since) { - return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since); + # Skip the cache if this is a since, since_id or max_id qry + if ($since_id > 0 || $max_id > 0 || $since) { + return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since); } else { return Notice::getCachedStream($qry, $cachekey, $offset, $limit, $order); } } - return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since); + return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since); } - static function getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since) { + static function getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since) { $needAnd = false; $needWhere = true; @@ -463,7 +463,7 @@ class Notice extends Memcached_DataObject $qry .= ' notice.id > ' . $since_id; } - if ($before_id > 0) { + if ($max_id > 0) { if ($needWhere) { $qry .= ' WHERE '; @@ -472,7 +472,7 @@ class Notice extends Memcached_DataObject $qry .= ' AND '; } - $qry .= ' notice.id < ' . $before_id; + $qry .= ' notice.id <= ' . $max_id; } if ($since) { @@ -630,17 +630,17 @@ class Notice extends Memcached_DataObject } } - function publicStream($offset=0, $limit=20, $since_id=0, $before_id=0, $since=null) + function publicStream($offset=0, $limit=20, $since_id=0, $max_id=0, $since=null) { $ids = Notice::stream(array('Notice', '_publicStreamDirect'), array(), 'public', - $offset, $limit, $since_id, $before_id, $since); + $offset, $limit, $since_id, $max_id, $since); return Notice::getStreamByIds($ids); } - function _publicStreamDirect($offset=0, $limit=20, $since_id=0, $before_id=0, $since=null) + function _publicStreamDirect($offset=0, $limit=20, $since_id=0, $max_id=0, $since=null) { $notice = new Notice(); @@ -664,8 +664,8 @@ class Notice extends Memcached_DataObject $notice->whereAdd('id > ' . $since_id); } - if ($before_id != 0) { - $notice->whereAdd('id < ' . $before_id); + if ($max_id != 0) { + $notice->whereAdd('id <= ' . $max_id); } if (!is_null($since)) { @@ -998,15 +998,15 @@ class Notice extends Memcached_DataObject } } - function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $before_id=0, $since=null) + function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0, $since=null) { $cache = common_memcache(); if (empty($cache) || - $since_id != 0 || $before_id != 0 || !is_null($since) || + $since_id != 0 || $max_id != 0 || !is_null($since) || ($offset + $limit) > NOTICE_CACHE_WINDOW) { return call_user_func_array($fn, array_merge($args, array($offset, $limit, $since_id, - $before_id, $since))); + $max_id, $since))); } $idkey = common_cache_key($cachekey); diff --git a/classes/Notice_inbox.php b/classes/Notice_inbox.php index 65d7927b7d..8a27e17478 100644 --- a/classes/Notice_inbox.php +++ b/classes/Notice_inbox.php @@ -62,7 +62,7 @@ class Notice_inbox extends Memcached_DataObject } if ($max_id != 0) { - $inbox->whereAdd('notice_id < ' . $max_id); + $inbox->whereAdd('notice_id <= ' . $max_id); } if (!is_null($since)) { diff --git a/classes/Profile.php b/classes/Profile.php index ae5641d79d..3d13cd46af 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -153,18 +153,18 @@ class Profile extends Memcached_DataObject return null; } - function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) + function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) { // XXX: I'm not sure this is going to be any faster. It probably isn't. $ids = Notice::stream(array($this, '_streamDirect'), array(), 'profile:notice_ids:' . $this->id, - $offset, $limit, $since_id, $before_id); + $offset, $limit, $since_id, $max_id); return Notice::getStreamByIds($ids); } - function _streamDirect($offset, $limit, $since_id, $before_id, $since) + function _streamDirect($offset, $limit, $since_id, $max_id, $since) { $notice = new Notice(); @@ -177,8 +177,8 @@ class Profile extends Memcached_DataObject $notice->whereAdd('id > ' . $since_id); } - if ($before_id != 0) { - $notice->whereAdd('id < ' . $before_id); + if ($max_id != 0) { + $notice->whereAdd('id <= ' . $max_id); } if (!is_null($since)) { diff --git a/classes/User_group.php b/classes/User_group.php index 43591d6559..a135015bac 100755 --- a/classes/User_group.php +++ b/classes/User_group.php @@ -72,7 +72,7 @@ class User_group extends Memcached_DataObject } if ($max_id != 0) { - $inbox->whereAdd('notice_id < ' . $max_id); + $inbox->whereAdd('notice_id <= ' . $max_id); } if (!is_null($since)) { From 78f8b91a13ee07ed3344fd6411a0829a16bef4b1 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 29 May 2009 19:23:53 -0700 Subject: [PATCH 34/71] Ticket #1568 - API should return full user objects --- actions/twitapiusers.php | 125 ++++++++++++--------------------------- lib/twitterapi.php | 81 +++++++++++++++++++++---- 2 files changed, 109 insertions(+), 97 deletions(-) diff --git a/actions/twitapiusers.php b/actions/twitapiusers.php index 1542cfb33e..b90bbfa985 100644 --- a/actions/twitapiusers.php +++ b/actions/twitapiusers.php @@ -25,110 +25,61 @@ 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; } - - $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']); - } 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; - } + $user = null; + $email = $this->arg('email'); + $user_id = $this->arg('user_id'); - $profile = $user->getProfile(); + // XXX: email field deprecated in Twitter's API - if (!$profile) { - common_server_error(_('User has no profile.')); - return; - } + // XXX: Also: need to add screen_name param - $twitter_user = $this->twitter_user_array($profile, true); + 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']); + } elseif (isset($apidata['user'])) { + $user = $apidata['user']; + } - // Add in extended user fields offered up by this method - $twitter_user['created_at'] = $this->date_twitter($profile->created); + if (!$user) { + $this->client_error(_('Not found.'), 404, $apidata['content-type']); + return; + } - $subbed = DB_DataObject::factory('subscription'); - $subbed->subscriber = $profile->id; - $subbed_count = (int) $subbed->count() - 1; + $profile = $user->getProfile(); - $notices = DB_DataObject::factory('notice'); - $notices->profile_id = $profile->id; - $notice_count = (int) $notices->count(); + if (!$profile) { + common_server_error(_('User has no profile.')); + return; + } - $twitter_user['friends_count'] = (is_int($subbed_count)) ? $subbed_count : 0; - $twitter_user['statuses_count'] = (is_int($notice_count)) ? $notice_count : 0; + $twitter_user = $this->twitter_user_array($profile, true); - // 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; + if ($apidata['content-type'] == 'xml') { + $this->init_document('xml'); + $this->show_twitter_xml_user($twitter_user); + $this->end_document('xml'); + } elseif ($apidata['content-type'] == 'json') { + $this->init_document('json'); + $this->show_json_objects($twitter_user); + $this->end_document('json'); + } else { - $faves = DB_DataObject::factory('fave'); - $faves->user_id = $user->id; - $faves_count = (int) $faves->count(); - $twitter_user['favourites_count'] = $faves_count; - - $timezone = 'UTC'; - - if ($user->timezone) { - $timezone = $user->timezone; - } - - $t = new DateTime; - $t->setTimezone(new DateTimeZone($timezone)); - $twitter_user['utc_offset'] = $t->format('Z'); - $twitter_user['time_zone'] = $timezone; - - if (isset($apidata['user'])) { - - $twitter_user['following'] = $apidata['user']->isSubscribed($profile); - - // Notifications on? - $sub = Subscription::pkeyGet(array('subscriber' => - $apidata['user']->id, 'subscribed' => $profile->id)); - - if ($sub) { - $twitter_user['notifications'] = ($sub->jabber || $sub->sms); - } - } - - if ($apidata['content-type'] == 'xml') { - $this->init_document('xml'); - $this->show_twitter_xml_user($twitter_user); - $this->end_document('xml'); - } elseif ($apidata['content-type'] == 'json') { - $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). + // 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/twitterapi.php b/lib/twitterapi.php index caf8c07163..1d527b935f 100644 --- a/lib/twitterapi.php +++ b/lib/twitterapi.php @@ -54,7 +54,7 @@ class TwitterapiAction extends Action /** * Overrides XMLOutputter::element to write booleans as strings (true|false). * See that method's documentation for more info. - * + * * @param string $tag Element type or tagname * @param array $attrs Array of element attributes, as * key-value pairs @@ -70,24 +70,85 @@ class TwitterapiAction extends Action return parent::element($tag, $attrs, $content); } - + function twitter_user_array($profile, $get_notice=false) { - $twitter_user = array(); - $twitter_user['name'] = $profile->getBestName(); - $twitter_user['followers_count'] = $this->count_subscriptions($profile); - $twitter_user['screen_name'] = $profile->nickname; - $twitter_user['description'] = ($profile->bio) ? $profile->bio : null; - $twitter_user['location'] = ($profile->location) ? $profile->location : null; $twitter_user['id'] = intval($profile->id); + $twitter_user['name'] = $profile->getBestName(); + $twitter_user['screen_name'] = $profile->nickname; + $twitter_user['location'] = ($profile->location) ? $profile->location : null; + $twitter_user['description'] = ($profile->bio) ? $profile->bio : null; $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE); + $twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() : + Avatar::defaultImage(AVATAR_STREAM_SIZE); - $twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_STREAM_SIZE); - $twitter_user['protected'] = false; # not supported by Laconica yet $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null; + $twitter_user['protected'] = false; # not supported by Laconica yet + $twitter_user['followers_count'] = $this->count_subscriptions($profile); + + // To be supported soon... + $twitter_user['profile_background_color'] = ''; + $twitter_user['profile_text_color'] = ''; + $twitter_user['profile_link_color'] = ''; + $twitter_user['profile_sidebar_fill_color'] = ''; + $twitter_user['profile_sidebar_border_color'] = ''; + + $subbed = DB_DataObject::factory('subscription'); + $subbed->subscriber = $profile->id; + $subbed_count = (int) $subbed->count() - 1; + $twitter_user['friends_count'] = (is_int($subbed_count)) ? $subbed_count : 0; + + $twitter_user['created_at'] = $this->date_twitter($profile->created); + + $faves = DB_DataObject::factory('fave'); + $faves->user_id = $user->id; + $faves_count = (int) $faves->count(); + $twitter_user['favourites_count'] = $faves_count; // British spelling! + + // Need to pull up the user for some of this + $user = User::staticGet($profile->id); + + $timezone = 'UTC'; + + if ($user->timezone) { + $timezone = $user->timezone; + } + + $t = new DateTime; + $t->setTimezone(new DateTimeZone($timezone)); + + $twitter_user['utc_offset'] = $t->format('Z'); + $twitter_user['time_zone'] = $timezone; + + // To be supported some day, perhaps + $twitter_user['profile_background_image_url'] = ''; + $twitter_user['profile_background_tile'] = false; + + $notices = DB_DataObject::factory('notice'); + $notices->profile_id = $profile->id; + $notice_count = (int) $notices->count(); + + $twitter_user['statuses_count'] = (is_int($notice_count)) ? $notice_count : 0; + + // Is the requesting user following this user? + $twitter_user['following'] = false; + $twitter_user['notifications'] = false; + + if (isset($apidata['user'])) { + + $twitter_user['following'] = $apidata['user']->isSubscribed($profile); + + // Notifications on? + $sub = Subscription::pkeyGet(array('subscriber' => + $apidata['user']->id, 'subscribed' => $profile->id)); + + if ($sub) { + $twitter_user['notifications'] = ($sub->jabber || $sub->sms); + } + } if ($get_notice) { $notice = $profile->getCurrentNotice(); From 791f5defaf07372de6453ac7903a9a32611b0284 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 29 May 2009 20:23:32 -0700 Subject: [PATCH 35/71] Ticket #1572 - Implement Twitter's account/rate_limit_status API method We don't have a rate limit (yet) but some clients check this method. This dummy implementation always says the auth user has 100 requests left. --- actions/twitapiaccount.php | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/actions/twitapiaccount.php b/actions/twitapiaccount.php index 68a18cb57b..8b956f897a 100644 --- a/actions/twitapiaccount.php +++ b/actions/twitapiaccount.php @@ -98,9 +98,31 @@ class TwitapiaccountAction extends TwitterapiAction $this->serverError(_('API method under construction.'), $code=501); } + // We don't have a rate limit, but some clients check this method. + // It always returns the same thing: 100 hit left. function rate_limit_status($args, $apidata) { parent::handle($args); - $this->serverError(_('API method under construction.'), $code=501); + + $type = $apidata['content-type']; + $this->init_document($type); + + if ($apidata['content-type'] == 'xml') { + $this->elementStart('hash'); + $this->element('remaining-hits', array('type' => 'integer'), 100); + $this->element('hourly-limit', array('type' => 'integer'), 100); + $this->element('reset-time', array('type' => 'datetime'), null); + $this->element('reset_time_in_seconds', array('type' => 'integer'), 0); + $this->elementEnd('hash'); + } elseif ($apidata['content-type'] == 'json') { + + $out = array('reset_time_in_seconds' => 0, + 'remaining_hits' => 100, + 'hourly_limit' => 100, + 'reset_time' => ''); + print json_encode($out); + } + + $this->end_document($type); } } From 78968a31a7459a3c564924754380b64655b46928 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 30 May 2009 00:49:14 -0400 Subject: [PATCH 36/71] check for since=0 in Notice::stream() --- classes/Notice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/Notice.php b/classes/Notice.php index 3f6c5cebbe..3a90d18b47 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1003,7 +1003,7 @@ class Notice extends Memcached_DataObject $cache = common_memcache(); if (empty($cache) || - $since_id != 0 || $max_id != 0 || !is_null($since) || + $since_id != 0 || $max_id != 0 || (!is_null($since) && $since > 0) || ($offset + $limit) > NOTICE_CACHE_WINDOW) { return call_user_func_array($fn, array_merge($args, array($offset, $limit, $since_id, $max_id, $since))); From 79ede2afdb1479fff19f428a0ca57a9cd6ff2326 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 30 May 2009 01:30:10 -0400 Subject: [PATCH 37/71] add get2gnow --- db/notice_source.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/db/notice_source.sql b/db/notice_source.sql index cf4bd3db44..4c3dc2113d 100644 --- a/db/notice_source.sql +++ b/db/notice_source.sql @@ -13,6 +13,7 @@ VALUES ('eventbox','EventBox','http://thecosmicmachine.com/eventbox/ ', now()), ('Facebook','Facebook','http://apps.facebook.com/identica/', now()), ('feed2omb','feed2omb','http://projects.ciarang.com/p/feed2omb/', now()), + ('get2gnow', 'get2gnow', 'http://uberchicgeekchick.com/?projects=get2gnow', now()), ('gravity', 'Gravity', 'http://mobileways.de/gravity', now()), ('Gwibber','Gwibber','http://launchpad.net/gwibber', now()), ('HelloTxt','HelloTxt','http://hellotxt.com/', now()), From 7656277313e351bcab86b246bc5b13d8dcd65931 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 30 May 2009 03:02:26 -0400 Subject: [PATCH 38/71] remove debug code from replies --- classes/User.php | 1 - 1 file changed, 1 deletion(-) diff --git a/classes/User.php b/classes/User.php index b5ac7b2206..59451258ec 100644 --- a/classes/User.php +++ b/classes/User.php @@ -403,7 +403,6 @@ class User extends Memcached_DataObject function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) { $ids = Reply::stream($this->id, $offset, $limit, $since_id, $before_id, $since); - common_debug("Ids = " . implode(',', $ids)); return Notice::getStreamByIds($ids); } From 45c58c74d4d2c2c0a886ff7816f27aa9c6033652 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 30 May 2009 03:31:26 -0400 Subject: [PATCH 39/71] take debug code out of omb.php --- lib/omb.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/omb.php b/lib/omb.php index e8e1acc413..40cb847dfa 100644 --- a/lib/omb.php +++ b/lib/omb.php @@ -159,13 +159,9 @@ function omb_post_notice($notice, $remote_profile, $subscription) function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret) { - - common_debug('Posting notice ' . $notice->id . ' to ' . $postnoticeurl, __FILE__); - $user = User::staticGet('id', $notice->profile_id); if (!$user) { - common_debug('Failed to get user for notice ' . $notice->id . ', profile = ' . $notice->profile_id, __FILE__); return false; } @@ -208,8 +204,6 @@ function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret) $req->to_postdata(), array('User-Agent: Laconica/' . LACONICA_VERSION)); - common_debug('Got HTTP result "'.print_r($result,true).'"', __FILE__); - if ($result->status == 403) { # not authorized, don't send again common_debug('403 result, deleting subscription', __FILE__); # FIXME: figure out how to delete this @@ -286,14 +280,10 @@ function omb_update_profile($profile, $remote_profile, $subscription) $fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); - common_debug('request URL = '.$req->get_normalized_http_url(), __FILE__); - common_debug('postdata = '.$req->to_postdata(), __FILE__); $result = $fetcher->post($req->get_normalized_http_url(), $req->to_postdata(), array('User-Agent: Laconica/' . LACONICA_VERSION)); - common_debug('Got HTTP result "'.print_r($result,true).'"', __FILE__); - if (empty($result) || !$result) { common_debug("Unable to contact " . $req->get_normalized_http_url()); } else if ($result->status == 403) { # not authorized, don't send again From 76ee1fd5daa6ab1999224d8e93df1b7116f24c36 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 30 May 2009 04:40:47 -0400 Subject: [PATCH 40/71] Removing inbox and memcached daemon handling --- classes/Notice.php | 54 ++-------------------- lib/util.php | 13 +----- scripts/getvaliddaemons.php | 5 -- scripts/inboxqueuehandler.php | 77 ------------------------------- scripts/memcachedqueuehandler.php | 70 ---------------------------- scripts/stopdaemons.sh | 3 +- 6 files changed, 6 insertions(+), 216 deletions(-) delete mode 100755 scripts/inboxqueuehandler.php delete mode 100755 scripts/memcachedqueuehandler.php diff --git a/classes/Notice.php b/classes/Notice.php index 3a90d18b47..b4c86ebeb5 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -196,12 +196,8 @@ class Notice extends Memcached_DataObject $notice->saveReplies(); $notice->saveTags(); - if (common_config('queue', 'enabled')) { - $notice->addToAuthorInbox(); - } else { - $notice->addToInboxes(); - $notice->saveGroups(); - } + $notice->addToInboxes(); + $notice->saveGroups(); $notice->query('COMMIT'); @@ -211,13 +207,7 @@ class Notice extends Memcached_DataObject # Clear the cache for subscribed users, so they'll update at next request # XXX: someone clever could prepend instead of clearing the cache - if (common_config('memcached', 'enabled')) { - if (common_config('queue', 'enabled')) { - $notice->blowAuthorCaches(); - } else { - $notice->blowCaches(); - } - } + $notice->blowCaches(); return $notice; } @@ -280,17 +270,6 @@ class Notice extends Memcached_DataObject $this->blowGroupCache($blowLast); } - function blowAuthorCaches($blowLast=false) - { - // Clear the user's cache - $cache = common_memcache(); - if (!empty($cache)) { - $cache->delete(common_cache_key('notice_inbox:by_user:'.$this->profile_id)); - } - $this->blowNoticeCache($blowLast); - $this->blowPublicCache($blowLast); - } - function blowGroupCache($blowLast=false) { $cache = common_memcache(); @@ -709,33 +688,6 @@ class Notice extends Memcached_DataObject return; } - function addToAuthorInbox() - { - $enabled = common_config('inboxes', 'enabled'); - - if ($enabled === true || $enabled === 'transitional') { - $user = User::staticGet('id', $this->profile_id); - if (empty($user)) { - return; - } - $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 . "' " . - "FROM $UT " . - "WHERE $UT.id = " . $this->profile_id . ' ' . - 'AND NOT EXISTS (SELECT user_id, notice_id ' . - 'FROM notice_inbox ' . - "WHERE user_id = " . $this->profile_id . ' '. - 'AND notice_id = ' . $this->id . ' )'; - if ($enabled === 'transitional') { - $qry .= " AND $UT.inboxed = 1"; - } - $inbox->query($qry); - } - return; - } - function saveGroups() { $enabled = common_config('inboxes', 'enabled'); diff --git a/lib/util.php b/lib/util.php index 214c332799..22308f432c 100644 --- a/lib/util.php +++ b/lib/util.php @@ -884,16 +884,12 @@ function common_enqueue_notice($notice) // If inboxes are enabled, wait till inboxes are filled // before doing inbox-dependent broadcasts - if (common_config('inboxes', 'enabled') === true || - common_config('inboxes', 'enabled') === 'transitional') { - $transports[] = 'inbox'; - } else { - $transports = array_merge($transports, common_post_inbox_transports()); - } + $transports = array_merge($transports, common_post_inbox_transports()); foreach ($transports as $transport) { common_enqueue_notice_transport($notice, $transport); } + return $result; } @@ -905,11 +901,6 @@ function common_post_inbox_transports() $transports = array_merge($transports, array('jabber', 'public')); } - if (common_config('memcached', 'enabled')) { - // Note: limited to 8 chars - $transports[] = 'memcache'; - } - return $transports; } diff --git a/scripts/getvaliddaemons.php b/scripts/getvaliddaemons.php index 482e63af70..0996ba9f41 100755 --- a/scripts/getvaliddaemons.php +++ b/scripts/getvaliddaemons.php @@ -25,7 +25,6 @@ * daemon names. */ - # 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"; @@ -41,12 +40,8 @@ if(common_config('xmpp','enabled')) { echo "xmppdaemon.php jabberqueuehandler.php publicqueuehandler.php "; echo "xmppconfirmhandler.php "; } -if(common_config('memcached','enabled')) { - echo "memcachedqueuehandler.php "; -} echo "ombqueuehandler.php "; echo "twitterqueuehandler.php "; echo "facebookqueuehandler.php "; echo "pingqueuehandler.php "; -echo "inboxqueuehandler.php "; echo "smsqueuehandler.php "; diff --git a/scripts/inboxqueuehandler.php b/scripts/inboxqueuehandler.php deleted file mode 100755 index c7aab4fe16..0000000000 --- a/scripts/inboxqueuehandler.php +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env php -. - */ - -// Abort if called from a web server - -if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { - print "This script must be run from the command line\n"; - exit(); -} - -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); -define('LACONICA', true); - -require_once(INSTALLDIR . '/lib/common.php'); -require_once(INSTALLDIR . '/lib/queuehandler.php'); - -set_error_handler('common_error_handler'); - -class InboxQueueHandler extends QueueHandler -{ - function transport() - { - return 'inbox'; - } - - function start() { - $this->log(LOG_INFO, "Initialize inbox queue handler"); - return true; - } - - function handle_notice($notice) - { - $this->log(LOG_INFO, "Distributing notice to inboxes for $notice->id"); - $notice->addToInboxes(); - $notice->saveGroups(); - $notice->blowSubsCache(); - $transports = common_post_inbox_transports(); - - foreach ($transports as $transport) { - common_enqueue_notice_transport($notice, $transport); - } - - return true; - } - - function finish() { - $this->log(LOG_INFO, "Terminating inbox queue handler"); - } -} - -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 InboxQueueHandler($id); - -$handler->runOnce(); diff --git a/scripts/memcachedqueuehandler.php b/scripts/memcachedqueuehandler.php deleted file mode 100755 index 185b781f75..0000000000 --- a/scripts/memcachedqueuehandler.php +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env php -. - */ - -// Abort if called from a web server - -if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { - print "This script must be run from the command line\n"; - exit(); -} - -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); -define('LACONICA', true); - -require_once(INSTALLDIR . '/lib/common.php'); -require_once(INSTALLDIR . '/lib/queuehandler.php'); - -set_error_handler('common_error_handler'); - -class MemcachedQueueHandler extends QueueHandler -{ - function transport() - { - return 'memcache'; - } - - function start() { - $this->log(LOG_INFO, "INITIALIZE"); - return true; - } - - function handle_notice($notice) - { - // XXX: fork here - $this->log(LOG_INFO, "Blowing memcached for $notice->id"); - $notice->blowCaches(); - return true; - } - - 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 MemcachedQueueHandler($id); - -$handler->runOnce(); diff --git a/scripts/stopdaemons.sh b/scripts/stopdaemons.sh index f6d71eddfb..2134b4ab00 100755 --- a/scripts/stopdaemons.sh +++ b/scripts/stopdaemons.sh @@ -24,8 +24,7 @@ SDIR=`dirname $0` DIR=`php $SDIR/getpiddir.php` for f in jabberhandler ombhandler publichandler smshandler pinghandler \ - xmppconfirmhandler xmppdaemon twitterhandler facebookhandler \ - memcachehandler inboxhandler; do + xmppconfirmhandler xmppdaemon twitterhandler facebookhandler; do FILES="$DIR/$f.*.pid" for ff in "$FILES" ; do From 7b1a72da307694bee40a2278482003855a2a2ab1 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 30 May 2009 11:52:35 -0400 Subject: [PATCH 41/71] take a max and min argument for fixup_utf8 --- scripts/fixup_utf8.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/scripts/fixup_utf8.php b/scripts/fixup_utf8.php index e5021ff343..67f9c46236 100644 --- a/scripts/fixup_utf8.php +++ b/scripts/fixup_utf8.php @@ -35,7 +35,7 @@ define('LACONICA', true); require_once(INSTALLDIR . '/lib/common.php'); require_once('DB.php'); -function fixup_utf8($id) { +function fixup_utf8($max_id, $min_id) { $dbl = doConnect('latin1'); @@ -61,8 +61,12 @@ function fixup_utf8($id) { $sql = 'SELECT id, content, rendered FROM notice ' . 'WHERE LENGTH(content) != CHAR_LENGTH(content)'; - if (!empty($id)) { - $sql .= ' AND id < ' . $id; + if (!empty($max_id)) { + $sql .= ' AND id <= ' . $max_id; + } + + if (!empty($min_id)) { + $sql .= ' AND id >= ' . $min_id; } $sql .= ' ORDER BY id DESC'; @@ -136,6 +140,7 @@ function doConnect($charset) return $db; } -$id = ($argc > 1) ? $argv[1] : null; +$max_id = ($argc > 1) ? $argv[1] : null; +$min_id = ($argc > 2) ? $argv[2] : null; -fixup_utf8($id); +fixup_utf8($max_id, $min_id); From a262f83210f6a405bfe79148b52387d1dbdd9a5d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 30 May 2009 12:20:42 -0400 Subject: [PATCH 42/71] use mysqli_set_charset instead of set names --- scripts/fixup_utf8.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/fixup_utf8.php b/scripts/fixup_utf8.php index 67f9c46236..925da24844 100644 --- a/scripts/fixup_utf8.php +++ b/scripts/fixup_utf8.php @@ -121,10 +121,14 @@ function doConnect($charset) return NULL; } - $result = $db->query("SET NAMES $charset"); +// $result = $db->query("SET NAMES $charset"); - if (PEAR::isError($result)) { - echo "ERROR: " . $result->getMessage() . "\n"; + $conn = $db->connection; + + $succ = mysqli_set_charset($conn, $charset); + + if (!$succ) { + echo "ERROR: couldn't set charset\n"; $db->disconnect(); return NULL; } From 9e16b7d89b101d8dfebdb8bf22ea56e9b8731bdd Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 30 May 2009 13:59:57 -0400 Subject: [PATCH 43/71] Use mysql_set_charset for connection instead of SET NAMES PHP doesn't get the info about the charset of the connection if you use SET NAMES. So, we use the appropriate PHP function instead. --- classes/Memcached_DataObject.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 52ad4100fc..33ac70dd04 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -239,8 +239,14 @@ class Memcached_DataObject extends DB_DataObject $result = parent::_connect(); if (!$exists) { $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - if (common_config('db', 'utf8')) { - $DB->query('SET NAMES "utf8"'); + if (common_config('db', 'type') == 'mysql' && + common_config('db', 'utf8')) { + $conn = $DB->connection; + if ($DB instanceof DB_mysqli) { + mysqli_set_charset($conn, 'utf8'); + } else if ($DB instanceof DB_mysql) { + mysql_set_charset('utf8', $conn); + } } } return $result; From f56f2f52e74ec3d0037bc33f9bbcee170e982d49 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 30 May 2009 19:42:15 +0000 Subject: [PATCH 44/71] Some cross-browser updates for conversation UI --- js/util.js | 2 +- theme/base/css/display.css | 12 ++++++++++-- theme/base/css/ie.css | 6 ++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/js/util.js b/js/util.js index d5697e880d..c710ae8392 100644 --- a/js/util.js +++ b/js/util.js @@ -261,7 +261,7 @@ function NoticeAttachments() { url : '', color : '#000', opacity : '0.6', - zIndex : '9999', + zIndex : 99, center : true, imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif', bgClickToClose : true, diff --git a/theme/base/css/display.css b/theme/base/css/display.css index e3c499a5ed..5ad4217cd4 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -951,7 +951,7 @@ font-size:1.3em; margin-bottom:4px; } -#attachments li { +#attachments ol li { margin-bottom:18px; list-style-type:decimal; float:left; @@ -962,6 +962,11 @@ clear:both; #jOverlayContent #content, #jOverlayContent #content_inner { width: auto !important; +margin-bottom:0; +} +#jOverlayContent #content { +padding:11px; +min-height:auto; } #jOverlayContent .external span { display:block; @@ -971,8 +976,11 @@ margin-bottom:11px; position:absolute; top:0; right:0; -padding:3px 4px; +width:29px; +height:29px; +text-align:center; font-weight:bold; +padding:0; } #jOverlayContent h1 { max-width:475px; diff --git a/theme/base/css/ie.css b/theme/base/css/ie.css index 5d8bea8ae3..df0388a5ad 100644 --- a/theme/base/css/ie.css +++ b/theme/base/css/ie.css @@ -30,3 +30,9 @@ margin-right:4px; .entity_profile { width:64%; } +#jOverlayContent .notice * { +z-index:1; +} +#jOverlayContent .notice .attachment img { +z-index:9999; +} From 6981a708aedf9d28d5396aedf8ef64188c2545bb Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 30 May 2009 19:43:31 +0000 Subject: [PATCH 45/71] Cross-browser update for Design form labels --- theme/base/css/display.css | 1 + 1 file changed, 1 insertion(+) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 5ad4217cd4..7d495c5395 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -1237,6 +1237,7 @@ width:33%; } #settings_design_color .form_data label { float:none; +display:block; } #settings_design_color .form_data .swatch { padding:11px; From 08b98a3d4d8edf2bc0b781953706744c026388b9 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 30 May 2009 19:45:34 +0000 Subject: [PATCH 46/71] Typo in label @for --- actions/designsettings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/designsettings.php b/actions/designsettings.php index 315e5a199c..5d3bf1a03b 100644 --- a/actions/designsettings.php +++ b/actions/designsettings.php @@ -82,7 +82,7 @@ class DesignsettingsAction extends AccountSettingsAction $this->element('legend', null, _('Change background image')); $this->elementStart('ul', 'form_data'); $this->elementStart('li'); - $this->element('label', array('for' => 'design_ background-image_file'), + $this->element('label', array('for' => 'design_background-image_file'), _('Upload file')); $this->element('input', array('name' => 'design_background-image_file', 'type' => 'file', From 36fe5a85b2de9f71a5c963abd0074dd5b7ae1e61 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 30 May 2009 19:47:05 +0000 Subject: [PATCH 47/71] Using lowercase for form action 'post' --- actions/designsettings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/designsettings.php b/actions/designsettings.php index 5d3bf1a03b..5774b85378 100644 --- a/actions/designsettings.php +++ b/actions/designsettings.php @@ -70,7 +70,7 @@ class DesignsettingsAction extends AccountSettingsAction function showContent() { $user = common_current_user(); - $this->elementStart('form', array('method' => 'POST', + $this->elementStart('form', array('method' => 'post', 'id' => 'form_settings_design', 'class' => 'form_settings', 'action' => From da0d2a9745ea072cd261990e152d08198331a415 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 30 May 2009 20:58:29 -0400 Subject: [PATCH 48/71] make fixup_utf8.php handle profiles and groups too --- scripts/fixup_utf8.php | 335 ++++++++++++++++++++++++++++++----------- 1 file changed, 246 insertions(+), 89 deletions(-) diff --git a/scripts/fixup_utf8.php b/scripts/fixup_utf8.php index 925da24844..2046d2b770 100644 --- a/scripts/fixup_utf8.php +++ b/scripts/fixup_utf8.php @@ -35,116 +35,273 @@ define('LACONICA', true); require_once(INSTALLDIR . '/lib/common.php'); require_once('DB.php'); -function fixup_utf8($max_id, $min_id) { +class UTF8FixerUpper +{ + var $dbl = null; + var $dbu = null; + var $args = array(); - $dbl = doConnect('latin1'); + function __construct($args) + { + $this->args = $args; - if (empty($dbl)) { - return; + if (array_key_exists('max_date', $args)) { + $this->max_date = strftime('%Y-%m-%d %H:%M:%S', strtotime($args['max_date'])); + } else { + $this->max_date = strftime('%Y-%m-%d %H:%M:%S', time()); + } + + $this->dbl = $this->doConnect('latin1'); + + if (empty($this->dbl)) { + return; + } + + $this->dbu = $this->doConnect('utf8'); + + if (empty($this->dbu)) { + return; + } } - $dbu = doConnect('utf8'); + function doConnect($charset) + { + $db = DB::connect(common_config('db', 'database'), + array('persistent' => false)); - if (empty($dbu)) { - return; - } + if (PEAR::isError($db)) { + echo "ERROR: " . $db->getMessage() . "\n"; + return NULL; + } - // Do a separate DB connection + $conn = $db->connection; - $sth = $dbu->prepare("UPDATE notice SET content = UNHEX(?), rendered = UNHEX(?) WHERE id = ?"); + $succ = mysqli_set_charset($conn, $charset); - if (PEAR::isError($sth)) { - echo "ERROR: " . $sth->getMessage() . "\n"; - return; - } + if (!$succ) { + echo "ERROR: couldn't set charset\n"; + $db->disconnect(); + return NULL; + } - $sql = 'SELECT id, content, rendered FROM notice ' . - 'WHERE LENGTH(content) != CHAR_LENGTH(content)'; - - if (!empty($max_id)) { - $sql .= ' AND id <= ' . $max_id; - } - - if (!empty($min_id)) { - $sql .= ' AND id >= ' . $min_id; - } - - $sql .= ' ORDER BY id DESC'; - - $rn = $dbl->query($sql); - - if (PEAR::isError($rn)) { - echo "ERROR: " . $rn->getMessage() . "\n"; - return; - } - - echo "Number of rows: " . $rn->numRows() . "\n"; - - $notice = array(); - - while (DB_OK == $rn->fetchInto($notice)) { - - $id = ($notice[0])+0; - $content = bin2hex($notice[1]); - $rendered = bin2hex($notice[2]); - - echo "$id..."; - - $result =& $dbu->execute($sth, array($content, $rendered, $id)); + $result = $db->autoCommit(true); if (PEAR::isError($result)) { echo "ERROR: " . $result->getMessage() . "\n"; - continue; + $db->disconnect(); + return NULL; } - $cnt = $dbu->affectedRows(); + return $db; + } - if ($cnt != 1) { - echo "ERROR: 0 rows affected\n"; - continue; + function fixup() + { + $this->fixupNotices($this->args['max_notice'], + $this->args['min_notice']); + $this->fixupProfiles(); + $this->fixupGroups(); + } + + function fixupNotices($max_id, $min_id) { + + // Do a separate DB connection + + $sth = $this->dbu->prepare("UPDATE notice SET content = UNHEX(?), rendered = UNHEX(?) WHERE id = ?"); + + if (PEAR::isError($sth)) { + echo "ERROR: " . $sth->getMessage() . "\n"; + return; } - $notice = Notice::staticGet('id', $id); - $notice->decache(); + $sql = 'SELECT id, content, rendered FROM notice ' . + 'WHERE LENGTH(content) != CHAR_LENGTH(content) '. + 'AND modified < "'.$this->max_date.'" '; - echo "OK\n"; + if (!empty($max_id)) { + $sql .= ' AND id <= ' . $max_id; + } + + if (!empty($min_id)) { + $sql .= ' AND id >= ' . $min_id; + } + + $sql .= ' ORDER BY id DESC'; + + $rn = $this->dbl->query($sql); + + if (PEAR::isError($rn)) { + echo "ERROR: " . $rn->getMessage() . "\n"; + return; + } + + echo "Number of rows: " . $rn->numRows() . "\n"; + + $notice = array(); + + while (DB_OK == $rn->fetchInto($notice)) { + + $id = ($notice[0])+0; + $content = bin2hex($notice[1]); + $rendered = bin2hex($notice[2]); + + echo "$id..."; + + $result =& $this->dbu->execute($sth, array($content, $rendered, $id)); + + if (PEAR::isError($result)) { + echo "ERROR: " . $result->getMessage() . "\n"; + continue; + } + + $cnt = $this->dbu->affectedRows(); + + if ($cnt != 1) { + echo "ERROR: 0 rows affected\n"; + continue; + } + + $notice = Notice::staticGet('id', $id); + $notice->decache(); + + echo "OK\n"; + } + } + + function fixupProfiles() + { + // Do a separate DB connection + + $sth = $this->dbu->prepare("UPDATE profile SET ". + "fullname = UNHEX(?),". + "location = UNHEX(?), ". + "bio = UNHEX(?) ". + "WHERE id = ?"); + + if (PEAR::isError($sth)) { + echo "ERROR: " . $sth->getMessage() . "\n"; + return; + } + + $sql = 'SELECT id, fullname, location, bio FROM profile ' . + 'WHERE (LENGTH(fullname) != CHAR_LENGTH(fullname) '. + 'OR LENGTH(location) != CHAR_LENGTH(location) '. + 'OR LENGTH(bio) != CHAR_LENGTH(bio)) '. + 'AND modified < "'.$this->max_date.'" '. + ' ORDER BY modified DESC'; + + $rn = $this->dbl->query($sql); + + if (PEAR::isError($rn)) { + echo "ERROR: " . $rn->getMessage() . "\n"; + return; + } + + echo "Number of rows: " . $rn->numRows() . "\n"; + + $profile = array(); + + while (DB_OK == $rn->fetchInto($profile)) { + + $id = ($profile[0])+0; + $fullname = bin2hex($profile[1]); + $location = bin2hex($profile[2]); + $bio = bin2hex($profile[3]); + + echo "$id..."; + + $result =& $this->dbu->execute($sth, array($fullname, $location, $bio, $id)); + + if (PEAR::isError($result)) { + echo "ERROR: " . $result->getMessage() . "\n"; + continue; + } + + $cnt = $this->dbu->affectedRows(); + + if ($cnt != 1) { + echo "ERROR: 0 rows affected\n"; + continue; + } + + $profile = Profile::staticGet('id', $id); + $profile->decache(); + + echo "OK\n"; + } + } + + function fixupGroups() + { + // Do a separate DB connection + + $sth = $this->dbu->prepare("UPDATE user_group SET ". + "fullname = UNHEX(?),". + "location = UNHEX(?), ". + "description = UNHEX(?) ". + "WHERE id = ?"); + + if (PEAR::isError($sth)) { + echo "ERROR: " . $sth->getMessage() . "\n"; + return; + } + + $sql = 'SELECT id, fullname, location, description FROM user_group ' . + 'WHERE LENGTH(fullname) != CHAR_LENGTH(fullname) '. + 'OR LENGTH(location) != CHAR_LENGTH(location) '. + 'OR LENGTH(description) != CHAR_LENGTH(description) '; + 'AND modified < "'.$this->max_date.'" '. + 'ORDER BY modified DESC'; + + $rn = $this->dbl->query($sql); + + if (PEAR::isError($rn)) { + echo "ERROR: " . $rn->getMessage() . "\n"; + return; + } + + echo "Number of rows: " . $rn->numRows() . "\n"; + + $user_group = array(); + + while (DB_OK == $rn->fetchInto($user_group)) { + + $id = ($user_group[0])+0; + $fullname = bin2hex($user_group[1]); + $location = bin2hex($user_group[2]); + $description = bin2hex($user_group[3]); + + echo "$id..."; + + $result =& $this->dbu->execute($sth, array($fullname, $location, $description, $id)); + + if (PEAR::isError($result)) { + echo "ERROR: " . $result->getMessage() . "\n"; + continue; + } + + $cnt = $this->dbu->affectedRows(); + + if ($cnt != 1) { + echo "ERROR: 0 rows affected\n"; + continue; + } + + $user_group = User_group::staticGet('id', $id); + $user_group->decache(); + + echo "OK\n"; + } } } -function doConnect($charset) -{ - $db = DB::connect(common_config('db', 'database'), - array('persistent' => false)); +$max_date = ($argc > 1) ? $argv[1] : null; +$max_id = ($argc > 2) ? $argv[2] : null; +$min_id = ($argc > 3) ? $argv[3] : null; - if (PEAR::isError($db)) { - echo "ERROR: " . $db->getMessage() . "\n"; - return NULL; - } +$fixer = new UTF8FixerUpper(array('max_date' => $max_date, + 'max_notice' => $max_id, + 'min_notice' => $min_id)); -// $result = $db->query("SET NAMES $charset"); +$fixer->fixup(); - $conn = $db->connection; - - $succ = mysqli_set_charset($conn, $charset); - - if (!$succ) { - echo "ERROR: couldn't set charset\n"; - $db->disconnect(); - return NULL; - } - - $result = $db->autoCommit(true); - - if (PEAR::isError($result)) { - echo "ERROR: " . $result->getMessage() . "\n"; - $db->disconnect(); - return NULL; - } - - return $db; -} - -$max_id = ($argc > 1) ? $argv[1] : null; -$min_id = ($argc > 2) ? $argv[2] : null; - -fixup_utf8($max_id, $min_id); From f134ba68ab6a7e9f94579d2a4e1f02eca2a7ebc0 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 30 May 2009 21:04:35 -0400 Subject: [PATCH 49/71] free memory for each iteration DB_DataObject in fixup_utf8 --- scripts/fixup_utf8.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/fixup_utf8.php b/scripts/fixup_utf8.php index 2046d2b770..f9debd3eca 100644 --- a/scripts/fixup_utf8.php +++ b/scripts/fixup_utf8.php @@ -163,6 +163,7 @@ class UTF8FixerUpper $notice = Notice::staticGet('id', $id); $notice->decache(); + $notice->free(); echo "OK\n"; } @@ -226,6 +227,7 @@ class UTF8FixerUpper $profile = Profile::staticGet('id', $id); $profile->decache(); + $profile->free(); echo "OK\n"; } @@ -289,6 +291,7 @@ class UTF8FixerUpper $user_group = User_group::staticGet('id', $id); $user_group->decache(); + $user_group->free(); echo "OK\n"; } From 4ec1c333073fd693147ff34895cf7e3664f83cf6 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 30 May 2009 21:45:11 -0400 Subject: [PATCH 50/71] correct last-modified date for shownotice.php --- actions/shownotice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/shownotice.php b/actions/shownotice.php index 2c469c9dee..1be1e2414c 100644 --- a/actions/shownotice.php +++ b/actions/shownotice.php @@ -122,7 +122,7 @@ class ShownoticeAction extends Action function lastModified() { - return max(strtotime($this->notice->created), + return max(strtotime($this->notice->modified), strtotime($this->profile->modified), ($this->avatar) ? strtotime($this->avatar->modified) : 0); } From 3e0c291810d23bb54c337c0fe95c1ba441459c4c Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Sun, 31 May 2009 16:42:29 -0400 Subject: [PATCH 51/71] Added configurable options for attachments: supported mimetypes and quotas for uploads. --- actions/newnotice.php | 118 ++++++-- config.php.sample | 8 + extlib/MIME/Type.php | 523 +++++++++++++++++++++++++++++++++ extlib/MIME/Type/Extension.php | 298 +++++++++++++++++++ extlib/MIME/Type/Parameter.php | 163 ++++++++++ lib/common.php | 56 +++- 6 files changed, 1136 insertions(+), 30 deletions(-) create mode 100644 extlib/MIME/Type.php create mode 100644 extlib/MIME/Type/Extension.php create mode 100644 extlib/MIME/Type/Parameter.php diff --git a/actions/newnotice.php b/actions/newnotice.php index aebdab3cca..29b748dd1a 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -84,20 +84,24 @@ class NewnoticeAction extends Action function handle($args) { - parent::handle($args); - if (!common_logged_in()) { $this->clientError(_('Not logged in.')); } else if ($_SERVER['REQUEST_METHOD'] == 'POST') { + // check for this before token since all POST and FILES data + // is losts when size is exceeded + if (empty($_POST) && $_SERVER['CONTENT_LENGTH']) { + $this->clientError(sprintf(_('The server was unable to handle ' . + 'that much POST data (%s bytes) due to its current configuration.'), + $_SERVER['CONTENT_LENGTH'])); + } + parent::handle($args); // CSRF protection $token = $this->trimmed('token'); if (!$token || $token != common_session_token()) { $this->clientError(_('There was a problem with your session token. '. 'Try again, please.')); - return; } - try { $this->saveNewNotice(); } catch (Exception $e) { @@ -109,8 +113,55 @@ class NewnoticeAction extends Action } } - function isFileAttached() { - return isset($_FILES['attach']['error']) && ($_FILES['attach']['error'] === UPLOAD_ERR_OK); + function isSupportedFileType() { + require_once 'MIME/Type.php'; + + $filetype = MIME_Type::autoDetect($_FILES['attach']['tmp_name']); + if (in_array($filetype, common_config('attachments', 'supported'))) { + return true; + } + $media = MIME_Type::getMedia($filetype); + if ('application' !== $media) { + $hint = sprintf(_(' Try using another %s format.'), $media); + } else { + $hint = ''; + } + $this->clientError(sprintf( + _('%s is not a supported filetype on this server.'), $filetype) . $hint); + } + + function isRespectsQuota($user) { + if ($_FILES['attach']['size'] > common_config('attachments', 'file_quota')) { + $this->clientError(sprintf(_('No file may be larger than %d bytes ' . + 'and the file you sent was %d bytes. Try to upload a smaller version.'), + common_config('attachments', 'file_quota'), $_FILES['attach']['size'])); + } + + $query = "select sum(size) as total from file join file_to_post on file_to_post.file_id = file.id join notice on file_to_post.post_id = notice.id where profile_id = {$user->id} and file.url like '%/notice/%/file'"; + $file = new File; + $file->query($query); + $file->fetch(); + $total = $file->total + $_FILES['attach']['size']; + if ($total > common_config('attachments', 'user_quota')) { + $this->clientError(sprintf(_('A file this large would exceed your user quota of %d bytes.'), common_config('attachments', 'user_quota'))); + } + + $query .= ' month(modified) = month(now()) and year(modified) = year(now())'; + $file2 = new File; + $file2->query($query); + $file2->fetch(); + $total2 = $file2->total + $_FILES['attach']['size']; + if ($total2 > common_config('attachments', 'monthly_quota')) { + $this->clientError(sprintf(_('A file this large would exceed your monthly quota of %d bytes.'), common_config('attachments', 'monthly_quota'))); + } + return true; + } + + function isValidFileAttached($user) { + return isset($_FILES['attach']['error']) + && ($_FILES['attach']['error'] === UPLOAD_ERR_OK) + && $this->isSupportedFileType() + && $this->isRespectsQuota($user); } /** @@ -135,7 +186,6 @@ class NewnoticeAction extends Action $this->clientError(_('No content!')); } else { $content_shortened = common_shorten_links($content); - if (mb_strlen($content_shortened) > 140) { $this->clientError(_('That\'s too long. '. 'Max notice size is 140 chars.')); @@ -162,19 +212,54 @@ class NewnoticeAction extends Action $replyto = 'false'; } + switch ($_FILES['attach']['error']) { + case UPLOAD_ERR_NO_FILE: + // no file uploaded + // nothing to do + break; + + case UPLOAD_ERR_OK: + // file was uploaded alright + // lets check if we really support its format + // and it doesn't go over quotas + + + if (!$this->isValidFileAttached($user)) { + die('clientError() should trigger an exception before reaching here.'); + } + break; + + case UPLOAD_ERR_INI_SIZE: + $this->clientError(_('The uploaded file exceeds the upload_max_filesize directive in php.ini.')); + + case UPLOAD_ERR_FORM_SIZE: + $this->clientError(_('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.')); + + case UPLOAD_ERR_PARTIAL: + $this->clientError(_('The uploaded file was only partially uploaded.')); + + case UPLOAD_ERR_NO_TMP_DIR: + $this->clientError(_('Missing a temporary folder.')); + + case UPLOAD_ERR_CANT_WRITE: + $this->clientError(_('Failed to write file to disk.')); + + case UPLOAD_ERR_EXTENSION: + $this->clientError(_('File upload stopped by extension.')); + + default: + die('Should never reach here.'); + } + $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1, ($replyto == 'false') ? null : $replyto); if (is_string($notice)) { $this->clientError($notice); - return; } - if ($this->isFileAttached()) { - $this->storeFile($notice); - } + $this->storeFile($notice); $this->saveUrls($notice); - common_broadcast_notice($notice); if ($this->boolean('ajax')) { @@ -201,12 +286,12 @@ class NewnoticeAction extends Action } function storeFile($notice) { + if (UPLOAD_ERR_NO_FILE === $_FILES['attach']['error']) return; $filename = basename($_FILES['attach']['name']); $destination = "file/{$notice->id}-$filename"; if (move_uploaded_file($_FILES['attach']['tmp_name'], INSTALLDIR . "/$destination")) { $file = new File; $file->url = common_local_url('file', array('notice' => $notice->id)); -// $file->url = common_path($destination); $file->size = filesize(INSTALLDIR . "/$destination"); $file->date = time(); $file->mimetype = $_FILES['attach']['type']; @@ -221,14 +306,9 @@ class NewnoticeAction extends Action $f2p->post_id = $notice->id; $f2p->insert(); } else { - die('inserting file, dying'); + $this->clientError(_('There was a database error while saving your file. Please try again.')); } } -/* - $url = common_local_url('file', array('notice' => $notice->id)); - echo "$destination
"; - die($url); -*/ } diff --git a/config.php.sample b/config.php.sample index 282826a7fb..636f4cf8e2 100644 --- a/config.php.sample +++ b/config.php.sample @@ -215,3 +215,11 @@ $config['sphinx']['port'] = 3312; // $config['snapshot']['run'] = 'never'; // If you want to report statistics in a cron job instead. // $config['snapshot']['run'] = 'cron'; + +// Support for file uploads (attachments), +// select supported mimetypes and quotas (in bytes) +// $config['attachments']['supported'] = array('image/png', 'application/ogg'); +// $config['attachments']['file_quota'] = 5000000; +// $config['attachments']['user_quota'] = 50000000; +// $config['attachments']['monthly_quota'] = 15000000; + diff --git a/extlib/MIME/Type.php b/extlib/MIME/Type.php new file mode 100644 index 0000000000..c335f8d92d --- /dev/null +++ b/extlib/MIME/Type.php @@ -0,0 +1,523 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Type.php,v 1.6 2009/01/16 11:49:45 cweiske Exp $ + +require_once 'PEAR.php'; + +$_fileCmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd'); +$_fileCmd = 'file'; + +/** + * Class for working with MIME types + * + * @category MIME + * @package MIME_Type + * @license PHP License 3.0 + * @version 1.2.0 + * @link http://pear.php.net/package/MIME_Type + * @author Ian Eure + */ +class MIME_Type +{ + /** + * The MIME media type + * + * @var string + */ + var $media = ''; + + /** + * The MIME media sub-type + * + * @var string + */ + var $subType = ''; + + /** + * Optional MIME parameters + * + * @var array + */ + var $parameters = array(); + + /** + * List of valid media types. + * A media type is the string in front of the slash. + * The media type of "text/xml" would be "text". + * + * @var array + */ + var $validMediaTypes = array( + 'text', + 'image', + 'audio', + 'video', + 'application', + 'multipart', + 'message' + ); + + + /** + * Constructor. + * + * If $type is set, if will be parsed and the appropriate class vars set. + * If not, you get an empty class. + * This is useful, but not quite as useful as parsing a type. + * + * @param string $type MIME type + * + * @return void + */ + function MIME_Type($type = false) + { + if ($type) { + $this->parse($type); + } + } + + + /** + * Parse a mime-type and set the class variables. + * + * @param string $type MIME type to parse + * + * @return void + */ + function parse($type) + { + $this->media = $this->getMedia($type); + $this->subType = $this->getSubType($type); + $this->parameters = array(); + if (MIME_Type::hasParameters($type)) { + require_once 'MIME/Type/Parameter.php'; + foreach (MIME_Type::getParameters($type) as $param) { + $param = new MIME_Type_Parameter($param); + $this->parameters[$param->name] = $param; + } + } + } + + + /** + * Does this type have any parameters? + * + * @param string $type MIME type to check + * + * @return boolean true if $type has parameters, false otherwise + * @static + */ + function hasParameters($type) + { + if (strstr($type, ';')) { + return true; + } + return false; + } + + + /** + * Get a MIME type's parameters + * + * @param string $type MIME type to get parameters of + * + * @return array $type's parameters + * @static + */ + function getParameters($type) + { + $params = array(); + $tmp = explode(';', $type); + for ($i = 1; $i < count($tmp); $i++) { + $params[] = trim($tmp[$i]); + } + return $params; + } + + + /** + * Strip parameters from a MIME type string. + * + * @param string $type MIME type string + * + * @return string MIME type with parameters removed + * @static + */ + function stripParameters($type) + { + if (strstr($type, ';')) { + return substr($type, 0, strpos($type, ';')); + } + return $type; + } + + + /** + * Removes comments from a media type, subtype or parameter. + * + * @param string $string String to strip comments from + * @param string &$comment Comment is stored in there. + * + * @return string String without comments + * @static + */ + function stripComments($string, &$comment) + { + if (strpos($string, '(') === false) { + return $string; + } + + $inquote = false; + $quoting = false; + $incomment = 0; + $newstring = ''; + + for ($n = 0; $n < strlen($string); $n++) { + if ($quoting) { + if ($incomment == 0) { + $newstring .= $string[$n]; + } else if ($comment !== null) { + $comment .= $string[$n]; + } + $quoting = false; + } else if ($string[$n] == '\\') { + $quoting = true; + } else if (!$inquote && $incomment > 0 && $string[$n] == ')') { + $incomment--; + if ($incomment == 0 && $comment !== null) { + $comment .= ' '; + } + } else if (!$inquote && $string[$n] == '(') { + $incomment++; + } else if ($string[$n] == '"') { + if ($inquote) { + $inquote = false; + } else { + $inquote = true; + } + } else if ($incomment == 0) { + $newstring .= $string[$n]; + } else if ($comment !== null) { + $comment .= $string[$n]; + } + } + + if ($comment !== null) { + $comment = trim($comment); + } + + return $newstring; + } + + + /** + * Get a MIME type's media + * + * @note 'media' refers to the portion before the first slash + * + * @param string $type MIME type to get media of + * + * @return string $type's media + * @static + */ + function getMedia($type) + { + $tmp = explode('/', $type); + return strtolower(trim(MIME_Type::stripComments($tmp[0], $null))); + } + + + /** + * Get a MIME type's subtype + * + * @param string $type MIME type to get subtype of + * + * @return string $type's subtype, null if invalid mime type + * @static + */ + function getSubType($type) + { + $tmp = explode('/', $type); + if (!isset($tmp[1])) { + return null; + } + $tmp = explode(';', $tmp[1]); + return strtolower(trim(MIME_Type::stripComments($tmp[0], $null))); + } + + + /** + * Create a textual MIME type from object values + * + * This function performs the opposite function of parse(). + * + * @return string MIME type string + */ + function get() + { + $type = strtolower($this->media . '/' . $this->subType); + if (count($this->parameters)) { + foreach ($this->parameters as $key => $null) { + $type .= '; ' . $this->parameters[$key]->get(); + } + } + return $type; + } + + + /** + * Is this type experimental? + * + * @note Experimental types are denoted by a leading 'x-' in the media or + * subtype, e.g. text/x-vcard or x-world/x-vrml. + * + * @param string $type MIME type to check + * + * @return boolean true if $type is experimental, false otherwise + * @static + */ + function isExperimental($type) + { + if (substr(MIME_Type::getMedia($type), 0, 2) == 'x-' || + substr(MIME_Type::getSubType($type), 0, 2) == 'x-') { + return true; + } + return false; + } + + + /** + * Is this a vendor MIME type? + * + * @note Vendor types are denoted with a leading 'vnd. in the subtype. + * + * @param string $type MIME type to check + * + * @return boolean true if $type is a vendor type, false otherwise + * @static + */ + function isVendor($type) + { + if (substr(MIME_Type::getSubType($type), 0, 4) == 'vnd.') { + return true; + } + return false; + } + + + /** + * Is this a wildcard type? + * + * @param string $type MIME type to check + * + * @return boolean true if $type is a wildcard, false otherwise + * @static + */ + function isWildcard($type) + { + if ($type == '*/*' || MIME_Type::getSubtype($type) == '*') { + return true; + } + return false; + } + + + /** + * Perform a wildcard match on a MIME type + * + * Example: + * MIME_Type::wildcardMatch('image/*', 'image/png') + * + * @param string $card Wildcard to check against + * @param string $type MIME type to check + * + * @return boolean true if there was a match, false otherwise + * @static + */ + function wildcardMatch($card, $type) + { + if (!MIME_Type::isWildcard($card)) { + return false; + } + + if ($card == '*/*') { + return true; + } + + if (MIME_Type::getMedia($card) == MIME_Type::getMedia($type)) { + return true; + } + + return false; + } + + + /** + * Add a parameter to this type + * + * @param string $name Attribute name + * @param string $value Attribute value + * @param string $comment Comment for this parameter + * + * @return void + */ + function addParameter($name, $value, $comment = false) + { + $tmp = new MIME_Type_Parameter(); + + $tmp->name = $name; + $tmp->value = $value; + $tmp->comment = $comment; + $this->parameters[$name] = $tmp; + } + + + /** + * Remove a parameter from this type + * + * @param string $name Parameter name + * + * @return void + */ + function removeParameter($name) + { + unset($this->parameters[$name]); + } + + + /** + * Autodetect a file's MIME-type + * + * This function may be called staticly. + * + * @internal Tries to use fileinfo extension at first. If that + * does not work, mime_magic is used. If this is also not available + * or does not succeed, "file" command is tried to be executed with + * System_Command. When that fails, too, then we use our in-built + * extension-to-mimetype-mapping list. + * + * @param string $file Path to the file to get the type of + * @param bool $params Append MIME parameters if true + * + * @return string $file's MIME-type on success, PEAR_Error otherwise + * + * @since 1.0.0beta1 + * @static + */ + function autoDetect($file, $params = false) + { + // Sanity checks + if (!file_exists($file)) { + return PEAR::raiseError("File \"$file\" doesn't exist"); + } + + if (!is_readable($file)) { + return PEAR::raiseError("File \"$file\" is not readable"); + } + + if (function_exists('finfo_file')) { + $finfo = finfo_open(FILEINFO_MIME); + $type = finfo_file($finfo, $file); + finfo_close($finfo); + if ($type !== false && $type !== '') { + return MIME_Type::_handleDetection($type, $params); + } + } + + if (function_exists('mime_content_type')) { + $type = mime_content_type($file); + if ($type !== false && $type !== '') { + return MIME_Type::_handleDetection($type, $params); + } + } + + @include_once 'System/Command.php'; + if (class_exists('System_Command')) { + return MIME_Type::_handleDetection( + MIME_Type::_fileAutoDetect($file), + $params + ); + } + + require_once 'MIME/Type/Extension.php'; + $mte = new MIME_Type_Extension(); + return $mte->getMIMEType($file); + } + + + /** + * Handles a detected MIME type and modifies it if necessary. + * + * @param string $type MIME Type of a file + * @param bool $params Append MIME parameters if true + * + * @return string $file's MIME-type on success, PEAR_Error otherwise + */ + function _handleDetection($type, $params) + { + // _fileAutoDetect() may have returned an error. + if (PEAR::isError($type)) { + return $type; + } + + // Don't return an empty string + if (!$type || !strlen($type)) { + return PEAR::raiseError("Sorry, couldn't determine file type."); + } + + // Strip parameters if present & requested + if (MIME_Type::hasParameters($type) && !$params) { + $type = MIME_Type::stripParameters($type); + } + + return $type; + } + + + /** + * Autodetect a file's MIME-type with 'file' and System_Command + * + * This function may be called staticly. + * + * @param string $file Path to the file to get the type of + * + * @return string $file's MIME-type + * + * @since 1.0.0beta1 + * @static + */ + function _fileAutoDetect($file) + { + $cmd = new System_Command(); + + // Make sure we have the 'file' command. + $fileCmd = PEAR::getStaticProperty('MIME_Type', 'fileCmd'); + if (!$cmd->which($fileCmd)) { + unset($cmd); + return PEAR::raiseError("Can't find file command \"{$fileCmd}\""); + } + + $cmd->pushCommand($fileCmd, "-bi " . escapeshellarg($file)); + $res = $cmd->execute(); + unset($cmd); + + return $res; + } +} + diff --git a/extlib/MIME/Type/Extension.php b/extlib/MIME/Type/Extension.php new file mode 100644 index 0000000000..1987e2a10e --- /dev/null +++ b/extlib/MIME/Type/Extension.php @@ -0,0 +1,298 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Extension.php,v 1.1 2009/01/16 11:49:45 cweiske Exp $ + +require_once 'PEAR.php'; + +/** + * Class for mapping file extensions to MIME types. + * + * @category MIME + * @package MIME_Type + * @author Christian Schmidt + * @license PHP License 3.0 + * @version 1.2.0 + * @link http://pear.php.net/package/MIME_Type + */ +class MIME_Type_Extension +{ + /** + * Mapping between file extension and MIME type. + * + * @internal The array is sorted alphabetically by value and with primary + * extension first. Be careful about not adding duplicate keys - PHP + * silently ignores duplicates. The following command can be used for + * checking for duplicates: + * grep "=> '" Extension.php | cut -d\' -f2 | sort | uniq -d + * application/octet-stream is generally used as fallback when no other + * MIME-type can be found, but the array does not contain a lot of such + * unknown extension. One entry exists, though, to allow detection of + * file extension for this MIME-type. + * + * @var array + */ + var $extensionToType = array ( + 'ez' => 'application/andrew-inset', + 'atom' => 'application/atom+xml', + 'jar' => 'application/java-archive', + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'mathml' => 'application/mathml+xml', + 'doc' => 'application/msword', + 'dat' => 'application/octet-stream', + 'oda' => 'application/oda', + 'ogg' => 'application/ogg', + 'pdf' => 'application/pdf', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'rdf' => 'application/rdf+xml', + 'rss' => 'application/rss+xml', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'gram' => 'application/srgs', + 'grxml' => 'application/srgs+xml', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'mif' => 'application/vnd.mif', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xls' => 'application/vnd.ms-excel', + 'xlb' => 'application/vnd.ms-excel', + 'xlt' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', + 'docm' => 'application/vnd.ms-word.document.macroEnabled.12', + 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 'potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pps' => 'application/vnd.ms-powerpoint', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'vsd' => 'application/vnd.visio', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'vxml' => 'application/voicexml+xml', + 'bcpio' => 'application/x-bcpio', + 'vcd' => 'application/x-cdlink', + 'pgn' => 'application/x-chess-pgn', + 'cpio' => 'application/x-cpio', + 'csh' => 'application/x-csh', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'spl' => 'application/x-futuresplash', + 'tgz' => 'application/x-gtar', + 'gtar' => 'application/x-gtar', + 'hdf' => 'application/x-hdf', + 'js' => 'application/x-javascript', + 'skp' => 'application/x-koan', + 'skd' => 'application/x-koan', + 'skt' => 'application/x-koan', + 'skm' => 'application/x-koan', + 'latex' => 'application/x-latex', + 'nc' => 'application/x-netcdf', + 'cdf' => 'application/x-netcdf', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'tar' => 'application/x-tar', + 'tcl' => 'application/x-tcl', + 'tex' => 'application/x-tex', + 'texinfo' => 'application/x-texinfo', + 'texi' => 'application/x-texinfo', + 't' => 'application/x-troff', + 'tr' => 'application/x-troff', + 'roff' => 'application/x-troff', + 'man' => 'application/x-troff-man', + 'me' => 'application/x-troff-me', + 'ms' => 'application/x-troff-ms', + 'ustar' => 'application/x-ustar', + 'src' => 'application/x-wais-source', + 'xhtml' => 'application/xhtml+xml', + 'xht' => 'application/xhtml+xml', + 'xslt' => 'application/xslt+xml', + 'xml' => 'application/xml', + 'xsl' => 'application/xml', + 'dtd' => 'application/xml-dtd', + 'zip' => 'application/zip', + 'au' => 'audio/basic', + 'snd' => 'audio/basic', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'kar' => 'audio/midi', + 'mpga' => 'audio/mpeg', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'm3u' => 'audio/x-mpegurl', + 'wma' => 'audio/x-ms-wma', + 'wax' => 'audio/x-ms-wax', + 'ram' => 'audio/x-pn-realaudio', + 'ra' => 'audio/x-pn-realaudio', + 'rm' => 'application/vnd.rn-realmedia', + 'wav' => 'audio/x-wav', + 'pdb' => 'chemical/x-pdb', + 'xyz' => 'chemical/x-xyz', + 'bmp' => 'image/bmp', + 'cgm' => 'image/cgm', + 'gif' => 'image/gif', + 'ief' => 'image/ief', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'png' => 'image/png', + 'svg' => 'image/svg+xml', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'djvu' => 'image/vnd.djvu', + 'djv' => 'image/vnd.djvu', + 'wbmp' => 'image/vnd.wap.wbmp', + 'ras' => 'image/x-cmu-raster', + 'ico' => 'image/x-icon', + 'pnm' => 'image/x-portable-anymap', + 'pbm' => 'image/x-portable-bitmap', + 'pgm' => 'image/x-portable-graymap', + 'ppm' => 'image/x-portable-pixmap', + 'rgb' => 'image/x-rgb', + 'xbm' => 'image/x-xbitmap', + 'psd' => 'image/x-photoshop', + 'xpm' => 'image/x-xpixmap', + 'xwd' => 'image/x-xwindowdump', + 'eml' => 'message/rfc822', + 'igs' => 'model/iges', + 'iges' => 'model/iges', + 'msh' => 'model/mesh', + 'mesh' => 'model/mesh', + 'silo' => 'model/mesh', + 'wrl' => 'model/vrml', + 'vrml' => 'model/vrml', + 'ics' => 'text/calendar', + 'ifb' => 'text/calendar', + 'css' => 'text/css', + 'csv' => 'text/csv', + 'html' => 'text/html', + 'htm' => 'text/html', + 'txt' => 'text/plain', + 'asc' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'sgml' => 'text/sgml', + 'sgm' => 'text/sgml', + 'tsv' => 'text/tab-separated-values', + 'wml' => 'text/vnd.wap.wml', + 'wmls' => 'text/vnd.wap.wmlscript', + 'etx' => 'text/x-setext', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + 'mxu' => 'video/vnd.mpegurl', + 'm4u' => 'video/vnd.mpegurl', + 'flv' => 'video/x-flv', + 'asf' => 'video/x-ms-asf', + 'asx' => 'video/x-ms-asf', + 'wmv' => 'video/x-ms-wmv', + 'wm' => 'video/x-ms-wm', + 'wmx' => 'video/x-ms-wmx', + 'avi' => 'video/x-msvideo', + 'ogv' => 'video/ogg', + 'movie' => 'video/x-sgi-movie', + 'ice' => 'x-conference/x-cooltalk', + ); + + + + /** + * Autodetect a file's MIME-type. + * + * @param string $file Path to the file to get the type of + * + * @return string $file's MIME-type on success, PEAR_Error otherwise + */ + function getMIMEType($file) + { + $extension = substr(strrchr($file, '.'), 1); + if ($extension === false) { + return PEAR::raiseError("File has no extension."); + } + + if (!isset($this->extensionToType[$extension])) { + return PEAR::raiseError("Sorry, couldn't determine file type."); + } + + return $this->extensionToType[$extension]; + } + + + + /** + * Return default MIME-type for the specified extension. + * + * @param string $type MIME-type + * + * @return string A file extension without leading period. + */ + function getExtension($type) + { + require_once 'MIME/Type.php'; + // Strip parameters and comments. + $type = MIME_Type::getMedia($type) . '/' . MIME_Type::getSubType($type); + + $extension = array_search($type, $this->extensionToType); + if ($extension === false) { + return PEAR::raiseError("Sorry, couldn't determine extension."); + } + return $extension; + } + +} + +?> \ No newline at end of file diff --git a/extlib/MIME/Type/Parameter.php b/extlib/MIME/Type/Parameter.php new file mode 100644 index 0000000000..399d3dd7ad --- /dev/null +++ b/extlib/MIME/Type/Parameter.php @@ -0,0 +1,163 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Parameter.php,v 1.1 2007/03/25 10:10:21 cweiske Exp $ + +/** + * Class for working with MIME type parameters + * + * @version 1.2.0 + * @package MIME_Type + * @author Ian Eure + */ +class MIME_Type_Parameter { + /** + * Parameter name + * + * @var string + */ + var $name; + + /** + * Parameter value + * + * @var string + */ + var $value; + + /** + * Parameter comment + * + * @var string + */ + var $comment; + + + /** + * Constructor. + * + * @param string $param MIME parameter to parse, if set. + * @return void + */ + function MIME_Type_Parameter($param = false) + { + if ($param) { + $this->parse($param); + } + } + + + /** + * Parse a MIME type parameter and set object fields + * + * @param string $param MIME type parameter to parse + * @return void + */ + function parse($param) + { + $comment = ''; + $param = MIME_Type::stripComments($param, $comment); + $this->name = $this->getAttribute($param); + $this->value = $this->getValue($param); + $this->comment = $comment; + } + + + /** + * Get a parameter attribute (e.g. name) + * + * @param string MIME type parameter + * @return string Attribute name + * @static + */ + function getAttribute($param) + { + $tmp = explode('=', $param); + return trim($tmp[0]); + } + + + /** + * Get a parameter value + * + * @param string $param MIME type parameter + * @return string Value + * @static + */ + function getValue($param) + { + $tmp = explode('=', $param, 2); + $value = $tmp[1]; + $value = trim($value); + if ($value[0] == '"' && $value[strlen($value)-1] == '"') { + $value = substr($value, 1, -1); + } + $value = str_replace('\\"', '"', $value); + return $value; + } + + + /** + * Get a parameter comment + * + * @param string $param MIME type parameter + * @return string Parameter comment + * @see getComment() + * @static + */ + function getComment($param) + { + $cs = strpos($param, '('); + $comment = substr($param, $cs); + return trim($comment, '() '); + } + + + /** + * Does this parameter have a comment? + * + * @param string $param MIME type parameter + * @return boolean true if $param has a comment, false otherwise + * @static + */ + function hasComment($param) + { + if (strstr($param, '(')) { + return true; + } + return false; + } + + + /** + * Get a string representation of this parameter + * + * This function performs the oppsite of parse() + * + * @return string String representation of parameter + */ + function get() + { + $val = $this->name . '="' . str_replace('"', '\\"', $this->value) . '"'; + if ($this->comment) { + $val .= ' (' . $this->comment . ')'; + } + return $val; + } +} +?> \ No newline at end of file diff --git a/lib/common.php b/lib/common.php index 0ce46442de..ede8d6277f 100644 --- a/lib/common.php +++ b/lib/common.php @@ -163,6 +163,40 @@ $config = array('run' => 'web', 'frequency' => 10000, 'reporturl' => 'http://laconi.ca/stats/report'), + 'attachments' => + array('supported' => array('image/png', + 'image/jpeg', + 'image/gif', + 'image/svg+xml', + 'audio/mpeg', + 'application/ogg', + 'application/pdf', + 'application/vnd.oasis.opendocument.text', + 'application/vnd.oasis.opendocument.text-template', + 'application/vnd.oasis.opendocument.graphics', + 'application/vnd.oasis.opendocument.graphics-template', + 'application/vnd.oasis.opendocument.presentation', + 'application/vnd.oasis.opendocument.presentation-template', + 'application/vnd.oasis.opendocument.spreadsheet', + 'application/vnd.oasis.opendocument.spreadsheet-template', + 'application/vnd.oasis.opendocument.chart', + 'application/vnd.oasis.opendocument.chart-template', + 'application/vnd.oasis.opendocument.image', + 'application/vnd.oasis.opendocument.image-template', + 'application/vnd.oasis.opendocument.formula', + 'application/vnd.oasis.opendocument.formula-template', + 'application/vnd.oasis.opendocument.text-master', + 'application/vnd.oasis.opendocument.text-web', + 'application/zip', + 'text/plain', + 'video/mpeg', + 'video/mp4', + 'video/quicktime', + 'video/mpeg'), + 'file_quota' => 5000000, + 'user_quota' => 50000000, + 'monthly_quota' => 15000000, + ), ); $config['db'] = &PEAR::getStaticProperty('DB_DataObject','options'); @@ -223,19 +257,19 @@ if ($_db_name != 'laconica' && !array_key_exists('ini_'.$_db_name, $config['db'] // XXX: how many of these could be auto-loaded on use? -require_once('Validate.php'); -require_once('markdown.php'); +require_once 'Validate.php'; +require_once 'markdown.php'; -require_once(INSTALLDIR.'/lib/util.php'); -require_once(INSTALLDIR.'/lib/action.php'); -require_once(INSTALLDIR.'/lib/theme.php'); -require_once(INSTALLDIR.'/lib/mail.php'); -require_once(INSTALLDIR.'/lib/subs.php'); -require_once(INSTALLDIR.'/lib/Shorturl_api.php'); -require_once(INSTALLDIR.'/lib/twitter.php'); +require_once INSTALLDIR.'/lib/util.php'; +require_once INSTALLDIR.'/lib/action.php'; +require_once INSTALLDIR.'/lib/theme.php'; +require_once INSTALLDIR.'/lib/mail.php'; +require_once INSTALLDIR.'/lib/subs.php'; +require_once INSTALLDIR.'/lib/Shorturl_api.php'; +require_once INSTALLDIR.'/lib/twitter.php'; -require_once(INSTALLDIR.'/lib/clientexception.php'); -require_once(INSTALLDIR.'/lib/serverexception.php'); +require_once INSTALLDIR.'/lib/clientexception.php'; +require_once INSTALLDIR.'/lib/serverexception.php'; // XXX: other formats here From abe68f4318009ed839c1998d91459f26d09f76c8 Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Sun, 31 May 2009 18:33:38 -0400 Subject: [PATCH 52/71] Added attachments config descriptions to README. --- README | 24 ++++++++++++++++++++---- lib/noticeform.php | 8 +++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/README b/README index 76b56a52e5..9cbe84f0e5 100644 --- a/README +++ b/README @@ -1204,11 +1204,27 @@ The software lets users upload files with their notices. You can configure the types of accepted files by mime types and a trio of quota options: per file, per user (total), per user per month. +We suggest the use of the pecl file_info extension to handle mime type +detection. + supported: an array of mime types you accept to store and distribute, - like 'image/gif', 'video/mpeg', 'audio/mpeg', etc. -file_quota: maximum size for a single file upload in bytes. -user_quota: total size in bytes a user can store. -monthly_quota: total size permitted in the current month. + like 'image/gif', 'video/mpeg', 'audio/mpeg', etc. Make sure you + setup your server to properly reckognize the types you want to + support. + +For quotas, be sure you've set the upload_max_filesize and post_max_size +in php.ini to be large enough to handle your upload. In httpd.conf +(if you're using apache), check that the LimitRequestBody directive isn't +set too low (it's optional, so it may not be there at all). + +file_quota: maximum size for a single file upload in bytes. A user can send + any amount of notices with attachments as long as each attachment + is smaller than file_quota. +user_quota: total size in bytes a user can store on this server. Each user + can store any number of files as long as their total size does + not exceed the user_quota. +monthly_quota: total size permitted in the current month. This is the total + size in bytes that a user can upload each month. Troubleshooting diff --git a/lib/noticeform.php b/lib/noticeform.php index 707768cd59..5f438327d2 100644 --- a/lib/noticeform.php +++ b/lib/noticeform.php @@ -137,9 +137,6 @@ class NoticeForm extends Form { $this->out->element('label', array('for' => 'notice_data-text'), sprintf(_('What\'s up, %s?'), $this->user->nickname)); - $this->out->elementStart('span', array('style' => 'float: right; margin-top: 2em;')); -// $this->out->element('a', array('href' => '#attach'), ' [ATTACH]'); - $this->out->elementEnd('span'); // XXX: vary by defined max size $this->out->element('textarea', array('id' => 'notice_data-text', 'cols' => 35, @@ -152,15 +149,16 @@ class NoticeForm extends Form '140'); $this->out->elementEnd('dl'); $this->out->element('br', array('style' => 'clear:both')); -// $this->out->elementStart('a', array('href' => '#')); $this->out->element('label', array('for' => 'notice_data-attach'), _('Upload: ')); -// $this->out->elementEnd('a'); $this->out->element('input', array('id' => 'notice_data-attach', 'type' => 'file', 'name' => 'attach')); if ($this->action) { $this->out->hidden('notice_return-to', $this->action, 'returnto'); } $this->out->hidden('notice_in-reply-to', $this->action, 'inreplyto'); + + $this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota')); + } /** From f8dae2bbc9600a4ad474e924dc540491e29e2767 Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Sun, 31 May 2009 21:03:55 -0400 Subject: [PATCH 53/71] Refactored some attachment code and fixed upload bug in interface. --- actions/newnotice.php | 38 ++++++-------------------------------- classes/File.php | 26 ++++++++++++++++++++++++++ lib/noticeform.php | 2 -- 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/actions/newnotice.php b/actions/newnotice.php index 29b748dd1a..a24d626840 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -131,37 +131,10 @@ class NewnoticeAction extends Action } function isRespectsQuota($user) { - if ($_FILES['attach']['size'] > common_config('attachments', 'file_quota')) { - $this->clientError(sprintf(_('No file may be larger than %d bytes ' . - 'and the file you sent was %d bytes. Try to upload a smaller version.'), - common_config('attachments', 'file_quota'), $_FILES['attach']['size'])); - } - - $query = "select sum(size) as total from file join file_to_post on file_to_post.file_id = file.id join notice on file_to_post.post_id = notice.id where profile_id = {$user->id} and file.url like '%/notice/%/file'"; $file = new File; - $file->query($query); - $file->fetch(); - $total = $file->total + $_FILES['attach']['size']; - if ($total > common_config('attachments', 'user_quota')) { - $this->clientError(sprintf(_('A file this large would exceed your user quota of %d bytes.'), common_config('attachments', 'user_quota'))); - } - - $query .= ' month(modified) = month(now()) and year(modified) = year(now())'; - $file2 = new File; - $file2->query($query); - $file2->fetch(); - $total2 = $file2->total + $_FILES['attach']['size']; - if ($total2 > common_config('attachments', 'monthly_quota')) { - $this->clientError(sprintf(_('A file this large would exceed your monthly quota of %d bytes.'), common_config('attachments', 'monthly_quota'))); - } - return true; - } - - function isValidFileAttached($user) { - return isset($_FILES['attach']['error']) - && ($_FILES['attach']['error'] === UPLOAD_ERR_OK) - && $this->isSupportedFileType() - && $this->isRespectsQuota($user); + $ret = $file->isRespectsQuota($user); + if (true === $ret) return true; + $this->clientError($ret); } /** @@ -212,6 +185,7 @@ class NewnoticeAction extends Action $replyto = 'false'; } + if (isset($_FILES['attach']['error'])) { switch ($_FILES['attach']['error']) { case UPLOAD_ERR_NO_FILE: // no file uploaded @@ -223,8 +197,7 @@ class NewnoticeAction extends Action // lets check if we really support its format // and it doesn't go over quotas - - if (!$this->isValidFileAttached($user)) { + if (!$this->isSupportedFileType() || !$this->isRespectsQuota($user)) { die('clientError() should trigger an exception before reaching here.'); } break; @@ -250,6 +223,7 @@ class NewnoticeAction extends Action default: die('Should never reach here.'); } + } $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1, ($replyto == 'false') ? null : $replyto); diff --git a/classes/File.php b/classes/File.php index e5913115b7..24ab11b8eb 100644 --- a/classes/File.php +++ b/classes/File.php @@ -120,4 +120,30 @@ class File extends Memcached_DataObject File_to_post::processNew($file_id, $notice_id); return $x; } + + function isRespectsQuota($user) { + if ($_FILES['attach']['size'] > common_config('attachments', 'file_quota')) { + return sprintf(_('No file may be larger than %d bytes ' . + 'and the file you sent was %d bytes. Try to upload a smaller version.'), + common_config('attachments', 'file_quota'), $_FILES['attach']['size']); + } + + $query = "select sum(size) as total from file join file_to_post on file_to_post.file_id = file.id join notice on file_to_post.post_id = notice.id where profile_id = {$user->id} and file.url like '%/notice/%/file'"; + $this->query($query); + $this->fetch(); + $total = $this->total + $_FILES['attach']['size']; + if ($total > common_config('attachments', 'user_quota')) { + return sprintf(_('A file this large would exceed your user quota of %d bytes.'), common_config('attachments', 'user_quota')); + } + + $query .= ' month(modified) = month(now()) and year(modified) = year(now())'; + $this->query($query); + $this->fetch(); + $total = $this->total + $_FILES['attach']['size']; + if ($total > common_config('attachments', 'monthly_quota')) { + return sprintf(_('A file this large would exceed your monthly quota of %d bytes.'), common_config('attachments', 'monthly_quota')); + } + return true; + } } + diff --git a/lib/noticeform.php b/lib/noticeform.php index 5f438327d2..7c88bd7b19 100644 --- a/lib/noticeform.php +++ b/lib/noticeform.php @@ -156,9 +156,7 @@ class NoticeForm extends Form $this->out->hidden('notice_return-to', $this->action, 'returnto'); } $this->out->hidden('notice_in-reply-to', $this->action, 'inreplyto'); - $this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota')); - } /** From c5d105f186484fdeaa3d5a14aba9920fdef58c7c Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 1 Jun 2009 17:12:41 +0000 Subject: [PATCH 54/71] Fixed z-index for IE7 on attachment thumbnail --- theme/base/css/ie.css | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/theme/base/css/ie.css b/theme/base/css/ie.css index df0388a5ad..8183fee679 100644 --- a/theme/base/css/ie.css +++ b/theme/base/css/ie.css @@ -30,9 +30,12 @@ margin-right:4px; .entity_profile { width:64%; } -#jOverlayContent .notice * { +.notice { z-index:1; } -#jOverlayContent .notice .attachment img { +.notice:hover { z-index:9999; } +.notice .thumbnail img { +z-index:9999; +} \ No newline at end of file From 683b835c3ed0be5f691795c64b1217896e1dad95 Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Mon, 1 Jun 2009 14:02:59 -0400 Subject: [PATCH 55/71] Attachments popups for supported files are now embedded with the object xhtml tag. --- actions/newnotice.php | 69 ++++++++++++++++++++---------------------- lib/attachmentlist.php | 17 +++++++++++ lib/common.php | 1 + 3 files changed, 51 insertions(+), 36 deletions(-) diff --git a/actions/newnotice.php b/actions/newnotice.php index a24d626840..3e6ff15189 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -113,12 +113,12 @@ class NewnoticeAction extends Action } } - function isSupportedFileType() { + function getUploadedFileType() { require_once 'MIME/Type.php'; $filetype = MIME_Type::autoDetect($_FILES['attach']['tmp_name']); if (in_array($filetype, common_config('attachments', 'supported'))) { - return true; + return $filetype; } $media = MIME_Type::getMedia($filetype); if ('application' !== $media) { @@ -186,43 +186,39 @@ class NewnoticeAction extends Action } if (isset($_FILES['attach']['error'])) { - switch ($_FILES['attach']['error']) { - case UPLOAD_ERR_NO_FILE: - // no file uploaded - // nothing to do - break; + switch ($_FILES['attach']['error']) { + case UPLOAD_ERR_NO_FILE: + // no file uploaded, nothing to do + break; - case UPLOAD_ERR_OK: - // file was uploaded alright - // lets check if we really support its format - // and it doesn't go over quotas + case UPLOAD_ERR_OK: + $mimetype = $this->getUploadedFileType(); + if (!$this->isRespectsQuota($user)) { + die('clientError() should trigger an exception before reaching here.'); + } + break; - if (!$this->isSupportedFileType() || !$this->isRespectsQuota($user)) { - die('clientError() should trigger an exception before reaching here.'); - } - break; + case UPLOAD_ERR_INI_SIZE: + $this->clientError(_('The uploaded file exceeds the upload_max_filesize directive in php.ini.')); - case UPLOAD_ERR_INI_SIZE: - $this->clientError(_('The uploaded file exceeds the upload_max_filesize directive in php.ini.')); + case UPLOAD_ERR_FORM_SIZE: + $this->clientError(_('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.')); - case UPLOAD_ERR_FORM_SIZE: - $this->clientError(_('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.')); + case UPLOAD_ERR_PARTIAL: + $this->clientError(_('The uploaded file was only partially uploaded.')); - case UPLOAD_ERR_PARTIAL: - $this->clientError(_('The uploaded file was only partially uploaded.')); + case UPLOAD_ERR_NO_TMP_DIR: + $this->clientError(_('Missing a temporary folder.')); - case UPLOAD_ERR_NO_TMP_DIR: - $this->clientError(_('Missing a temporary folder.')); + case UPLOAD_ERR_CANT_WRITE: + $this->clientError(_('Failed to write file to disk.')); - case UPLOAD_ERR_CANT_WRITE: - $this->clientError(_('Failed to write file to disk.')); + case UPLOAD_ERR_EXTENSION: + $this->clientError(_('File upload stopped by extension.')); - case UPLOAD_ERR_EXTENSION: - $this->clientError(_('File upload stopped by extension.')); - - default: - die('Should never reach here.'); - } + default: + die('Should never reach here.'); + } } $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1, @@ -232,7 +228,9 @@ class NewnoticeAction extends Action $this->clientError($notice); } - $this->storeFile($notice); + if (isset($mimetype)) { + $this->storeFile($notice, $mimetype); + } $this->saveUrls($notice); common_broadcast_notice($notice); @@ -259,8 +257,7 @@ class NewnoticeAction extends Action } } - function storeFile($notice) { - if (UPLOAD_ERR_NO_FILE === $_FILES['attach']['error']) return; + function storeFile($notice, $mimetype) { $filename = basename($_FILES['attach']['name']); $destination = "file/{$notice->id}-$filename"; if (move_uploaded_file($_FILES['attach']['tmp_name'], INSTALLDIR . "/$destination")) { @@ -268,7 +265,7 @@ class NewnoticeAction extends Action $file->url = common_local_url('file', array('notice' => $notice->id)); $file->size = filesize(INSTALLDIR . "/$destination"); $file->date = time(); - $file->mimetype = $_FILES['attach']['type']; + $file->mimetype = $mimetype; if ($file_id = $file->insert()) { $file_redir = new File_redirection; $file_redir->url = common_path($destination); @@ -285,7 +282,6 @@ class NewnoticeAction extends Action } } - /** save all urls in the notice to the db * * follow redirects and save all available file information @@ -408,3 +404,4 @@ class NewnoticeAction extends Action $nli->show(); } } + diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index 61749dca57..45e4fa3196 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -268,6 +268,23 @@ class Attachment extends AttachmentListItem case 'image/jpeg': $this->out->element('img', array('src' => $this->attachment->url, 'alt' => 'alt')); break; + + case 'application/ogg': + case 'audio/x-speex': + case 'video/mpeg': + case 'audio/mpeg': + case 'video/mp4': + case 'video/quicktime': + $arr = array('type' => $this->attachment->mimetype, + 'data' => $this->attachment->url, + 'width' => 320, + 'height' => 240 + ); + $this->out->elementStart('object', $arr); + $this->out->element('param', array('name' => 'src', 'value' => $this->attachment->url)); + $this->out->element('param', array('name' => 'autoStart', 'value' => 1)); + $this->out->elementEnd('object'); + break; } } } else { diff --git a/lib/common.php b/lib/common.php index 838f52f9d5..ceb50337cc 100644 --- a/lib/common.php +++ b/lib/common.php @@ -169,6 +169,7 @@ $config = 'image/gif', 'image/svg+xml', 'audio/mpeg', + 'audio/x-speex', 'application/ogg', 'application/pdf', 'application/vnd.oasis.opendocument.text', From 95bcc5afa11ccbff82c2d0d16a286014ba96acb1 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 1 Jun 2009 15:18:23 -0400 Subject: [PATCH 56/71] Updated markup for notice form attachment --- lib/noticeform.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/noticeform.php b/lib/noticeform.php index 7c88bd7b19..5a6c7cf38c 100644 --- a/lib/noticeform.php +++ b/lib/noticeform.php @@ -148,10 +148,10 @@ class NoticeForm extends Form $this->out->element('dd', array('id' => 'notice_text-count'), '140'); $this->out->elementEnd('dl'); - $this->out->element('br', array('style' => 'clear:both')); - $this->out->element('label', array('for' => 'notice_data-attach'), _('Upload: ')); - $this->out->element('input', array('id' => 'notice_data-attach', 'type' => 'file', 'name' => 'attach')); - + $this->out->element('label', array('for' => 'notice_data-attach'), _('Attach')); + $this->out->element('input', array('id' => 'notice_data-attach', + 'type' => 'file', + 'name' => 'attach')); if ($this->action) { $this->out->hidden('notice_return-to', $this->action, 'returnto'); } From a39fa0ce2bceadbfec8c1ddb8d1148edeb84634d Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 1 Jun 2009 15:20:12 -0400 Subject: [PATCH 57/71] Unnecessary JavaScript for notice form attachment --- js/util.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/js/util.js b/js/util.js index acf44a17c7..20f17f6e76 100644 --- a/js/util.js +++ b/js/util.js @@ -17,17 +17,6 @@ */ $(document).ready(function(){ - $('input#notice_data-attach').toggle(); - $('label[for=notice_data-attach]').text('Upload a file as an attachment?'); - $('label[for=notice_data-attach]').click(function () { - if ('Upload a file as an attachment?' == $(this).text()) { - $(this).text('Upload: '); - $('input#notice_data-attach').slideDown('fast'); - } else { - $('input#notice_data-attach').slideUp('fast', function() {$('label[for=notice_data-attach]').text('Upload a file as an attachment?');}); - } - }); - $('a.attachment').click(function() {$().jOverlay({url: $('address .url')[0].href+'/attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'}); return false; }); $("a.thumbnail").hover( function() { From 4bd195b80f1bf9f7da11ed8cc14a0027228f733e Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Mon, 1 Jun 2009 19:40:39 +0000 Subject: [PATCH 58/71] Fixed small bug for attachment layout (css/js). --- lib/attachmentlist.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index 45e4fa3196..898be1bb00 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -83,7 +83,7 @@ class AttachmentList extends Widget $atts = new File; $att = $atts->getAttachments($this->notice->id); if (empty($att)) return 0; - $this->out->elementStart('dl', array('id' =>'attachments')); + $this->out->elementStart('dl', array('id' =>'attachment')); $this->out->element('dt', null, _('Attachments')); $this->out->elementStart('dd'); $this->out->elementStart('ol', array('class' => 'attachments')); From 0cbd72e0927cbe9b605e34d7bbd86d2046ea0c49 Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Mon, 1 Jun 2009 15:52:52 -0400 Subject: [PATCH 59/71] Re-added some javascript to toggle upload field. --- js/util.js | 11 +++++++++++ lib/noticeform.php | 1 + 2 files changed, 12 insertions(+) diff --git a/js/util.js b/js/util.js index 20f17f6e76..acf44a17c7 100644 --- a/js/util.js +++ b/js/util.js @@ -17,6 +17,17 @@ */ $(document).ready(function(){ + $('input#notice_data-attach').toggle(); + $('label[for=notice_data-attach]').text('Upload a file as an attachment?'); + $('label[for=notice_data-attach]').click(function () { + if ('Upload a file as an attachment?' == $(this).text()) { + $(this).text('Upload: '); + $('input#notice_data-attach').slideDown('fast'); + } else { + $('input#notice_data-attach').slideUp('fast', function() {$('label[for=notice_data-attach]').text('Upload a file as an attachment?');}); + } + }); + $('a.attachment').click(function() {$().jOverlay({url: $('address .url')[0].href+'/attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'}); return false; }); $("a.thumbnail").hover( function() { diff --git a/lib/noticeform.php b/lib/noticeform.php index 5a6c7cf38c..805cd56b13 100644 --- a/lib/noticeform.php +++ b/lib/noticeform.php @@ -148,6 +148,7 @@ class NoticeForm extends Form $this->out->element('dd', array('id' => 'notice_text-count'), '140'); $this->out->elementEnd('dl'); + $this->out->element('br', array('style' => 'clear:both')); $this->out->element('label', array('for' => 'notice_data-attach'), _('Attach')); $this->out->element('input', array('id' => 'notice_data-attach', 'type' => 'file', From fe38827a76520e4a910f9e988b11d0914872d44e Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Mon, 1 Jun 2009 17:40:53 -0400 Subject: [PATCH 60/71] Remove js that crept back in, added another error string. --- actions/newnotice.php | 2 ++ js/util.js | 24 ------------------------ lib/attachmentlist.php | 2 +- 3 files changed, 3 insertions(+), 25 deletions(-) diff --git a/actions/newnotice.php b/actions/newnotice.php index 3e6ff15189..02976a2ae2 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -279,6 +279,8 @@ class NewnoticeAction extends Action } else { $this->clientError(_('There was a database error while saving your file. Please try again.')); } + } else { + $this->clientError(_('File could not be moved to destination directory.')); } } diff --git a/js/util.js b/js/util.js index acf44a17c7..bffbf916fa 100644 --- a/js/util.js +++ b/js/util.js @@ -28,30 +28,6 @@ $(document).ready(function(){ } }); - $('a.attachment').click(function() {$().jOverlay({url: $('address .url')[0].href+'/attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'}); return false; }); - $("a.thumbnail").hover( - function() { - var anchor = $(this); - $("a.thumbnail").children('img').remove(); - - setTimeout(function() { - anchor.closest(".entry-title").addClass('ov'); - $.get($('address .url')[0].href+'/attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) { - anchor.append(data); - }); - }, 250); - - setTimeout(function() { - anchor.children('img').remove(); - anchor.closest(".entry-title").removeClass('ov'); - }, 3000); - }, - function() { - $(this).children('img').remove(); - $(this).closest(".entry-title").removeClass('ov'); - } - ); - // count character on keyup function counter(event){ var maxLength = 140; diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index 898be1bb00..45e4fa3196 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -83,7 +83,7 @@ class AttachmentList extends Widget $atts = new File; $att = $atts->getAttachments($this->notice->id); if (empty($att)) return 0; - $this->out->elementStart('dl', array('id' =>'attachment')); + $this->out->elementStart('dl', array('id' =>'attachments')); $this->out->element('dt', null, _('Attachments')); $this->out->elementStart('dd'); $this->out->elementStart('ol', array('class' => 'attachments')); From 4f5630099fd0042170350f18512dc2cbe3b896b2 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 4 Jun 2009 21:33:04 +0000 Subject: [PATCH 61/71] Facebook Connect auth finally works with all major browsers! --- plugins/FBConnect/FBC_XDReceiver.php | 73 +++++++++++++++++++++++++++ plugins/FBConnect/FBConnectLogin.php | 2 - plugins/FBConnect/FBConnectPlugin.php | 65 ++++++++++++++---------- plugins/FBConnect/xd_receiver.htm | 10 ---- 4 files changed, 111 insertions(+), 39 deletions(-) create mode 100644 plugins/FBConnect/FBC_XDReceiver.php delete mode 100644 plugins/FBConnect/xd_receiver.htm diff --git a/plugins/FBConnect/FBC_XDReceiver.php b/plugins/FBConnect/FBC_XDReceiver.php new file mode 100644 index 0000000000..57c98b4f14 --- /dev/null +++ b/plugins/FBConnect/FBC_XDReceiver.php @@ -0,0 +1,73 @@ +showPage(); + } + + function showPage() + { + // cache the xd_receiver + header('Cache-Control: max-age=225065900'); + header('Expires:'); + header('Pragma:'); + + $this->startXML('html', + '-//W3C//DTD XHTML 1.0 Strict//EN', + 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'); + + $language = $this->getLanguage(); + + $this->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml', + 'xml:lang' => $language, + 'lang' => $language)); + $this->elementStart('head'); + $this->element('title', null, 'cross domain receiver page'); + $this->element('script', + array('src' => + 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js', + 'type' => 'text/javascript'), ''); + $this->elementEnd('head'); + $this->elementStart('body'); + $this->elementEnd('body'); + + $this->elementEnd('html'); + } + +} + diff --git a/plugins/FBConnect/FBConnectLogin.php b/plugins/FBConnect/FBConnectLogin.php index 7989dc854d..205086cd8b 100644 --- a/plugins/FBConnect/FBConnectLogin.php +++ b/plugins/FBConnect/FBConnectLogin.php @@ -58,8 +58,6 @@ class FBConnectLoginAction extends Action function showContent() { $this->elementStart('fieldset'); - - $this->element('fb:login-button', array('onlogin' => 'goto_login()', 'length' => 'long')); diff --git a/plugins/FBConnect/FBConnectPlugin.php b/plugins/FBConnect/FBConnectPlugin.php index c85ef432d4..a366985be4 100644 --- a/plugins/FBConnect/FBConnectPlugin.php +++ b/plugins/FBConnect/FBConnectPlugin.php @@ -39,6 +39,7 @@ require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php'; require_once INSTALLDIR . '/plugins/FBConnect/FBConnectSettings.php'; require_once INSTALLDIR . '/plugins/FBConnect/FBCLoginGroupNav.php'; require_once INSTALLDIR . '/plugins/FBConnect/FBCSettingsNav.php'; +require_once INSTALLDIR . '/plugins/FBConnect/FBC_XDReceiver.php'; /** * Plugin to enable Facebook Connect @@ -62,27 +63,19 @@ class FBConnectPlugin extends Plugin $m->connect('main/facebookconnect', array('action' => 'FBConnectAuth')); $m->connect('main/facebooklogin', array('action' => 'FBConnectLogin')); $m->connect('settings/facebook', array('action' => 'FBConnectSettings')); + $m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver')); } // Add in xmlns:fb function onStartShowHTML($action) { - $httpaccept = isset($_SERVER['HTTP_ACCEPT']) ? - $_SERVER['HTTP_ACCEPT'] : null; - - // XXX: allow content negotiation for RDF, RSS, or XRDS - - $cp = common_accept_to_prefs($httpaccept); - $sp = common_accept_to_prefs(PAGE_TYPE_PREFS); - - $type = common_negotiate_type($cp, $sp); - - if (!$type) { - throw new ClientException(_('This page is not available in a '. - 'media type you accept'), 406); - } - - header('Content-Type: '.$type); + // XXX: Horrible hack to make Safari, FF2, and Chrome work with + // Facebook Connect. These browser cannot use Facebook's + // DOM parsing routines unless the mime type of the page is + // text/html even though Facebook Connect uses XHTML. This is + // A bug in Facebook Connect, and this is a temporary solution + // until they fix their JavaScript libs. + header('Content-Type: text/html'); $action->extraHeaders(); @@ -101,20 +94,27 @@ class FBConnectPlugin extends Plugin return false; } - function onEndShowLaconicaScripts($action) - { - $action->element('script', - array('type' => 'text/javascript', - 'src' => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'), - ' '); + // Note: this script needs to appear in the + function onStartShowHeader($action) + { $apikey = common_config('facebook', 'apikey'); $plugin_path = common_path('plugins/FBConnect'); $login_url = common_local_url('FBConnectAuth'); $logout_url = common_local_url('logout'); - $html = sprintf('', $apikey, $plugin_path, $login_url, $logout_url); - + ', $apikey, + $login_url, $logout_url); $action->raw($html); } + // Note: this script needs to appear as close as possible to + + function onEndShowFooter($action) + { + + $action->element('script', + array('type' => 'text/javascript', + 'src' => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'), + ''); + } + function onEndShowLaconicaStyles($action) { $action->element('link', array('rel' => 'stylesheet', @@ -143,7 +153,8 @@ class FBConnectPlugin extends Plugin if ($user) { - $flink = Foreign_link::getByUserId($user->id, FACEBOOK_CONNECT_SERVICE); + $flink = Foreign_link::getByUserId($user->id, + FACEBOOK_CONNECT_SERVICE); $fbuid = 0; if ($flink) { diff --git a/plugins/FBConnect/xd_receiver.htm b/plugins/FBConnect/xd_receiver.htm deleted file mode 100644 index 43fb2c4e48..0000000000 --- a/plugins/FBConnect/xd_receiver.htm +++ /dev/null @@ -1,10 +0,0 @@ - - - - cross domain receiver page - - - - - From 6658e2a2ee9517bebd59cf69d7483e6eda691b4e Mon Sep 17 00:00:00 2001 From: Dan Moore Date: Thu, 4 Jun 2009 17:57:03 -0400 Subject: [PATCH 62/71] Handle the ways Twitter accepts passing the user in the query string. --- actions/api.php | 4 ++-- lib/twitterapi.php | 22 +++++++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/actions/api.php b/actions/api.php index b25ba99f30..b8da852b53 100644 --- a/actions/api.php +++ b/actions/api.php @@ -144,8 +144,8 @@ class ApiAction extends Action } if (in_array($fullname, $bareauth)) { - # bareauth: only needs auth if without an argument - if ($this->api_arg) { + # bareauth: only needs auth if without an argument or query param specifying user + if ($this->api_arg || $this->arg('id') || is_numeric($this->arg('user_id')) || $this->arg('screen_name')) { return false; } else { return true; diff --git a/lib/twitterapi.php b/lib/twitterapi.php index 1d527b935f..ca8b03cdcd 100644 --- a/lib/twitterapi.php +++ b/lib/twitterapi.php @@ -673,7 +673,27 @@ class TwitterapiAction extends Action function get_user($id, $apidata=null) { if (!$id) { - return $apidata['user']; + + // Twitter supports these other ways of passing the user ID + if (is_numeric($this->arg('id'))) { + return User::staticGet($this->arg('id')); + } else if ($this->arg('id')) { + $nickname = common_canonical_nickname($this->arg('id')); + return User::staticGet('nickname', $nickname); + } else if ($this->arg('user_id')) { + // This is to ensure that a non-numeric user_id still + // overrides screen_name even if it doesn't get used + if (is_numeric($this->arg('user_id'))) { + return User::staticGet('id', $this->arg('user_id')); + } + } else if ($this->arg('screen_name')) { + $nickname = common_canonical_nickname($this->arg('screen_name')); + return User::staticGet('nickname', $nickname); + } else { + // Fall back to trying the currently authenticated user + return $apidata['user']; + } + } else if (is_numeric($id)) { return User::staticGet($id); } else { From b708b81065b4adc46026e570d8e2cb9d37bc24af Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 5 Jun 2009 00:00:04 -0400 Subject: [PATCH 63/71] Init notice_data-attach UI for form_notice --- js/util.js | 11 ----------- lib/noticeform.php | 1 - theme/base/css/display.css | 16 ++++++++++++++++ theme/identica/css/display.css | 7 +++++++ 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/js/util.js b/js/util.js index bffbf916fa..b712ba8e2b 100644 --- a/js/util.js +++ b/js/util.js @@ -17,17 +17,6 @@ */ $(document).ready(function(){ - $('input#notice_data-attach').toggle(); - $('label[for=notice_data-attach]').text('Upload a file as an attachment?'); - $('label[for=notice_data-attach]').click(function () { - if ('Upload a file as an attachment?' == $(this).text()) { - $(this).text('Upload: '); - $('input#notice_data-attach').slideDown('fast'); - } else { - $('input#notice_data-attach').slideUp('fast', function() {$('label[for=notice_data-attach]').text('Upload a file as an attachment?');}); - } - }); - // count character on keyup function counter(event){ var maxLength = 140; diff --git a/lib/noticeform.php b/lib/noticeform.php index 805cd56b13..5a6c7cf38c 100644 --- a/lib/noticeform.php +++ b/lib/noticeform.php @@ -148,7 +148,6 @@ class NoticeForm extends Form $this->out->element('dd', array('id' => 'notice_text-count'), '140'); $this->out->elementEnd('dl'); - $this->out->element('br', array('style' => 'clear:both')); $this->out->element('label', array('for' => 'notice_data-attach'), _('Attach')); $this->out->element('input', array('id' => 'notice_data-attach', 'type' => 'file', diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 7d495c5395..7eea54eb2a 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -452,6 +452,22 @@ float:left; font-size:1.3em; margin-bottom:7px; } +#form_notice label[for=notice_data-attach] { +text-indent:-9999px; +display:block; +} +#form_notice label[for=notice_data-attach], +#form_notice #notice_data-attach { +position:absolute; +top:25px; +right:49px; +width:16px; +height:16px; +cursor:pointer; +} +#form_notice #notice_data-attach { +text-indent:-279px; +} #form_notice #notice_submit label { display:none; } diff --git a/theme/identica/css/display.css b/theme/identica/css/display.css index 1f1298737c..8a03a4d772 100644 --- a/theme/identica/css/display.css +++ b/theme/identica/css/display.css @@ -82,6 +82,13 @@ color:#333; #form_notice.warning #notice_text-count { color:#000; } +#form_notice label[for=notice_data-attach] { +background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%; +} +#form_notice #notice_data-attach { +opacity:0; +} + #form_notice.processing #notice_action-submit { background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%; cursor:wait; From 1a126032efbcb265a59f4d682bd072251f857c97 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 5 Jun 2009 12:33:46 -0400 Subject: [PATCH 64/71] Adding clip illustration for notice attachments. --- theme/base/images/icons/twotone/green/clip-01.gif | Bin 0 -> 78 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 theme/base/images/icons/twotone/green/clip-01.gif diff --git a/theme/base/images/icons/twotone/green/clip-01.gif b/theme/base/images/icons/twotone/green/clip-01.gif new file mode 100644 index 0000000000000000000000000000000000000000..f2dee7e5e9f0f5f9f99d771128fe88d5b9ae178a GIT binary patch literal 78 zcmZ?wbhEHb6krfwSjfom|NsB^DFP6r_>+Z^fq{uZhXDvc@(fJ!A$>EKz2#SC31Mp} aI1*E48~aGD>3n+Tvh>*O-3Oc)8LR Date: Fri, 5 Jun 2009 10:07:26 -0700 Subject: [PATCH 65/71] don't send smses to sender --- lib/mail.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/mail.php b/lib/mail.php index 27a1d99dcb..4e1f1dbb1d 100644 --- a/lib/mail.php +++ b/lib/mail.php @@ -335,6 +335,7 @@ function mail_broadcast_notice_sms($notice) "FROM $UT JOIN subscription " . "ON $UT.id = subscription.subscriber " . 'WHERE subscription.subscribed = ' . $notice->profile_id . ' ' . + 'AND subscription.subscribed != subscription.subscriber ' . "AND $UT.smsemail IS NOT null " . "AND $UT.smsnotify = 1 " . 'AND subscription.sms = 1 '); From d15fdac3d348eda7314a206c2403494e9a096c98 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 6 Jun 2009 09:54:40 -0700 Subject: [PATCH 66/71] add fixup messages for utf8 --- scripts/fixup_utf8.php | 57 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/scripts/fixup_utf8.php b/scripts/fixup_utf8.php index f9debd3eca..83e4850217 100644 --- a/scripts/fixup_utf8.php +++ b/scripts/fixup_utf8.php @@ -296,6 +296,63 @@ class UTF8FixerUpper echo "OK\n"; } } + + function fixupMessages() { + + // Do a separate DB connection + + $sth = $this->dbu->prepare("UPDATE message SET content = UNHEX(?), rendered = UNHEX(?) WHERE id = ?"); + + if (PEAR::isError($sth)) { + echo "ERROR: " . $sth->getMessage() . "\n"; + return; + } + + $sql = 'SELECT id, content, rendered FROM message ' . + 'WHERE LENGTH(content) != CHAR_LENGTH(content) '. + 'AND modified < "'.$this->max_date.'" '. + 'ORDER BY id DESC'; + + $rn = $this->dbl->query($sql); + + if (PEAR::isError($rn)) { + echo "ERROR: " . $rn->getMessage() . "\n"; + return; + } + + echo "Number of rows: " . $rn->numRows() . "\n"; + + $message = array(); + + while (DB_OK == $rn->fetchInto($message)) { + + $id = ($message[0])+0; + $content = bin2hex($message[1]); + $rendered = bin2hex($message[2]); + + echo "$id..."; + + $result =& $this->dbu->execute($sth, array($content, $rendered, $id)); + + if (PEAR::isError($result)) { + echo "ERROR: " . $result->getMessage() . "\n"; + continue; + } + + $cnt = $this->dbu->affectedRows(); + + if ($cnt != 1) { + echo "ERROR: 0 rows affected\n"; + continue; + } + + $message = Message::staticGet('id', $id); + $message->decache(); + $message->free(); + + echo "OK\n"; + } + } } $max_date = ($argc > 1) ? $argv[1] : null; From 265e2bd58de2a01e0d7840310eb44b21b70e3914 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 8 Jun 2009 10:57:35 -0700 Subject: [PATCH 67/71] remember to convert messages --- scripts/fixup_utf8.php | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/fixup_utf8.php b/scripts/fixup_utf8.php index 83e4850217..1693760914 100644 --- a/scripts/fixup_utf8.php +++ b/scripts/fixup_utf8.php @@ -101,6 +101,7 @@ class UTF8FixerUpper $this->args['min_notice']); $this->fixupProfiles(); $this->fixupGroups(); + $this->fixupMessages(); } function fixupNotices($max_id, $min_id) { From 3aebd847e72e929f05e5d217e037b3ccc9169306 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 9 Jun 2009 17:34:33 +0000 Subject: [PATCH 68/71] Added title and minor CSS cleanup to notice attach --- lib/noticeform.php | 3 ++- theme/base/css/display.css | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/noticeform.php b/lib/noticeform.php index 5a6c7cf38c..5d7cf194ee 100644 --- a/lib/noticeform.php +++ b/lib/noticeform.php @@ -151,7 +151,8 @@ class NoticeForm extends Form $this->out->element('label', array('for' => 'notice_data-attach'), _('Attach')); $this->out->element('input', array('id' => 'notice_data-attach', 'type' => 'file', - 'name' => 'attach')); + 'name' => 'attach', + 'title' => _('Attach a file'))); if ($this->action) { $this->out->hidden('notice_return-to', $this->action, 'returnto'); } diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 7eea54eb2a..dc275e19f7 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -454,7 +454,6 @@ margin-bottom:7px; } #form_notice label[for=notice_data-attach] { text-indent:-9999px; -display:block; } #form_notice label[for=notice_data-attach], #form_notice #notice_data-attach { From 6af79f47557807dea741f9d31132e5123119a0ad Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 9 Jun 2009 17:49:24 +0000 Subject: [PATCH 69/71] Updated default stylesheet for notice attach --- theme/default/css/display.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/theme/default/css/display.css b/theme/default/css/display.css index e08a4783bd..34f6b3b8a6 100644 --- a/theme/default/css/display.css +++ b/theme/default/css/display.css @@ -82,6 +82,13 @@ color:#333; #form_notice.warning #notice_text-count { color:#000; } +#form_notice label[for=notice_data-attach] { +background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%; +} +#form_notice #notice_data-attach { +opacity:0; +} + #form_notice.processing #notice_action-submit { background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%; cursor:wait; From 388677a6a684754a7dbde13551ab1bffff4d1164 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 9 Jun 2009 17:59:08 +0000 Subject: [PATCH 70/71] Updated notice attach UI for biz theme. --- theme/biz/css/base.css | 21 +++++++++++++++++++++ theme/biz/css/display.css | 7 +++++++ 2 files changed, 28 insertions(+) diff --git a/theme/biz/css/base.css b/theme/biz/css/base.css index 0e37a6ee4e..696fd0645b 100644 --- a/theme/biz/css/base.css +++ b/theme/biz/css/base.css @@ -446,6 +446,27 @@ float:left; font-size:1.3em; margin-bottom:7px; } +#form_notice label { +display:block; +float:left; +font-size:1.3em; +margin-bottom:7px; +} +#form_notice label[for=notice_data-attach] { +text-indent:-9999px; +} +#form_notice label[for=notice_data-attach], +#form_notice #notice_data-attach { +position:absolute; +top:25px; +right:49px; +width:16px; +height:16px; +cursor:pointer; +} +#form_notice #notice_data-attach { +text-indent:-279px; +} #form_notice #notice_submit label { display:none; } diff --git a/theme/biz/css/display.css b/theme/biz/css/display.css index 14092d964e..3af4c06b91 100644 --- a/theme/biz/css/display.css +++ b/theme/biz/css/display.css @@ -102,6 +102,13 @@ color:#333; #form_notice.warning #notice_text-count { color:#000; } +#form_notice label[for=notice_data-attach] { +background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%; +} +#form_notice #notice_data-attach { +opacity:0; +} + #form_notice.processing #notice_action-submit { background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%; cursor:wait; From 27af3c67a2a9ea856aa2de041a986d452cce3025 Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Tue, 9 Jun 2009 15:18:12 -0400 Subject: [PATCH 71/71] Improve file upload and attachment interface. --- classes/Notice.php | 12 ++++++++++++ lib/common.php | 1 + lib/noticelist.php | 8 ++++---- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 1b5c0ab0a5..0b1017e126 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -277,6 +277,18 @@ class Notice extends Memcached_DataObject return true; } + function getUploadedAttachment() { + $post = clone $this; + $query = 'select file.url as uploaded from file join file_to_post on file.id = file_id where post_id=' . $post->escape($post->id) . ' and url like "%/notice/%/file"'; + $post->query($query); + $post->fetch(); + $ret = $post->uploaded; +// var_dump($post); + $post->free(); +// die(); + return $ret; + } + function hasAttachments() { $post = clone $this; $query = "select count(file_id) as n_attachments from file join file_to_post on (file_id = file.id) join notice on (post_id = notice.id) where post_id = " . $post->escape($post->id); diff --git a/lib/common.php b/lib/common.php index ceb50337cc..5aafdfe0ee 100644 --- a/lib/common.php +++ b/lib/common.php @@ -188,6 +188,7 @@ $config = 'application/vnd.oasis.opendocument.formula-template', 'application/vnd.oasis.opendocument.text-master', 'application/vnd.oasis.opendocument.text-web', + 'application/x-zip', 'application/zip', 'text/plain', 'video/mpeg', diff --git a/lib/noticelist.php b/lib/noticelist.php index 5513e317e0..9ace341d80 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -364,6 +364,10 @@ class NoticeListItem extends Widget // versions (>> 0.4.x) $this->out->raw(common_render_content($this->notice->content, $this->notice)); } + $uploaded = $this->notice->getUploadedAttachment(); + if ($uploaded) { + $this->out->element('a', array('href' => $uploaded, 'class' => 'attachment'), $uploaded); + } $this->out->elementEnd('p'); } @@ -395,10 +399,6 @@ class NoticeListItem extends Widget 'title' => $dt), common_date_string($this->notice->created)); - $f2p = File_to_post::staticGet('post_id', $this->notice->id); - if (!empty($f2p)) { - $this->out->text(_(' (with attachments) ')); - } $this->out->elementEnd('a'); $this->out->elementEnd('dd'); $this->out->elementEnd('dl');