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 $data = array('collection' => &$collection, 'uploadfile' => $uploadfile, 'filename' => &$filename);
210 $manager->notify('PreMediaUpload', $data);
212 // don't allow uploads to unknown or forbidden collections
213 $exceptReadOnly = TRUE;
214 if ( !self::isValidCollection($collection,$exceptReadOnly) )
216 return _ERROR_DISALLOWED;
219 // check dir permissions (try to create dir if it does not exist)
220 $mediadir = $DIR_MEDIA . $collection;
222 // try to create new private media directories if needed
223 if ( !@is_dir($mediadir) && is_numeric($collection) )
225 $oldumask = umask(0000);
226 if ( !@mkdir($mediadir, 0777) )
228 return _ERROR_BADPERMISSIONS;
233 // if dir still not exists, the action is disallowed
234 if ( !@is_dir($mediadir) )
236 return _ERROR_DISALLOWED;
239 if ( !is_writeable($mediadir) )
241 return _ERROR_BADPERMISSIONS;
244 // add trailing slash (don't add it earlier since it causes mkdir to fail on some systems)
247 if ( file_exists($mediadir . $filename) )
249 return _ERROR_UPLOADDUPLICATE;
252 // move file to directory
253 if ( is_uploaded_file($uploadfile) )
255 if ( !@move_uploaded_file($uploadfile, $mediadir . $filename) )
257 return _ERROR_UPLOADMOVEP;
262 if ( !copy($uploadfile, $mediadir . $filename) )
264 return _ERROR_UPLOADCOPY ;
268 // chmod uploaded file
269 $oldumask = umask(0000);
270 @chmod($mediadir . $filename, 0644);
273 $data = array('collection' => $collection, 'mediadir' => $mediadir, 'filename' => $filename);
274 $manager->notify('PostMediaUpload', $data);
280 * Media::addMediaObjectRaw()
281 * Adds an uploaded file to the media dir.
283 * NOTE: does not check if $collection is valid.
285 * @param string $collection collection to use
286 * @param string $filename the filename that should be used to save the file
287 * as (date prefix should be already added here)
288 * @param &$data File data (binary)
289 * @return string blank if success, message if failed
291 static public function addMediaObjectRaw($collection, $filename, &$data)
295 // check dir permissions (try to create dir if it does not exist)
296 $mediadir = $DIR_MEDIA . $collection;
298 // try to create new private media directories if needed
299 if ( !@is_dir($mediadir) && is_numeric($collection) )
301 $oldumask = umask(0000);
302 if ( !@mkdir($mediadir, 0777) )
304 return _ERROR_BADPERMISSIONS;
309 // if dir still not exists, the action is disallowed
310 if ( !@is_dir($mediadir) )
312 return _ERROR_DISALLOWED;
315 if ( !is_writeable($mediadir) )
317 return _ERROR_BADPERMISSIONS;
320 // add trailing slash (don't add it earlier since it causes mkdir to fail on some systems)
323 if ( file_exists($mediadir . $filename) )
325 return _ERROR_UPLOADDUPLICATE;
329 $fh = @fopen($mediadir . $filename, 'wb');
332 return _ERROR_UPLOADFAILED;
334 $ok = @fwrite($fh, $data);
338 return _ERROR_UPLOADFAILED;
341 // chmod uploaded file
342 $oldumask = umask(0000);
343 @chmod($mediadir . $filename, 0644);
350 * Media::responseResampledImage()
351 * send resampled image via HTTP
353 * @param object $medium MediaObject Object
356 static public function responseResampledImage($medium, $maxwidth=0, $maxheight=0)
358 if ( get_class($medium) !== 'MediaObject' )
360 header("HTTP/1.1 500 Internal Server Error");
361 exit('Nucleus CMS: Fail to generate resampled image');
365 $resampledimage = $medium->getResampledBinary($maxwidth, $maxheight);
366 if ( $resampledimage === FALSE )
368 unset($resampledimage);
369 header("HTTP/1.1 503 Service Unavailable");
370 exit('Nucleus CMS: Fail to generate resampled image');
374 header("Content-type: {$medium->mime}");
375 echo $resampledimage;
377 unset($resampledimage);
383 * Media::storeResampledImage()
384 * Store resampled image binary to filesystem as file
386 * @param object $medium MediaObject Object
387 * @param integer $maxwidth maximum width
388 * @param integer $maxheight maximum height
389 * @param string $path directory path for destination
390 * @param string $name file name for destination
393 static public function storeResampledImage($medium, $maxwidth=0, $maxheight=0, $path='', $name='')
397 if ( get_class($medium) !== 'MediaObject' )
404 $path = realpath($path);
405 if ( !file_exists($path)
406 || strpos($path, $DIR_MEDIA) !== 0 )
413 $path = '$DIR_MEDIA/' . self::$thumbdir;
418 $name = $medium->getHashedname();
421 $resampledimage = $medium->getResampledBinary($maxwidth, $maxheight);
422 if ( !$resampledimage )
424 unset($resampledimage);
428 $handle = @fopen("{$path}/{$name}", 'w');
431 unset ($resampledimage);
435 if ( !@fwrite($handle, $resampledimage) )
437 unset($resampledimage);
438 @unlink("{$path}/{$name}");
442 unset($resampledimage);
445 if ( !@chmod("{$path}/{$name}", 0774) )
447 @unlink("{$path}/{$name}");
455 * Media::sort_media_by_timestamp()
456 * User-defined sort method to sort an array of MediaObjects
462 static private function sort_media_by_timestamp($a, $b)
464 if ($a->timestamp == $b->timestamp) return 0;
465 return ($a->timestamp > $b->timestamp) ? -1 : 1;
469 * Media::sort_media_by_filename()
470 * User-defined sort method to sort an array of MediaObjects
476 static private function sort_media_by_filename($a, $b)
478 if ($a->filename == $b->filename) return 0;
479 return ($a->filename > $b->filename) ? -1 : 1;
491 public $filename = '';
497 public $timestamp = 0;
502 public $resampledwidth = 0;
503 public $resampledheight = 0;
506 * MediaObject::__construct()
508 * @param string $collection
509 * @param string $filename
510 * @param string $root fullpath to media directory
512 public function __construct($collection, $filename, $root=0)
514 global $CONF, $DIR_MEDIA;
516 /* for backward compatibility */
517 if ( is_numeric($root) )
522 $root = preg_replace('#/*$#', '', $root);
524 /* get and validate fullpath for the medium */
525 if ( !file_exists($root)
526 || FALSE === ($fullpath = realpath("{$root}/{$collection}/{$filename}"))
527 || strpos($fullpath, $root) !== 0
528 || !file_exists($fullpath) )
533 /* store fundamentals */
535 $this->private = (integer) $collection;
536 $this->collection = $collection;
537 $this->filename = basename($fullpath);
538 $this->timestamp = filemtime($fullpath);
540 /* store relative directory path from root directory for media */
541 $this->path = preg_replace(array("#{$this->root}/#", "#/{$this->filename}#"), '', $fullpath);
542 if ( $this->path === $this->name )
551 * MediaObject::refine()
557 public function refine()
561 /* store size (byte order) */
562 $this->size = filesize("{$this->root}/{$this->path}/{$this->filename}");
564 /* get width and height if this is image binary */
565 if ( FALSE === ($info = @getimagesize ("{$this->root}/{$this->path}/{$this->filename}")) )
567 $this->mime = 'application/octet-stream';
573 $this->mime = $info['mime'];
574 $this->width = $info[0];
575 $this->height = $info[1];
578 /* utilise Fileinfo subsystem if available */
579 if ( defined('FILEINFO_MIME_TYPE') && function_exists ('finfo_open')
580 && (FALSE !== ($info = finfo_open(FILEINFO_MIME_TYPE))) )
582 $this->mime = finfo_file($info, "{$this->root}/{$this->path}/{$this->filename}");
585 /* store data with parsed filename */
586 if ( preg_match('#^(.*)\.([a-zA-Z0-9]{2,})$#', $this->filename, $info) === 1 )
588 $this->name = $info[1];
589 $this->suffix = $info[2];
591 if ( $CONF['MediaPrefix'] && preg_match('#^([0-9]{8})\-(.*)$#', $this->name, $info) == 1 )
593 $this->prefix = preg_replace('#^([0-9]{4})([0-9]{2})([0-9]{2})$#', '$1/$2/$3', $info[1]);
594 $this->name = $info[2];
602 * MediaObject::setResampledSize()
605 * @param integer $maxwidth
606 * @param integer $maxheight
609 public function setResampledSize($maxwidth=0, $maxheight=0)
611 if ( ($maxwidth == 0) && ($maxheight == 0) )
615 else if ( $this->width == 0 || $this->height == 0 )
619 else if ($this->width < $maxwidth && $this->height < $maxheight )
621 $this->resampledwidth = $this->width;
622 $this->resampledheight = $this->height;
624 else if ( $maxheight == 0 || $this->width > $this->height )
626 $this->resampledheight = intval ($this->height * $maxwidth / $this->width);
627 $this->resampledwidth = $maxwidth;
629 else if ( $maxwidth == 0 || $this->width <= $this->height )
631 $this->resampledwidth = intval ($this->width * $maxheight / $this->height);
632 $this->resampledheight = $maxheight;
638 * MediaObject::getResampledBinary()
639 * Return resampled image binary
642 * @return mixed binary if success, FALSE if failed
644 public function getResampledBinary($maxwidth=0, $maxheight=0)
646 static $gdinfo = array();
648 static $resampledimage;
650 if ( !$this->setResampledSize($maxwidth, $maxheight) )
655 if ( $gdinfo = array() )
660 if ( $this->path !== '' )
662 $fullpath = "{$this->root}/{$this->path}/{$this->name}";
666 $fullpath = "{$this->root}/{$this->name}";
668 if ( !file_exists($fullpath) )
673 if ( !array_key_exists($this->mime, Media::$image_mime)
675 || $this->height == 0
676 || $this->resampledwidth == 0
677 || $this->resampledheight == 0 )
682 /* check current available memory */
683 $memorymax = trim(ini_get("memory_limit"));
684 switch ( strtolower ($memorymax[strlen($memorymax)-1]) )
695 * this code is based on analyze if gd.c in php source code
696 * if you can read C/C++, please check these elements and notify us if you have some ideas
698 if ( (memory_get_usage()
699 + ($this->resampledwidth * $this->resampledheight * 5 + $this->resampledheight * 24 + 10000)
700 + ($this->width * $this->height * 5 + $this->height * 24 + 10000))
706 switch ( $this->mime )
709 if ( (!array_key_exists('GIF Read Support', $gdinfo) || !isset($gdinfo['GIF Read Support']))
710 || (!array_key_exists('GIF Create Support', $gdinfo) || !isset($gdinfo['GIF Create Support'])) )
714 $function = 'imagecreatefromgif';
717 if ( (!array_key_exists('JPEG Support', $gdinfo) || !isset($gdinfo['JPEG Support']))
718 && (!array_key_exists('JPG Support', $gdinfo) || !isset($gdinfo['JPG Support'])) )
722 $function = 'imagecreatefromjpeg';
725 if ( !array_key_exists('PNG Support', $gdinfo) || !isset($gdinfo['PNG Support']) )
729 $function = 'imagecreatefrompng';
735 if ( !is_callable($function) )
740 $original = call_user_func_array($function, array(&$fullpath));
746 $resampledimage = imagecreatetruecolor($this->resampledwidth, $this->resampledheight);
747 if ( !$resampledimage )
749 imagedestroy($original);
753 @set_time_limit(ini_get('max_execution_time'));
754 if ( !ImageCopyResampled($resampledimage, $original, 0, 0, 0, 0, $this->resampledwidth, $this->resampledheight, $this->width, $this->height) )
759 imagedestroy($original);
763 switch ( $this->mime )
766 imagegif($resampledimage);
769 imagejpeg($resampledimage);
772 imagepng($resampledimage);
775 case 'image/x-ms-bmp':
776 imagepng($resampledimage);
782 imagedestroy($resampledimage);
784 return ob_get_clean();
787 public function getHashedName()
789 return (string) hash(Media::$algorism, "{$this->path}/{$this->name}", FALSE);