From 69a656af2210acf9211aa4fcb1c05bfd5c5a2e1f Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 May 2009 23:21:10 +0000 Subject: Some adjustments to allow the Facebook app work with Laconica 0.8 --- lib/facebookaction.php | 262 ++++++++++++++++++++----------------------------- 1 file changed, 109 insertions(+), 153 deletions(-) (limited to 'lib') diff --git a/lib/facebookaction.php b/lib/facebookaction.php index 637a6284d..a445750f7 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); -- cgit v1.2.3-54-g00ecf From 2b81dcb25329c496c7c4f2c99334b79ba0421c1e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 15 May 2009 21:44:58 +0000 Subject: Better err handling when trying to get Facebook client --- lib/facebookutil.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/facebookutil.php b/lib/facebookutil.php index ec3987273..242d2e06f 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(); -- cgit v1.2.3-54-g00ecf From af700ea27703bbec5aa1078a84f9fd44c0260322 Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Tue, 26 May 2009 21:20:04 -0400 Subject: 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(-) (limited to 'lib') diff --git a/actions/newnotice.php b/actions/newnotice.php index ae0ff9636..d7507c118 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 cb8b5a660..936b847ab 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 b1b6ec82b..85ab48b4c 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 d0478bad3..a58a50f6f 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 5317df471..f872aef0b 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 606b5d028..707768cd5 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 50a95cfcb..420272515 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 fc119821b..bd482eafa 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]+')); -- cgit v1.2.3-54-g00ecf From ed1e1d9f440acfbb99c7a6e3d2b5c11fdcfa27ce Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Wed, 27 May 2009 03:47:45 -0400 Subject: 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 (limited to 'lib') diff --git a/.gitignore b/.gitignore index da6947bfd..3418d8ee5 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 000000000..1179dbe9a --- /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 d7507c118..aebdab3cc 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 0eae68178..212cc3615 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 bd482eafa..456d1793e 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 d56f44f7b..ab1272309 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); -- cgit v1.2.3-54-g00ecf From d068680e1913a32eba0f5b0e1e746ac66ec638d3 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 May 2009 14:57:45 -0400 Subject: 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(-) (limited to 'lib') diff --git a/README b/README index 7b2dcacc5..c23d4e464 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 877bbf2e0..52ad4100f 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 f983c4d16..8f95c2361 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' ); -- cgit v1.2.3-54-g00ecf From 60ac9bc6fda74278d98890a2c26f3834acad8222 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 27 May 2009 19:43:43 +0000 Subject: 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 +++++++++++++++----- theme/base/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 (limited to 'lib') diff --git a/js/util.js b/js/util.js index b1b6ec82b..08cc1d370 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 d0478bad3..559962acc 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 9bc1417b1..16c9e3c00 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 000000000..77a729799 Binary files /dev/null and b/theme/base/images/icons/twotone/green/clip-02.gif differ diff --git a/theme/default/css/display.css b/theme/default/css/display.css index 737db7ce9..e08a4783b 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 f7abac482..1f1298737 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; -- cgit v1.2.3-54-g00ecf From 3532cd0490a70b54a92108549de6042459fd8469 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 28 May 2009 18:19:22 -0400 Subject: 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(-) (limited to 'lib') diff --git a/classes/Notice.php b/classes/Notice.php index 771a4e715..8dd14b3b7 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 f862d7fcc..214c33279 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 73d31e854..c7aab4fe1 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"); } } -- cgit v1.2.3-54-g00ecf From b8de7935568e631bc99aec6120f2bc4090f50fec Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 29 May 2009 02:38:38 +0000 Subject: 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(-) (limited to 'lib') diff --git a/actions/conversation.php b/actions/conversation.php index ef189016a..0d7cb9a87 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 2c469c9de..34c8a8e94 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 50a95cfcb..0e80a9778 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 37aafdaf6..ca1432686 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); } -- cgit v1.2.3-54-g00ecf From f66e996073b296ecbcaa76ac2fdb2378508bc2ac Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 29 May 2009 16:28:39 -0400 Subject: 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 | 62 ++++++++++++++++++++++++++++++---------------------------- lib/common.php | 2 +- 2 files changed, 33 insertions(+), 31 deletions(-) (limited to 'lib') diff --git a/README b/README index 4649eb79c..769c416d3 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: - -- 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 +This is a minor bug-fix and feature release since version 0.7.3, +released Apr 4 2009. Notable changes this version: + +- 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 8f95c2361..01d2c78ea 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); -- cgit v1.2.3-54-g00ecf From 78f8b91a13ee07ed3344fd6411a0829a16bef4b1 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 29 May 2009 19:23:53 -0700 Subject: Ticket #1568 - API should return full user objects --- actions/twitapiusers.php | 145 ++++++++++++++++------------------------------- lib/twitterapi.php | 77 ++++++++++++++++++++++--- 2 files changed, 117 insertions(+), 105 deletions(-) (limited to 'lib') diff --git a/actions/twitapiusers.php b/actions/twitapiusers.php index 1542cfb33..b90bbfa98 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; - } - - $profile = $user->getProfile(); - - if (!$profile) { - common_server_error(_('User has no profile.')); - return; - } - - $twitter_user = $this->twitter_user_array($profile, true); - - // Add in extended user fields offered up by this method - $twitter_user['created_at'] = $this->date_twitter($profile->created); - - $subbed = DB_DataObject::factory('subscription'); - $subbed->subscriber = $profile->id; - $subbed_count = (int) $subbed->count() - 1; - - $notices = DB_DataObject::factory('notice'); - $notices->profile_id = $profile->id; - $notice_count = (int) $notices->count(); - - $twitter_user['friends_count'] = (is_int($subbed_count)) ? $subbed_count : 0; - $twitter_user['statuses_count'] = (is_int($notice_count)) ? $notice_count : 0; - - // Other fields Twitter sends... - $twitter_user['profile_background_color'] = ''; - $twitter_user['profile_background_image_url'] = ''; - $twitter_user['profile_text_color'] = ''; - $twitter_user['profile_link_color'] = ''; - $twitter_user['profile_sidebar_fill_color'] = ''; - $twitter_user['profile_sidebar_border_color'] = ''; - $twitter_user['profile_background_tile'] = false; - - $faves = DB_DataObject::factory('fave'); - $faves->user_id = $user->id; - $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). + + $user = null; + $email = $this->arg('email'); + $user_id = $this->arg('user_id'); + + // XXX: email field deprecated in Twitter's API + + // XXX: Also: need to add screen_name param + + 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) { + $this->client_error(_('Not found.'), 404, $apidata['content-type']); + return; + } + + $profile = $user->getProfile(); + + if (!$profile) { + common_server_error(_('User has no profile.')); + return; + } + + $twitter_user = $this->twitter_user_array($profile, true); + + 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). header('Content-Type: text/html; charset=utf-8'); print 'Authorized'; } - } + } } diff --git a/lib/twitterapi.php b/lib/twitterapi.php index caf8c0716..1d527b935 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['id'] = intval($profile->id); $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['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(); -- cgit v1.2.3-54-g00ecf From 45c58c74d4d2c2c0a886ff7816f27aa9c6033652 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 30 May 2009 03:31:26 -0400 Subject: take debug code out of omb.php --- lib/omb.php | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'lib') diff --git a/lib/omb.php b/lib/omb.php index e8e1acc41..40cb847df 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 -- cgit v1.2.3-54-g00ecf From 76ee1fd5daa6ab1999224d8e93df1b7116f24c36 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 30 May 2009 04:40:47 -0400 Subject: 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 (limited to 'lib') diff --git a/classes/Notice.php b/classes/Notice.php index 3a90d18b4..b4c86ebeb 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 214c33279..22308f432 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 482e63af7..0996ba9f4 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 c7aab4fe1..000000000 --- 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 185b781f7..000000000 --- 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 f6d71eddf..2134b4ab0 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 -- cgit v1.2.3-54-g00ecf From 3e0c291810d23bb54c337c0fe95c1ba441459c4c Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Sun, 31 May 2009 16:42:29 -0400 Subject: 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 (limited to 'lib') diff --git a/actions/newnotice.php b/actions/newnotice.php index aebdab3cc..29b748dd1 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 282826a7f..636f4cf8e 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 000000000..c335f8d92 --- /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 000000000..1987e2a10 --- /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 000000000..399d3dd7a --- /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 0ce46442d..ede8d6277 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 -- cgit v1.2.3-54-g00ecf From abe68f4318009ed839c1998d91459f26d09f76c8 Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Sun, 31 May 2009 18:33:38 -0400 Subject: Added attachments config descriptions to README. --- README | 24 ++++++++++++++++++++---- lib/noticeform.php | 8 +++----- 2 files changed, 23 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/README b/README index 76b56a52e..9cbe84f0e 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 707768cd5..5f438327d 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')); + } /** -- cgit v1.2.3-54-g00ecf From f8dae2bbc9600a4ad474e924dc540491e29e2767 Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Sun, 31 May 2009 21:03:55 -0400 Subject: 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(-) (limited to 'lib') diff --git a/actions/newnotice.php b/actions/newnotice.php index 29b748dd1..a24d62684 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 e5913115b..24ab11b8e 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 5f438327d..7c88bd7b1 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')); - } /** -- cgit v1.2.3-54-g00ecf From 683b835c3ed0be5f691795c64b1217896e1dad95 Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Mon, 1 Jun 2009 14:02:59 -0400 Subject: 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(-) (limited to 'lib') diff --git a/actions/newnotice.php b/actions/newnotice.php index a24d62684..3e6ff1518 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 61749dca5..45e4fa319 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 838f52f9d..ceb50337c 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', -- cgit v1.2.3-54-g00ecf From 95bcc5afa11ccbff82c2d0d16a286014ba96acb1 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 1 Jun 2009 15:18:23 -0400 Subject: Updated markup for notice form attachment --- lib/noticeform.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/noticeform.php b/lib/noticeform.php index 7c88bd7b1..5a6c7cf38 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'); } -- cgit v1.2.3-54-g00ecf From 4bd195b80f1bf9f7da11ed8cc14a0027228f733e Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Mon, 1 Jun 2009 19:40:39 +0000 Subject: Fixed small bug for attachment layout (css/js). --- lib/attachmentlist.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index 45e4fa319..898be1bb0 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')); -- cgit v1.2.3-54-g00ecf From 0cbd72e0927cbe9b605e34d7bbd86d2046ea0c49 Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Mon, 1 Jun 2009 15:52:52 -0400 Subject: Re-added some javascript to toggle upload field. --- js/util.js | 11 +++++++++++ lib/noticeform.php | 1 + 2 files changed, 12 insertions(+) (limited to 'lib') diff --git a/js/util.js b/js/util.js index 20f17f6e7..acf44a17c 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 5a6c7cf38..805cd56b1 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', -- cgit v1.2.3-54-g00ecf From fe38827a76520e4a910f9e988b11d0914872d44e Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Mon, 1 Jun 2009 17:40:53 -0400 Subject: 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(-) (limited to 'lib') diff --git a/actions/newnotice.php b/actions/newnotice.php index 3e6ff1518..02976a2ae 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 acf44a17c..bffbf916f 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 898be1bb0..45e4fa319 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')); -- cgit v1.2.3-54-g00ecf From 6658e2a2ee9517bebd59cf69d7483e6eda691b4e Mon Sep 17 00:00:00 2001 From: Dan Moore Date: Thu, 4 Jun 2009 17:57:03 -0400 Subject: 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(-) (limited to 'lib') diff --git a/actions/api.php b/actions/api.php index b25ba99f3..b8da852b5 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 1d527b935..ca8b03cdc 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 { -- cgit v1.2.3-54-g00ecf From b708b81065b4adc46026e570d8e2cb9d37bc24af Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 5 Jun 2009 00:00:04 -0400 Subject: 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(-) (limited to 'lib') diff --git a/js/util.js b/js/util.js index bffbf916f..b712ba8e2 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 805cd56b1..5a6c7cf38 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 7d495c539..7eea54eb2 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 1f1298737..8a03a4d77 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; -- cgit v1.2.3-54-g00ecf From 2c81d8db76135af2e4343316f67a2f535971d013 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 5 Jun 2009 10:07:26 -0700 Subject: don't send smses to sender --- lib/mail.php | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/mail.php b/lib/mail.php index 27a1d99dc..4e1f1dbb1 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 '); -- cgit v1.2.3-54-g00ecf From 3aebd847e72e929f05e5d217e037b3ccc9169306 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 9 Jun 2009 17:34:33 +0000 Subject: 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(-) (limited to 'lib') diff --git a/lib/noticeform.php b/lib/noticeform.php index 5a6c7cf38..5d7cf194e 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 7eea54eb2..dc275e19f 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 { -- cgit v1.2.3-54-g00ecf From 27af3c67a2a9ea856aa2de041a986d452cce3025 Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Tue, 9 Jun 2009 15:18:12 -0400 Subject: 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(-) (limited to 'lib') diff --git a/classes/Notice.php b/classes/Notice.php index 1b5c0ab0a..0b1017e12 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 ceb50337c..5aafdfe0e 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 5513e317e..9ace341d8 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'); -- cgit v1.2.3-54-g00ecf