]>
git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Core/Picture.php
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
10 * Copyright © 2006-2012 The Galette Team
12 * This file is part of Galette (http://galette.tuxfamily.org).
14 * Galette is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
19 * Galette is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
30 * @author Frédéric Jaqcuot <unknown@unknow.com>
31 * @author Johan Cwiklinski <johan@x-tnd.be>
32 * @copyright 2006-2012 The Galette Team
33 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
35 * @link http://galette.tuxfamily.org
38 namespace Galette\Core
;
40 use Galette\Entity\Adherent
;
41 use Galette\Common\KLogger
as KLogger
;
49 * @author Frédéric Jaqcuot <unknown@unknow.com>
50 * @author Johan Cwiklinski <johan@x-tnd.be>
51 * @copyright 2006-2012 The Galette Team
52 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
53 * @link http://galette.tuxfamily.org
57 //constants that will not be overrided
58 const INVALID_FILE
= -1;
59 const INVALID_EXTENSION
= -2;
60 const FILE_TOO_BIG
= -3;
61 const MIME_NOT_ALLOWED
= -4;
63 const SQL_BLOB_ERROR
= -6;
64 //constants that can be overrided
65 //(do not use self::CONSTANT, but get_class[$this]::CONSTANT)
66 const MAX_FILE_SIZE
= 1024;
67 const TABLE
= 'pictures';
68 const PK
= Adherent
::PK
;
70 /*private $_bad_chars = array(
71 '\.', '\\\\', "'", ' ', '\/', ':', '\*', '\?', '"', '<', '>', '|'
73 //array keys contain litteral value of each forbidden character
74 //(to be used when showing an error).
75 //Maybe is there a better way to handle this...
76 private $_bad_chars = array(
90 private $_allowed_extensions = array('jpeg', 'jpg', 'png', 'gif');
91 private $_allowed_mimes = array(
92 'jpg' => 'image/jpeg',
97 protected $tbl_prefix = '';
102 protected $optimal_height;
103 protected $optimal_width;
104 protected $file_path;
107 protected $has_picture = true;
108 protected $store_path = GALETTE_PHOTOS_PATH
;
109 protected $max_width = 200;
110 protected $max_height = 200;
113 * Default constructor.
115 * @param int $id_adh the id of the member
117 public function __construct( $id_adh='' )
119 // '!==' needed, otherwise ''==0
120 if ( $id_adh !== '' ) {
122 if ( !isset ($this->db_id
) ) {
123 $this->db_id
= $id_adh;
126 //if file does not exists on the FileSystem, check for it in the database
127 if ( !$this->_checkFileOnFS() ) {
128 $this->_checkFileInDB();
132 // if we still have no picture, take the default one
133 if ( $this->file_path
=='' ) {
134 $this->getDefaultPicture();
137 //we should not have an empty file_path, but...
138 if ( $this->file_path
!== '' ) {
144 * "Magic" function called on unserialize
148 public function __wakeup()
150 //if file has been deleted since we store our object in the session,
151 //we try to retrieve it
152 if ( !$this->_checkFileOnFS() ) {
153 //if file does not exists on the FileSystem,
154 //check for it in the database
155 //$this->_checkFileInDB();
158 // if we still have no picture, take the default one
159 if ( $this->file_path
=='' ) {
160 $this->getDefaultPicture();
163 //we should not have an empty file_path, but...
164 if ( $this->file_path
!== '' ) {
170 * Check if current file is present on the File System
172 * @return boolean true if file is present on FS, false otherwise
174 private function _checkFileOnFS()
176 $file_wo_ext = $this->store_path
. $this->id
;
177 if ( file_exists($file_wo_ext . '.jpg') ) {
178 $this->file_path
= $file_wo_ext . '.jpg';
179 $this->format
= 'jpg';
180 $this->mime
= 'image/jpeg';
182 } elseif ( file_exists($file_wo_ext . '.png') ) {
183 $this->file_path
= $file_wo_ext . '.png';
184 $this->format
= 'png';
185 $this->mime
= 'image/png';
187 } elseif ( file_exists($file_wo_ext . '.gif') ) {
188 $this->file_path
= $file_wo_ext . '.gif';
189 $this->format
= 'gif';
190 $this->mime
= 'image/gif';
197 * Check if current file is present in the database,
198 * and copy it to the File System
200 * @return boolean true if file is present in the DB, false otherwise
202 private function _checkFileInDB()
207 $select = $this->getCheckFileQuery();
208 $pic = $zdb->db
->fetchRow($select);
209 //what's $pic if no result?
210 if ( $pic !== false ) {
211 // we must regenerate the picture file
212 $file_wo_ext = $this->store_path
. $this->id
;
213 $f = fopen($file_wo_ext . '.' . $pic->format
, 'wb');
214 fwrite($f, $pic->picture
);
216 $this->format
= $pic->format
;
217 switch($this->format
) {
219 $this->mime
= 'image/jpeg';
222 $this->mime
= 'image/png';
225 $this->mime
= 'image/gif';
228 $this->file_path
= $file_wo_ext . '.' . $this->format
;
231 } catch (\Exception
$e) {
237 * Returns the relevant query to check if picture exists in database.
239 * @return string SELECT query
241 protected function getCheckFileQuery()
244 $class = get_class($this);
246 $select = new \
Zend_Db_Select($zdb->db
);
248 array(PREFIX_DB
. $this->tbl_prefix
. $class::TABLE
),
254 $select->where($class::PK
. ' = ?', $this->db_id
);
259 * Gets the default picture to show, anyways
263 protected function getDefaultPicture()
265 $this->file_path
= _CURRENT_TEMPLATE_PATH
. 'images/default.png';
266 $this->format
= 'png';
267 $this->mime
= 'image/png';
268 $this->has_picture
= false;
276 private function _setSizes()
278 list($width, $height) = getimagesize($this->file_path
);
279 $this->height
= $height;
280 $this->width
= $width;
281 $this->optimal_height
= $height;
282 $this->optimal_width
= $width;
284 if ($this->height
> $this->width
) {
285 if ($this->height
> $this->max_height
) {
286 $ratio = $this->max_height
/ $this->height
;
287 $this->optimal_height
= $this->max_height
;
288 $this->optimal_width
= $this->width
* $ratio;
291 if ($this->width
> $this->max_width
) {
292 $ratio = $this->max_width
/ $this->width
;
293 $this->optimal_width
= $this->max_width
;
294 $this->optimal_height
= $this->height
* $ratio;
300 * Set header and displays the picture.
302 * @return object the binary file
304 public function display()
306 header('Content-type: '.$this->mime
);
307 header('Content-Length: ' . filesize($this->file_path
));
310 readfile($this->file_path
);
314 * Deletes a picture, from both database and filesystem
316 * @param boolean $transaction Whether to use a transaction here or not
318 * @return boolean true if image was successfully deleted, false otherwise
320 public function delete($transaction = true)
323 $class = get_class($this);
326 if ( $transaction === true ) {
327 $zdb->db
->beginTransaction();
329 $del = $zdb->db
->delete(
330 PREFIX_DB
. $this->tbl_prefix
. $class::TABLE
,
331 $zdb->db
->quoteInto($class::PK
. ' = ?', $this->db_id
)
334 $file_wo_ext = $this->store_path
. $this->id
;
336 // take back default picture
337 $this->getDefaultPicture();
343 if ( file_exists($file_wo_ext . '.jpg') ) {
344 //return unlink($file_wo_ext . '.jpg');
345 $_file = $file_wo_ext . '.jpg';
346 $success = unlink($_file);
347 } elseif ( file_exists($file_wo_ext . '.png') ) {
348 //return unlink($file_wo_ext . '.png');
349 $_file = $file_wo_ext . '.png';
350 $success = unlink($_file);
351 } elseif ( file_exists($file_wo_ext . '.gif') ) {
352 //return unlink($file_wo_ext . '.gif');
353 $_file = $file_wo_ext . '.gif';
354 $success = unlink($_file);
357 if ( $_file !== null && $success !== true ) {
358 //unable to remove file that exists!
359 if ( $transaction === true ) {
360 $zdb->db
->rollBack();
363 'The file ' . $_file . ' was found on the disk but cannot be removed.',
368 if ( $transaction === true ) {
374 if ( $transaction === true ) {
375 //properly ends transaction
376 $zdb->db
->rollBack();
382 } catch (\Exception
$e) {
383 if ( $transaction === true ) {
384 $zdb->db
->rollBack();
387 'An error occured attempting to delete picture ' . $this->db_id
.
388 'from database | ' . $e->getMessage(),
396 * Stores an image on the disk and in the database
398 * @param object $file the uploaded file
399 * @param boolean $ajax If the image cames from an ajax call (dnd)
401 * @return true|false result of the storage process
403 public function store($file, $ajax = false)
406 - fix max size (by preferences ?)
407 - make possible to store images in database, filesystem or both
411 $class = get_class($this);
413 $name = $file['name'];
414 $tmpfile = $file['tmp_name'];
416 //First, does the file have a valid name?
417 $reg = "/^(.[^" . implode('', $this->_bad_chars
) . "]+)\.(" .
418 implode('|', $this->_allowed_extensions
) . ")$/i";
419 if ( preg_match($reg, $name, $matches) ) {
421 '[' . $class . '] Filename and extension are OK, proceed.',
424 $extension = $matches[2];
425 if ( $extension == 'jpeg' ) {
426 //jpeg is an allowed extension,
427 //but we change it to jpg to reduce further tests :)
431 $erreg = "/^(.[^" . implode('', $this->_bad_chars
) . "]+)\.(.*)/i";
432 $m = preg_match($erreg, $name, $errmatches);
434 $err_msg = '[' . $class . '] ';
436 //ok, we got a good filename and an extension. Extension is bad :)
437 $err_msg .= 'Invalid extension for file ' . $name . '.';
438 $ret = self
::INVALID_EXTENSION
;
440 $err_msg = 'Invalid filename `' . $name . '` (Tip: ';
441 $err_msg .= preg_replace(
443 htmlentities($this->getbadChars()),
444 "file name should not contain any of: %s). "
446 $ret = self
::INVALID_FILE
;
456 //Second, let's check file size
457 if ( $file['size'] > ( $class::MAX_FILE_SIZE
* 1024 ) ) {
459 '[' . $class . '] File is too big (' . ( $file['size'] * 1024 ) .
460 'Ko for maximum authorized ' . ( $class::MAX_FILE_SIZE
* 1024 ) .
464 return self
::FILE_TOO_BIG
;
466 $log->log('[' . $class . '] Filesize is OK, proceed', KLogger
::DEBUG
);
469 $current = getimagesize($tmpfile);
471 if ( !in_array($current['mime'], $this->_allowed_mimes
) ) {
473 '[' . $class . '] Mimetype `' . $current['mime'] . '` not allowed',
476 return self
::MIME_NOT_ALLOWED
;
479 '[' . $class . '] Mimetype is allowed, proceed',
486 $new_file = $this->store_path
.
487 $this->id
. '.' . $extension;
488 if ( $ajax === true ) {
489 rename($tmpfile, $new_file);
491 move_uploaded_file($tmpfile, $new_file);
494 // current[0] gives width ; current[1] gives height
495 if ( $current[0] > $this->max_width ||
$current[1] > $this->max_height
) {
496 /** FIXME: what if image cannot be resized?
497 Should'nt we want to stop the process here? */
498 $this->_resizeImage($new_file, $extension);
501 //store file in database
502 $f = fopen($new_file, 'r');
504 while ( $r=fread($f, 8192) ) {
510 $stmt = $zdb->db
->prepare(
511 'INSERT INTO ' . PREFIX_DB
.
512 $this->tbl_prefix
. $class::TABLE
. ' (' . $class::PK
.
513 ', picture, format) VALUES (:id, :picture, :format)'
516 $stmt->bindParam('id', $this->db_id
);
517 $stmt->bindParam('picture', $picture, \PDO
::PARAM_LOB
);
518 $stmt->bindParam('format', $extension);
520 } catch (\Exception
$e) {
523 'An error occured storing picture in database: ' .
527 return self
::SQL_ERROR
;
534 * Resize the image if it exceed max allowed sizes
536 * @param string $source the source image
537 * @param string $ext file's extension
538 * @param string $dest the destination image.
539 * If null, we'll use the source image. Defaults to null
543 private function _resizeImage($source, $ext, $dest = null)
546 $class = get_class($this);
548 if (function_exists("gd_info")) {
550 $h = $this->max_height
;
551 $w = $this->max_width
;
552 if ( $dest == null ) {
556 switch(strtolower($ext)) {
558 if (!$gdinfo['JPEG Support']) {
560 '[' . $class . '] GD has no JPEG Support - ' .
561 'pictures could not be resized!',
568 if (!$gdinfo['PNG Support']) {
570 '[' . $class . '] GD has no PNG Support - ' .
571 'pictures could not be resized!',
578 if (!$gdinfo['GIF Create Support']) {
580 '[' . $class . '] GD has no GIF Support - ' .
581 'pictures could not be resized!',
591 list($cur_width, $cur_height, $cur_type, $curattr)
592 = getimagesize($source);
594 $ratio = $cur_width / $cur_height;
596 // calculate image size according to ratio
597 if ($cur_width>$cur_height) {
603 $thumb = imagecreatetruecolor($w, $h);
606 $image = ImageCreateFromJpeg($source);
608 $thumb, $image, 0, 0, 0, 0, $w, $h, $cur_width, $cur_height
610 imagejpeg($thumb, $dest);
613 $image = ImageCreateFromPng($source);
614 // Turn off alpha blending and set alpha flag. That prevent alpha
615 // transparency to be saved as an arbitrary color (black in my tests)
616 imagealphablending($thumb, false);
617 imagealphablending($image, false);
618 imagesavealpha($thumb, true);
619 imagesavealpha($image, true);
621 $thumb, $image, 0, 0, 0, 0, $w, $h, $cur_width, $cur_height
623 imagepng($thumb, $dest);
626 $image = ImageCreateFromGif($source);
628 $thumb, $image, 0, 0, 0, 0, $w, $h, $cur_width, $cur_height
630 imagegif($thumb, $dest);
635 '[' . $class . '] GD is not present - ' .
636 'pictures could not be resized!',
644 * Returns current file optimal height (resized)
646 * @return int optimal height
648 public function getOptimalHeight()
650 return round($this->optimal_height
);
654 * Returns current file height
656 * @return int current height
658 public function getHeight()
660 return $this->height
;
664 * Returns current file optimal width (resized)
666 * @return int optimal width
668 public function getOptimalWidth()
670 return $this->optimal_width
;
674 * Returns current file width
676 * @return int current width
678 public function getWidth()
684 * Returns current file format
688 public function getFormat()
690 return $this->format
;
694 * Have we got a picture ?
696 * @return bool True if a picture matches adherent's id, false otherwise
698 public function hasPicture()
700 return $this->has_picture
;
704 * Returns unauthorized characters litteral values quoted, comma separated values
706 * @return string comma separated disallowed characters
708 public function getBadChars()
711 foreach ( $this->_bad_chars
as $char=>$regchar ) {
712 $ret .= '`' . $char . '`, ';
718 * Returns allowed extensions
720 * @return string comma separated allowed extensiosn
722 public function getAllowedExts()
724 return implode(', ', $this->_allowed_extensions
);
728 * Return the array of allowed mime types
732 public function getAllowedMimeTypes()
734 return $this->_allowed_mimes
;
738 * Returns current file full path
740 * @return string full file path
742 public function getPath()
744 return $this->file_path
;
748 * Returns current mime type
752 public function getMime()
758 * Return textual error message
760 * @param int $code The error code
762 * @return string Localized message
764 public function getErrorMessage($code)
766 $error = _T("An error occued.");
768 case self
::INVALID_FILE
:
769 $error = _T("File name is invalid, it should not contain any special character or space.");
771 case self
::INVALID_EXTENSION
:
772 $error = preg_replace(
774 $this->getAllowedExts(),
775 _T("- File extension is not allowed, only %s files are.")
778 case self
::FILE_TOO_BIG
:
779 $error = preg_replace(
782 _T("File is too big. Maximum allowed size is %d")
785 case self
::MIME_NOT_ALLOWED
:
786 /** FIXME: should be more descriptive */
787 $error = _T("Mime-Type not allowed");
789 case self
::SQL_ERROR
:
790 case self
::SQL_BLOB_ERROR
:
791 $error = _T("An SQL error has occured.");
799 * Return textual erro rmessage send by PHP after upload attempt
801 * @param int $error_code The error code
803 * @return string Localized message
805 public function getPhpErrorMessage($error_code)
807 switch ($error_code) {
808 case UPLOAD_ERR_INI_SIZE
:
809 return _T("The uploaded file exceeds the upload_max_filesize directive in php.ini");
810 case UPLOAD_ERR_FORM_SIZE
:
811 return _T("The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form");
812 case UPLOAD_ERR_PARTIAL
:
813 return _T("The uploaded file was only partially uploaded");
814 case UPLOAD_ERR_NO_FILE
:
815 return _T("No file was uploaded");
816 case UPLOAD_ERR_NO_TMP_DIR
:
817 return _T("Missing a temporary folder");
818 case UPLOAD_ERR_CANT_WRITE
:
819 return _T("Failed to write file to disk");
820 case UPLOAD_ERR_EXTENSION
:
821 return _T("File upload stopped by extension");
823 return _T("Unknown upload error");