3 * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
4 * Copyright (C) 2002-2012 The Nucleus Group
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 * (see nucleus/documentation/index.html#license for more info)
13 * Media classes for nucleus
15 * @license http://nucleuscms.org/license.txt GNU General Public License
16 * @copyright Copyright (C) 2002-2012 The Nucleus Group
17 * @version $Id: MEDIA.php 1875 2012-06-17 07:30:44Z sakamocchi $
20 define('PRIVATE_COLLECTION', 'Private Collection');
21 define('READ_ONLY_MEDIA_FOLDER', '(Read Only)');
25 static public $thumbdir = '.thumb';
26 static public $algorism = 'md5';
27 static public $image_mime = array(
28 'image/jpeg' => '.jpeg',
29 'image/png' => '.png',
30 'image/gif' => '.gif',
34 * Media::getCollectionList()
35 * Gets the list of collections available to the currently logged
38 * @param boolean $exceptReadOnly
39 * @return array dirname => display name
41 static public function getCollectionList($exceptReadOnly = FALSE)
43 global $member, $DIR_MEDIA;
45 $collections = array();
47 // add private directory for member
48 $collections[$member->getID()] = PRIVATE_COLLECTION;
50 // add global collections
51 if ( !is_dir($DIR_MEDIA) )
56 $dirhandle = opendir($DIR_MEDIA);
57 while ( $dirname = readdir($dirhandle) )
59 // only add non-numeric (numeric=private) dirs
60 if ( @is_dir($DIR_MEDIA . $dirname) &&
63 ($dirname != self::$thumbdir) &&
64 (!is_numeric($dirname)) )
66 if ( @is_writable($DIR_MEDIA . $dirname) )
68 $collections[$dirname] = $dirname;
70 else if ( $exceptReadOnly == FALSE )
72 $collections[$dirname] = $dirname . ' ' . READ_ONLY_MEDIA_FOLDER;
82 * Media::getMediaListByCollection()
83 * Returns an array of MediaObject objects for a certain collection
85 * @param string $collection name of the collection
86 * @param string $filter filter on filename (defaults to none)
89 static public function getMediaListByCollection($collection, $filter = '')
91 global $CONF, $DIR_MEDIA;
95 // 1. go through all objects and add them to the filelist
96 $mediadir = $DIR_MEDIA . $collection . '/';
98 // return if dir does not exist
99 if ( !is_dir($mediadir) )
104 $dirhandle = opendir($mediadir);
105 while ( $filename = readdir($dirhandle) )
107 // only add files that match the filter
108 if ( !is_dir($mediadir . $filename) && self::checkFilter($filename, $filter) )
110 array_push($filelist, new MediaObject($collection, $filename, $DIR_MEDIA));
113 closedir($dirhandle);
116 if ( !$CONF['MediaPrefix'] )
118 usort($filelist, array(__CLASS__, 'sort_media_by_timestamp'));
122 usort($filelist, array(__CLASS__, 'sort_media_by_filename'));
129 * Media::checkFilter()
131 * @param string $strText
132 * @param string $strFilter
135 static public function checkFilter($strText, $strFilter)
137 if ( $strFilter == '' )
143 return is_integer(i18n::strpos(strtolower($strText), strtolower($strFilter)));
148 * Media::isValidCollection()
149 * checks if a collection exists with the given name, and if it's
150 * allowed for the currently logged in member to upload files to it
152 * @param string $collectionName
153 * @param string $exceptReadOnly
156 static public function isValidCollection($collectionName, $exceptReadOnly = FALSE)
158 global $member, $DIR_MEDIA;
160 // allow creating new private directory
161 if ( $collectionName === (string)$member->getID() )
166 $collections = self::getCollectionList($exceptReadOnly);
167 $dirname = $collections[$collectionName];
169 if ( $dirname == NULL || $dirname === PRIVATE_COLLECTION )
174 // other collections should exist and be writable
175 $collectionDir = $DIR_MEDIA . $collectionName;
176 if ( $exceptReadOnly )
178 return ( @is_dir($collectionDir) && @is_writable($collectionDir) );
181 // other collections should exist
182 return @is_dir($collectionDir);
186 * Media::addMediaObject()
187 * Adds an uploaded file to the media archive
189 * @param string $collection collection
190 * @param array $uploadfile the postFileInfo(..) array
191 * @param string $filename the filename that should be used to save the file as
192 * (date prefix should be already added here)
193 * @return string blank if success, message if failed
195 static public function addMediaObject($collection, $uploadfile, $filename)
197 global $DIR_MEDIA, $manager;
199 // clean filename of characters that may cause trouble in a filename using cleanFileName() function from globalfunctions.php
200 $filename = cleanFileName($filename);
202 // should already have tested for allowable types before calling this method. This will only catch files with no extension at all
203 if ( $filename === FALSE )
205 return _ERROR_BADFILETYPE;
208 // trigger PreMediaUpload event
209 $manager->notify('PreMediaUpload',array('collection' => &$collection, 'uploadfile' => $uploadfile, 'filename' => &$filename));
211 // don't allow uploads to unknown or forbidden collections
212 $exceptReadOnly = TRUE;
213 if ( !self::isValidCollection($collection,$exceptReadOnly) )
215 return _ERROR_DISALLOWED;
218 // check dir permissions (try to create dir if it does not exist)
219 $mediadir = $DIR_MEDIA . $collection;
221 // try to create new private media directories if needed
222 if ( !@is_dir($mediadir) && is_numeric($collection) )
224 $oldumask = umask(0000);
225 if ( !@mkdir($mediadir, 0777) )
227 return _ERROR_BADPERMISSIONS;
232 // if dir still not exists, the action is disallowed
233 if ( !@is_dir($mediadir) )
235 return _ERROR_DISALLOWED;
238 if ( !is_writeable($mediadir) )
240 return _ERROR_BADPERMISSIONS;
243 // add trailing slash (don't add it earlier since it causes mkdir to fail on some systems)
246 if ( file_exists($mediadir . $filename) )
248 return _ERROR_UPLOADDUPLICATE;
251 // move file to directory
252 if ( is_uploaded_file($uploadfile) )
254 if ( !@move_uploaded_file($uploadfile, $mediadir . $filename) )
256 return _ERROR_UPLOADMOVEP;
261 if ( !copy($uploadfile, $mediadir . $filename) )
263 return _ERROR_UPLOADCOPY ;
267 // chmod uploaded file
268 $oldumask = umask(0000);
269 @chmod($mediadir . $filename, 0644);
272 $manager->notify('PostMediaUpload',array('collection' => $collection, 'mediadir' => $mediadir, 'filename' => $filename));
278 * Media::addMediaObjectRaw()
279 * Adds an uploaded file to the media dir.
281 * NOTE: does not check if $collection is valid.
283 * @param string $collection collection to use
284 * @param string $filename the filename that should be used to save the file
285 * as (date prefix should be already added here)
286 * @param &$data File data (binary)
287 * @return string blank if success, message if failed
289 static public function addMediaObjectRaw($collection, $filename, &$data)
293 // check dir permissions (try to create dir if it does not exist)
294 $mediadir = $DIR_MEDIA . $collection;
296 // try to create new private media directories if needed
297 if ( !@is_dir($mediadir) && is_numeric($collection) )
299 $oldumask = umask(0000);
300 if ( !@mkdir($mediadir, 0777) )
302 return _ERROR_BADPERMISSIONS;
307 // if dir still not exists, the action is disallowed
308 if ( !@is_dir($mediadir) )
310 return _ERROR_DISALLOWED;
313 if ( !is_writeable($mediadir) )
315 return _ERROR_BADPERMISSIONS;
318 // add trailing slash (don't add it earlier since it causes mkdir to fail on some systems)
321 if ( file_exists($mediadir . $filename) )
323 return _ERROR_UPLOADDUPLICATE;
327 $fh = @fopen($mediadir . $filename, 'wb');
330 return _ERROR_UPLOADFAILED;
332 $ok = @fwrite($fh, $data);
336 return _ERROR_UPLOADFAILED;
339 // chmod uploaded file
340 $oldumask = umask(0000);
341 @chmod($mediadir . $filename, 0644);
348 * Media::responseResampledImage()
349 * send resampled image via HTTP
351 * @param object $medium MediaObject Object
354 static public function responseResampledImage($medium, $maxwidth=0, $maxheight=0)
356 if ( get_class($medium) !== 'MediaObject' )
358 header("HTTP/1.1 500 Internal Server Error");
359 exit('Nucleus CMS: Fail to generate resampled image');
363 $resampledimage = $medium->getResampledBinary($maxwidth, $maxheight);
364 if ( $resampledimage === FALSE )
366 unset($resampledimage);
367 header("HTTP/1.1 503 Service Unavailable");
368 exit('Nucleus CMS: Fail to generate resampled image');
372 header("Content-type: {$medium->mime}");
373 echo $resampledimage;
375 unset($resampledimage);
381 * Media::storeResampledImage()
382 * Store resampled image binary to filesystem as file
384 * @param object $medium MediaObject Object
385 * @param integer $maxwidth maximum width
386 * @param integer $maxheight maximum height
387 * @param string $path directory path for destination
388 * @param string $name file name for destination
391 static public function storeResampledImage($medium, $maxwidth=0, $maxheight=0, $path='', $name='')
395 if ( get_class($medium) !== 'MediaObject' )
402 $path = realpath($path);
403 if ( !file_exists($path)
404 || strpos($path, $DIR_MEDIA) !== 0 )
411 $path = '$DIR_MEDIA/' . self::$thumbdir;
416 $name = $medium->getHashedname();
419 $resampledimage = $medium->getResampledBinary($maxwidth, $maxheight);
420 if ( !$resampledimage )
422 unset($resampledimage);
426 $handle = @fopen("{$path}/{$name}", 'w');
429 unset ($resampledimage);
433 if ( !@fwrite($handle, $resampledimage) )
435 unset($resampledimage);
436 @unlink("{$path}/{$name}");
440 unset($resampledimage);
443 if ( !@chmod("{$path}/{$name}", 0774) )
445 @unlink("{$path}/{$name}");
453 * Media::sort_media_by_timestamp()
454 * User-defined sort method to sort an array of MediaObjects
460 static private function sort_media_by_timestamp($a, $b)
462 if ($a->timestamp == $b->timestamp) return 0;
463 return ($a->timestamp > $b->timestamp) ? -1 : 1;
467 * Media::sort_media_by_filename()
468 * User-defined sort method to sort an array of MediaObjects
474 static private function sort_media_by_filename($a, $b)
476 if ($a->filename == $b->filename) return 0;
477 return ($a->filename > $b->filename) ? -1 : 1;
489 public $filename = '';
495 public $timestamp = 0;
500 public $resampledwidth = 0;
501 public $resampledheight = 0;
504 * MediaObject::__construct()
506 * @param string $collection
507 * @param string $filename
508 * @param string $root fullpath to media directory
510 public function __construct($collection, $filename, $root=0)
512 global $CONF, $DIR_MEDIA;
514 /* for backward compatibility */
515 if ( is_numeric($root) )
520 $root = preg_replace('#/*$#', '', $root);
522 /* get and validate fullpath for the medium */
523 if ( !file_exists($root)
524 || FALSE === ($fullpath = realpath("{$root}/{$collection}/{$filename}"))
525 || strpos($fullpath, $root) !== 0
526 || !file_exists($fullpath) )
531 /* store fundamentals */
533 $this->private = (integer) $collection;
534 $this->collection = $collection;
535 $this->filename = basename($fullpath);
536 $this->timestamp = filemtime($fullpath);
538 /* store relative directory path from root directory for media */
539 $this->path = preg_replace(array("#{$this->root}/#", "#/{$this->filename}#"), '', $fullpath);
540 if ( $this->path === $this->name )
549 * MediaObject::refine()
555 public function refine()
559 /* store size (byte order) */
560 $this->size = filesize("{$this->root}/{$this->path}/{$this->filename}");
562 /* get width and height if this is image binary */
563 if ( FALSE === ($info = @getimagesize ("{$this->root}/{$this->path}/{$this->filename}")) )
565 $this->mime = 'application/octet-stream';
571 $this->mime = $info['mime'];
572 $this->width = $info[0];
573 $this->height = $info[1];
576 /* utilise Fileinfo subsystem if available */
577 if ( defined('FILEINFO_MIME_TYPE') && function_exists ('finfo_open')
578 && (FALSE !== ($info = finfo_open(FILEINFO_MIME_TYPE))) )
580 $this->mime = finfo_file($info, "{$this->root}/{$this->path}/{$this->filename}");
583 /* store data with parsed filename */
584 if ( preg_match('#^(.*)\.([a-zA-Z0-9]{2,})$#', $this->filename, $info) === 1 )
586 $this->name = $info[1];
587 $this->suffix = $info[2];
589 if ( $CONF['MediaPrefix'] && preg_match('#^([0-9]{8})\-(.*)$#', $this->name, $info) == 1 )
591 $this->prefix = preg_replace('#^([0-9]{4})([0-9]{2})([0-9]{2})$#', '$1/$2/$3', $info[1]);
592 $this->name = $info[2];
600 * MediaObject::setResampledSize()
603 * @param integer $maxwidth
604 * @param integer $maxheight
607 public function setResampledSize($maxwidth=0, $maxheight=0)
609 if ( ($maxwidth == 0) && ($maxheight == 0) )
613 else if ( $this->width == 0 || $this->height == 0 )
617 else if ($this->width < $maxwidth && $this->height < $maxheight )
619 $this->resampledwidth = $this->width;
620 $this->resampledheight = $this->height;
622 else if ( $maxheight == 0 || $this->width > $this->height )
624 $this->resampledheight = intval ($this->height * $maxwidth / $this->width);
625 $this->resampledwidth = $maxwidth;
627 else if ( $maxwidth == 0 || $this->width <= $this->height )
629 $this->resampledwidth = intval ($this->width * $maxheight / $this->height);
630 $this->resampledheight = $maxheight;
636 * MediaObject::getResampledBinary()
637 * Return resampled image binary
640 * @return mixed binary if success, FALSE if failed
642 public function getResampledBinary($maxwidth=0, $maxheight=0)
644 static $gdinfo = array();
646 static $resampledimage;
648 if ( !$this->setResampledSize($maxwidth, $maxheight) )
653 if ( $gdinfo = array() )
658 if ( $this->path !== '' )
660 $fullpath = "{$this->root}/{$this->path}/{$this->name}";
664 $fullpath = "{$this->root}/{$this->name}";
666 if ( !file_exists($fullpath) )
671 if ( !array_key_exists($this->mime, Media::$image_mime)
673 || $this->height == 0
674 || $this->resampledwidth == 0
675 || $this->resampledheight == 0 )
680 /* check current available memory */
681 $memorymax = trim(ini_get("memory_limit"));
682 switch ( strtolower ($memorymax[strlen($memorymax)-1]) )
693 * this code is based on analyze if gd.c in php source code
694 * if you can read C/C++, please check these elements and notify us if you have some ideas
696 if ( (memory_get_usage()
697 + ($this->resampledwidth * $this->resampledheight * 5 + $this->resampledheight * 24 + 10000)
698 + ($this->width * $this->height * 5 + $this->height * 24 + 10000))
704 switch ( $this->mime )
707 if ( (!array_key_exists('GIF Read Support', $gdinfo) || !isset($gdinfo['GIF Read Support']))
708 || (!array_key_exists('GIF Create Support', $gdinfo) || !isset($gdinfo['GIF Create Support'])) )
712 $function = 'imagecreatefromgif';
715 if ( (!array_key_exists('JPEG Support', $gdinfo) || !isset($gdinfo['JPEG Support']))
716 && (!array_key_exists('JPG Support', $gdinfo) || !isset($gdinfo['JPG Support'])) )
720 $function = 'imagecreatefromjpeg';
723 if ( !array_key_exists('PNG Support', $gdinfo) || !isset($gdinfo['PNG Support']) )
727 $function = 'imagecreatefrompng';
733 if ( !is_callable($function) )
738 $original = call_user_func_array($function, array(&$fullpath));
744 $resampledimage = imagecreatetruecolor($this->resampledwidth, $this->resampledheight);
745 if ( !$resampledimage )
747 imagedestroy($original);
751 @set_time_limit(ini_get('max_execution_time'));
752 if ( !ImageCopyResampled($resampledimage, $original, 0, 0, 0, 0, $this->resampledwidth, $this->resampledheight, $this->width, $this->height) )
757 imagedestroy($original);
761 switch ( $this->mime )
764 imagegif($resampledimage);
767 imagejpeg($resampledimage);
770 imagepng($resampledimage);
773 case 'image/x-ms-bmp':
774 imagepng($resampledimage);
780 imagedestroy($resampledimage);
782 return ob_get_clean();
785 public function getHashedName()
787 return (string) hash(Media::$algorism, "{$this->path}/{$this->name}", FALSE);