Merge branch 'api-media-upload' into 0.9.x

* api-media-upload:
  Rearanged a couple things & removed debugging statements
  Rework MailDaemon to use the MediaFile class for uploads
  Implement media upload in the API
  Extract media upload stuff into its own library class.
This commit is contained in:
Zach Copley 2009-10-28 17:18:33 -07:00
commit 9ef05030fe
4 changed files with 360 additions and 317 deletions

View File

@ -38,6 +38,7 @@ if (!defined('STATUSNET')) {
}
require_once INSTALLDIR . '/lib/apiauth.php';
require_once INSTALLDIR . '/lib/mediafile.php';
/**
* Updates the authenticating user's status (posts a notice).
@ -60,7 +61,6 @@ class ApiStatusesUpdateAction extends ApiAuthAction
var $source = null;
var $status = null;
var $in_reply_to_status_id = null;
static $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api');
/**
@ -76,25 +76,8 @@ class ApiStatusesUpdateAction extends ApiAuthAction
{
parent::prepare($args);
$this->user = $this->auth_user;
if (empty($this->user)) {
$this->clientError(_('No such user!'), 404, $this->format);
return false;
}
$this->user = $this->auth_user;
$this->status = $this->trimmed('status');
if (empty($this->status)) {
$this->clientError(
'Client must provide a \'status\' parameter with a value.',
400,
$this->format
);
return false;
}
$this->source = $this->trimmed('source');
if (empty($this->source) || in_array($source, $this->reserved_sources)) {
@ -129,6 +112,27 @@ class ApiStatusesUpdateAction extends ApiAuthAction
return;
}
if (empty($this->status)) {
$this->clientError(
'Client must provide a \'status\' parameter with a value.',
400,
$this->format
);
return;
}
if (empty($this->user)) {
$this->clientError(_('No such user!'), 404, $this->format);
return;
}
// Workaround for PHP returning empty $_FILES when POST length > PHP settings
if (empty($_POST) && ($_SERVER['CONTENT_LENGTH'] > 0)) {
$this->clientError(_('Unable to handle that much POST data!'));
return;
}
$status_shortened = common_shorten_links($this->status);
if (Notice::contentTooLong($status_shortened)) {
@ -187,14 +191,34 @@ class ApiStatusesUpdateAction extends ApiAuthAction
}
}
$upload = null;
$upload = MediaFile::fromUpload('media', $this->user);
if (isset($upload)) {
$status_shortened .= ' ' . $upload->shortUrl();
if (Notice::contentTooLong($status_shortened)) {
$upload->delete();
$msg = _(
'Max notice size is %d chars, ' .
'including attachment URL.'
);
$this->clientError(sprintf($msg, Notice::maxContent()));
}
}
$this->notice = Notice::saveNew(
$this->user->id,
html_entity_decode($this->status, ENT_NOQUOTES, 'UTF-8'),
html_entity_decode($status_shortened, ENT_NOQUOTES, 'UTF-8'),
$this->source,
1,
$reply_to
);
if (isset($upload)) {
$upload->attachToNotice($this->notice);
}
common_broadcast_notice($this->notice);
}

View File

@ -33,7 +33,8 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/lib/noticelist.php';
require_once INSTALLDIR . '/lib/noticelist.php';
require_once INSTALLDIR . '/lib/mediafile.php';
/**
* Action for posting new notices
@ -113,33 +114,6 @@ class NewnoticeAction extends Action
}
}
function getUploadedFileType() {
require_once 'MIME/Type.php';
$cmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd');
$cmd = common_config('attachments', 'filecommand');
$filetype = MIME_Type::autoDetect($_FILES['attach']['tmp_name']);
if (in_array($filetype, common_config('attachments', 'supported'))) {
return $filetype;
}
$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) {
$file = new File;
$ret = $file->isRespectsQuota($user,$_FILES['attach']['size']);
if (true === $ret) return true;
$this->clientError($ret);
}
/**
* Save a new notice, based on arguments
*
@ -190,78 +164,29 @@ class NewnoticeAction extends Action
$replyto = 'false';
}
if (isset($_FILES['attach']['error'])) {
switch ($_FILES['attach']['error']) {
case UPLOAD_ERR_NO_FILE:
// no file uploaded, nothing to do
break;
$upload = null;
$upload = MediaFile::fromUpload('attach');
case UPLOAD_ERR_OK:
$mimetype = $this->getUploadedFileType();
if (!$this->isRespectsQuota($user)) {
die('clientError() should trigger an exception before reaching here.');
}
break;
if (isset($upload)) {
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.');
}
}
if (isset($mimetype)) {
$filename = $this->saveFile($mimetype);
if (empty($filename)) {
$this->clientError(_('Couldn\'t save file.'));
}
$fileRecord = $this->storeFile($filename, $mimetype);
$fileurl = common_local_url('attachment',
array('attachment' => $fileRecord->id));
// not sure this is necessary -- Zach
$this->maybeAddRedir($fileRecord->id, $fileurl);
$short_fileurl = common_shorten_url($fileurl);
if (!$short_fileurl) {
// todo -- Consider forcing default shortener if none selected?
$short_fileurl = $fileurl;
}
$content_shortened .= ' ' . $short_fileurl;
$content_shortened .= ' ' . $upload->shortUrl();
if (Notice::contentTooLong($content_shortened)) {
$this->deleteFile($filename);
$this->clientError(sprintf(_('Max notice size is %d chars, including attachment URL.'),
Notice::maxContent()));
$upload->delete();
$this->clientError(
sprintf(
_('Max notice size is %d chars, including attachment URL.'),
Notice::maxContent()
)
);
}
// Also, not sure this is necessary -- Zach
$this->maybeAddRedir($fileRecord->id, $short_fileurl);
}
$notice = Notice::saveNew($user->id, $content_shortened, 'web', 1,
($replyto == 'false') ? null : $replyto);
if (isset($mimetype)) {
$this->attachFile($notice, $fileRecord);
if (isset($upload)) {
$upload->attachToNotice($notice);
}
common_broadcast_notice($notice);
@ -289,87 +214,6 @@ class NewnoticeAction extends Action
}
}
function saveFile($mimetype) {
$cur = common_current_user();
if (empty($cur)) {
$this->serverError(_('Somehow lost the login in saveFile'));
}
$basename = basename($_FILES['attach']['name']);
$filename = File::filename($cur->getProfile(), $basename, $mimetype);
$filepath = File::path($filename);
if (move_uploaded_file($_FILES['attach']['tmp_name'], $filepath)) {
return $filename;
} else {
$this->clientError(_('File could not be moved to destination directory.'));
}
}
function deleteFile($filename)
{
$filepath = File::path($filename);
@unlink($filepath);
}
function storeFile($filename, $mimetype) {
$file = new File;
$file->filename = $filename;
$file->url = File::url($filename);
$filepath = File::path($filename);
$file->size = filesize($filepath);
$file->date = time();
$file->mimetype = $mimetype;
$file_id = $file->insert();
if (!$file_id) {
common_log_db_error($file, "INSERT", __FILE__);
$this->clientError(_('There was a database error while saving your file. Please try again.'));
}
return $file;
}
function rememberFile($file, $short)
{
$this->maybeAddRedir($file->id, $short);
}
function maybeAddRedir($file_id, $url)
{
$file_redir = File_redirection::staticGet('url', $url);
if (empty($file_redir)) {
$file_redir = new File_redirection;
$file_redir->url = $url;
$file_redir->file_id = $file_id;
$result = $file_redir->insert();
if (!$result) {
common_log_db_error($file_redir, "INSERT", __FILE__);
$this->clientError(_('There was a database error while saving your file. Please try again.'));
}
}
}
function attachFile($notice, $filerec)
{
File_to_post::processNew($filerec->id, $notice->id);
$this->maybeAddRedir($filerec->id,
common_local_url('file', array('notice' => $notice->id)));
}
/**
* Show an Ajax-y error message
*

281
lib/mediafile.php Normal file
View File

@ -0,0 +1,281 @@
<?php
/**
* StatusNet, the distributed open-source mMediaFileicroblogging tool
*
* Abstraction for a media files in general
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Media
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2008-2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
class MediaFile
{
var $filename = null;
var $fileRecord = null;
var $user = null;
var $fileurl = null;
var $short_fileurl = null;
var $mimetype = null;
function __construct($user = null, $filename = null, $mimetype = null)
{
if ($user == null) {
$this->user = common_current_user();
}
$this->filename = $filename;
$this->mimetype = $mimetype;
$this->fileRecord = $this->storeFile();
$this->fileurl = common_local_url('attachment',
array('attachment' => $this->fileRecord->id));
$this->maybeAddRedir($this->fileRecord->id, $this->fileurl);
$this->short_fileurl = common_shorten_url($this->fileurl);
$this->maybeAddRedir($this->fileRecord->id, $this->short_fileurl);
}
function attachToNotice($notice)
{
File_to_post::processNew($this->fileRecord->id, $notice->id);
$this->maybeAddRedir($this->fileRecord->id,
common_local_url('file', array('notice' => $notice->id)));
}
function shortUrl()
{
return $this->short_fileurl;
}
function delete()
{
$filepath = File::path($this->filename);
@unlink($filepath);
}
function storeFile() {
$file = new File;
$file->filename = $this->filename;
$file->url = File::url($this->filename);
$filepath = File::path($this->filename);
$file->size = filesize($filepath);
$file->date = time();
$file->mimetype = $this->mimetype;
$file_id = $file->insert();
if (!$file_id) {
common_log_db_error($file, "INSERT", __FILE__);
throw new ClientException(_('There was a database error while saving your file. Please try again.'));
}
return $file;
}
function rememberFile($file, $short)
{
$this->maybeAddRedir($file->id, $short);
}
function maybeAddRedir($file_id, $url)
{
$file_redir = File_redirection::staticGet('url', $url);
if (empty($file_redir)) {
$file_redir = new File_redirection;
$file_redir->url = $url;
$file_redir->file_id = $file_id;
$result = $file_redir->insert();
if (!$result) {
common_log_db_error($file_redir, "INSERT", __FILE__);
throw new ClientException(_('There was a database error while saving your file. Please try again.'));
}
}
}
static function fromUpload($param = 'media', $user = null)
{
if (empty($user)) {
$user = common_current_user();
}
if (!isset($_FILES[$param]['error'])){
return;
}
switch ($_FILES[$param]['error']) {
case UPLOAD_ERR_OK: // success, jump out
break;
case UPLOAD_ERR_INI_SIZE:
throw new ClientException(_('The uploaded file exceeds the ' .
'upload_max_filesize directive in php.ini.'));
return;
case UPLOAD_ERR_FORM_SIZE:
throw new ClientException(
_('The uploaded file exceeds the MAX_FILE_SIZE directive' .
' that was specified in the HTML form.'));
return;
case UPLOAD_ERR_PARTIAL:
@unlink($_FILES[$param]['tmp_name']);
throw new ClientException(_('The uploaded file was only' .
' partially uploaded.'));
return;
case UPLOAD_ERR_NO_TMP_DIR:
throw new ClientException(_('Missing a temporary folder.'));
return;
case UPLOAD_ERR_CANT_WRITE:
throw new ClientException(_('Failed to write file to disk.'));
return;
case UPLOAD_ERR_EXTENSION:
throw new ClientException(_('File upload stopped by extension.'));
return;
default:
throw new ClientException(_('System error uploading file.'));
return;
}
if (!MediaFile::respectsQuota($user, $_FILES['attach']['size'])) {
// Should never actually get here
@unlink($_FILES[$param]['tmp_name']);
throw new ClientException(_('File exceeds user\'s quota!'));
return;
}
$mimetype = MediaFile::getUploadedFileType($_FILES[$param]['tmp_name']);
$filename = null;
if (isset($mimetype)) {
$basename = basename($_FILES[$param]['name']);
$filename = File::filename($user->getProfile(), $basename, $mimetype);
$filepath = File::path($filename);
$result = move_uploaded_file($_FILES[$param]['tmp_name'], $filepath);
if (!$result) {
throw new ClientException(_('File could not be moved to destination directory.'));
return;
}
} else {
throw new ClientException(_('Could not determine file\'s mime-type!'));
return;
}
return new MediaFile($user, $filename, $mimetype);
}
static function fromFilehandle($fh, $user) {
$stream = stream_get_meta_data($fh);
if (!MediaFile::respectsQuota($user, filesize($stream['uri']))) {
// Should never actually get here
throw new ClientException(_('File exceeds user\'s quota!'));
return;
}
$mimetype = MediaFile::getUploadedFileType($fh);
$filename = null;
if (isset($mimetype)) {
$filename = File::filename($user->getProfile(), "email", $mimetype);
$filepath = File::path($filename);
$result = copy($stream['uri'], $filepath) && chmod($filepath, 0664);
if (!$result) {
throw new ClientException(_('File could not be moved to destination directory.' .
$stream['uri'] . ' ' . $filepath));
}
} else {
throw new ClientException(_('Could not determine file\'s mime-type!'));
return;
}
return new MediaFile($user, $filename, $mimetype);
}
static function getUploadedFileType($f) {
require_once 'MIME/Type.php';
$cmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd');
$cmd = common_config('attachments', 'filecommand');
$filetype = null;
if (is_string($f)) {
// assuming a filename
$filetype = MIME_Type::autoDetect($f);
} else {
// assuming a filehandle
$stream = stream_get_meta_data($f);
$filetype = MIME_Type::autoDetect($stream['uri']);
}
if (in_array($filetype, common_config('attachments', 'supported'))) {
return $filetype;
}
$media = MIME_Type::getMedia($filetype);
if ('application' !== $media) {
$hint = sprintf(_(' Try using another %s format.'), $media);
} else {
$hint = '';
}
throw new ClientException(sprintf(
_('%s is not a supported filetype on this server.'), $filetype) . $hint);
}
static function respectsQuota($user, $filesize)
{
$file = new File;
$result = $file->isRespectsQuota($user, $filesize);
if ($result === true) {
return true;
} else {
throw new ClientException($result);
}
}
}

View File

@ -29,6 +29,7 @@ END_OF_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc';
require_once(INSTALLDIR . '/lib/mail.php');
require_once(INSTALLDIR . '/lib/mediafile.php');
require_once('Mail/mimeDecode.php');
# FIXME: we use both Mail_mimeDecode and mailparse
@ -71,43 +72,27 @@ class MailerDaemon
'Max notice size is %d chars.'),
Notice::maxContent()));
}
$fileRecords = array();
$mediafiles = array();
foreach($attachments as $attachment){
$mimetype = $this->getUploadedFileType($attachment);
$stream = stream_get_meta_data($attachment);
if (!$this->isRespectsQuota($user,filesize($stream['uri']))) {
die('error() should trigger an exception before reaching here.');
}
$filename = $this->saveFile($user, $attachment,$mimetype);
$mf = null;
try {
$mf = MediaFile::fromFileHandle($attachment, $user);
} catch(ClientException $ce) {
$this->error($from, $ce->getMessage());
}
$msg .= ' ' . $mf->shortUrl();
array_push($mediafiles, $mf);
fclose($attachment);
if (empty($filename)) {
$this->error($from,_('Couldn\'t save file.'));
}
$fileRecord = $this->storeFile($filename, $mimetype);
$fileRecords[] = $fileRecord;
$fileurl = common_local_url('attachment',
array('attachment' => $fileRecord->id));
// not sure this is necessary -- Zach
$this->maybeAddRedir($fileRecord->id, $fileurl);
$short_fileurl = common_shorten_url($fileurl);
$msg .= ' ' . $short_fileurl;
if (Notice::contentTooLong($msg)) {
$this->deleteFile($filename);
$this->error($from, sprintf(_('Max notice size is %d chars, including attachment URL.'),
Notice::maxContent()));
}
// Also, not sure this is necessary -- Zach
$this->maybeAddRedir($fileRecord->id, $short_fileurl);
}
$err = $this->add_notice($user, $msg, $fileRecords);
$err = $this->add_notice($user, $msg, $mediafiles);
if (is_string($err)) {
$this->error($from, $err);
return false;
@ -116,89 +101,6 @@ class MailerDaemon
}
}
function saveFile($user, $attachment, $mimetype) {
$filename = File::filename($user->getProfile(), "email", $mimetype);
$filepath = File::path($filename);
$stream = stream_get_meta_data($attachment);
if (copy($stream['uri'], $filepath) && chmod($filepath,0664)) {
return $filename;
} else {
$this->error(null,_('File could not be moved to destination directory.' . $stream['uri'] . ' ' . $filepath));
}
}
function storeFile($filename, $mimetype) {
$file = new File;
$file->filename = $filename;
$file->url = File::url($filename);
$filepath = File::path($filename);
$file->size = filesize($filepath);
$file->date = time();
$file->mimetype = $mimetype;
$file_id = $file->insert();
if (!$file_id) {
common_log_db_error($file, "INSERT", __FILE__);
$this->error(null,_('There was a database error while saving your file. Please try again.'));
}
return $file;
}
function maybeAddRedir($file_id, $url)
{
$file_redir = File_redirection::staticGet('url', $url);
if (empty($file_redir)) {
$file_redir = new File_redirection;
$file_redir->url = $url;
$file_redir->file_id = $file_id;
$result = $file_redir->insert();
if (!$result) {
common_log_db_error($file_redir, "INSERT", __FILE__);
$this->error(null,_('There was a database error while saving your file. Please try again.'));
}
}
}
function getUploadedFileType($fileHandle) {
require_once 'MIME/Type.php';
$cmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd');
$cmd = common_config('attachments', 'filecommand');
$stream = stream_get_meta_data($fileHandle);
$filetype = MIME_Type::autoDetect($stream['uri']);
if (in_array($filetype, common_config('attachments', 'supported'))) {
return $filetype;
}
$media = MIME_Type::getMedia($filetype);
if ('application' !== $media) {
$hint = sprintf(_(' Try using another %s format.'), $media);
} else {
$hint = '';
}
$this->error(null,sprintf(
_('%s is not a supported filetype on this server.'), $filetype) . $hint);
}
function isRespectsQuota($user,$fileSize) {
$file = new File;
$ret = $file->isRespectsQuota($user,$fileSize);
if (true === $ret) return true;
$this->error(null,$ret);
}
function error($from, $msg)
{
file_put_contents("php://stderr", $msg . "\n");
@ -258,7 +160,7 @@ class MailerDaemon
common_log($level, 'MailDaemon: '.$msg);
}
function add_notice($user, $msg, $fileRecords)
function add_notice($user, $msg, $mediafiles)
{
try {
$notice = Notice::saveNew($user->id, $msg, 'mail');
@ -266,8 +168,8 @@ class MailerDaemon
$this->log(LOG_ERR, $e->getMessage());
return $e->getMessage();
}
foreach($fileRecords as $fileRecord){
$this->attachFile($notice, $fileRecord);
foreach($mediafiles as $mf){
$mf->attachToNotice($notice);
}
common_broadcast_notice($notice);
$this->log(LOG_INFO,
@ -275,14 +177,6 @@ class MailerDaemon
return true;
}
function attachFile($notice, $filerec)
{
File_to_post::processNew($filerec->id, $notice->id);
$this->maybeAddRedir($filerec->id,
common_local_url('file', array('notice' => $notice->id)));
}
function parse_message($fname)
{
$contents = file_get_contents($fname);