*
* PHP version 5
*
- * Copyright © 2006-2014 The Galette Team
+ * Copyright © 2006-2023 The Galette Team
*
* This file is part of Galette (http://galette.tuxfamily.org).
*
* @category Core
* @package Galette
*
- * @author Frédéric Jaqcuot <unknown@unknow.com>
+ * @author Frédéric Jacquot <unknown@unknow.com>
* @author Johan Cwiklinski <johan@x-tnd.be>
- * @copyright 2006-2014 The Galette Team
+ * @copyright 2006-2023 The Galette Team
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
- * @version SVN: $Id$
* @link http://galette.tuxfamily.org
*/
namespace Galette\Core;
-use Analog\Analog as Analog;
+use ArrayObject;
+use Slim\Psr7\Response;
+use Throwable;
+use Analog\Analog;
use Galette\Entity\Adherent;
+use Galette\Repository\Members;
+use Galette\IO\FileInterface;
+use Galette\IO\FileTrait;
/**
* Picture handling
* @name Picture
* @category Core
* @package Galette
- * @author Frédéric Jaqcuot <unknown@unknow.com>
+ * @author Frédéric Jacquot <unknown@unknow.com>
* @author Johan Cwiklinski <johan@x-tnd.be>
- * @copyright 2006-2014 The Galette Team
+ * @copyright 2006-2023 The Galette Team
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
* @link http://galette.tuxfamily.org
*/
-class Picture
+class Picture implements FileInterface
{
+ use FileTrait;
+
//constants that will not be overrided
- const INVALID_FILE = -1;
- const INVALID_EXTENSION = -2;
- const FILE_TOO_BIG = -3;
- const MIME_NOT_ALLOWED = -4;
- const SQL_ERROR = -5;
- const SQL_BLOB_ERROR = -6;
+ public const SQL_ERROR = -10;
+ public const SQL_BLOB_ERROR = -11;
//constants that can be overrided
//(do not use self::CONSTANT, but get_class[$this]::CONSTANT)
- const MAX_FILE_SIZE = 1024;
- const TABLE = 'pictures';
- const PK = Adherent::PK;
-
- /*private $_bad_chars = array(
- '\.', '\\\\', "'", ' ', '\/', ':', '\*', '\?', '"', '<', '>', '|'
- );*/
- //array keys contain litteral value of each forbidden character
- //(to be used when showing an error).
- //Maybe is there a better way to handle this...
- private $_bad_chars = array(
- '.' => '\.',
- '\\' => '\\\\',
- "'" => "'",
- ' ' => ' ',
- '/' => '\/',
- ':' => ':',
- '*' => '\*',
- '?' => '\?',
- '"' => '"',
- '<' => '<',
- '>' => '>',
- '|' => '|'
- );
- private $_allowed_extensions = array('jpeg', 'jpg', 'png', 'gif');
- private $_allowed_mimes = array(
- 'jpg' => 'image/jpeg',
- 'png' => 'image/png',
- 'gif' => 'image/gif'
- );
+ public const TABLE = 'pictures';
+ public const PK = Adherent::PK;
protected $tbl_prefix = '';
protected $id;
+ protected $db_id;
protected $height;
protected $width;
protected $optimal_height;
protected $file_path;
protected $format;
protected $mime;
- protected $has_picture = true;
+ protected $has_picture = false;
protected $store_path = GALETTE_PHOTOS_PATH;
protected $max_width = 200;
protected $max_height = 200;
+ private $insert_stmt;
/**
* Default constructor.
*
- * @param int $id_adh the id of the member
+ * @param mixed|null $id_adh the id of the member
*/
- public function __construct( $id_adh='' )
+ public function __construct($id_adh = null)
{
+
+ $this->init(
+ null,
+ array('jpeg', 'jpg', 'png', 'gif', 'webp'),
+ array(
+ 'jpg' => 'image/jpeg',
+ 'png' => 'image/png',
+ 'gif' => 'image/gif',
+ 'webp' => 'image/webp'
+ )
+ );
+
// '!==' needed, otherwise ''==0
- if ( $id_adh !== '' ) {
+ if ($id_adh !== '' && $id_adh !== null) {
$this->id = $id_adh;
- if ( !isset ($this->db_id) ) {
+ if (!isset($this->db_id)) {
$this->db_id = $id_adh;
}
- //if file does not exists on the FileSystem, check for it in the database
- if ( !$this->_checkFileOnFS() ) {
- $this->_checkFileInDB();
+ //if file does not exist on the FileSystem, check for it in the database
+ if (!$this->checkFileOnFS()) {
+ if ($this->checkFileInDB()) {
+ $this->has_picture = true;
+ }
+ } else {
+ $this->has_picture = true;
}
}
// if we still have no picture, take the default one
- if ( $this->file_path=='' ) {
+ if (empty($this->file_path)) {
$this->getDefaultPicture();
}
//we should not have an empty file_path, but...
- if ( $this->file_path !== '' ) {
- $this->_setSizes();
+ if (!empty($this->file_path)) {
+ $this->setSizes();
}
}
{
//if file has been deleted since we store our object in the session,
//we try to retrieve it
- if ( !$this->_checkFileOnFS() ) {
- //if file does not exists on the FileSystem,
+ if (!$this->checkFileOnFS()) {
+ //if file does not exist on the FileSystem,
//check for it in the database
- //$this->_checkFileInDB();
+ //$this->checkFileInDB();
+ } else {
+ $this->has_picture = false;
}
// if we still have no picture, take the default one
- if ( $this->file_path=='' ) {
+ if (empty($this->file_path)) {
$this->getDefaultPicture();
}
//we should not have an empty file_path, but...
- if ( $this->file_path !== '' ) {
- $this->_setSizes();
+ if (!empty($this->file_path)) {
+ $this->setSizes();
}
}
*
* @return boolean true if file is present on FS, false otherwise
*/
- private function _checkFileOnFS()
+ private function checkFileOnFS()
{
$file_wo_ext = $this->store_path . $this->id;
- if ( file_exists($file_wo_ext . '.jpg') ) {
- $this->file_path = $file_wo_ext . '.jpg';
+ if (file_exists($file_wo_ext . '.jpg')) {
+ $this->file_path = realpath($file_wo_ext . '.jpg');
$this->format = 'jpg';
$this->mime = 'image/jpeg';
return true;
- } elseif ( file_exists($file_wo_ext . '.png') ) {
- $this->file_path = $file_wo_ext . '.png';
+ } elseif (file_exists($file_wo_ext . '.png')) {
+ $this->file_path = realpath($file_wo_ext . '.png');
$this->format = 'png';
$this->mime = 'image/png';
return true;
- } elseif ( file_exists($file_wo_ext . '.gif') ) {
- $this->file_path = $file_wo_ext . '.gif';
+ } elseif (file_exists($file_wo_ext . '.gif')) {
+ $this->file_path = realpath($file_wo_ext . '.gif');
$this->format = 'gif';
$this->mime = 'image/gif';
return true;
+ } elseif (file_exists($file_wo_ext . '.webp')) {
+ $this->file_path = realpath($file_wo_ext . '.webp');
+ $this->format = 'webp';
+ $this->mime = 'image/webp';
+ return true;
}
return false;
}
*
* @return boolean true if file is present in the DB, false otherwise
*/
- private function _checkFileInDB()
+ private function checkFileInDB()
{
global $zdb;
$select = $this->getCheckFileQuery();
$results = $zdb->execute($select);
$pic = $results->current();
- //what's $pic if no result?
- if ( $pic !== false ) {
+
+ if ($pic) {
// we must regenerate the picture file
$file_wo_ext = $this->store_path . $this->id;
file_put_contents(
);
$this->format = $pic->format;
- switch($this->format) {
- case 'jpg':
- $this->mime = 'image/jpeg';
- break;
- case 'png':
- $this->mime = 'image/png';
- break;
- case 'gif':
- $this->mime = 'image/gif';
- break;
+ switch ($this->format) {
+ case 'jpg':
+ $this->mime = 'image/jpeg';
+ break;
+ case 'png':
+ $this->mime = 'image/png';
+ break;
+ case 'gif':
+ $this->mime = 'image/gif';
+ break;
+ case 'webp':
+ $this->mime = 'image/webp';
+ break;
}
- $this->file_path = $file_wo_ext . '.' . $this->format;
+ $this->file_path = realpath($file_wo_ext . '.' . $this->format);
return true;
}
- } catch (\Exception $e) {
+ } catch (Throwable $e) {
return false;
}
+ return false;
}
/**
*/
protected function getDefaultPicture()
{
- $this->file_path = _CURRENT_TEMPLATE_PATH . 'images/default.png';
+ $this->file_path = realpath(_CURRENT_THEME_PATH . 'images/default.png');
$this->format = 'png';
$this->mime = 'image/png';
$this->has_picture = false;
*
* @return void
*/
- private function _setSizes()
+ private function setSizes()
{
list($width, $height) = getimagesize($this->file_path);
$this->height = $height;
}
}
+ /**
+ * Get image file content
+ *
+ * @return mixed
+ */
+ public function getContents()
+ {
+ readfile($this->file_path);
+ }
+
/**
* Set header and displays the picture.
*
+ * @param Response $response Response
+ *
* @return object the binary file
*/
- public function display()
+ public function display(Response $response)
{
- header('Content-type: '.$this->mime);
- header('Content-Length: ' . filesize($this->file_path));
- ob_clean();
- flush();
- readfile($this->file_path);
+ $response = $response->withHeader('Content-Type', $this->mime)
+ ->withHeader('Content-Transfer-Encoding', 'binary')
+ ->withHeader('Expires', '0')
+ ->withHeader('Cache-Control', 'must-revalidate')
+ ->withHeader('Pragma', 'public');
+
+ $stream = fopen('php://memory', 'r+');
+ fwrite($stream, file_get_contents($this->file_path));
+ rewind($stream);
+
+ return $response->withBody(new \Slim\Psr7\Stream($stream));
}
/**
$class = get_class($this);
try {
- if ( $transaction === true ) {
+ if ($transaction === true) {
$zdb->connection->beginTransaction();
}
$delete = $zdb->delete($this->tbl_prefix . $class::TABLE);
- $delete->where(
- $class::PK . ' = ' . $this->db_id
- );
+ $delete->where([$class::PK => $this->db_id]);
$del = $zdb->execute($delete);
- if ( !$del->count() > 0 ) {
+ if (!$del->count() > 0) {
Analog::log(
'Unable to remove picture database entry for ' . $this->db_id,
Analog::ERROR
// take back default picture
$this->getDefaultPicture();
// fix sizes
- $this->_setSizes();
+ $this->setSizes();
$success = false;
$_file = null;
- if ( file_exists($file_wo_ext . '.jpg') ) {
+ if (file_exists($file_wo_ext . '.jpg')) {
//return unlink($file_wo_ext . '.jpg');
$_file = $file_wo_ext . '.jpg';
$success = unlink($_file);
- } elseif ( file_exists($file_wo_ext . '.png') ) {
+ } elseif (file_exists($file_wo_ext . '.png')) {
//return unlink($file_wo_ext . '.png');
$_file = $file_wo_ext . '.png';
$success = unlink($_file);
- } elseif ( file_exists($file_wo_ext . '.gif') ) {
+ } elseif (file_exists($file_wo_ext . '.gif')) {
//return unlink($file_wo_ext . '.gif');
$_file = $file_wo_ext . '.gif';
$success = unlink($_file);
+ } elseif (file_exists($file_wo_ext . '.webp')) {
+ //return unlink($file_wo_ext . '.webp');
+ $_file = $file_wo_ext . '.webp';
+ $success = unlink($_file);
}
- if ( $_file !== null && $success !== true ) {
+ if ($_file !== null && $success !== true) {
//unable to remove file that exists!
- if ( $transaction === true ) {
+ if ($transaction === true) {
$zdb->connection->rollBack();
}
Analog::log(
);
return false;
} else {
- if ( $transaction === true ) {
+ if ($transaction === true) {
$zdb->connection->commit();
}
+ $this->has_picture = false;
return true;
}
- } catch (\Exception $e) {
- if ( $transaction === true ) {
+ } catch (Throwable $e) {
+ if ($transaction === true) {
$zdb->connection->rollBack();
}
Analog::log(
- 'An error occured attempting to delete picture ' . $this->db_id .
+ 'An error occurred attempting to delete picture ' . $this->db_id .
'from database | ' . $e->getMessage(),
Analog::ERROR
);
/**
* Stores an image on the disk and in the database
*
- * @param object $file the uploaded file
- * @param boolean $ajax If the image cames from an ajax call (dnd)
+ * @param object $file The uploaded file
+ * @param boolean $ajax If the image cames from an ajax call (dnd)
+ * @param array $cropping Cropping properties
*
- * @return true|false result of the storage process
+ * @return bool|int
*/
- public function store($file, $ajax = false)
+ public function store($file, $ajax = false, $cropping = null)
{
- /** TODO:
- - fix max size (by preferences ?)
- - make possible to store images in database, filesystem or both
- */
+ /** TODO: fix max size (by preferences ?) */
global $zdb;
$class = get_class($this);
$tmpfile = $file['tmp_name'];
//First, does the file have a valid name?
- $reg = "/^(.[^" . implode('', $this->_bad_chars) . "]+)\.(" .
- implode('|', $this->_allowed_extensions) . ")$/i";
- if ( preg_match($reg, $name, $matches) ) {
+ $reg = "/^([^" . implode('', $this->bad_chars) . "]+)\.(" .
+ implode('|', $this->allowed_extensions) . ")$/i";
+ if (preg_match($reg, $name, $matches)) {
Analog::log(
'[' . $class . '] Filename and extension are OK, proceed.',
Analog::DEBUG
);
$extension = strtolower($matches[2]);
- if ( $extension == 'jpeg' ) {
+ if ($extension == 'jpeg') {
//jpeg is an allowed extension,
//but we change it to jpg to reduce further tests :)
$extension = 'jpg';
}
} else {
- $erreg = "/^(.[^" . implode('', $this->_bad_chars) . "]+)\.(.*)/i";
+ $erreg = "/^([^" . implode('', $this->bad_chars) . "]+)\.(.*)/i";
$m = preg_match($erreg, $name, $errmatches);
$err_msg = '[' . $class . '] ';
- if ( $m == 1 ) {
+ if ($m == 1) {
//ok, we got a good filename and an extension. Extension is bad :)
$err_msg .= 'Invalid extension for file ' . $name . '.';
$ret = self::INVALID_EXTENSION;
} else {
- $err_msg = 'Invalid filename `' . $name . '` (Tip: ';
+ $err_msg = 'Invalid filename `' . $name . '` (Tip: ';
$err_msg .= preg_replace(
'|%s|',
- htmlentities($this->getbadChars()),
+ htmlentities($this->getBadChars()),
"file name should not contain any of: %s). "
);
- $ret = self::INVALID_FILE;
+ $ret = self::INVALID_FILENAME;
}
Analog::log(
}
//Second, let's check file size
- if ( $file['size'] > ( $class::MAX_FILE_SIZE * 1024 ) ) {
+ if ($file['size'] > ($this->maxlenght * 1024)) {
Analog::log(
- '[' . $class . '] File is too big (' . ( $file['size'] * 1024 ) .
- 'Ko for maximum authorized ' . ( $class::MAX_FILE_SIZE * 1024 ) .
+ '[' . $class . '] File is too big (' . ($file['size'] * 1024) .
+ 'Ko for maximum authorized ' . ($this->maxlenght * 1024) .
'Ko',
Analog::ERROR
);
$current = getimagesize($tmpfile);
- if ( !in_array($current['mime'], $this->_allowed_mimes) ) {
+ if (!in_array($current['mime'], $this->allowed_mimes)) {
Analog::log(
'[' . $class . '] Mimetype `' . $current['mime'] . '` not allowed',
Analog::ERROR
);
}
+ // Source image must have minimum dimensions to match the cropping process requirements
+ // and ensure the final picture will fit the maximum allowed resizing dimensions.
+ if (isset($cropping['ratio']) && isset($cropping['focus'])) {
+ if ($current[0] < $this->mincropsize || $current[1] < $this->mincropsize) {
+ $min_current = min($current[0], $current[1]);
+ Analog::log(
+ '[' . $class . '] Image is too small. The minimum image side size allowed is ' .
+ $this->mincropsize . 'px, but current is ' . $min_current . 'px.',
+ Analog::ERROR
+ );
+ return self::IMAGE_TOO_SMALL;
+ } else {
+ Analog::log('[' . $class . '] Image dimensions are OK, proceed', Analog::DEBUG);
+ }
+ }
+
$this->delete();
$new_file = $this->store_path .
$this->id . '.' . $extension;
- if ( $ajax === true ) {
+ if ($ajax === true) {
rename($tmpfile, $new_file);
} else {
move_uploaded_file($tmpfile, $new_file);
}
// current[0] gives width ; current[1] gives height
- if ( $current[0] > $this->max_width || $current[1] > $this->max_height ) {
+ if ($current[0] > $this->max_width || $current[1] > $this->max_height) {
/** FIXME: what if image cannot be resized?
Should'nt we want to stop the process here? */
- $this->_resizeImage($new_file, $extension);
+ $this->resizeImage($new_file, $extension, null, $cropping);
}
- //store file in database
- $f = fopen($new_file, 'r');
+ return $this->storeInDb($zdb, $this->db_id, $new_file, $extension);
+ }
+
+ /**
+ * Stores an image in the database
+ *
+ * @param Db $zdb Database instance
+ * @param int $id Member ID
+ * @param string $file File path on disk
+ * @param string $ext File extension
+ *
+ * @return bool|int
+ */
+ private function storeInDb(Db $zdb, $id, $file, $ext)
+ {
+ $f = fopen($file, 'r');
$picture = '';
- while ( $r=fread($f, 8192) ) {
+ while ($r = fread($f, 8192)) {
$picture .= $r;
}
fclose($f);
+ $class = get_class($this);
+
try {
- $insert = $zdb->insert($this->tbl_prefix . $class::TABLE);
- $insert->values(
- array(
- $class::PK => ':id',
- 'picture' => ':picture',
- 'format' => ':format'
- )
- );
- $stmt = $zdb->sql->prepareStatementForSqlObject($insert);
- $container = $stmt->getParameterContainer();
- $container->offsetSet(
- $class::PK,
- ':id'
- );
- $container->offsetSet(
- 'picture',
- ':picture',
- $container::TYPE_LOB
- );
- $container->offsetSet(
- 'format',
- ':format'
- );
- $stmt->setParameterContainer($container);
+ $zdb->connection->beginTransaction();
+ $stmt = $this->insert_stmt;
+ if ($stmt == null) {
+ $insert = $zdb->insert($this->tbl_prefix . $class::TABLE);
+ $insert->values(
+ array(
+ $class::PK => ':' . $class::PK,
+ 'picture' => ':picture',
+ 'format' => ':format'
+ )
+ );
+ $stmt = $zdb->sql->prepareStatementForSqlObject($insert);
+ $container = $stmt->getParameterContainer();
+ $container->offsetSet(
+ 'picture', //'picture',
+ ':picture',
+ $container::TYPE_LOB
+ );
+ $stmt->setParameterContainer($container);
+ $this->insert_stmt = $stmt;
+ }
$stmt->execute(
array(
- $class::PK => $this->db_id,
+ $class::PK => $id,
'picture' => $picture,
- 'format' => $extension
+ 'format' => $ext
)
);
- } catch (\Exception $e) {
+ $zdb->connection->commit();
+ $this->has_picture = true;
+ } catch (Throwable $e) {
+ $zdb->connection->rollBack();
Analog::log(
- 'An error occured storing picture in database: ' .
+ 'An error occurred storing picture in database: ' .
$e->getMessage(),
Analog::ERROR
);
}
/**
- * Resize the image if it exceed max allowed sizes
+ * Check for missing images in database
*
- * @param string $source the source image
- * @param string $ext file's extension
- * @param string $dest the destination image.
- * If null, we'll use the source image. Defaults to null
+ * @param Db $zdb Database instance
*
* @return void
*/
- private function _resizeImage($source, $ext, $dest = null)
+ public function missingInDb(Db $zdb)
{
- $class = get_class($this);
+ $existing_disk = array();
+
+ //retrieve files on disk
+ if ($handle = opendir($this->store_path)) {
+ while (false !== ($entry = readdir($handle))) {
+ $reg = "/^(\d+)\.(" .
+ implode('|', $this->allowed_extensions) . ")$/i";
+ if (preg_match($reg, $entry, $matches)) {
+ $id = $matches[1];
+ $extension = strtolower($matches[2]);
+ if ($extension == 'jpeg') {
+ //jpeg is an allowed extension,
+ //but we change it to jpg to reduce further tests :)
+ $extension = 'jpg';
+ }
+ $existing_disk[$id] = array(
+ 'name' => $entry,
+ 'id' => $id,
+ 'ext' => $extension
+ );
+ }
+ }
+ closedir($handle);
+
+ if (count($existing_disk) === 0) {
+ //no image on disk, nothing to do :)
+ return;
+ }
+
+ //retrieve files in database
+ $class = get_class($this);
+ $select = $zdb->select($this->tbl_prefix . $class::TABLE);
+ $select
+ ->columns(array($class::PK))
+ ->where->in($class::PK, array_keys($existing_disk));
+
+ $results = $zdb->execute($select);
+
+ $existing_db = array();
+ foreach ($results as $result) {
+ $existing_db[] = (int)$result[self::PK];
+ }
+
+ $existing_diff = array_diff(array_keys($existing_disk), $existing_db);
+
+ //retrieve valid members ids
+ $members = new Members();
+ $valids = $members->getArrayList(
+ $existing_diff,
+ null,
+ false,
+ false,
+ array(self::PK)
+ );
- if (function_exists("gd_info")) {
- $gdinfo = gd_info();
- $h = $this->max_height;
- $w = $this->max_width;
- if ( $dest == null ) {
- $dest = $source;
+ foreach ($valids as $valid) {
+ /** @var ArrayObject $valid */
+ $file = $existing_disk[$valid->id_adh];
+ $this->storeInDb(
+ $zdb,
+ $file['id'],
+ $this->store_path . $file['id'] . '.' . $file['ext'],
+ $file['ext']
+ );
}
+ } else {
+ Analog::log(
+ 'Something went wrong opening images directory ' .
+ $this->store_path,
+ Analog::ERROR
+ );
+ }
+ }
- switch(strtolower($ext)) {
+ /**
+ * Resize and eventually crop the image if it exceeds max allowed sizes
+ *
+ * @param string $source The source image
+ * @param string $ext File's extension
+ * @param string $dest The destination image.
+ * If null, we'll use the source image. Defaults to null
+ * @param array $cropping Cropping properties
+ *
+ * @return boolean
+ */
+ private function resizeImage($source, $ext, $dest = null, $cropping = null)
+ {
+ $class = get_class($this);
+
+ if (!function_exists("gd_info")) {
+ Analog::log(
+ '[' . $class . '] GD is not present - ' .
+ 'pictures could not be resized!',
+ Analog::ERROR
+ );
+ return false;
+ }
+
+ $gdinfo = gd_info();
+ $h = $this->max_height;
+ $w = $this->max_width;
+ if ($dest == null) {
+ $dest = $source;
+ }
+
+ switch (strtolower($ext)) {
case 'jpg':
if (!$gdinfo['JPEG Support']) {
Analog::log(
return false;
}
break;
+ case 'webp':
+ if (!$gdinfo['WebP Support']) {
+ Analog::log(
+ '[' . $class . '] GD has no WebP Support - ' .
+ 'pictures could not be resized!',
+ Analog::ERROR
+ );
+ return false;
+ }
+ break;
+
default:
return false;
- }
-
- list($cur_width, $cur_height, $cur_type, $curattr)
- = getimagesize($source);
-
- $ratio = $cur_width / $cur_height;
+ }
- // calculate image size according to ratio
- if ($cur_width>$cur_height) {
- $h = $w/$ratio;
+ list($cur_width, $cur_height, $cur_type, $curattr)
+ = getimagesize($source);
+
+ $ratio = $cur_width / $cur_height;
+
+ // Define cropping variables if necessary.
+ $thumb_cropped = false;
+ // Cropping is based on the smallest side of the source in order to
+ // provide as less focusing options as possible if the source doesn't
+ // fit the final ratio (center, top, bottom, left, right).
+ $min_size = min($cur_width, $cur_height);
+ // Cropping dimensions.
+ $crop_width = $min_size;
+ $crop_height = $min_size;
+ // Cropping focus.
+ $crop_x = 0;
+ $crop_y = 0;
+ if (isset($cropping['ratio']) && isset($cropping['focus'])) {
+ // Calculate cropping dimensions
+ switch ($cropping['ratio']) {
+ case 'portrait_ratio':
+ // Calculate cropping dimensions
+ if ($ratio < 1) {
+ $crop_height = ceil($crop_width * 4 / 3);
+ } else {
+ $crop_width = ceil($crop_height * 3 / 4);
+ }
+ // Calculate resizing dimensions
+ $w = ceil($h * 3 / 4);
+ break;
+ case 'landscape_ratio':
+ // Calculate cropping dimensions
+ if ($ratio > 1) {
+ $crop_width = ceil($crop_height * 4 / 3);
+ } else {
+ $crop_height = ceil($crop_width * 3 / 4);
+ }
+ // Calculate resizing dimensions
+ $h = ceil($w * 3 / 4);
+ break;
+ }
+ // Calculate focus coordinates
+ switch ($cropping['focus']) {
+ case 'center':
+ if ($ratio > 1) {
+ $crop_x = ceil(($cur_width - $crop_width) / 2);
+ } elseif ($ratio == 1) {
+ $crop_x = ceil(($cur_width - $crop_width) / 2);
+ $crop_y = ceil(($cur_height - $crop_height) / 2);
+ } else {
+ $crop_y = ceil(($cur_height - $crop_height) / 2);
+ }
+ break;
+ case 'top':
+ $crop_x = ceil(($cur_width - $crop_width) / 2);
+ break;
+ case 'bottom':
+ $crop_y = $cur_height - $crop_height;
+ break;
+ case 'right':
+ $crop_x = $cur_width - $crop_width;
+ break;
+ }
+ // Cropped image.
+ $thumb_cropped = imagecreatetruecolor($crop_width, $crop_height);
+ // Cropped ratio.
+ $ratio = $crop_width / $crop_height;
+ // Otherwise, calculate image size according to the source's ratio.
+ } else {
+ if ($cur_width > $cur_height) {
+ $h = round($w / $ratio);
} else {
- $w = $h*$ratio;
+ $w = round($h * $ratio);
}
+ }
+
+ // Resized image.
+ $thumb = imagecreatetruecolor($w, $h);
- $thumb = imagecreatetruecolor($w, $h);
- switch($ext) {
+ $image = false;
+ switch ($ext) {
case 'jpg':
- $image = ImageCreateFromJpeg($source);
- imagecopyresampled(
- $thumb, $image, 0, 0, 0, 0, $w, $h, $cur_width, $cur_height
- );
+ $image = imagecreatefromjpeg($source);
+ // Crop
+ if ($thumb_cropped !== false) {
+ // First, crop.
+ imagecopyresampled($thumb_cropped, $image, 0, 0, $crop_x, $crop_y, $cur_width, $cur_height, $cur_width, $cur_height);
+ // Then, resize.
+ imagecopyresampled($thumb, $thumb_cropped, 0, 0, 0, 0, $w, $h, $crop_width, $crop_height);
+ // Resize
+ } else {
+ imagecopyresampled($thumb, $image, 0, 0, 0, 0, $w, $h, $cur_width, $cur_height);
+ }
imagejpeg($thumb, $dest);
break;
case 'png':
- $image = ImageCreateFromPng($source);
+ $image = imagecreatefrompng($source);
// Turn off alpha blending and set alpha flag. That prevent alpha
// transparency to be saved as an arbitrary color (black in my tests)
- imagealphablending($thumb, false);
imagealphablending($image, false);
- imagesavealpha($thumb, true);
imagesavealpha($image, true);
- imagecopyresampled(
- $thumb, $image, 0, 0, 0, 0, $w, $h, $cur_width, $cur_height
- );
+ imagealphablending($thumb, false);
+ imagesavealpha($thumb, true);
+ // Crop
+ if ($thumb_cropped !== false) {
+ imagealphablending($thumb_cropped, false);
+ imagesavealpha($thumb_cropped, true);
+ // First, crop.
+ imagecopyresampled($thumb_cropped, $image, 0, 0, $crop_x, $crop_y, $cur_width, $cur_height, $cur_width, $cur_height);
+ // Then, resize.
+ imagecopyresampled($thumb, $thumb_cropped, 0, 0, 0, 0, $w, $h, $crop_width, $crop_height);
+ // Resize
+ } else {
+ imagecopyresampled($thumb, $image, 0, 0, 0, 0, $w, $h, $cur_width, $cur_height);
+ }
imagepng($thumb, $dest);
break;
case 'gif':
- $image = ImageCreateFromGif($source);
- imagecopyresampled(
- $thumb, $image, 0, 0, 0, 0, $w, $h, $cur_width, $cur_height
- );
+ $image = imagecreatefromgif($source);
+ // Crop
+ if ($thumb_cropped !== false) {
+ // First, crop.
+ imagecopyresampled($thumb_cropped, $image, 0, 0, $crop_x, $crop_y, $cur_width, $cur_height, $cur_width, $cur_height);
+ // Then, resize.
+ imagecopyresampled($thumb, $thumb_cropped, 0, 0, 0, 0, $w, $h, $crop_width, $crop_height);
+ // Resize
+ } else {
+ imagecopyresampled($thumb, $image, 0, 0, 0, 0, $w, $h, $cur_width, $cur_height);
+ }
imagegif($thumb, $dest);
break;
- }
- } else {
- Analog::log(
- '[' . $class . '] GD is not present - ' .
- 'pictures could not be resized!',
- Analog::ERROR
- );
+ case 'webp':
+ $image = imagecreatefromwebp($source);
+ // Crop
+ if ($thumb_cropped !== false) {
+ // First, crop.
+ imagecopyresampled($thumb_cropped, $image, 0, 0, $crop_x, $crop_y, $cur_width, $cur_height, $cur_width, $cur_height);
+ // Then, resize.
+ imagecopyresampled($thumb, $thumb_cropped, 0, 0, 0, 0, $w, $h, $crop_width, $crop_height);
+ // Resize
+ } else {
+ imagecopyresampled($thumb, $image, 0, 0, 0, 0, $w, $h, $cur_width, $cur_height);
+ }
+ imagewebp($thumb, $dest);
+ break;
}
+
+ return true;
}
/**
*/
public function getOptimalHeight()
{
- return round($this->optimal_height);
+ return (int)round($this->optimal_height, 1);
}
/**
*/
public function getOptimalWidth()
{
- return $this->optimal_width;
+ return (int)round($this->optimal_width, 1);
}
/**
return $this->has_picture;
}
- /**
- * Returns unauthorized characters litteral values quoted, comma separated values
- *
- * @return string comma separated disallowed characters
- */
- public function getBadChars()
- {
- $ret = '';
- foreach ( $this->_bad_chars as $char=>$regchar ) {
- $ret .= '`' . $char . '`, ';
- }
- return $ret;
- }
-
- /**
- * Returns allowed extensions
- *
- * @return string comma separated allowed extensiosn
- */
- public function getAllowedExts()
- {
- return implode(', ', $this->_allowed_extensions);
- }
-
- /**
- * Return the array of allowed mime types
- *
- * @return array
- */
- public function getAllowedMimeTypes()
- {
- return $this->_allowed_mimes;
- }
-
/**
* Returns current file full path
*
*/
public function getErrorMessage($code)
{
- $error = _T("An error occued.");
- switch( $code ) {
- case self::INVALID_FILE:
- $error = _T("File name is invalid, it should not contain any special character or space.");
- break;
- case self::INVALID_EXTENSION:
- $error = preg_replace(
- '|%s|',
- $this->getAllowedExts(),
- _T("- File extension is not allowed, only %s files are.")
- );
- break;
- case self::FILE_TOO_BIG:
- $error = preg_replace(
- '|%d|',
- self::MAX_FILE_SIZE,
- _T("File is too big. Maximum allowed size is %dKo")
- );
- break;
- case self::MIME_NOT_ALLOWED:
- /** FIXME: should be more descriptive */
- $error = _T("Mime-Type not allowed");
- break;
- case self::SQL_ERROR:
- case self::SQL_BLOB_ERROR:
- $error = _T("An SQL error has occured.");
- break;
-
+ $error = null;
+ switch ($code) {
+ case self::SQL_ERROR:
+ case self::SQL_BLOB_ERROR:
+ $error = _T("An SQL error has occurred.");
+ break;
}
- return $error;
- }
- /**
- * Return textual error message send by PHP after upload attempt
- *
- * @param int $error_code The error code
- *
- * @return string Localized message
- */
- public function getPhpErrorMessage($error_code)
- {
- switch ($error_code) {
- case UPLOAD_ERR_INI_SIZE:
- return _T("The uploaded file exceeds the upload_max_filesize directive in php.ini");
- case UPLOAD_ERR_FORM_SIZE:
- return _T("The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form");
- case UPLOAD_ERR_PARTIAL:
- return _T("The uploaded file was only partially uploaded");
- case UPLOAD_ERR_NO_FILE:
- return _T("No file was uploaded");
- case UPLOAD_ERR_NO_TMP_DIR:
- return _T("Missing a temporary folder");
- case UPLOAD_ERR_CANT_WRITE:
- return _T("Failed to write file to disk");
- case UPLOAD_ERR_EXTENSION:
- return _T("File upload stopped by extension");
- default:
- return _T("Unknown upload error");
+ if ($error === null) {
+ $error = $this->getErrorMessageFromCode($code);
}
+
+ return $error;
}
}