OSDN Git Service

FIX:$manager->notify()の第二引数に変数を渡すように修正。
[nucleus-jp/nucleus-next.git] / nucleus / libs / MEDIA.php
1 <?php
2 /*
3  * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
4  * Copyright (C) 2002-2012 The Nucleus Group
5  *
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)
11  */
12 /**
13  * Media classes for nucleus
14  *
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 $
18  */
19
20 define('PRIVATE_COLLECTION',            'Private Collection');
21 define('READ_ONLY_MEDIA_FOLDER',        '(Read Only)');
22
23 class Media
24 {
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',
31         );
32         
33         /**
34          * Media::getCollectionList()
35          * Gets the list of collections available to the currently logged
36          * in member
37          * 
38          * @param       boolean $exceptReadOnly
39          * @return array        dirname => display name
40          */
41         static public function getCollectionList($exceptReadOnly = FALSE)
42         {
43                 global $member, $DIR_MEDIA;
44                 
45                 $collections = array();
46                 
47                 // add private directory for member
48                 $collections[$member->getID()] = PRIVATE_COLLECTION;
49                 
50                 // add global collections
51                 if ( !is_dir($DIR_MEDIA) )
52                 {
53                         return $collections;
54                 }
55                 
56                 $dirhandle = opendir($DIR_MEDIA);
57                 while ( $dirname = readdir($dirhandle) )
58                 {
59                         // only add non-numeric (numeric=private) dirs
60                         if ( @is_dir($DIR_MEDIA . $dirname) &&
61                                 ($dirname != '.') &&
62                                 ($dirname != '..') &&
63                                 ($dirname != self::$thumbdir) &&
64                                 (!is_numeric($dirname)) )
65                                 {
66                                 if ( @is_writable($DIR_MEDIA . $dirname) )
67                                 {
68                                         $collections[$dirname] = $dirname;
69                                 }
70                                 else if ( $exceptReadOnly == FALSE )
71                                 {
72                                         $collections[$dirname] = $dirname . ' ' . READ_ONLY_MEDIA_FOLDER;
73                                 }
74                         }
75                 }
76                 closedir($dirhandle);
77                 
78                 return $collections;
79         }
80         
81         /**
82          * Media::getMediaListByCollection()
83          * Returns an array of MediaObject objects for a certain collection
84          *
85          * @param       string  $collection     name of the collection
86          * @param       string  $filter         filter on filename (defaults to none)
87          * @return      void
88          */
89         static public function getMediaListByCollection($collection, $filter = '')
90         {
91                 global $CONF, $DIR_MEDIA;
92                 
93                 $filelist = array();
94                 
95                 // 1. go through all objects and add them to the filelist
96                 $mediadir = $DIR_MEDIA . $collection . '/';
97                 
98                 // return if dir does not exist
99                 if ( !is_dir($mediadir) )
100                 {
101                         return $filelist;
102                 }
103                 
104                 $dirhandle = opendir($mediadir);
105                 while ( $filename = readdir($dirhandle) )
106                 {
107                         // only add files that match the filter
108                         if ( !is_dir($mediadir . $filename) && self::checkFilter($filename, $filter) )
109                         {
110                                 array_push($filelist, new MediaObject($collection, $filename, $DIR_MEDIA));
111                         }
112                 }
113                 closedir($dirhandle);
114                 
115                 /* sort array */
116                 if ( !$CONF['MediaPrefix'] )
117                 {
118                         usort($filelist,  array(__CLASS__, 'sort_media_by_timestamp'));
119                 }
120                 else
121                 {
122                         usort($filelist,  array(__CLASS__, 'sort_media_by_filename'));
123                 }
124                 
125                 return $filelist;
126         }
127         
128         /**
129          * Media::checkFilter()
130          * 
131          * @param       string  $strText
132          * @param       string  $strFilter
133          * @return      boolean
134          */
135         static public function checkFilter($strText, $strFilter)
136         {
137                 if ( $strFilter == '' )
138                 {
139                         return 1;
140                 }
141                 else
142                 {
143                         return is_integer(i18n::strpos(strtolower($strText), strtolower($strFilter)));
144                 }
145         }
146         
147         /**
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
151          * 
152          * @param       string  $collectionName
153          * @param       string  $exceptReadOnly
154          * @return      boolean
155          */
156         static public function isValidCollection($collectionName, $exceptReadOnly = FALSE)
157         {
158                 global $member, $DIR_MEDIA;
159                 
160                 // allow creating new private directory
161                 if ( $collectionName === (string)$member->getID() )
162                 {
163                         return TRUE;
164                 }
165                 
166                 $collections = self::getCollectionList($exceptReadOnly);
167                 $dirname = $collections[$collectionName];
168                 
169                 if ( $dirname == NULL || $dirname === PRIVATE_COLLECTION )
170                 {
171                         return FALSE;
172                 }
173                 
174                 // other collections should exist and be writable
175                 $collectionDir = $DIR_MEDIA . $collectionName;
176                 if ( $exceptReadOnly )
177                 {
178                         return ( @is_dir($collectionDir) && @is_writable($collectionDir) );
179                 }
180                 
181                 // other collections should exist
182                 return @is_dir($collectionDir);
183         }
184         
185         /**
186          * Media::addMediaObject()
187          * Adds an uploaded file to the media archive
188          *
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
194          */
195         static public function addMediaObject($collection, $uploadfile, $filename)
196         {
197                 global $DIR_MEDIA, $manager;
198                 
199                 // clean filename of characters that may cause trouble in a filename using cleanFileName() function from globalfunctions.php
200                 $filename = cleanFileName($filename);
201                 
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 )
204                 {
205                         return _ERROR_BADFILETYPE;
206                 }
207                 
208                 // trigger PreMediaUpload event
209                 $data = array('collection' => &$collection, 'uploadfile' => $uploadfile, 'filename' => &$filename);
210                 $manager->notify('PreMediaUpload', $data);
211                 
212                 // don't allow uploads to unknown or forbidden collections
213                 $exceptReadOnly = TRUE;
214                 if ( !self::isValidCollection($collection,$exceptReadOnly) )
215                 {
216                         return _ERROR_DISALLOWED;
217                 }
218                 
219                 // check dir permissions (try to create dir if it does not exist)
220                 $mediadir = $DIR_MEDIA . $collection;
221                 
222                 // try to create new private media directories if needed
223                 if ( !@is_dir($mediadir) && is_numeric($collection) )
224                 {
225                         $oldumask = umask(0000);
226                         if ( !@mkdir($mediadir, 0777) )
227                         {
228                                 return _ERROR_BADPERMISSIONS;
229                         }
230                         umask($oldumask);
231                 }
232                 
233                 // if dir still not exists, the action is disallowed
234                 if ( !@is_dir($mediadir) )
235                 {
236                         return _ERROR_DISALLOWED;
237                 }
238                 
239                 if ( !is_writeable($mediadir) )
240                 {
241                         return _ERROR_BADPERMISSIONS;
242                 }
243                 
244                 // add trailing slash (don't add it earlier since it causes mkdir to fail on some systems)
245                 $mediadir .= '/';
246                 
247                 if ( file_exists($mediadir . $filename) )
248                 {
249                         return _ERROR_UPLOADDUPLICATE;
250                 }
251                 
252                 // move file to directory
253                 if ( is_uploaded_file($uploadfile) )
254                 {
255                         if ( !@move_uploaded_file($uploadfile, $mediadir . $filename) )
256                         {
257                                 return _ERROR_UPLOADMOVEP;
258                         }
259                 }
260                 else
261                 {
262                         if ( !copy($uploadfile, $mediadir . $filename) )
263                         {
264                                 return _ERROR_UPLOADCOPY ;
265                         }
266                 }
267                 
268                 // chmod uploaded file
269                 $oldumask = umask(0000);
270                 @chmod($mediadir . $filename, 0644);
271                 umask($oldumask);
272
273                 $data = array('collection' => $collection, 'mediadir' => $mediadir, 'filename' => $filename);
274                 $manager->notify('PostMediaUpload', $data);
275                 
276                 return '';
277         }
278         
279         /**
280          * Media::addMediaObjectRaw()
281          * Adds an uploaded file to the media dir.
282          * 
283          * NOTE: does not check if $collection is valid.
284          * 
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
290          */
291         static public function addMediaObjectRaw($collection, $filename, &$data)
292         {
293                 global $DIR_MEDIA;
294                 
295                 // check dir permissions (try to create dir if it does not exist)
296                 $mediadir = $DIR_MEDIA . $collection;
297                 
298                 // try to create new private media directories if needed
299                 if ( !@is_dir($mediadir) && is_numeric($collection) )
300                 {
301                         $oldumask = umask(0000);
302                         if ( !@mkdir($mediadir, 0777) )
303                         {
304                                 return _ERROR_BADPERMISSIONS;
305                         }
306                         umask($oldumask);
307                 }
308                 
309                 // if dir still not exists, the action is disallowed
310                 if ( !@is_dir($mediadir) )
311                 {
312                         return _ERROR_DISALLOWED;
313                 }
314                 
315                 if ( !is_writeable($mediadir) )
316                 {
317                         return _ERROR_BADPERMISSIONS;
318                 }
319                 
320                 // add trailing slash (don't add it earlier since it causes mkdir to fail on some systems)
321                 $mediadir .= '/';
322                 
323                 if ( file_exists($mediadir . $filename) )
324                 {
325                         return _ERROR_UPLOADDUPLICATE;
326                 }
327                 
328                 // create file
329                 $fh = @fopen($mediadir . $filename, 'wb');
330                 if ( !$fh )
331                 {
332                         return _ERROR_UPLOADFAILED;
333                 }
334                 $ok = @fwrite($fh, $data);
335                 @fclose($fh);
336                 if ( !$ok )
337                 {
338                         return _ERROR_UPLOADFAILED;
339                 }
340                 
341                 // chmod uploaded file
342                 $oldumask = umask(0000);
343                 @chmod($mediadir . $filename, 0644);
344                 umask($oldumask);
345                 
346                 return '';
347         }
348         
349         /**
350          * Media::responseResampledImage()
351          * send resampled image via HTTP
352          * 
353          * @param       object  $medium         MediaObject Object
354          * @exit
355          */
356         static public function responseResampledImage($medium, $maxwidth=0, $maxheight=0)
357         {
358                 if ( get_class($medium) !== 'MediaObject' )
359                 {
360                         header("HTTP/1.1 500 Internal Server Error");
361                         exit('Nucleus CMS: Fail to generate resampled image');
362                         return;
363                 }
364                 
365                 $resampledimage = $medium->getResampledBinary($maxwidth, $maxheight);
366                 if ( $resampledimage === FALSE )
367                 {
368                         unset($resampledimage);
369                         header("HTTP/1.1 503 Service Unavailable");
370                         exit('Nucleus CMS: Fail to generate resampled image');
371                         return;
372                 }
373                 
374                 header("Content-type: {$medium->mime}");
375                 echo $resampledimage;
376                 
377                 unset($resampledimage);
378                 
379                 exit;
380         }
381         
382         /**
383          * Media::storeResampledImage()
384          * Store resampled image binary to filesystem as file
385          * 
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
391          * @return      boolean
392          */
393         static public function storeResampledImage($medium, $maxwidth=0, $maxheight=0, $path='', $name='')
394         {
395                 global $DIR_MEDIA;
396                 
397                 if ( get_class($medium) !== 'MediaObject' )
398                 {
399                         return FALSE;
400                 }
401                 
402                 if ( $path !== '' )
403                 {
404                         $path = realpath($path);
405                         if ( !file_exists($path)
406                           || strpos($path, $DIR_MEDIA) !== 0 )
407                         {
408                                 return FALSE;
409                         }
410                 }
411                 else
412                 {
413                         $path = '$DIR_MEDIA/' . self::$thumbdir;
414                 }
415                 
416                 if ( $name === '' )
417                 {
418                         $name = $medium->getHashedname();
419                 }
420                 
421                 $resampledimage = $medium->getResampledBinary($maxwidth, $maxheight);
422                 if ( !$resampledimage )
423                 {
424                         unset($resampledimage);
425                         return FALSE;
426                 }
427                 
428                 $handle = @fopen("{$path}/{$name}", 'w');
429                 if ( !$handle )
430                 {
431                         unset ($resampledimage);
432                         return FALSE;
433                 }
434                 
435                 if ( !@fwrite($handle, $resampledimage) )
436                 {
437                         unset($resampledimage);
438                         @unlink("{$path}/{$name}");
439                         return FALSE;
440                 }
441                 
442                 unset($resampledimage);
443                 fclose($handle);
444                 
445                 if ( !@chmod("{$path}/{$name}", 0774) )
446                 {
447                         @unlink("{$path}/{$name}");
448                         return FALSE;
449                 }
450                 
451                 return TRUE;
452         }
453         
454         /**
455          * Media::sort_media_by_timestamp()
456          * User-defined sort method to sort an array of MediaObjects
457          * 
458          * @param       object  $a
459          * @param       object  $b
460          * @return      boolean
461          */
462         static private function sort_media_by_timestamp($a, $b)
463         {
464                 if ($a->timestamp == $b->timestamp) return 0;
465                 return ($a->timestamp > $b->timestamp) ? -1 : 1;
466         }
467         
468         /**
469          * Media::sort_media_by_filename()
470          * User-defined sort method to sort an array of MediaObjects
471          * 
472          * @param       object  $a
473          * @param       object  $b
474          * @return      boolean
475          */
476         static private function sort_media_by_filename($a, $b)
477         {
478                 if ($a->filename == $b->filename) return 0;
479                 return ($a->filename > $b->filename) ? -1 : 1;
480         }
481 }
482
483 class MediaObject
484 {
485         public $mime = '';
486         
487         public $root = '';
488         public $path = '';
489         public $private;
490         public $collection;
491         public $filename = '';
492         
493         public $prefix = '';
494         public $name = '';
495         public $suffix = '';
496         
497         public $timestamp = 0;
498         public $size = 0;
499         
500         public $width = 0;
501         public $height = 0;
502         public $resampledwidth = 0;
503         public $resampledheight = 0;
504         
505         /**
506          * MediaObject::__construct()
507          * 
508          * @param       string          $collection     
509          * @param       string          $filename       
510          * @param       string          $root           fullpath to media directory
511          */
512         public function __construct($collection, $filename, $root=0)
513         {
514                 global $CONF, $DIR_MEDIA;
515                 
516                 /* for backward compatibility */
517                 if ( is_numeric($root) )
518                 {
519                         $root = $DIR_MEDIA;
520                 }
521                 
522                 $root = preg_replace('#/*$#', '', $root);
523                 
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) )
529                 {
530                         return FALSE;
531                 }
532                 
533                 /* store fundamentals */
534                 $this->root = $root;
535                 $this->private = (integer) $collection;
536                 $this->collection = $collection;
537                 $this->filename = basename($fullpath);
538                 $this->timestamp = filemtime($fullpath);
539                 
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 )
543                 {
544                         $this->path = ''; 
545                 }
546                 
547                 return;
548         }
549         
550         /**
551          * MediaObject::refine()
552          * refine data
553          * 
554          * @param       void
555          * @return      void
556          */
557         public function refine()
558         {
559                 global $CONF;
560                 
561                 /* store size (byte order) */
562                 $this->size = filesize("{$this->root}/{$this->path}/{$this->filename}");
563                 
564                 /* get width and height if this is image binary */
565                 if ( FALSE === ($info = @getimagesize ("{$this->root}/{$this->path}/{$this->filename}")) )
566                 {
567                         $this->mime = 'application/octet-stream';
568                         $this->width = 0;
569                         $this->height = 0;
570                 }
571                 else
572                 {
573                         $this->mime = $info['mime'];
574                         $this->width = $info[0];
575                         $this->height = $info[1];
576                 }
577                 
578                 /* utilise Fileinfo subsystem if available */
579                 if ( defined('FILEINFO_MIME_TYPE') && function_exists ('finfo_open')
580                   && (FALSE !== ($info = finfo_open(FILEINFO_MIME_TYPE))) )
581                 {
582                         $this->mime = finfo_file($info, "{$this->root}/{$this->path}/{$this->filename}");
583                 }
584                 
585                 /* store data with parsed filename */
586                 if ( preg_match('#^(.*)\.([a-zA-Z0-9]{2,})$#', $this->filename, $info) === 1 )
587                 {
588                         $this->name = $info[1];
589                         $this->suffix = $info[2];
590                         
591                         if ( $CONF['MediaPrefix'] && preg_match('#^([0-9]{8})\-(.*)$#', $this->name, $info) == 1 )
592                         {
593                                 $this->prefix = preg_replace('#^([0-9]{4})([0-9]{2})([0-9]{2})$#', '$1/$2/$3', $info[1]);
594                                 $this->name = $info[2];
595                         }
596                 }
597                 
598                 return;
599         }
600         
601         /**
602          * MediaObject::setResampledSize()
603          * Set resampled size
604          * 
605          * @param       integer $maxwidth
606          * @param       integer $maxheight
607          * @return      boolean
608          */
609         public function setResampledSize($maxwidth=0, $maxheight=0)
610         {
611                 if ( ($maxwidth == 0) && ($maxheight == 0) )
612                 {
613                         return FALSE;
614                 }
615                 else if ( $this->width == 0 || $this->height  == 0 )
616                 {
617                         return FALSE;
618                 }
619                 else if ($this->width < $maxwidth && $this->height < $maxheight )
620                 {
621                         $this->resampledwidth = $this->width;
622                         $this->resampledheight = $this->height;
623                 }
624                 else if ( $maxheight == 0 || $this->width > $this->height )
625                 {
626                         $this->resampledheight = intval ($this->height * $maxwidth / $this->width);
627                         $this->resampledwidth = $maxwidth;
628                 }
629                 else if ( $maxwidth == 0 || $this->width <= $this->height )
630                 {
631                         $this->resampledwidth = intval ($this->width * $maxheight / $this->height);
632                         $this->resampledheight = $maxheight;
633                 }
634                 return TRUE;
635         }
636         
637         /**
638          * MediaObject::getResampledBinary()
639          * Return resampled image binary
640          * 
641          * @param       void
642          * @return      mixed   binary if success, FALSE if failed
643          */
644         public function getResampledBinary($maxwidth=0, $maxheight=0)
645         {
646                 static $gdinfo = array();
647                 static $original;
648                 static $resampledimage;
649                 
650                 if ( !$this->setResampledSize($maxwidth, $maxheight) )
651                 {
652                         return FALSE;
653                 }
654                 
655                 if ( $gdinfo = array() )
656                 {
657                         $gdinfo = gd_info();
658                 }
659                 
660                 if ( $this->path !== '' )
661                 {
662                         $fullpath = "{$this->root}/{$this->path}/{$this->name}";
663                 }
664                 else
665                 {
666                         $fullpath = "{$this->root}/{$this->name}";
667                 }
668                 if ( !file_exists($fullpath) )
669                 {
670                         return FALSE;
671                 }
672                 
673                 if ( !array_key_exists($this->mime, Media::$image_mime)
674                   || $this->width == 0
675                   || $this->height == 0
676                   || $this->resampledwidth == 0
677                   || $this->resampledheight == 0 )
678                 {
679                         return FALSE;
680                 }
681                 
682                 /* check current available memory */
683                 $memorymax = trim(ini_get("memory_limit"));
684                 switch ( strtolower ($memorymax[strlen($memorymax)-1]) )
685                 {
686                         case 'g':
687                                 $memorymax *= 1024;
688                         case 'm':
689                                 $memorymax *= 1024;
690                         case 'k':
691                                 $memorymax *= 1024;
692                 }
693                 
694                 /*
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
697                  */
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))
701                   > $memorymax )
702                 {
703                         return FALSE;
704                 }
705                 
706                 switch ( $this->mime )
707                 {
708                         case 'image/gif':
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'])) )
711                                 {
712                                         return FALSE;
713                                 }
714                                 $function = 'imagecreatefromgif';
715                                 break;
716                         case 'image/jpeg':
717                                 if ( (!array_key_exists('JPEG Support', $gdinfo) || !isset($gdinfo['JPEG Support']))
718                                   && (!array_key_exists('JPG Support', $gdinfo) || !isset($gdinfo['JPG Support'])) )
719                                 {
720                                         return FALSE;
721                                 }
722                                 $function = 'imagecreatefromjpeg';
723                                 break;
724                         case 'image/png':
725                                 if ( !array_key_exists('PNG Support', $gdinfo) || !isset($gdinfo['PNG Support']) )
726                                 {
727                                         return FALSE;
728                                 }
729                                 $function = 'imagecreatefrompng';
730                                 break;
731                         default:
732                                 return FALSE;
733                 }
734                 
735                 if ( !is_callable($function) )
736                 {
737                         return FALSE;
738                 }
739                 
740                 $original = call_user_func_array($function, array(&$fullpath));
741                 if ( !$original )
742                 {
743                         return FALSE;
744                 }
745                 
746                 $resampledimage = imagecreatetruecolor($this->resampledwidth, $this->resampledheight);
747                 if ( !$resampledimage )
748                 {
749                         imagedestroy($original);
750                         return FALSE;
751                 }
752                 
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) )
755                 {
756                         return FALSE;
757                 }
758                 
759                 imagedestroy($original);
760                 
761                 ob_start();
762                 
763                 switch ( $this->mime )
764                 {
765                         case 'image/gif':
766                                 imagegif($resampledimage);
767                                 break;
768                         case 'image/jpeg':
769                                 imagejpeg($resampledimage);
770                                 break;
771                         case 'image/png':
772                                 imagepng($resampledimage);
773                                 break;
774                         case 'image/bmp':
775                         case 'image/x-ms-bmp':
776                                 imagepng($resampledimage);
777                                 break;
778                         default:
779                                 return FALSE;
780                 }
781                 
782                 imagedestroy($resampledimage);
783                 
784                 return ob_get_clean();
785         }
786         
787         public function getHashedName()
788         {
789                 return (string) hash(Media::$algorism, "{$this->path}/{$this->name}", FALSE);
790         }
791 }