From ec8469333ec0c6359ea7c720fbd6cbd79d17a3da Mon Sep 17 00:00:00 2001 From: Rodolfo Berrios Date: Mon, 30 Sep 2019 15:26:56 -0300 Subject: [PATCH] tss --- app/lib/classes/class.upload.php | 737 +++++++++++---------- lib/G/classes/class.db.php | 1036 +++++++++++++++--------------- 2 files changed, 911 insertions(+), 862 deletions(-) diff --git a/app/lib/classes/class.upload.php b/app/lib/classes/class.upload.php index 07c53fc..e8c0ef7 100644 --- a/app/lib/classes/class.upload.php +++ b/app/lib/classes/class.upload.php @@ -6,7 +6,7 @@ http://chevereto.com/ @author Rodolfo Berrios A. - + Copyright (C) Rodolfo Berrios A. All rights reserved. @@ -16,449 +16,476 @@ --------------------------------------------------------------------- */ namespace CHV; -use G, Exception; -class Upload { - // filename => name.ext - // file => /full/path/to/name.ext - // name => name +use G; +use Exception; - public $source; - public $uploaded; +class Upload +{ + // filename => name.ext + // file => /full/path/to/name.ext + // name => name - // Sets the type of resource being uploaded - public function setType($type) { - $this->type = $type; - } + public $source; + public $uploaded; - // Set source - public function setSource($source) { - $this->source = $source; - $this->type = G\is_url($this->source) ? 'url' : 'file'; - } + // Sets the type of resource being uploaded + public function setType($type) + { + $this->type = $type; + } - // Set destination - public function setDestination($destination) { - $this->destination = G\forward_slash($destination); - } + // Set source + public function setSource($source) + { + $this->source = $source; + $this->type = G\is_url($this->source) ? 'url' : 'file'; + } - // Set storage - public function setStorageId($storage_id) { - $this->storage_id = is_numeric($storage_id) ? $storage_id : NULL; - } + // Set destination + public function setDestination($destination) + { + $this->destination = G\forward_slash($destination); + } - // Set file basename - public function setFilename($name) { - $this->name = $name; - } + // Set storage + public function setStorageId($storage_id) + { + $this->storage_id = is_numeric($storage_id) ? $storage_id : null; + } - // Set options - public function setOptions($options) { - $this->options = $options; - } + // Set file basename + public function setFilename($name) + { + $this->name = $name; + } - // Set individual option - public function setOption($key, $value) { - $this->options[$key] = $value; - } + // Set options + public function setOptions($options) + { + $this->options = $options; + } - // Default options - public static function getDefaultOptions() { - return array( - 'max_size' => G\get_bytes('2 MB'), // it should be 'max_filesize' - 'filenaming' => 'original', - 'exif' => TRUE, - 'allowed_formats' => self::getAvailableImageFormats(), // array - ); - } + // Set individual option + public function setOption($key, $value) + { + $this->options[$key] = $value; + } - /** - * Do the thing - * @Exeption 4xx - */ - public function exec() { + // Default options + public static function getDefaultOptions() + { + return array( + 'max_size' => G\get_bytes('2 MB'), // it should be 'max_filesize' + 'filenaming' => 'original', + 'exif' => true, + 'allowed_formats' => self::getAvailableImageFormats(), // array + ); + } - // Merge options - $this->options = array_merge(self::getDefaultOptions(), (array) $this->options); + /** + * Do the thing + * @Exeption 4xx + */ + public function exec() + { - $this->validateInput(); // Exception 1 + // Merge options + $this->options = array_merge(self::getDefaultOptions(), (array) $this->options); - $this->fetchSource(); // Exception 2 + $this->validateInput(); // Exception 1 - $this->validateSourceFile(); // Exception 3 + $this->fetchSource(); // Exception 2 - if(!is_array($this->options['allowed_formats'])) { - $this->options['allowed_formats'] = explode(',', $this->options['allowed_formats']); - } + $this->validateSourceFile(); // Exception 3 - // Save the source name - $this->source_name = G\get_filename_without_extension($this->type == "url" ? $this->source : $this->source["name"]); + if (!is_array($this->options['allowed_formats'])) { + $this->options['allowed_formats'] = explode(',', $this->options['allowed_formats']); + } - // Set file extension - $this->extension = $this->source_image_fileinfo["extension"]; + // Save the source name + $this->source_name = G\get_filename_without_extension($this->type == "url" ? $this->getNameFromURL($this->source) : $this->source["name"]); - // Workaround the $name - if(!$this->name) { - $this->name = $this->source_name; - } + // Set file extension + $this->extension = $this->source_image_fileinfo["extension"]; - // Fix conflicting starting dots (some Apache installs) - $this->name = ltrim($this->name, '.'); + // Workaround the $name + if (!$this->name) { + $this->name = $this->source_name; + } - // Fix file extension - if(G\get_file_extension($this->name) == $this->extension) { - $this->name = G\get_filename_without_extension($this->name); - } + // Fix conflicting starting dots (some Apache installs) + $this->name = ltrim($this->name, '.'); - // Set the fixed filename - $this->fixed_filename = preg_replace('/(.*)\.(th|md|original|lg)\.([\w]+)$/', '$1.$3', $this->name . '.' . $this->extension); + // Fix file extension + if (G\get_file_extension($this->name) == $this->extension) { + $this->name = G\get_filename_without_extension($this->name); + } + + // Set the fixed filename + $this->fixed_filename = preg_replace('/(.*)\.(th|md|original|lg)\.([\w]+)$/', '$1.$3', $this->name . '.' . $this->extension); // Workaround for JPEG Exif data - if($this->extension == 'jpg' and array_key_exists('exif', $this->options)) { - $this->source_image_exif = NULL; - if($this->options['exif']) { + if ($this->extension == 'jpg' and array_key_exists('exif', $this->options)) { + $this->source_image_exif = null; + if ($this->options['exif']) { // Fetch JPEG Exif data (when available) - if(function_exists('exif_read_data')) { + if (function_exists('exif_read_data')) { $this->source_image_exif = @exif_read_data($this->downstream); - if($this->source_image_exif) { + if ($this->source_image_exif) { $this->source_image_exif['FileName'] = $this->source_filename; } } } else { // Remove JPEG Exif data $img = imagecreatefromjpeg($this->downstream); - if($img) { + if ($img) { imagejpeg($img, $this->downstream, 90); imagedestroy($img); } } } - /* - * Set uploaded_file - * Local storage uploads will be allocated at the target destination - * External storage will be allocated to the temp directory - */ - $this->uploaded_file = G\name_unique_file($this->destination, $this->options['filenaming'], $this->fixed_filename); - - $this->source = [ - 'filename' => $this->source_filename, // file.ext - 'name' => $this->source_name, // file - 'image_exif' => $this->source_image_exif, // exif-data array - 'fileinfo' => G\get_image_fileinfo($this->downstream), // fileinfo array - ]; + /* + * Set uploaded_file + * Local storage uploads will be allocated at the target destination + * External storage will be allocated to the temp directory + */ + $this->uploaded_file = G\name_unique_file($this->destination, $this->options['filenaming'], $this->fixed_filename); + + $this->source = [ + 'filename' => $this->source_filename, // file.ext + 'name' => $this->source_name, // file + 'image_exif' => $this->source_image_exif, // exif-data array + 'fileinfo' => G\get_image_fileinfo($this->downstream), // fileinfo array + ]; - // Fix image orientation - if($this->source_image_exif and $this->source_image_exif["Orientation"]) { - $this->fixImageOrientation($this->downstream, $this->source_image_exif); - } + // Fix image orientation + if ($this->source_image_exif and $this->source_image_exif["Orientation"]) { + $this->fixImageOrientation($this->downstream, $this->source_image_exif); + } - $uploaded = @rename($this->downstream, $this->uploaded_file); - @unlink($this->downstream); + $uploaded = @rename($this->downstream, $this->uploaded_file); + @unlink($this->downstream); - if(file_exists($this->downstream)) { - error_log("Warning: temp file " . $this->downstream . "wasn't removed."); - } + if (file_exists($this->downstream)) { + error_log("Warning: temp file " . $this->downstream . "wasn't removed."); + } - if(!$uploaded) { - throw new UploadException("Can't move temp file to its destination", 400); - } + if (!$uploaded) { + throw new UploadException("Can't move temp file to its destination", 400); + } - // For some PHP environments - if(!$this->storage_id) { - @chmod($this->uploaded_file, 0644); - } + // For some PHP environments + if (!$this->storage_id) { + @chmod($this->uploaded_file, 0644); + } - $this->uploaded = array( - 'file' => $this->uploaded_file, - 'filename' => G\get_filename($this->uploaded_file), - 'name' => G\get_filename_without_extension($this->uploaded_file), - 'fileinfo' => G\get_image_fileinfo($this->uploaded_file) - ); + $this->uploaded = array( + 'file' => $this->uploaded_file, + 'filename' => G\get_filename($this->uploaded_file), + 'name' => G\get_filename_without_extension($this->uploaded_file), + 'fileinfo' => G\get_image_fileinfo($this->uploaded_file) + ); + } - } + // Get available (supported) extensions + public static function getAvailableImageFormats() + { + $formats = Settings::get('upload_available_image_formats'); + return explode(',', $formats); + } - // Get available (supported) extensions - public static function getAvailableImageFormats() { - $formats = Settings::get('upload_available_image_formats'); - return explode(',', $formats); - } + //remove query string from url to get correct image name + protected function getNameFromURL() + { + if (strpos($this->source, '?')) { + return substr($this->source, 0, strpos($this->source, '?')); + } else { + return $this->source; + } + } - // Failover since v3.8.12 - public static function getEnabledImageFormats() { - return Image::getEnabledImageFormats(); - } + // Failover since v3.8.12 + public static function getEnabledImageFormats() + { + return Image::getEnabledImageFormats(); + } - /** - * validate_input aka "first stage validation" - * This checks for valid input source data - * @Exception 1XX - */ - protected function validateInput() { + /** + * validate_input aka "first stage validation" + * This checks for valid input source data + * @Exception 1XX + */ + protected function validateInput() + { + $check_missing = ["type", "source", "destination"]; + missing_values_to_exception($this, "CHV\UploadException", $check_missing, 100); - $check_missing = ["type", "source", "destination"]; - missing_values_to_exception($this, "CHV\UploadException", $check_missing, 100); + // Validate $type + if (!preg_match("/^(url|file)$/", $this->type)) { + throw new UploadException('Invalid $type "'.$this->type.'"', 110); + } - // Validate $type - if(!preg_match("/^(url|file)$/", $this->type)) { - throw new UploadException('Invalid $type "'.$this->type.'"', 110); - } + // Handle flood + $flood = self::handleFlood(); + if ($flood) { + throw new UploadException(strtr('Flood detected. You can only upload %limit% images per %time%', ['%limit%' => $flood['limit'], '%time%' => $flood['by']]), 130); + } - // Handle flood - $flood = self::handleFlood(); - if($flood) { - throw new UploadException(strtr('Flood detected. You can only upload %limit% images per %time%', ['%limit%' => $flood['limit'], '%time%' => $flood['by']]), 130); - } + // Validate $source + if ($this->type == 'file') { + if (count($this->source) < 5) { // Valid $_FILES ? + throw new UploadException("Invalid file source", 120); + } + } elseif ($this->type == "url") { + if (!G\is_image_url($this->source) && !G\is_url($this->source)) { + throw new UploadException("Invalid image URL", 122); + } + } - // Validate $source - if($this->type == 'file') { - if(count($this->source) < 5) { // Valid $_FILES ? - throw new UploadException("Invalid file source", 120); - } - } else if($this->type == "url") { - if(!G\is_image_url($this->source) && !G\is_url($this->source)) { - throw new UploadException("Invalid image URL", 122); - } - } + // Validate $destination + if (!is_dir($this->destination)) { // Try to create the missing directory - // Validate $destination - if(!is_dir($this->destination)) { // Try to create the missing directory + $base_dir = G\add_ending_slash(G_ROOT_PATH . explode('/', preg_replace('#'.G_ROOT_PATH.'#', '', $this->destination, 1))[0]); + $base_perms = fileperms($base_dir); - $base_dir = G\add_ending_slash(G_ROOT_PATH . explode('/', preg_replace('#'.G_ROOT_PATH.'#', '', $this->destination, 1))[0]); - $base_perms = fileperms($base_dir); + $old_umask = umask(0); + $make_destination = mkdir($this->destination, $base_perms, true); + chmod($this->destination, $base_perms); + umask($old_umask); - $old_umask = umask(0); - $make_destination = mkdir($this->destination, $base_perms, true); - chmod($this->destination, $base_perms); - umask($old_umask); + if (!$make_destination) { + throw new UploadException('$destination '.$this->destination.' is not a dir', 130); + } + } - if(!$make_destination) { - throw new UploadException('$destination '.$this->destination.' is not a dir', 130); - } + // Can read $destination dir? + if (!is_readable($this->destination)) { + throw new UploadException("Can't read target destination dir", 131); + } - } + // Can write $destination dir? + if (!is_writable($this->destination)) { + throw new UploadException("Can't write target destination dir", 132); + } - // Can read $destination dir? - if(!is_readable($this->destination)) { - throw new UploadException("Can't read target destination dir", 131); - } + // Fix $destination trailing + $this->destination = G\add_ending_slash($this->destination); + } - // Can write $destination dir? - if(!is_writable($this->destination)) { - throw new UploadException("Can't write target destination dir", 132); - } + /** + * Fetch the $source file + * @Exception 2XX + */ + protected function fetchSource() + { - // Fix $destination trailing - $this->destination = G\add_ending_slash($this->destination); + // Set the downstream file + $this->downstream = @tempnam(sys_get_temp_dir(), 'chvtemp'); - } + if (!$this->downstream || !@is_writable($this->downstream)) { + $this->downstream = @tempnam($this->destination, 'chvtemp'); + if (!$this->downstream) { + throw new UploadException("Can't get a tempnam", 200); + } + } - /** - * Fetch the $source file - * @Exception 2XX - */ - protected function fetchSource() { + if ($this->type == 'file') { + if ($this->source['error'] !== UPLOAD_ERR_OK) { + switch ($this->source['error']) { + case UPLOAD_ERR_INI_SIZE: // 1 + throw new UploadException('File too big', 201); + break; + case UPLOAD_ERR_FORM_SIZE: // 2 + throw new UploadException('File exceeds form max size', 201); + break; + case UPLOAD_ERR_PARTIAL: // 3 + throw new UploadException('File was partially uploaded', 201); + break; + case UPLOAD_ERR_NO_FILE: // 4 + throw new UploadException('No file was uploaded', 201); + break; + case UPLOAD_ERR_NO_TMP_DIR: // 5 + throw new UploadException('Missing temp folder', 201); + break; + case UPLOAD_ERR_CANT_WRITE: // 6 + throw new UploadException('System write error', 201); + break; + case UPLOAD_ERR_EXTENSION: // 7 + throw new UploadException('The upload was stopped', 201); + break; + } + } - // Set the downstream file - $this->downstream = @tempnam(sys_get_temp_dir(), 'chvtemp'); + if (!@rename($this->source['tmp_name'], $this->downstream)) { + throw new UploadException("Can't move temp file to the target upload dir", 203); + } + } elseif ($this->type == "url") { + try { + G\fetch_url($this->source, $this->downstream); + } catch (Exception $e) { + throw new UploadException($e->getMessage(), 202); + } + } - if(!$this->downstream || !@is_writable($this->downstream)) { - $this->downstream = @tempnam($this->destination, 'chvtemp'); - if(!$this->downstream) { - throw new UploadException("Can't get a tempnam", 200); - } - } + $this->source_filename = basename($this->type == "file" ? $this->source["name"] : $this->source); + } - if($this->type == 'file') { + protected function fixImageOrientation($image_filename, $exif) + { + if ($exif['Orientation'] == 1) { + return; + } + switch ($this->extension) { + case 'jpg': + $image = imagecreatefromjpeg($image_filename); + break; + } + switch ($exif['Orientation']) { + case 3: + $image = imagerotate($image, 180, 0); + break; + case 6: + $image = imagerotate($image, -90, 0); + break; + case 8: + $image = imagerotate($image, 90, 0); + break; + } + imagejpeg($image, $image_filename, 90); + } - if($this->source['error'] !== UPLOAD_ERR_OK) { + /** + * validate_source_file aka "second stage validation" + * This checks for valid input source data + * @Exception 3XX + */ + protected function validateSourceFile() + { - switch($this->source['error']) { - case UPLOAD_ERR_INI_SIZE: // 1 - throw new UploadException('File too big', 201); - break; - case UPLOAD_ERR_FORM_SIZE: // 2 - throw new UploadException('File exceeds form max size', 201); - break; - case UPLOAD_ERR_PARTIAL: // 3 - throw new UploadException('File was partially uploaded', 201); - break; - case UPLOAD_ERR_NO_FILE: // 4 - throw new UploadException('No file was uploaded', 201); - break; - case UPLOAD_ERR_NO_TMP_DIR: // 5 - throw new UploadException('Missing temp folder', 201); - break; - case UPLOAD_ERR_CANT_WRITE: // 6 - throw new UploadException('System write error', 201); - break; - case UPLOAD_ERR_EXTENSION: // 7 - throw new UploadException('The upload was stopped', 201); - break; - } + // Nothing to do here + if (!file_exists($this->downstream)) { + throw new UploadException("Can't fetch target upload source (downstream)", 300); + } - } + $this->source_image_fileinfo = G\get_image_fileinfo($this->downstream); - if(!@rename($this->source['tmp_name'], $this->downstream)) { - throw new UploadException("Can't move temp file to the target upload dir", 203); - } + // file info? + if (!$this->source_image_fileinfo) { + throw new UploadException("Can't get target upload source info", 310); + } - } else if($this->type == "url") { - try { - G\fetch_url($this->source, $this->downstream); - } catch(Exception $e) { - throw new UploadException($e->getMessage(), 202); - } - } + // Valid image fileinto? + if ($this->source_image_fileinfo['width'] == '' || $this->source_image_fileinfo['height'] == '') { + throw new UploadException("Invalid image", 311); + } - $this->source_filename = basename($this->type == "file" ? $this->source["name"] : $this->source); + // Available image format? + if (!in_array($this->source_image_fileinfo['extension'], self::getAvailableImageFormats())) { + throw new UploadException("Unavailable image format", 313); + } - } + // Enabled image format? + if (!in_array($this->source_image_fileinfo['extension'], $this->options['allowed_formats'])) { + throw new UploadException(sprintf("Disabled image format (%s)", $this->source_image_fileinfo['extension']), 314); + } - protected function fixImageOrientation($image_filename, $exif) { - if($exif['Orientation'] == 1) return; - switch($this->extension) { - case 'jpg': - $image = imagecreatefromjpeg($image_filename); - break; - } - switch($exif['Orientation']) { - case 3: - $image = imagerotate($image, 180, 0); - break; - case 6: - $image = imagerotate($image, -90, 0); - break; - case 8: - $image = imagerotate($image, 90, 0); - break; - } - imagejpeg($image, $image_filename, 90); - } + // Mime + if (!$this->isValidImageMime($this->source_image_fileinfo["mime"])) { + throw new UploadException("Invalid image mimetype", 312); + } - /** - * validate_source_file aka "second stage validation" - * This checks for valid input source data - * @Exception 3XX - */ - protected function validateSourceFile() { + // Size + if (!$this->options['max_size']) { + $this->options['max_size'] = self::getDefaultOptions()['max_size']; + } + if ($this->source_image_fileinfo["size"] > $this->options["max_size"]) { + throw new UploadException("File too big - max " . G\format_bytes($this->options["max_size"]), 313); + } - // Nothing to do here - if(!file_exists($this->downstream)) { - throw new UploadException("Can't fetch target upload source (downstream)", 300); - } + // BMP? + if ($this->source_image_fileinfo['extension'] == 'bmp') { + $this->ImageConvert = new ImageConvert($this->downstream, "png", $this->downstream); + $this->downstream = $this->ImageConvert->out; + $this->source_image_fileinfo = G\get_image_fileinfo($this->downstream); + } + } - $this->source_image_fileinfo = G\get_image_fileinfo($this->downstream); + // Handle flood uploads + protected static function handleFlood() + { + $logged_user = Login::getUser(); - // file info? - if(!$this->source_image_fileinfo) { - throw new UploadException("Can't get target upload source info", 310); - } + if (!getSetting('flood_uploads_protection') || $logged_user['is_admin']) { + return false; + } - // Valid image fileinto? - if($this->source_image_fileinfo['width'] == '' || $this->source_image_fileinfo['height'] == '') { - throw new UploadException("Invalid image", 311); - } + $flood_limit = []; + foreach (['minute', 'hour', 'day', 'week', 'month'] as $v) { + $flood_limit[$v] = getSetting('flood_uploads_' . $v); + } - // Available image format? - if(!in_array($this->source_image_fileinfo['extension'], self::getAvailableImageFormats())) { - throw new UploadException("Unavailable image format", 313); - } - - // Enabled image format? - if(!in_array($this->source_image_fileinfo['extension'], $this->options['allowed_formats'])) { - throw new UploadException(sprintf("Disabled image format (%s)", $this->source_image_fileinfo['extension']), 314); - } - - // Mime - if(!$this->isValidImageMime($this->source_image_fileinfo["mime"])) { - throw new UploadException("Invalid image mimetype", 312); - } - - // Size - if(!$this->options['max_size']) { - $this->options['max_size'] = self::getDefaultOptions()['max_size']; - } - if($this->source_image_fileinfo["size"] > $this->options["max_size"]) { - throw new UploadException("File too big - max " . G\format_bytes($this->options["max_size"]), 313); - } - - // BMP? - if($this->source_image_fileinfo['extension'] == 'bmp') { - $this->ImageConvert = new ImageConvert($this->downstream, "png", $this->downstream); - $this->downstream = $this->ImageConvert->out; - $this->source_image_fileinfo = G\get_image_fileinfo($this->downstream); - } - - } - - // Handle flood uploads - protected static function handleFlood() { - - $logged_user = Login::getUser(); - - if(!getSetting('flood_uploads_protection') || $logged_user['is_admin']) { - return FALSE; - } - - $flood_limit = []; - foreach(['minute', 'hour', 'day', 'week', 'month'] as $v) { - $flood_limit[$v] = getSetting('flood_uploads_' . $v); - } - - try { - $db = DB::getInstance(); - $flood_db = $db->queryFetchSingle( - "SELECT + try { + $db = DB::getInstance(); + $flood_db = $db->queryFetchSingle( + "SELECT COUNT(IF(image_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 MINUTE), 1, NULL)) AS minute, COUNT(IF(image_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 HOUR), 1, NULL)) AS hour, COUNT(IF(image_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 DAY), 1, NULL)) AS day, COUNT(IF(image_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 WEEK), 1, NULL)) AS week, COUNT(IF(image_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 MONTH), 1, NULL)) AS month - FROM ".DB::getTable('images')." WHERE image_uploader_ip='".G\get_client_ip()."' AND image_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 MONTH)"); - } catch(Exception $e) {} // Silence + FROM ".DB::getTable('images')." WHERE image_uploader_ip='".G\get_client_ip()."' AND image_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 MONTH)" + ); + } catch (Exception $e) { + } // Silence - $is_flood = FALSE; - $flood_by = ''; - foreach(['minute', 'hour', 'day', 'week', 'month'] as $v) { - if($flood_limit[$v] > 0 and $flood_db[$v] >= $flood_limit[$v]) { - $flood_by = $v; - $is_flood = TRUE; - break; - } - } + $is_flood = false; + $flood_by = ''; + foreach (['minute', 'hour', 'day', 'week', 'month'] as $v) { + if ($flood_limit[$v] > 0 and $flood_db[$v] >= $flood_limit[$v]) { + $flood_by = $v; + $is_flood = true; + break; + } + } - if($is_flood) { - if(getSetting('flood_uploads_notify') and !$_SESSION['flood_uploads_notify'][$flood_by]) { - try { - $message = strtr('Flooding IP %ip', ['%ip' => G\get_client_ip()]) . '
'; - if($logged_user) { - $message .= 'User '.$logged_user['name'].'
'; - } - $message .= '
'; - $message .= 'Uploads per time period'."
"; - $message .= 'Minute: '.$flood_db['minute']."
"; - $message .= 'Hour: '.$flood_db['hour']."
"; - $message .= 'Week: '.$flood_db['day']."
"; - $message .= 'Month: '.$flood_db['week']."
"; - system_notification_email(['subject' => 'Flood report IP '. G\get_client_ip(), 'message' => $message]); - $_SESSION['flood_uploads_notify'][$flood_by] = true; - } catch(Exception $e) {} // Silence - } + if ($is_flood) { + if (getSetting('flood_uploads_notify') and !$_SESSION['flood_uploads_notify'][$flood_by]) { + try { + $message = strtr('Flooding IP %ip', ['%ip' => G\get_client_ip()]) . '
'; + if ($logged_user) { + $message .= 'User '.$logged_user['name'].'
'; + } + $message .= '
'; + $message .= 'Uploads per time period'."
"; + $message .= 'Minute: '.$flood_db['minute']."
"; + $message .= 'Hour: '.$flood_db['hour']."
"; + $message .= 'Week: '.$flood_db['day']."
"; + $message .= 'Month: '.$flood_db['week']."
"; + system_notification_email(['subject' => 'Flood report IP '. G\get_client_ip(), 'message' => $message]); + $_SESSION['flood_uploads_notify'][$flood_by] = true; + } catch (Exception $e) { + } // Silence + } - return ['flood' => TRUE, 'limit' => $flood_limit[$flood_by], 'count' => $flood_db[$flood_by], 'by' => $flood_by]; - } + return ['flood' => true, 'limit' => $flood_limit[$flood_by], 'count' => $flood_db[$flood_by], 'by' => $flood_by]; + } - return FALSE; - } + return false; + } - protected function isValidImageMime($mime) { - return preg_match("@image/(gif|pjpeg|jpeg|png|x-png|bmp|x-ms-bmp|x-windows-bmp)$@", $mime); - } + protected function isValidImageMime($mime) + { + return preg_match("@image/(gif|pjpeg|jpeg|png|x-png|bmp|x-ms-bmp|x-windows-bmp)$@", $mime); + } - protected function isValidNamingOption($string) { - return in_array($string, array("mixed", "random", "original")); - } + protected function isValidNamingOption($string) + { + return in_array($string, array("mixed", "random", "original")); + } } -class UploadException extends Exception {} +class UploadException extends Exception +{ +} diff --git a/lib/G/classes/class.db.php b/lib/G/classes/class.db.php index 93e97dc..2e2af0f 100644 --- a/lib/G/classes/class.db.php +++ b/lib/G/classes/class.db.php @@ -20,528 +20,550 @@ */ namespace G; -use PDO, PDOException, Exception; - -class DB { - - private static $instance; - - private $host = G_APP_DB_HOST; - private $port = G_APP_DB_PORT; - private $name = G_APP_DB_NAME; - private $user = G_APP_DB_USER; - private $pass = G_APP_DB_PASS; - private $driver = G_APP_DB_DRIVER; - private $pdo_attrs = G_APP_DB_PDO_ATTRS; - - static $dbh; - public $query; - - /** - * Connect to the DB server - * Throws an Exception on error (tay weando? en serio?) - */ - public function __construct($conn=[]) { - - try { - // PDO already connected - if(empty($conn) and isset(self::$dbh) and get_class(self::$dbh) == 'PDO') { - return TRUE; - } - - if(!empty($conn)) { - // Inject connection info - foreach(['host', 'user', 'name', 'pass', 'port', 'driver', 'pdo_attrs'] as $k) { - $this->{$k} = $conn[$k]; - } - } - - $pdo_connect = $this->driver . ':host=' . $this->host . ';dbname=' . $this->name; - if($this->port) { - $pdo_connect .= ';port=' . $this->port; - } - - $this->pdo_attrs = @unserialize($this->pdo_attrs) ?: $this->pdo_attrs; - - // PDO defaults - $this->pdo_default_attrs = [ - PDO::ATTR_TIMEOUT => 30, - //PDO::ATTR_PERSISTENT => FALSE - ]; - - // Override PDO defaults ? - $this->pdo_attrs = (is_array($this->pdo_attrs) ? $this->pdo_attrs : []) + $this->pdo_default_attrs; - - // PDO hard overrides - $this->pdo_attrs[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION; - $this->pdo_attrs[PDO::MYSQL_ATTR_INIT_COMMAND] = "SET NAMES 'UTF8'"; - - // Turn off PHP error reporting just for the connection here (invalid host names will trigger a PHP warning) - $error_reporting = error_reporting(); - error_reporting(0); - - // Note that PDO::ERRMODE_SILENT has no effect on connection. Connections always throw an exception if it fails - self::$dbh = new PDO($pdo_connect, $this->user, $this->pass, $this->pdo_attrs); - - // Re-enable the error_reporting level - error_reporting($error_reporting); - - // PDO emulate prepares if needed - if(version_compare(self::$dbh->getAttribute(PDO::ATTR_SERVER_VERSION), '5.1.17', '<')) { - self::$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); - } - - self::$instance = $this; - - } catch(Exception $e) { - self::$dbh = NULL; - throw new DBException($e->getMessage(), 400); - } - - } - - /** - * Singleton instance handler - * Used for the static methods of this class - */ - public static function getInstance() { - if(is_null(self::$instance)) { - self::$instance = new self; - } - return self::$instance; - } - - /** - * Populates the class DB own PDO attributes array with an entire array - * Attribute list here: http://php.net/manual/en/pdo.setattribute.php - */ - public function setPDOAttrs($attributes) { - $this->pdo_attrs = $attributes; - } - - /** - * Populates the class DB own PDO attributes array with a single key - * Attributes list here: http://php.net/manual/en/pdo.setattribute.php - */ - public function setPDOAttr($key, $value) { - $this->pdo_attrs[$key] = $value; - } - - public function getAttr($attr) { - return self::$dbh->getAttribute($attr); - } - - /** - * Prepares an SQL statement to be executed by the PDOStatement::execute() method - * http://php.net/manual/en/pdo.prepare.php - */ - public function query($query) { - $this->query = self::$dbh->prepare($query); - } - - public function errorInfo() { - return self::$dbh->errorInfo(); - } - - /** - * Binds a value to a corresponding named or question mark placeholder in the SQL statement that was used to prepare the statement - * http://php.net/manual/en/pdostatement.bindvalue.php - */ - public function bind($param, $value, $type = null) { - if(is_null($type)) { - switch(true) { - case is_int($value): - $type = PDO::PARAM_INT; - break; - case is_bool($value): - $type = PDO::PARAM_BOOL; - break; - case is_null($value): - $type = PDO::PARAM_NULL; - break; - default: - $type = PDO::PARAM_STR; - break; - } - } - $this->query->bindValue($param, $value, $type); - } - - public function exec() { - return $this->query->execute(); - } - - public function fetchColumn() { - return $this->query->fetchColumn(); - } - - public function closeCursor() { - return $this->query->closeCursor(); - } - - public function fetchAll($mode=PDO::FETCH_ASSOC) { - $this->exec(); - return $this->query->fetchAll(is_int($mode) ? $mode : PDO::FETCH_ASSOC); - } - - /** - * Execute and returns the single result from the prepared statement - * http://php.net/manual/en/pdostatement.fetch.php - */ - public function fetchSingle($mode=PDO::FETCH_ASSOC) { - $this->exec(); - return $this->query->fetch(is_int($mode) ? $mode : PDO::FETCH_ASSOC); - } - - /** - * Query and exec, return number of affected rows or FALSE - */ - public static function queryExec($query) { - try { - $db = self::getInstance(); - $db->query($query); - return $db->exec() ? $db->rowCount() : FALSE; - } catch(Exception $e) { - throw new DBException($e->getMessage(), 400); - } - } - - /** - * Query and fetch single record - */ - public static function queryFetchSingle($query, $fetch_style=NULL) { - try { - return self::queryFetch($query, 1, $fetch_style); - } catch(Exception $e) { - throw new DBException($e->getMessage(), 400); - } - } - - /** - * Query and fetch all records - */ - public static function queryFetchAll($query, $fetch_style=NULL) { - try { - return self::queryFetch($query, NULL, $fetch_style); - } catch(Exception $e) { - throw new DBException($e->getMessage(), 400); - } - } - - /** - * Query fetch (core version) - */ - public static function queryFetch($query, $limit=1, $fetch_style=NULL) { - try { - $db = self::getInstance(); - $db->query($query); - return $limit == 1 ? $db->fetchSingle($fetch_style) : $db->fetchAll($fetch_style); - } catch(Exception $e) { - throw new DBException($e->getMessage(), 400); - } - } - - /** - * Returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement executed - * http://php.net/manual/en/pdostatement.rowcount.php - */ - public function rowCount() { - return $this->query->rowCount(); - } - - /** - * Returns the ID of the last inserted row, or the last value from a sequence object, depending on the underlying driver - * http://php.net/manual/en/pdo.lastinsertid.php - */ - public function lastInsertId() { - return self::$dbh->lastInsertId(); - } - - /** - * Turns off autocommit mode - * http://php.net/manual/en/pdo.begintransaction.php - */ - public function beginTransaction(){ - return self::$dbh->beginTransaction(); - } - - /** - * Commits a transaction, returning the database connection to autocommit mode until the next call to PDO::beginTransaction() starts a new transaction - * http://php.net/manual/en/pdo.commit.php - */ - public function endTransaction(){ - return self::$dbh->commit(); - } - - /** - * Rolls back the current transaction, as initiated by PDO::beginTransaction() - * http://php.net/manual/en/pdo.rollback.php - */ - public function cancelTransaction(){ - return self::$dbh->rollBack(); - } - - /** - * Dumps the informations contained by a prepared statement directly on the output - * http://php.net/manual/en/pdostatement.debugdumpparams.php - */ - public function debugDumpParams(){ - return $this->query->debugDumpParams(); - } - - /* Now the G\ fast DB methods, presented by Chevereto */ - - /** - * Get the table with its prefix - */ - public static function getTable($table) { - return get_app_setting('db_table_prefix') . $table; - } - - /** - * Get values from DB - */ - public static function get($table, $values, $clause='AND', $sort=[], $limit=NULL, $fetch_style=NULL) { - - if(!is_array($values) and $values !== 'all') { - throw new DBException('Expecting array values, '.gettype($values).' given in ' . __METHOD__, 100); - } - - self::validateClause($clause, __METHOD__); - - if(is_array($table)) { - $join = $table['join']; - $table = $table['table']; - } - - $table = DB::getTable($table); - - $query = 'SELECT * FROM '.$table; - - if($join) { - $query .= ' ' . $join . ' '; - } - - if(is_array($values) and !empty($values)) { - $query .= ' WHERE '; - foreach($values as $k => $v) { - if(is_null($v)) { - $query .= '`'.$k.'` IS :'.$k.' '.$clause.' '; - } else { - $query .= '`'.$k.'`=:'.$k.' '.$clause.' '; - } - } - } - - $query = rtrim($query, $clause . ' '); - - if(is_array($sort) and !empty($sort)) { - if(!$sort['field']) { - $sort['field'] = 'date'; - } - if(!$sort['order']) { - $sort['order'] = 'desc'; - } - $query .= ' ORDER BY '.$sort['field'].' '.strtoupper($sort['order']).' '; - } - - if($limit and is_int($limit)) { - $query .= " LIMIT $limit"; - } - - try { - $db = self::getInstance(); - $db->query($query); - if(is_array($values)) { - foreach($values as $k => $v) { - $db->bind(':'.$k, $v); - } - } - return $limit == 1 ? $db->fetchSingle($fetch_style) : $db->fetchAll($fetch_style); - } catch(Exception $e) { - throw new DBException($e->getMessage(), 400); - } - } - - /** - * Update target table row(s) - * Returns the number of affected rows or false - */ - public static function update($table, $values, $wheres, $clause='AND') { - - if(!is_array($values)) { - throw new DBException('Expecting array values, '.gettype($values).' given in '. __METHOD__, 100); - } - if(!is_array($wheres)) { - throw new DBException('Expecting array values, '.gettype($wheres).' given in '. __METHOD__, 100); - } - - self::validateClause($clause, __METHOD__); - - $table = DB::getTable($table); - - $query = 'UPDATE `'.$table.'` SET '; - - // Set the value pairs - foreach($values as $k => $v) { - $query .= '`' . $k . '`=:value_' . $k . ','; - } - $query = rtrim($query, ',') . ' WHERE '; - - // Set the where pairs - foreach($wheres as $k => $v) { - $query .= '`'.$k.'`=:where_'.$k.' '.$clause.' '; - } - $query = rtrim($query, $clause.' '); - - try { - $db = self::getInstance(); - $db->query($query); - - // Bind the values - foreach($values as $k => $v) { - $db->bind(':value_'.$k, $v); - } - foreach($wheres as $k => $v) { - $db->bind(':where_'.$k, $v); - } - - return $db->exec() ? $db->rowCount() : FALSE; - } catch(Exception $e) { - throw new DBException($e->getMessage(), 400); - } - - } - - /** - * Insert single row to the table - */ - public static function insert($table, $values) { - - if(!is_array($values)) { - throw new DBException('Expecting array values, '.gettype($values).' given in '. __METHOD__, 100); - } - - $table = DB::getTable($table); - - $table_fields = []; - foreach($values as $k => $v) { - $table_fields[] = $k; - } - - $query = 'INSERT INTO + +use PDO; +use PDOException; +use Exception; + +class DB +{ + private static $instance; + + private $host = G_APP_DB_HOST; + private $port = G_APP_DB_PORT; + private $name = G_APP_DB_NAME; + private $user = G_APP_DB_USER; + private $pass = G_APP_DB_PASS; + private $driver = G_APP_DB_DRIVER; + private $pdo_attrs = G_APP_DB_PDO_ATTRS; + + public static $dbh; + public $query; + + /** + * Connect to the DB server + * Throws an Exception on error (tay weando? en serio?) + */ + public function __construct($conn=[]) + { + try { + // PDO already connected + if (empty($conn) and isset(self::$dbh) and get_class(self::$dbh) == 'PDO') { + return true; + } + + if (!empty($conn)) { + // Inject connection info + foreach (['host', 'user', 'name', 'pass', 'port', 'driver', 'pdo_attrs'] as $k) { + $this->{$k} = $conn[$k]; + } + } + + $pdo_connect = $this->driver . ':host=' . $this->host . ';dbname=' . $this->name; + if ($this->port) { + $pdo_connect .= ';port=' . $this->port; + } + + $this->pdo_attrs = @unserialize($this->pdo_attrs) ?: $this->pdo_attrs; + + // PDO defaults + $this->pdo_default_attrs = [ + PDO::ATTR_TIMEOUT => 30, + //PDO::ATTR_PERSISTENT => FALSE + ]; + + // Override PDO defaults ? + $this->pdo_attrs = (is_array($this->pdo_attrs) ? $this->pdo_attrs : []) + $this->pdo_default_attrs; + + // PDO hard overrides + $this->pdo_attrs[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION; + $this->pdo_attrs[PDO::MYSQL_ATTR_INIT_COMMAND] = "SET NAMES 'UTF8'"; + + // Turn off PHP error reporting just for the connection here (invalid host names will trigger a PHP warning) + $error_reporting = error_reporting(); + error_reporting(0); + + // Note that PDO::ERRMODE_SILENT has no effect on connection. Connections always throw an exception if it fails + self::$dbh = new PDO($pdo_connect, $this->user, $this->pass, $this->pdo_attrs); + + // Re-enable the error_reporting level + error_reporting($error_reporting); + + // PDO emulate prepares if needed + if (version_compare(self::$dbh->getAttribute(PDO::ATTR_SERVER_VERSION), '5.1.17', '<')) { + self::$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); + } + + self::$instance = $this; + } catch (Exception $e) { + self::$dbh = null; + throw new DBException($e->getMessage(), 400); + } + } + + /** + * Singleton instance handler + * Used for the static methods of this class + */ + public static function getInstance() + { + if (is_null(self::$instance)) { + self::$instance = new self; + } + return self::$instance; + } + + /** + * Populates the class DB own PDO attributes array with an entire array + * Attribute list here: http://php.net/manual/en/pdo.setattribute.php + */ + public function setPDOAttrs($attributes) + { + $this->pdo_attrs = $attributes; + } + + /** + * Populates the class DB own PDO attributes array with a single key + * Attributes list here: http://php.net/manual/en/pdo.setattribute.php + */ + public function setPDOAttr($key, $value) + { + $this->pdo_attrs[$key] = $value; + } + + public function getAttr($attr) + { + return self::$dbh->getAttribute($attr); + } + + /** + * Prepares an SQL statement to be executed by the PDOStatement::execute() method + * http://php.net/manual/en/pdo.prepare.php + */ + public function query($query) + { + $this->query = self::$dbh->prepare($query); + } + + public function errorInfo() + { + return self::$dbh->errorInfo(); + } + + /** + * Binds a value to a corresponding named or question mark placeholder in the SQL statement that was used to prepare the statement + * http://php.net/manual/en/pdostatement.bindvalue.php + */ + public function bind($param, $value, $type = null) + { + if (is_null($type)) { + switch (true) { + case is_int($value): + $type = PDO::PARAM_INT; + break; + case is_bool($value): + $type = PDO::PARAM_BOOL; + break; + case is_null($value): + $type = PDO::PARAM_NULL; + break; + default: + $type = PDO::PARAM_STR; + break; + } + } + $this->query->bindValue($param, $value, $type); + } + + public function exec() + { + return $this->query->execute(); + } + + public function fetchColumn() + { + return $this->query->fetchColumn(); + } + + public function closeCursor() + { + return $this->query->closeCursor(); + } + + public function fetchAll($mode=PDO::FETCH_ASSOC) + { + $this->exec(); + return $this->query->fetchAll(is_int($mode) ? $mode : PDO::FETCH_ASSOC); + } + + /** + * Execute and returns the single result from the prepared statement + * http://php.net/manual/en/pdostatement.fetch.php + */ + public function fetchSingle($mode=PDO::FETCH_ASSOC) + { + $this->exec(); + return $this->query->fetch(is_int($mode) ? $mode : PDO::FETCH_ASSOC); + } + + /** + * Query and exec, return number of affected rows or FALSE + */ + public static function queryExec($query) + { + try { + $db = self::getInstance(); + $db->query($query); + return $db->exec() ? $db->rowCount() : false; + } catch (Exception $e) { + throw new DBException($e->getMessage(), 400); + } + } + + /** + * Query and fetch single record + */ + public static function queryFetchSingle($query, $fetch_style=null) + { + try { + return self::queryFetch($query, 1, $fetch_style); + } catch (Exception $e) { + throw new DBException($e->getMessage(), 400); + } + } + + /** + * Query and fetch all records + */ + public static function queryFetchAll($query, $fetch_style=null) + { + try { + return self::queryFetch($query, null, $fetch_style); + } catch (Exception $e) { + throw new DBException($e->getMessage(), 400); + } + } + + /** + * Query fetch (core version) + */ + public static function queryFetch($query, $limit=1, $fetch_style=null) + { + try { + $db = self::getInstance(); + $db->query($query); + return $limit == 1 ? $db->fetchSingle($fetch_style) : $db->fetchAll($fetch_style); + } catch (Exception $e) { + throw new DBException($e->getMessage(), 400); + } + } + + /** + * Returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement executed + * http://php.net/manual/en/pdostatement.rowcount.php + */ + public function rowCount() + { + return $this->query->rowCount(); + } + + /** + * Returns the ID of the last inserted row, or the last value from a sequence object, depending on the underlying driver + * http://php.net/manual/en/pdo.lastinsertid.php + */ + public function lastInsertId() + { + return self::$dbh->lastInsertId(); + } + + /** + * Turns off autocommit mode + * http://php.net/manual/en/pdo.begintransaction.php + */ + public function beginTransaction() + { + return self::$dbh->beginTransaction(); + } + + /** + * Commits a transaction, returning the database connection to autocommit mode until the next call to PDO::beginTransaction() starts a new transaction + * http://php.net/manual/en/pdo.commit.php + */ + public function endTransaction() + { + return self::$dbh->commit(); + } + + /** + * Rolls back the current transaction, as initiated by PDO::beginTransaction() + * http://php.net/manual/en/pdo.rollback.php + */ + public function cancelTransaction() + { + return self::$dbh->rollBack(); + } + + /** + * Dumps the informations contained by a prepared statement directly on the output + * http://php.net/manual/en/pdostatement.debugdumpparams.php + */ + public function debugDumpParams() + { + return $this->query->debugDumpParams(); + } + + /* Now the G\ fast DB methods, presented by Chevereto */ + + /** + * Get the table with its prefix + */ + public static function getTable($table) + { + return get_app_setting('db_table_prefix') . $table; + } + + /** + * Get values from DB + */ + public static function get($table, $values, $clause='AND', $sort=[], $limit=null, $fetch_style=null) + { + if (!is_array($values) and $values !== 'all') { + throw new DBException('Expecting array values, '.gettype($values).' given in ' . __METHOD__, 100); + } + + self::validateClause($clause, __METHOD__); + + if (is_array($table)) { + $join = $table['join']; + $table = $table['table']; + } + + $table = DB::getTable($table); + + $query = 'SELECT * FROM '.$table; + + if (isset($join) && $join) { + $query .= ' ' . $join . ' '; + } + + if (is_array($values) and !empty($values)) { + $query .= ' WHERE '; + foreach ($values as $k => $v) { + if (is_null($v)) { + $query .= '`'.$k.'` IS :'.$k.' '.$clause.' '; + } else { + $query .= '`'.$k.'`=:'.$k.' '.$clause.' '; + } + } + } + + $query = rtrim($query, $clause . ' '); + + if (is_array($sort) and !empty($sort)) { + if (!$sort['field']) { + $sort['field'] = 'date'; + } + if (!$sort['order']) { + $sort['order'] = 'desc'; + } + $query .= ' ORDER BY '.$sort['field'].' '.strtoupper($sort['order']).' '; + } + + if ($limit and is_int($limit)) { + $query .= " LIMIT $limit"; + } + + try { + $db = self::getInstance(); + $db->query($query); + if (is_array($values)) { + foreach ($values as $k => $v) { + $db->bind(':'.$k, $v); + } + } + return $limit == 1 ? $db->fetchSingle($fetch_style) : $db->fetchAll($fetch_style); + } catch (Exception $e) { + throw new DBException($e->getMessage(), 400); + } + } + + /** + * Update target table row(s) + * Returns the number of affected rows or false + */ + public static function update($table, $values, $wheres, $clause='AND') + { + if (!is_array($values)) { + throw new DBException('Expecting array values, '.gettype($values).' given in '. __METHOD__, 100); + } + if (!is_array($wheres)) { + throw new DBException('Expecting array values, '.gettype($wheres).' given in '. __METHOD__, 100); + } + + self::validateClause($clause, __METHOD__); + + $table = DB::getTable($table); + + $query = 'UPDATE `'.$table.'` SET '; + + // Set the value pairs + foreach ($values as $k => $v) { + $query .= '`' . $k . '`=:value_' . $k . ','; + } + $query = rtrim($query, ',') . ' WHERE '; + + // Set the where pairs + foreach ($wheres as $k => $v) { + $query .= '`'.$k.'`=:where_'.$k.' '.$clause.' '; + } + $query = rtrim($query, $clause.' '); + + try { + $db = self::getInstance(); + $db->query($query); + + // Bind the values + foreach ($values as $k => $v) { + $db->bind(':value_'.$k, $v); + } + foreach ($wheres as $k => $v) { + $db->bind(':where_'.$k, $v); + } + + return $db->exec() ? $db->rowCount() : false; + } catch (Exception $e) { + throw new DBException($e->getMessage(), 400); + } + } + + /** + * Insert single row to the table + */ + public static function insert($table, $values) + { + if (!is_array($values)) { + throw new DBException('Expecting array values, '.gettype($values).' given in '. __METHOD__, 100); + } + + $table = DB::getTable($table); + + $table_fields = []; + foreach ($values as $k => $v) { + $table_fields[] = $k; + } + + $query = 'INSERT INTO `'.$table.'` (`' . ltrim(implode('`,`', $table_fields), '`,`') . '`) VALUES (' . ':' . str_replace(':', ',:', implode(':', $table_fields)) . ')'; - try { - $db = self::getInstance(); - $db->query($query); - foreach($values as $k => $v) { - $db->bind(':'.$k, $v); - } - return $db->exec() ? $db->lastInsertId() : FALSE; - } catch(Exception $e) { - throw new DBException($e->getMessage(), 400); - } + try { + $db = self::getInstance(); + $db->query($query); + foreach ($values as $k => $v) { + $db->bind(':'.$k, $v); + } + return $db->exec() ? $db->lastInsertId() : false; + } catch (Exception $e) { + throw new DBException($e->getMessage(), 400); + } + } - } + /** + * Update target numecic table row(s) with and increment (positive or negative) + * Returns the number of affected rows or false + * Note: Minimum value to be set is zero, no negative values here + */ + public static function increment($table, $values, $wheres, $clause='AND') + { + foreach (['values', 'wheres'] as $k) { + if (!is_array(${$k})) { + throw new DBException('Expecting array values, '.gettype(${$k}).' given in '. __METHOD__, 100); + } + } - /** - * Update target numecic table row(s) with and increment (positive or negative) - * Returns the number of affected rows or false - * Note: Minimum value to be set is zero, no negative values here - */ - public static function increment($table, $values, $wheres, $clause='AND') { + $table = DB::getTable($table); + $query = 'UPDATE `'.$table.'` SET '; - foreach(['values', 'wheres'] as $k) { - if(!is_array(${$k})) { - throw new DBException('Expecting array values, '.gettype(${$k}).' given in '. __METHOD__, 100); - } - } + foreach ($values as $k => $v) { + if (preg_match('/^([+-]{1})\s*([\d]+)$/', $v, $matches)) { // 1-> op 2-> number + $query .= '`' . $k . '`='; + if ($matches[1] == '+') { + $query .= '`' . $k . '`' . $matches[1] . $matches[2] . ','; + } + if ($matches[1] == '-') { + $query .= 'GREATEST(cast(`'.$k.'` AS SIGNED) - '.$matches[2].', 0),'; + } + } + } - $table = DB::getTable($table); - $query = 'UPDATE `'.$table.'` SET '; + $query = rtrim($query, ',') . ' WHERE '; - foreach($values as $k => $v) { - if(preg_match('/^([+-]{1})\s*([\d]+)$/', $v, $matches)) { // 1-> op 2-> number - $query .= '`' . $k . '`='; - if($matches[1] == '+') { - $query .= '`' . $k . '`' . $matches[1] . $matches[2] . ','; - } - if($matches[1] == '-') { - $query .= 'GREATEST(cast(`'.$k.'` AS SIGNED) - '.$matches[2].', 0),'; - } - } - } + // Set the where pairs + foreach ($wheres as $k => $v) { + $query .= '`'.$k.'`=:where_'.$k.' '.$clause.' '; + } + $query = rtrim($query, $clause.' '); - $query = rtrim($query, ',') . ' WHERE '; + try { + $db = self::getInstance(); + $db->query($query); + foreach ($wheres as $k => $v) { + $db->bind(':where_'.$k, $v); + } + return $db->exec() ? $db->rowCount() : false; + } catch (Exception $e) { + throw new DBException($e->getMessage(), 400); + } + } - // Set the where pairs - foreach($wheres as $k => $v) { - $query .= '`'.$k.'`=:where_'.$k.' '.$clause.' '; - } - $query = rtrim($query, $clause.' '); + /** + * Delete row(s) from table + * Returns the number of affected rows or false + */ + public static function delete($table, $values, $clause='AND') + { + if (!is_array($values)) { + throw new DBException('Expecting array values, '.gettype($values).' given in '. __METHOD__, 100); + } - try { - $db = self::getInstance(); - $db->query($query); - foreach($wheres as $k => $v) { - $db->bind(':where_'.$k, $v); - } - return $db->exec() ? $db->rowCount() : false; - } catch(Exception $e) { - throw new DBException($e->getMessage(), 400); - } + self::validateClause($clause, __METHOD__); - } + $table = DB::getTable($table); + $query = 'DELETE FROM `'.$table.'` WHERE '; - /** - * Delete row(s) from table - * Returns the number of affected rows or false - */ - public static function delete($table, $values, $clause='AND') { + $table_fields = array(); + foreach ($values as $k => $v) { + $query .= '`'.$k.'`=:'.$k.' '.$clause.' '; + } + $query = rtrim($query, $clause.' '); - if(!is_array($values)) { - throw new DBException('Expecting array values, '.gettype($values).' given in '. __METHOD__, 100); - } - - self::validateClause($clause, __METHOD__); - - $table = DB::getTable($table); - $query = 'DELETE FROM `'.$table.'` WHERE '; - - $table_fields = array(); - foreach($values as $k => $v) { - $query .= '`'.$k.'`=:'.$k.' '.$clause.' '; - } - $query = rtrim($query, $clause.' '); - - try { - $db = self::getInstance(); - $db->query($query); - foreach($values as $k => $v) { - $db->bind(':'.$k, $v); - } - return $db->exec() ? $db->rowCount() : FALSE; - } catch(Exception $e) { - throw new DBException($e->getMessage(), 400); - } - - } - - /** - * Validate clause - */ - private static function validateClause($clause, $method=NULL) { - if(!is_null($clause)) { - $clause = strtoupper($clause); - if(!in_array($clause, ['AND', 'OR'])) { - throw new DBException('Expecting clause string \'AND\' or \'OR\' in ' . (!is_null($method) ? $method : __CLASS__), 100); - } - } - } + try { + $db = self::getInstance(); + $db->query($query); + foreach ($values as $k => $v) { + $db->bind(':'.$k, $v); + } + return $db->exec() ? $db->rowCount() : false; + } catch (Exception $e) { + throw new DBException($e->getMessage(), 400); + } + } + /** + * Validate clause + */ + private static function validateClause($clause, $method=null) + { + if (!is_null($clause)) { + $clause = strtoupper($clause); + if (!in_array($clause, ['AND', 'OR'])) { + throw new DBException('Expecting clause string \'AND\' or \'OR\' in ' . (!is_null($method) ? $method : __CLASS__), 100); + } + } + } } // DB class own Exception -class DBException extends Exception {} \ No newline at end of file +class DBException extends Exception +{ +}