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