2009-05-11 18:45:00 +01:00
< ? php
/*
2009-08-25 23:14:12 +01:00
* StatusNet - the distributed open - source microblogging tool
2009-08-25 23:12:20 +01:00
* Copyright ( C ) 2008 , 2009 , StatusNet , Inc .
2009-05-11 18:45:00 +01: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 15:41:36 +01:00
if ( ! defined ( 'STATUSNET' ) && ! defined ( 'LACONICA' )) { exit ( 1 ); }
2009-05-11 18:45:00 +01:00
require_once INSTALLDIR . '/classes/Memcached_DataObject.php' ;
2009-05-13 19:27:32 +01: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-11 18:45:00 +01:00
/**
* Table Definition for file
*/
2009-06-22 23:48:31 +01:00
class File extends Memcached_DataObject
2009-05-11 18:45:00 +01:00
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'file' ; // table name
2009-06-22 22:19:41 +01:00
public $id ; // int(4) primary_key not_null
2009-05-11 18:45:00 +01:00
public $url ; // varchar(255) unique_key
2009-06-22 23:48:31 +01: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-22 22:19:41 +01:00
public $modified ; // timestamp() not_null default_CURRENT_TIMESTAMP
2009-05-11 18:45:00 +01:00
/* Static get */
2009-06-22 22:24:40 +01:00
function staticGet ( $k , $v = NULL ) { return Memcached_DataObject :: staticGet ( 'File' , $k , $v ); }
2009-05-11 18:45:00 +01:00
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
2009-05-13 19:27:32 +01:00
function isProtected ( $url ) {
return 'http://www.facebook.com/login.php' === $url ;
}
2009-05-15 20:04:58 +01: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-10 21:39:42 +00: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-13 19:27:32 +01: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-10 22:31:29 +00: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-13 19:27:32 +01:00
if ( isset ( $redir_data [ 'type' ])
2009-09-02 04:02:03 +01:00
&& (( 'text/html' === substr ( $redir_data [ 'type' ], 0 , 9 ) || 'application/xhtml+xml' === substr ( $redir_data [ 'type' ], 0 , 21 )))
2009-07-15 22:10:36 +01:00
&& ( $oembed_data = File_oembed :: _getOembed ( $given_url ))) {
2010-01-10 21:18:53 +00:00
2010-03-10 22:31:29 +00:00
$fo = File_oembed :: staticGet ( 'file_id' , $this -> id );
2010-01-10 21:18:53 +00:00
if ( empty ( $fo )) {
2010-03-10 22:31:29 +00:00
File_oembed :: saveNew ( $oembed_data , $this -> id );
return true ;
2010-01-10 21:18:53 +00:00
} else {
common_log ( LOG_WARNING , " Strangely, a File_oembed object exists for new file $file_id " , __FILE__ );
}
2009-05-13 19:27:32 +01:00
}
2010-03-10 22:31:29 +00:00
return false ;
2009-05-13 19:27:32 +01:00
}
2010-05-25 21:09:21 +01:00
/**
2010-11-15 19:00:42 +00:00
* Go look at a URL and possibly save data about it if it ' s new :
* - follow redirect chains and store them in file_redirection
* - look up oEmbed data and save it in file_oembed
* - if a thumbnail is available , save it in file_thumbnail
* - save file record with basic info
* - optionally save a file_to_post record
* - return the File object with the full reference
*
2010-05-25 21:09:21 +01:00
* @ fixme refactor this mess , it ' s gotten pretty scary .
2010-11-15 19:00:42 +00:00
* @ param string $given_url the URL we ' re looking at
* @ param int $notice_id ( optional )
* @ param bool $followRedirects defaults to true
*
* @ return mixed File on success , - 1 on some errors
*
* @ throws ServerException on some errors
2010-05-25 21:09:21 +01:00
*/
2010-11-15 19:00:42 +00:00
public function processNew ( $given_url , $notice_id = null , $followRedirects = true ) {
2009-05-13 19:27:32 +01: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-25 19:10:34 +01:00
if ( empty ( $file )) {
2009-05-13 19:27:32 +01:00
$file_redir = File_redirection :: staticGet ( 'url' , $given_url );
2009-06-25 19:10:34 +01:00
if ( empty ( $file_redir )) {
2010-05-25 21:09:21 +01: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-13 19:27:32 +01:00
$redir_data = File_redirection :: where ( $given_url );
2009-09-27 21:52:15 +01:00
if ( is_array ( $redir_data )) {
$redir_url = $redir_data [ 'url' ];
} elseif ( is_string ( $redir_data )) {
$redir_url = $redir_data ;
2010-03-10 22:31:29 +00:00
$redir_data = array ();
2009-09-27 21:52:15 +01:00
} else {
2010-07-29 12:01:04 +01:00
// TRANS: Server exception thrown when a URL cannot be processed.
2010-07-30 18:25:55 +01:00
throw new ServerException ( sprintf ( _ ( " Cannot process URL '%s' " ), $given_url ));
2009-09-27 21:52:15 +01:00
}
2009-08-12 05:00:46 +01:00
// TODO: max field length
2010-05-25 21:09:21 +01:00
if ( $redir_url === $given_url || strlen ( $redir_url ) > 255 || ! $followRedirects ) {
2009-05-13 19:27:32 +01:00
$x = File :: saveNew ( $redir_data , $given_url );
$file_id = $x -> id ;
} else {
2010-05-25 21:09:21 +01: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-13 19:27:32 +01: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-30 21:55:09 +01:00
if ( empty ( $x )) {
2010-11-04 17:33:39 +00:00
// @todo FIXME: This could possibly be a clearer message :)
2010-07-29 12:01:04 +01:00
// TRANS: Server exception thrown when... Robin thinks something is impossible!
2010-11-04 17:33:39 +00:00
throw new ServerException ( _ ( 'Robin thinks something is impossible.' ));
2009-07-30 21:55:09 +01:00
}
2009-05-13 19:27:32 +01:00
}
2009-06-22 23:48:31 +01:00
2009-08-28 04:23:31 +01:00
if ( ! empty ( $notice_id )) {
File_to_post :: processNew ( $file_id , $notice_id );
}
2009-05-13 19:27:32 +01:00
return $x ;
}
2009-06-01 02:03:55 +01:00
2009-07-07 20:55:10 +01:00
function isRespectsQuota ( $user , $fileSize ) {
2009-07-22 06:05:44 +01:00
2009-07-07 20:55:10 +01:00
if ( $fileSize > common_config ( 'attachments' , 'file_quota' )) {
2010-07-29 12:01:04 +01: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-04 17:33:39 +00: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-07 20:55:10 +01:00
common_config ( 'attachments' , 'file_quota' ), $fileSize );
2009-06-01 02:03:55 +01: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-07 20:55:10 +01:00
$total = $this -> total + $fileSize ;
2009-06-01 02:03:55 +01:00
if ( $total > common_config ( 'attachments' , 'user_quota' )) {
2010-07-29 12:01:04 +01:00
// TRANS: Message given if an upload would exceed user quota.
2010-11-04 17:33:39 +00: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 02:03:55 +01:00
}
2009-07-22 06:05:44 +01: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 02:03:55 +01:00
$this -> query ( $query );
$this -> fetch ();
2009-07-07 20:55:10 +01:00
$total = $this -> total + $fileSize ;
2009-06-01 02:03:55 +01:00
if ( $total > common_config ( 'attachments' , 'monthly_quota' )) {
2010-07-29 12:01:04 +01:00
// TRANS: Message given id an upload would exceed a user's monthly quota.
2010-11-04 17:33:39 +00: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 02:03:55 +01:00
}
return true ;
}
2009-06-22 23:48:31 +01:00
// where should the file go?
2009-06-23 15:29:43 +01:00
static function filename ( $profile , $basename , $mimetype )
{
require_once 'MIME/Type/Extension.php' ;
2010-11-04 00:05:26 +00:00
// We have to temporarily disable auto handling of PEAR errors...
PEAR :: staticPushErrorHandling ( PEAR_ERROR_RETURN );
2009-06-23 15:29:43 +01:00
$mte = new MIME_Type_Extension ();
2010-11-04 00:05:26 +00:00
$ext = $mte -> getExtension ( $mimetype );
if ( PEAR :: isError ( $ext )) {
2010-03-02 02:42:38 +00:00
$ext = strtolower ( preg_replace ( '/\W/' , '' , $mimetype ));
}
2010-11-04 00:05:26 +00:00
// Restore error handling.
PEAR :: staticPopErrorHandling ();
2009-06-23 15:29:43 +01: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-22 23:48:31 +01:00
2010-02-01 16:48:31 +00:00
/**
* Validation for as - saved base filenames
*/
static function validFilename ( $filename )
{
2010-02-02 17:30:15 +00:00
return preg_match ( '/^[A-Za-z0-9._-]+$/' , $filename );
2010-02-01 16:48:31 +00:00
}
/**
* @ throws ClientException on invalid filename
*/
2009-06-23 15:29:43 +01:00
static function path ( $filename )
{
2010-02-01 16:48:31 +00:00
if ( ! self :: validFilename ( $filename )) {
2010-07-29 12:01:04 +01:00
// TRANS: Client exception thrown if a file upload does not have a valid name.
throw new ClientException ( _ ( " Invalid filename. " ));
2010-02-01 16:48:31 +00:00
}
2009-06-23 15:29:43 +01:00
$dir = common_config ( 'attachments' , 'dir' );
2009-06-22 23:48:31 +01:00
2009-06-23 15:29:43 +01:00
if ( $dir [ strlen ( $dir ) - 1 ] != '/' ) {
$dir .= '/' ;
}
2009-06-22 23:48:31 +01:00
2009-06-23 15:29:43 +01:00
return $dir . $filename ;
}
2009-06-22 23:48:31 +01:00
2009-06-23 15:29:43 +01:00
static function url ( $filename )
{
2010-02-01 16:48:31 +00:00
if ( ! self :: validFilename ( $filename )) {
2010-07-29 12:01:04 +01:00
// TRANS: Client exception thrown if a file upload does not have a valid name.
throw new ClientException ( _ ( " Invalid filename. " ));
2010-02-01 16:48:31 +00:00
}
2010-10-14 19:22:17 +01:00
if ( common_config ( 'site' , 'private' )) {
2009-06-22 23:48:31 +01:00
2010-01-05 22:47:37 +00:00
return common_local_url ( 'getfile' ,
array ( 'filename' => $filename ));
2009-06-22 23:48:31 +01:00
2010-10-14 19:22:17 +01:00
}
2009-06-22 23:48:31 +01:00
2010-10-14 19:22:17 +01:00
if ( StatusNet :: isHTTPS ()) {
$sslserver = common_config ( 'attachments' , 'sslserver' );
2009-06-22 23:48:31 +01:00
2010-10-14 19:22:17 +01: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-05 22:47:37 +00:00
}
2010-10-14 19:22:17 +01:00
$protocol = 'https' ;
} else {
$path = common_config ( 'attachments' , 'path' );
2010-01-05 22:47:37 +00:00
$server = common_config ( 'attachments' , 'server' );
2009-06-22 23:48:31 +01:00
2010-01-05 22:47:37 +00:00
if ( empty ( $server )) {
$server = common_config ( 'site' , 'server' );
}
2009-06-22 23:48:31 +01:00
2010-02-11 22:06:57 +00:00
$ssl = common_config ( 'attachments' , 'ssl' );
2010-01-05 22:47:37 +00:00
2010-02-11 22:06:57 +00:00
$protocol = ( $ssl ) ? 'https' : 'http' ;
2010-10-14 19:22:17 +01:00
}
2010-02-11 22:06:57 +00:00
2010-10-14 19:22:17 +01:00
if ( $path [ strlen ( $path ) - 1 ] != '/' ) {
$path .= '/' ;
2010-01-05 22:47:37 +00:00
}
2010-10-14 19:22:17 +01:00
if ( $path [ 0 ] != '/' ) {
$path = '/' . $path ;
}
return $protocol . '://' . $server . $path . $filename ;
2009-06-23 15:29:43 +01:00
}
2009-07-14 18:33:40 +01:00
2009-08-26 20:40:51 +01: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 02:51:54 +01:00
if ( ! isset ( $this -> filename )){
2010-03-11 22:26:59 +00:00
$notEnclosureMimeTypes = array ( null , 'text/html' , 'application/xhtml+xml' );
2010-06-28 20:20:50 +01:00
$mimetype = $this -> mimetype ;
2010-03-24 23:30:27 +00:00
if ( $mimetype != null ){
$mimetype = strtolower ( $this -> mimetype );
}
2009-08-26 20:40:51 +01:00
$semicolon = strpos ( $mimetype , ';' );
if ( $semicolon ){
$mimetype = substr ( $mimetype , 0 , $semicolon );
}
if ( in_array ( $mimetype , $notEnclosureMimeTypes )){
2010-11-08 23:50:06 +00:00
// Never treat generic HTML links as an enclosure type!
// But if we have oEmbed info, we'll consider it golden.
2009-08-27 02:51:54 +01:00
$oembed = File_oembed :: staticGet ( 'file_id' , $this -> id );
2010-11-08 23:50:06 +00:00
if ( $oembed && in_array ( $oembed -> type , array ( 'photo' , 'video' ))){
2009-08-27 02:51:54 +01:00
$mimetype = strtolower ( $oembed -> mimetype );
2009-08-26 20:40:51 +01:00
$semicolon = strpos ( $mimetype , ';' );
if ( $semicolon ){
$mimetype = substr ( $mimetype , 0 , $semicolon );
}
2010-11-09 20:04:07 +00:00
// @fixme uncertain if this is right.
// we want to expose things like YouTube videos as
// viewable attachments, but don't expose them as
// downloadable enclosures.....?
//if (in_array($mimetype, $notEnclosureMimeTypes)) {
// return false;
//} else {
2009-08-27 02:51:54 +01: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 );
2010-11-09 20:04:07 +00:00
//}
2010-01-22 15:12:26 +00:00
} else {
return false ;
2009-08-26 20:40:51 +01:00
}
}
2009-07-14 18:33:40 +01:00
}
2009-08-27 02:51:54 +01:00
return $enclosure ;
2009-07-14 18:33:40 +01:00
}
2010-03-03 00:30:09 +00:00
// quick back-compat hack, since there's still code using this
function isEnclosure ()
{
$enclosure = $this -> getEnclosure ();
return ! empty ( $enclosure );
}
2010-11-09 01:22:01 +00:00
/**
* Get the attachment ' s thumbnail record , if any .
*
* @ return File_thumbnail
*/
function getThumbnail ()
{
return File_thumbnail :: staticGet ( 'file_id' , $this -> id );
}
2010-12-28 19:58:55 +00:00
function blowCache ( $last = false )
{
self :: blow ( 'file:notice-ids:%s' , $this -> url );
if ( $last ) {
self :: blow ( 'file:notice-ids:%s;last' , $this -> url );
}
}
/**
* Stream of notices linking to this URL
*
* @ param integer $offset Offset to show ; default is 0
* @ param integer $limit Limit of notices to show
* @ param integer $since_id Since this notice
* @ param integer $max_id Before this notice
*
* @ return array ids of notices that link to this file
*/
function stream ( $offset = 0 , $limit = NOTICES_PER_PAGE , $since_id = 0 , $max_id = 0 )
{
$ids = Notice :: stream ( array ( $this , '_streamDirect' ),
null ,
'file:notice-ids:' . $this -> url ,
$offset , $limit , $since_id , $max_id );
return $ids ;
}
/**
* Stream of notices linking to this URL
*
* @ param integer $offset Offset to show ; default is 0
* @ param integer $limit Limit of notices to show
* @ param integer $since_id Since this notice
* @ param integer $max_id Before this notice
*
* @ return array ids of notices that link to this file
*/
function _streamDirect ( $offset , $limit , $since_id , $max_id )
{
$f2p = new File_to_post ();
$f2p -> file_id = $this -> id ;
Notice :: addWhereSinceId ( $f2p , $since_id , 'post_id' , 'modified' );
Notice :: addWhereMaxId ( $f2p , $max_id , 'post_id' , 'modified' );
$f2p -> orderBy ( 'modified DESC, notice_id DESC' );
if ( ! is_null ( $offset )) {
$reply -> limit ( $offset , $limit );
}
$ids = array ();
if ( $f2p -> find ()) {
while ( $f2p -> fetch ()) {
$ids [] = $f2p -> notice_id ;
}
}
return $ids ;
}
2009-05-11 18:45:00 +01:00
}