2009-05-12 02:45:00 +09:00
< ? php
/*
2009-08-26 07:14:12 +09:00
* StatusNet - the distributed open - source microblogging tool
2009-08-26 07:12:20 +09:00
* Copyright ( C ) 2008 , 2009 , StatusNet , Inc .
2009-05-12 02:45:00 +09:00
*
* 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 />.
*/
2009-08-26 23:41:36 +09:00
if ( ! defined ( 'STATUSNET' ) && ! defined ( 'LACONICA' )) { exit ( 1 ); }
2009-05-12 02:45:00 +09:00
require_once INSTALLDIR . '/classes/Memcached_DataObject.php' ;
2009-05-14 03:27:32 +09:00
require_once INSTALLDIR . '/classes/File_redirection.php' ;
require_once INSTALLDIR . '/classes/File_oembed.php' ;
require_once INSTALLDIR . '/classes/File_thumbnail.php' ;
require_once INSTALLDIR . '/classes/File_to_post.php' ;
//require_once INSTALLDIR.'/classes/File_redirection.php';
2009-05-12 02:45:00 +09:00
/**
* Table Definition for file
*/
2009-06-23 07:48:31 +09:00
class File extends Memcached_DataObject
2009-05-12 02:45:00 +09:00
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'file' ; // table name
2009-06-23 06:19:41 +09:00
public $id ; // int(4) primary_key not_null
2009-05-12 02:45:00 +09:00
public $url ; // varchar(255) unique_key
2009-06-23 07:48:31 +09:00
public $mimetype ; // varchar(50)
public $size ; // int(4)
public $title ; // varchar(255)
public $date ; // int(4)
public $protected ; // int(4)
public $filename ; // varchar(255)
2009-06-23 06:19:41 +09:00
public $modified ; // timestamp() not_null default_CURRENT_TIMESTAMP
2009-05-12 02:45:00 +09:00
/* Static get */
2009-06-23 06:24:40 +09:00
function staticGet ( $k , $v = NULL ) { return Memcached_DataObject :: staticGet ( 'File' , $k , $v ); }
2009-05-12 02:45:00 +09:00
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
2009-05-14 03:27:32 +09:00
function isProtected ( $url ) {
return 'http://www.facebook.com/login.php' === $url ;
}
2009-05-16 04:04:58 +09:00
function getAttachments ( $post_id ) {
$query = " select file.* from file join file_to_post on (file_id = file.id) join notice on (post_id = notice.id) where post_id = " . $this -> escape ( $post_id );
$this -> query ( $query );
$att = array ();
while ( $this -> fetch ()) {
$att [] = clone ( $this );
}
$this -> free ();
return $att ;
}
2010-03-11 06:39:42 +09:00
/**
* Save a new file record .
*
* @ param array $redir_data lookup data eg from File_redirection :: where ()
* @ param string $given_url
* @ return File
*/
function saveNew ( array $redir_data , $given_url ) {
2009-05-14 03:27:32 +09:00
$x = new File ;
$x -> url = $given_url ;
if ( ! empty ( $redir_data [ 'protected' ])) $x -> protected = $redir_data [ 'protected' ];
if ( ! empty ( $redir_data [ 'title' ])) $x -> title = $redir_data [ 'title' ];
if ( ! empty ( $redir_data [ 'type' ])) $x -> mimetype = $redir_data [ 'type' ];
if ( ! empty ( $redir_data [ 'size' ])) $x -> size = intval ( $redir_data [ 'size' ]);
if ( isset ( $redir_data [ 'time' ]) && $redir_data [ 'time' ] > 0 ) $x -> date = intval ( $redir_data [ 'time' ]);
$file_id = $x -> insert ();
2010-03-11 07:31:29 +09:00
$x -> saveOembed ( $redir_data , $given_url );
return $x ;
}
/**
* Save embedding information for this file , if applicable .
*
* Normally this won ' t need to be called manually , as File :: saveNew ()
* takes care of it .
*
* @ param array $redir_data lookup data eg from File_redirection :: where ()
* @ param string $given_url
* @ return boolean success
*/
public function saveOembed ( $redir_data , $given_url )
{
2009-05-14 03:27:32 +09:00
if ( isset ( $redir_data [ 'type' ])
2009-09-02 12:02:03 +09:00
&& (( 'text/html' === substr ( $redir_data [ 'type' ], 0 , 9 ) || 'application/xhtml+xml' === substr ( $redir_data [ 'type' ], 0 , 21 )))
2009-07-16 06:10:36 +09:00
&& ( $oembed_data = File_oembed :: _getOembed ( $given_url ))) {
2010-01-11 06:18:53 +09:00
2010-03-11 07:31:29 +09:00
$fo = File_oembed :: staticGet ( 'file_id' , $this -> id );
2010-01-11 06:18:53 +09:00
if ( empty ( $fo )) {
2010-03-11 07:31:29 +09:00
File_oembed :: saveNew ( $oembed_data , $this -> id );
return true ;
2010-01-11 06:18:53 +09:00
} else {
common_log ( LOG_WARNING , " Strangely, a File_oembed object exists for new file $file_id " , __FILE__ );
}
2009-05-14 03:27:32 +09:00
}
2010-03-11 07:31:29 +09:00
return false ;
2009-05-14 03:27:32 +09:00
}
2010-05-26 05:09:21 +09:00
/**
* @ fixme refactor this mess , it ' s gotten pretty scary .
* @ param bool $followRedirects
*/
function processNew ( $given_url , $notice_id = null , $followRedirects = true ) {
2009-05-14 03:27:32 +09:00
if ( empty ( $given_url )) return - 1 ; // error, no url to process
$given_url = File_redirection :: _canonUrl ( $given_url );
if ( empty ( $given_url )) return - 1 ; // error, no url to process
$file = File :: staticGet ( 'url' , $given_url );
2009-06-26 03:10:34 +09:00
if ( empty ( $file )) {
2009-05-14 03:27:32 +09:00
$file_redir = File_redirection :: staticGet ( 'url' , $given_url );
2009-06-26 03:10:34 +09:00
if ( empty ( $file_redir )) {
2010-05-26 05:09:21 +09:00
// @fixme for new URLs this also looks up non-redirect data
// such as target content type, size, etc, which we need
// for File::saveNew(); so we call it even if not following
// new redirects.
2009-05-14 03:27:32 +09:00
$redir_data = File_redirection :: where ( $given_url );
2009-09-28 05:52:15 +09:00
if ( is_array ( $redir_data )) {
$redir_url = $redir_data [ 'url' ];
} elseif ( is_string ( $redir_data )) {
$redir_url = $redir_data ;
2010-03-11 07:31:29 +09:00
$redir_data = array ();
2009-09-28 05:52:15 +09:00
} else {
2010-07-29 20:01:04 +09:00
// TRANS: Server exception thrown when a URL cannot be processed.
2010-07-31 02:25:55 +09:00
throw new ServerException ( sprintf ( _ ( " Cannot process URL '%s' " ), $given_url ));
2009-09-28 05:52:15 +09:00
}
2009-08-12 13:00:46 +09:00
// TODO: max field length
2010-05-26 05:09:21 +09:00
if ( $redir_url === $given_url || strlen ( $redir_url ) > 255 || ! $followRedirects ) {
2009-05-14 03:27:32 +09:00
$x = File :: saveNew ( $redir_data , $given_url );
$file_id = $x -> id ;
} else {
2010-05-26 05:09:21 +09:00
// This seems kind of messed up... for now skipping this part
// if we're already under a redirect, so we don't go into
// horrible infinite loops if we've been given an unstable
// redirect (where the final destination of the first request
// doesn't match what we get when we ask for it again).
//
// Seen in the wild with clojure.org, which redirects through
// wikispaces for auth and appends session data in the URL params.
$x = File :: processNew ( $redir_url , $notice_id , /*followRedirects*/ false );
2009-05-14 03:27:32 +09:00
$file_id = $x -> id ;
File_redirection :: saveNew ( $redir_data , $file_id , $given_url );
}
} else {
$file_id = $file_redir -> file_id ;
}
} else {
$file_id = $file -> id ;
$x = $file ;
}
if ( empty ( $x )) {
$x = File :: staticGet ( $file_id );
2009-07-31 05:55:09 +09:00
if ( empty ( $x )) {
2010-11-05 02:33:39 +09:00
// @todo FIXME: This could possibly be a clearer message :)
2010-07-29 20:01:04 +09:00
// TRANS: Server exception thrown when... Robin thinks something is impossible!
2010-11-05 02:33:39 +09:00
throw new ServerException ( _ ( 'Robin thinks something is impossible.' ));
2009-07-31 05:55:09 +09:00
}
2009-05-14 03:27:32 +09:00
}
2009-06-23 07:48:31 +09:00
2009-08-28 12:23:31 +09:00
if ( ! empty ( $notice_id )) {
File_to_post :: processNew ( $file_id , $notice_id );
}
2009-05-14 03:27:32 +09:00
return $x ;
}
2009-06-01 10:03:55 +09:00
2009-07-08 04:55:10 +09:00
function isRespectsQuota ( $user , $fileSize ) {
2009-07-22 14:05:44 +09:00
2009-07-08 04:55:10 +09:00
if ( $fileSize > common_config ( 'attachments' , 'file_quota' )) {
2010-07-29 20:01:04 +09:00
// TRANS: Message given if an upload is larger than the configured maximum.
// TRANS: %1$d is the byte limit for uploads, %2$d is the byte count for the uploaded file.
2010-11-05 02:33:39 +09:00
// TRANS: %1$s is used for plural.
return sprintf ( _m ( 'No file may be larger than %1$d byte and the file you sent was %2$d bytes. Try to upload a smaller version.' ,
'No file may be larger than %1$d bytes and the file you sent was %2$d bytes. Try to upload a smaller version.' ,
common_config ( 'attachments' , 'file_quota' )),
2009-07-08 04:55:10 +09:00
common_config ( 'attachments' , 'file_quota' ), $fileSize );
2009-06-01 10:03:55 +09:00
}
$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 ();
2009-07-08 04:55:10 +09:00
$total = $this -> total + $fileSize ;
2009-06-01 10:03:55 +09:00
if ( $total > common_config ( 'attachments' , 'user_quota' )) {
2010-07-29 20:01:04 +09:00
// TRANS: Message given if an upload would exceed user quota.
2010-11-05 02:33:39 +09:00
// TRANS: %d (number) is the user quota in bytes and is used for plural.
return sprintf ( _m ( 'A file this large would exceed your user quota of %d byte.' ,
'A file this large would exceed your user quota of %d bytes.' ,
common_config ( 'attachments' , 'user_quota' )),
common_config ( 'attachments' , 'user_quota' ));
2009-06-01 10:03:55 +09:00
}
2009-07-22 14:05:44 +09:00
$query .= ' AND EXTRACT(month FROM file.modified) = EXTRACT(month FROM now()) and EXTRACT(year FROM file.modified) = EXTRACT(year FROM now())' ;
2009-06-01 10:03:55 +09:00
$this -> query ( $query );
$this -> fetch ();
2009-07-08 04:55:10 +09:00
$total = $this -> total + $fileSize ;
2009-06-01 10:03:55 +09:00
if ( $total > common_config ( 'attachments' , 'monthly_quota' )) {
2010-07-29 20:01:04 +09:00
// TRANS: Message given id an upload would exceed a user's monthly quota.
2010-11-05 02:33:39 +09:00
// TRANS: $d (number) is the monthly user quota in bytes and is used for plural.
return sprintf ( _m ( 'A file this large would exceed your monthly quota of %d byte.' ,
'A file this large would exceed your monthly quota of %d bytes.' ,
common_config ( 'attachments' , 'monthly_quota' )),
common_config ( 'attachments' , 'monthly_quota' ));
2009-06-01 10:03:55 +09:00
}
return true ;
}
2009-06-23 07:48:31 +09:00
// where should the file go?
2009-06-23 23:29:43 +09:00
static function filename ( $profile , $basename , $mimetype )
{
require_once 'MIME/Type/Extension.php' ;
2010-11-04 09:05:26 +09:00
// We have to temporarily disable auto handling of PEAR errors...
PEAR :: staticPushErrorHandling ( PEAR_ERROR_RETURN );
2009-06-23 23:29:43 +09:00
$mte = new MIME_Type_Extension ();
2010-11-04 09:05:26 +09:00
$ext = $mte -> getExtension ( $mimetype );
if ( PEAR :: isError ( $ext )) {
2010-03-02 11:42:38 +09:00
$ext = strtolower ( preg_replace ( '/\W/' , '' , $mimetype ));
}
2010-11-04 09:05:26 +09:00
// Restore error handling.
PEAR :: staticPopErrorHandling ();
2009-06-23 23:29:43 +09:00
$nickname = $profile -> nickname ;
$datestamp = strftime ( '%Y%m%dT%H%M%S' , time ());
$random = strtolower ( common_confirmation_code ( 32 ));
return " $nickname - $datestamp - $random . $ext " ;
}
2009-06-23 07:48:31 +09:00
2010-02-02 01:48:31 +09:00
/**
* Validation for as - saved base filenames
*/
static function validFilename ( $filename )
{
2010-02-03 02:30:15 +09:00
return preg_match ( '/^[A-Za-z0-9._-]+$/' , $filename );
2010-02-02 01:48:31 +09:00
}
/**
* @ throws ClientException on invalid filename
*/
2009-06-23 23:29:43 +09:00
static function path ( $filename )
{
2010-02-02 01:48:31 +09:00
if ( ! self :: validFilename ( $filename )) {
2010-07-29 20:01:04 +09:00
// TRANS: Client exception thrown if a file upload does not have a valid name.
throw new ClientException ( _ ( " Invalid filename. " ));
2010-02-02 01:48:31 +09:00
}
2009-06-23 23:29:43 +09:00
$dir = common_config ( 'attachments' , 'dir' );
2009-06-23 07:48:31 +09:00
2009-06-23 23:29:43 +09:00
if ( $dir [ strlen ( $dir ) - 1 ] != '/' ) {
$dir .= '/' ;
}
2009-06-23 07:48:31 +09:00
2009-06-23 23:29:43 +09:00
return $dir . $filename ;
}
2009-06-23 07:48:31 +09:00
2009-06-23 23:29:43 +09:00
static function url ( $filename )
{
2010-02-02 01:48:31 +09:00
if ( ! self :: validFilename ( $filename )) {
2010-07-29 20:01:04 +09:00
// TRANS: Client exception thrown if a file upload does not have a valid name.
throw new ClientException ( _ ( " Invalid filename. " ));
2010-02-02 01:48:31 +09:00
}
2010-10-15 03:22:17 +09:00
if ( common_config ( 'site' , 'private' )) {
2009-06-23 07:48:31 +09:00
2010-01-06 07:47:37 +09:00
return common_local_url ( 'getfile' ,
array ( 'filename' => $filename ));
2009-06-23 07:48:31 +09:00
2010-10-15 03:22:17 +09:00
}
2009-06-23 07:48:31 +09:00
2010-10-15 03:22:17 +09:00
if ( StatusNet :: isHTTPS ()) {
$sslserver = common_config ( 'attachments' , 'sslserver' );
2009-06-23 07:48:31 +09:00
2010-10-15 03:22:17 +09:00
if ( empty ( $sslserver )) {
// XXX: this assumes that background dir == site dir + /file/
// not true if there's another server
if ( is_string ( common_config ( 'site' , 'sslserver' )) &&
mb_strlen ( common_config ( 'site' , 'sslserver' )) > 0 ) {
$server = common_config ( 'site' , 'sslserver' );
} else if ( common_config ( 'site' , 'server' )) {
$server = common_config ( 'site' , 'server' );
}
$path = common_config ( 'site' , 'path' ) . '/file/' ;
} else {
$server = $sslserver ;
$path = common_config ( 'attachments' , 'sslpath' );
if ( empty ( $path )) {
$path = common_config ( 'attachments' , 'path' );
}
2010-01-06 07:47:37 +09:00
}
2010-10-15 03:22:17 +09:00
$protocol = 'https' ;
} else {
$path = common_config ( 'attachments' , 'path' );
2010-01-06 07:47:37 +09:00
$server = common_config ( 'attachments' , 'server' );
2009-06-23 07:48:31 +09:00
2010-01-06 07:47:37 +09:00
if ( empty ( $server )) {
$server = common_config ( 'site' , 'server' );
}
2009-06-23 07:48:31 +09:00
2010-02-12 07:06:57 +09:00
$ssl = common_config ( 'attachments' , 'ssl' );
2010-01-06 07:47:37 +09:00
2010-02-12 07:06:57 +09:00
$protocol = ( $ssl ) ? 'https' : 'http' ;
2010-10-15 03:22:17 +09:00
}
2010-02-12 07:06:57 +09:00
2010-10-15 03:22:17 +09:00
if ( $path [ strlen ( $path ) - 1 ] != '/' ) {
$path .= '/' ;
2010-01-06 07:47:37 +09:00
}
2010-10-15 03:22:17 +09:00
if ( $path [ 0 ] != '/' ) {
$path = '/' . $path ;
}
return $protocol . '://' . $server . $path . $filename ;
2009-06-23 23:29:43 +09:00
}
2009-07-15 02:33:40 +09:00
2009-08-27 04:40:51 +09:00
function getEnclosure (){
$enclosure = ( object ) array ();
$enclosure -> title = $this -> title ;
$enclosure -> url = $this -> url ;
$enclosure -> title = $this -> title ;
$enclosure -> date = $this -> date ;
$enclosure -> modified = $this -> modified ;
$enclosure -> size = $this -> size ;
$enclosure -> mimetype = $this -> mimetype ;
2009-08-27 10:51:54 +09:00
if ( ! isset ( $this -> filename )){
2010-03-12 07:26:59 +09:00
$notEnclosureMimeTypes = array ( null , 'text/html' , 'application/xhtml+xml' );
2010-06-29 04:20:50 +09:00
$mimetype = $this -> mimetype ;
2010-03-25 08:30:27 +09:00
if ( $mimetype != null ){
$mimetype = strtolower ( $this -> mimetype );
}
2009-08-27 04:40:51 +09:00
$semicolon = strpos ( $mimetype , ';' );
if ( $semicolon ){
$mimetype = substr ( $mimetype , 0 , $semicolon );
}
if ( in_array ( $mimetype , $notEnclosureMimeTypes )){
2010-11-09 08:50:06 +09:00
// Never treat generic HTML links as an enclosure type!
// But if we have oEmbed info, we'll consider it golden.
2009-08-27 10:51:54 +09:00
$oembed = File_oembed :: staticGet ( 'file_id' , $this -> id );
2010-11-09 08:50:06 +09:00
if ( $oembed && in_array ( $oembed -> type , array ( 'photo' , 'video' ))){
2009-08-27 10:51:54 +09:00
$mimetype = strtolower ( $oembed -> mimetype );
2009-08-27 04:40:51 +09:00
$semicolon = strpos ( $mimetype , ';' );
if ( $semicolon ){
$mimetype = substr ( $mimetype , 0 , $semicolon );
}
if ( in_array ( $mimetype , $notEnclosureMimeTypes )){
return false ;
} else {
2009-08-27 10:51:54 +09:00
if ( $oembed -> mimetype ) $enclosure -> mimetype = $oembed -> mimetype ;
if ( $oembed -> url ) $enclosure -> url = $oembed -> url ;
if ( $oembed -> title ) $enclosure -> title = $oembed -> title ;
if ( $oembed -> modified ) $enclosure -> modified = $oembed -> modified ;
unset ( $oembed -> size );
2009-08-27 04:40:51 +09:00
}
2010-01-23 00:12:26 +09:00
} else {
return false ;
2009-08-27 04:40:51 +09:00
}
}
2009-07-15 02:33:40 +09:00
}
2009-08-27 10:51:54 +09:00
return $enclosure ;
2009-07-15 02:33:40 +09:00
}
2010-03-03 09:30:09 +09:00
// quick back-compat hack, since there's still code using this
function isEnclosure ()
{
$enclosure = $this -> getEnclosure ();
return ! empty ( $enclosure );
}
2009-05-12 02:45:00 +09:00
}