From 3cd6a7b61e83647c44bd0c36b4acc9cb2562a5fa Mon Sep 17 00:00:00 2001 From: Sean Madsen Date: Fri, 21 Jul 2017 10:15:58 -0600 Subject: [PATCH 1/9] CRM-20821 - Move resizeImage() to Utils class - Move resizeImage() from CRM_Contribute_Form_ManagePremiums to CRM_Utils_File - Make it public static - Don't change anything about how it works --- CRM/Contribute/Form/ManagePremiums.php | 55 +------------------------- CRM/Utils/File.php | 49 +++++++++++++++++++++++ 2 files changed, 51 insertions(+), 53 deletions(-) diff --git a/CRM/Contribute/Form/ManagePremiums.php b/CRM/Contribute/Form/ManagePremiums.php index 2cd9d1a781df..47a7a7608c55 100644 --- a/CRM/Contribute/Form/ManagePremiums.php +++ b/CRM/Contribute/Form/ManagePremiums.php @@ -296,8 +296,8 @@ public function postProcess() { if ($gdSupport) { if ($imageFile) { $error = FALSE; - $params['image'] = $this->_resizeImage($imageFile, "_full", 200, 200); - $params['thumbnail'] = $this->_resizeImage($imageFile, "_thumb", 50, 50); + $params['image'] = CRM_Utils_File::resizeImage($imageFile, "_full", 200, 200); + $params['thumbnail'] = CRM_Utils_File::resizeImage($imageFile, "_thumb", 50, 50); } } else { @@ -344,55 +344,4 @@ public function postProcess() { } } - /** - * Resize a premium image to a different size. - * - * - * @param string $filename - * @param string $resizedName - * @param $width - * @param $height - * - * @return string - * Path to image - */ - private function _resizeImage($filename, $resizedName, $width, $height) { - // figure out the new filename - $pathParts = pathinfo($filename); - $newFilename = $pathParts['dirname'] . "/" . $pathParts['filename'] . $resizedName . "." . $pathParts['extension']; - - // get image about original image - $imageInfo = getimagesize($filename); - $widthOrig = $imageInfo[0]; - $heightOrig = $imageInfo[1]; - $image = imagecreatetruecolor($width, $height); - if ($imageInfo['mime'] == 'image/gif') { - $source = imagecreatefromgif($filename); - } - elseif ($imageInfo['mime'] == 'image/png') { - $source = imagecreatefrompng($filename); - } - else { - $source = imagecreatefromjpeg($filename); - } - - // resize - imagecopyresized($image, $source, 0, 0, 0, 0, $width, $height, $widthOrig, $heightOrig); - - // save the resized image - $fp = fopen($newFilename, 'w+'); - ob_start(); - imagejpeg($image); - $image_buffer = ob_get_contents(); - ob_end_clean(); - imagedestroy($image); - fwrite($fp, $image_buffer); - rewind($fp); - fclose($fp); - - // return the URL to link to - $config = CRM_Core_Config::singleton(); - return $config->imageUploadURL . basename($newFilename); - } - } diff --git a/CRM/Utils/File.php b/CRM/Utils/File.php index f377b742cd17..9c9449737098 100644 --- a/CRM/Utils/File.php +++ b/CRM/Utils/File.php @@ -916,6 +916,55 @@ public static function getImageURL($imageURL) { return self::getFileURL($path, $mimeType); } + /** + * Resize a premium image to a different size. + * + * @param string $filename + * @param string $resizedName + * @param $width + * @param $height + * + * @return string + * Path to image + */ + public static function resizeImage($filename, $resizedName, $width, $height) { + // figure out the new filename + $pathParts = pathinfo($filename); + $newFilename = $pathParts['dirname'] . "/" . $pathParts['filename'] . $resizedName . "." . $pathParts['extension']; + + // get image about original image + $imageInfo = getimagesize($filename); + $widthOrig = $imageInfo[0]; + $heightOrig = $imageInfo[1]; + $image = imagecreatetruecolor($width, $height); + if ($imageInfo['mime'] == 'image/gif') { + $source = imagecreatefromgif($filename); + } + elseif ($imageInfo['mime'] == 'image/png') { + $source = imagecreatefrompng($filename); + } + else { + $source = imagecreatefromjpeg($filename); + } + + // resize + imagecopyresized($image, $source, 0, 0, 0, 0, $width, $height, $widthOrig, $heightOrig); + + // save the resized image + $fp = fopen($newFilename, 'w+'); + ob_start(); + imagejpeg($image); + $image_buffer = ob_get_contents(); + ob_end_clean(); + imagedestroy($image); + fwrite($fp, $image_buffer); + rewind($fp); + fclose($fp); + + // return the URL to link to + $config = CRM_Core_Config::singleton(); + return $config->imageUploadURL . basename($newFilename); + } /** * Get file icon class for specific MIME Type From 62b3b6e7b36f16410ffff2c33ff452b99a954366 Mon Sep 17 00:00:00 2001 From: Sean Madsen Date: Fri, 21 Jul 2017 10:25:29 -0600 Subject: [PATCH 2/9] CRM-20821 - Refactor resizeImage() - Clean up CRM_Utils_File::resizeImage() - No major functionality changes - Better docblock - Better error handling - Better variable names --- CRM/Contribute/Form/ManagePremiums.php | 4 +- CRM/Utils/File.php | 76 +++++++++++++++++--------- 2 files changed, 53 insertions(+), 27 deletions(-) diff --git a/CRM/Contribute/Form/ManagePremiums.php b/CRM/Contribute/Form/ManagePremiums.php index 47a7a7608c55..55820406de0a 100644 --- a/CRM/Contribute/Form/ManagePremiums.php +++ b/CRM/Contribute/Form/ManagePremiums.php @@ -296,8 +296,8 @@ public function postProcess() { if ($gdSupport) { if ($imageFile) { $error = FALSE; - $params['image'] = CRM_Utils_File::resizeImage($imageFile, "_full", 200, 200); - $params['thumbnail'] = CRM_Utils_File::resizeImage($imageFile, "_thumb", 50, 50); + $params['image'] = CRM_Utils_File::resizeImage($imageFile, 200, 200, "_full"); + $params['thumbnail'] = CRM_Utils_File::resizeImage($imageFile, 50, 50, "_thumb"); } } else { diff --git a/CRM/Utils/File.php b/CRM/Utils/File.php index 9c9449737098..3c3ae27b154a 100644 --- a/CRM/Utils/File.php +++ b/CRM/Utils/File.php @@ -917,53 +917,79 @@ public static function getImageURL($imageURL) { } /** - * Resize a premium image to a different size. - * - * @param string $filename - * @param string $resizedName - * @param $width - * @param $height + * Resize an image. + * + * @param string $sourceFile + * Filesystem path to existing image on server + * @param int $targetWidth + * New width desired, in pixels + * @param int $targetHeight + * New height desired, in pixels + * @param string $suffix = "" + * If supplied, the image will be renamed to include this suffix. For + * example if the original file name is "foo.png" and $suffix = "_bar", + * then the final file name will be "foo_bar.png". * * @return string * Path to image + * @throws \CRM_Core_Exception + * Under the following conditions + * - When GD is not available. + * - When the source file is not an image. */ - public static function resizeImage($filename, $resizedName, $width, $height) { - // figure out the new filename - $pathParts = pathinfo($filename); - $newFilename = $pathParts['dirname'] . "/" . $pathParts['filename'] . $resizedName . "." . $pathParts['extension']; + public static function resizeImage($sourceFile, $targetWidth, $targetHeight, $suffix = "") { - // get image about original image - $imageInfo = getimagesize($filename); - $widthOrig = $imageInfo[0]; - $heightOrig = $imageInfo[1]; - $image = imagecreatetruecolor($width, $height); - if ($imageInfo['mime'] == 'image/gif') { - $source = imagecreatefromgif($filename); + // Check if GD is installed + $gdSupport = CRM_Utils_System::getModuleSetting('gd', 'GD Support'); + if (!$gdSupport) { + throw new CRM_Core_Exception(ts('Unable to resize image because the GD image library is not currently compiled in your PHP installation.')); + } + + $sourceMime = mime_content_type($sourceFile); + if ($sourceMime == 'image/gif') { + $sourceData = imagecreatefromgif($sourceFile); + } + elseif ($sourceMime == 'image/png') { + $sourceData = imagecreatefrompng($sourceFile); } - elseif ($imageInfo['mime'] == 'image/png') { - $source = imagecreatefrompng($filename); + elseif ($sourceMime == 'image/jpeg') { + $sourceData = imagecreatefromjpeg($sourceFile); } else { - $source = imagecreatefromjpeg($filename); + throw new CRM_Core_Exception(ts('Unable to resize image because the file supplied was not an image.')); } + // get image about original image + $sourceInfo = getimagesize($sourceFile); + $sourceWidth = $sourceInfo[0]; + $sourceHeight = $sourceInfo[1]; + + // figure out the new filename + $pathParts = pathinfo($sourceFile); + $targetFile = $pathParts['dirname'] . DIRECTORY_SEPARATOR + . $pathParts['filename'] . $suffix . "." . $pathParts['extension']; + + $targetData = imagecreatetruecolor($targetWidth, $targetHeight); + // resize - imagecopyresized($image, $source, 0, 0, 0, 0, $width, $height, $widthOrig, $heightOrig); + imagecopyresized($targetData, $sourceData, + 0, 0, 0, 0, + $targetWidth, $targetHeight, $sourceWidth, $sourceHeight); // save the resized image - $fp = fopen($newFilename, 'w+'); + $fp = fopen($targetFile, 'w+'); ob_start(); - imagejpeg($image); + imagejpeg($targetData); $image_buffer = ob_get_contents(); ob_end_clean(); - imagedestroy($image); + imagedestroy($targetData); fwrite($fp, $image_buffer); rewind($fp); fclose($fp); // return the URL to link to $config = CRM_Core_Config::singleton(); - return $config->imageUploadURL . basename($newFilename); + return $config->imageUploadURL . basename($targetFile); } /** From 706f6a014f442600e0b544b781f8087db119d877 Mon Sep 17 00:00:00 2001 From: Sean Madsen Date: Fri, 21 Jul 2017 10:31:12 -0600 Subject: [PATCH 3/9] CRM-20821 - Refactor PremiumProduct BAO add() - Refactor CRM_Contribute_BAO_ManagePremiums::add - Don't change any functionality - Improve docblock - Use a $defaults array --- CRM/Contribute/BAO/ManagePremiums.php | 29 ++++++++++++--------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/CRM/Contribute/BAO/ManagePremiums.php b/CRM/Contribute/BAO/ManagePremiums.php index 9ddf411b79f3..30f17ad3de0e 100644 --- a/CRM/Contribute/BAO/ManagePremiums.php +++ b/CRM/Contribute/BAO/ManagePremiums.php @@ -86,17 +86,26 @@ public static function setIsActive($id, $is_active) { } /** - * add the financial types. + * Add a premium product to the database, and return it. * * @param array $params * Reference array contains the values submitted by the form. * @param array $ids * Reference array contains the id. * - * - * @return object + * @return CRM_Contribute_DAO_Product */ public static function add(&$params, &$ids) { + $defaults = array( + 'id' => CRM_Utils_Array::value('premium', $ids), + 'image' => '', + 'thumbnail' => '', + 'is_active' => FALSE, + 'is_deductible' => FALSE, + 'currency' => CRM_Core_Config::singleton()->defaultCurrency, + ); + $params = array_merge($defaults, $params); + // CRM-14283 - strip protocol and domain from image URLs $image_type = array('image', 'thumbnail'); foreach ($image_type as $key) { @@ -107,21 +116,9 @@ public static function add(&$params, &$ids) { } } - $params['is_active'] = CRM_Utils_Array::value('is_active', $params, FALSE); - $params['is_deductible'] = CRM_Utils_Array::value('is_deductible', $params, FALSE); - - // action is taken depending upon the mode + // Save and return $premium = new CRM_Contribute_DAO_Product(); $premium->copyValues($params); - - $premium->id = CRM_Utils_Array::value('premium', $ids); - - // set currency for CRM-1496 - if (!isset($premium->currency)) { - $config = CRM_Core_Config::singleton(); - $premium->currency = $config->defaultCurrency; - } - $premium->save(); return $premium; } From 5459a6da39d666a203e2b4986bed54c4c94ed2e7 Mon Sep 17 00:00:00 2001 From: Sean Madsen Date: Fri, 21 Jul 2017 10:41:37 -0600 Subject: [PATCH 4/9] CRM-20821 - Refactor ManagePremiums postProcess() - Refactor CRM_Contribute_Form_ManagePremiums::postProcess() - No major changes to functionality - Eliminate deeply nested code (addressing "FIXME" comment) - Split code into multiple functions - Add more comments --- CRM/Contribute/Form/ManagePremiums.php | 148 ++++++++++++++----------- 1 file changed, 85 insertions(+), 63 deletions(-) diff --git a/CRM/Contribute/Form/ManagePremiums.php b/CRM/Contribute/Form/ManagePremiums.php index 55820406de0a..aabda090ddc2 100644 --- a/CRM/Contribute/Form/ManagePremiums.php +++ b/CRM/Contribute/Form/ManagePremiums.php @@ -266,82 +266,104 @@ public static function formRule($params, $files) { * Process the form submission. */ public function postProcess() { - + // If previewing, don't do any post-processing if ($this->_action & CRM_Core_Action::PREVIEW) { return; } + // If deleting, then only delete and skip the rest of the post-processing if ($this->_action & CRM_Core_Action::DELETE) { CRM_Contribute_BAO_ManagePremiums::del($this->_id); - CRM_Core_Session::setStatus(ts('Selected Premium Product type has been deleted.'), ts('Deleted'), 'info'); + CRM_Core_Session::setStatus( + ts('Selected Premium Product type has been deleted.'), + ts('Deleted'), 'info'); + return; } - else { - $params = $this->controller->exportValues($this->_name); - $imageFile = CRM_Utils_Array::value('uploadFile', $params); - $imageFile = $imageFile['name']; - - $config = CRM_Core_Config::singleton(); - - $ids = array(); - $error = FALSE; - // store the submitted values in an array - - // FIX ME - if (CRM_Utils_Array::value('imageOption', $params, FALSE)) { - $value = CRM_Utils_Array::value('imageOption', $params, FALSE); - if ($value == 'image') { - - // to check wether GD is installed or not - $gdSupport = CRM_Utils_System::getModuleSetting('gd', 'GD Support'); - if ($gdSupport) { - if ($imageFile) { - $error = FALSE; - $params['image'] = CRM_Utils_File::resizeImage($imageFile, 200, 200, "_full"); - $params['thumbnail'] = CRM_Utils_File::resizeImage($imageFile, 50, 50, "_thumb"); - } - } - else { - $error = TRUE; - $params['image'] = $config->resourceBase . 'i/contribute/default_premium.jpg'; - $params['thumbnail'] = $config->resourceBase . 'i/contribute/default_premium_thumb.jpg'; - } - } - elseif ($value == 'thumbnail') { - $params['image'] = $params['imageUrl']; - $params['thumbnail'] = $params['thumbnailUrl']; - } - elseif ($value == 'default_image') { - $url = parse_url($config->userFrameworkBaseURL); - $params['image'] = $config->resourceBase . 'i/contribute/default_premium.jpg'; - $params['thumbnail'] = $config->resourceBase . 'i/contribute/default_premium_thumb.jpg'; - } - else { - $params['image'] = ""; - $params['thumbnail'] = ""; - } - } - if ($this->_action & CRM_Core_Action::UPDATE) { - $ids['premium'] = $this->_id; - } + $params = $this->controller->exportValues($this->_name); - // fix the money fields - foreach (array( - 'cost', - 'price', - 'min_contribution', - ) as $f) { - $params[$f] = CRM_Utils_Rule::cleanMoney($params[$f]); - } + // Clean the the money fields + $moneyFields = array('cost', 'price', 'min_contribution'); + foreach ($moneyFields as $field) { + $params[$field] = CRM_Utils_Rule::cleanMoney($params[$field]); + } + + $ids = array(); + if ($this->_action & CRM_Core_Action::UPDATE) { + $ids['premium'] = $this->_id; + } + + $this->_processImages($params); - $premium = CRM_Contribute_BAO_ManagePremiums::add($params, $ids); - if ($error) { - CRM_Core_Session::setStatus(ts('No thumbnail of your image was created because the GD image library is not currently compiled in your PHP installation. Product is currently configured to use default thumbnail image. If you have a local thumbnail image you can upload it separately and input the thumbnail URL by editing this premium.'), ts('Notice'), 'alert'); + // Save to database + $premium = CRM_Contribute_BAO_ManagePremiums::add($params, $ids); + + CRM_Core_Session::setStatus( + ts("The Premium '%1' has been saved.", array(1 => $premium->name)), + ts('Saved'), 'success'); + } + + /** + * Look at $params to find form info about images. Manipulate images if + * necessary. Then alter $params to point to the newly manipulated images. + * + * @param array $params + */ + protected function _processImages(&$params) { + $defaults = array( + 'imageOption' => 'noImage', + 'uploadFile' => array('name' => ''), + 'image' => '', + 'thumbnail' => '', + 'imageUrl' => '', + 'thumbnailUrl' => '', + ); + $params = array_merge($defaults, $params); + + // User is uploading an image + if ($params['imageOption'] == 'image') { + $imageFile = $params['uploadFile']['name']; + try { + $params['image'] = CRM_Utils_File::resizeImage($imageFile, 200, 200, "_full"); + $params['thumbnail'] = CRM_Utils_File::resizeImage($imageFile, 50, 50, "_thumb"); } - else { - CRM_Core_Session::setStatus(ts("The Premium '%1' has been saved.", array(1 => $premium->name)), ts('Saved'), 'success'); + catch (CRM_Core_Exception $e) { + $params['image'] = self::_defaultImage(); + $params['thumbnail'] = self::_defaultThumbnail(); + $msg = ts('The product has been configured to use a default image.'); + CRM_Core_Session::setStatus($e->getMessage() . " $msg", ts('Notice'), 'alert'); } } + + // User is specifying existing URLs for the images + elseif ($params['imageOption'] == 'thumbnail') { + $params['image'] = $params['imageUrl']; + $params['thumbnail'] = $params['thumbnailUrl']; + } + + // User wants a default image + elseif ($params['imageOption'] == 'default_image') { + $params['image'] = self::_defaultImage(); + $params['thumbnail'] = self::_defaultThumbnail(); + } + } + + /** + * Returns the path to the default premium image + * @return string + */ + protected static function _defaultImage() { + $config = CRM_Core_Config::singleton(); + return $config->resourceBase . 'i/contribute/default_premium.jpg'; + } + + /** + * Returns the path to the default premium thumbnail + * @return string + */ + protected static function _defaultThumbnail() { + $config = CRM_Core_Config::singleton(); + return $config->resourceBase . 'i/contribute/default_premium_thumb.jpg'; } } From aa4ad33a7ab27bfa3ab8c88b824ed0d4cff4375f Mon Sep 17 00:00:00 2001 From: Sean Madsen Date: Fri, 21 Jul 2017 10:43:49 -0600 Subject: [PATCH 5/9] CRM-20821 - Improve ManagePremiums formRule() - Improve CRM_Contribute_Form_ManagePremiums::formRule() - Require an image file, if "upload" is chosen - Remove the commented-out code for a rule which required images to be within a certain size - Minor refactoring to improve code clarity --- CRM/Contribute/Form/ManagePremiums.php | 35 +++++++++++++------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/CRM/Contribute/Form/ManagePremiums.php b/CRM/Contribute/Form/ManagePremiums.php index aabda090ddc2..437505d8349f 100644 --- a/CRM/Contribute/Form/ManagePremiums.php +++ b/CRM/Contribute/Form/ManagePremiums.php @@ -201,35 +201,36 @@ public function buildQuickForm() { * * @param array $params * (ref.) an assoc array of name/value pairs. - * * @param $files * * @return bool|array * mixed true or array of errors */ public static function formRule($params, $files) { - if (isset($params['imageOption'])) { - if ($params['imageOption'] == 'thumbnail') { - if (!$params['imageUrl']) { - $errors['imageUrl'] = ts('Image URL is Required'); - } - if (!$params['thumbnailUrl']) { - $errors['thumbnailUrl'] = ts('Thumbnail URL is Required'); - } + + // If choosing to upload an image, then an image must be provided + if ( + isset($params['imageOption']) && + $params['imageOption'] == 'image' && + empty($files['uploadFile']['name']) + ) { + $errors['uploadFile'] = ts('A file must be selected'); + } + + // If choosing to use image URLs, then both URLs must be present + if (isset($params['imageOption']) && $params['imageOption'] == 'thumbnail') { + if (!$params['imageUrl']) { + $errors['imageUrl'] = ts('Image URL is Required'); + } + if (!$params['thumbnailUrl']) { + $errors['thumbnailUrl'] = ts('Thumbnail URL is Required'); } } + // CRM-13231 financial type required if product has cost if (!empty($params['cost']) && empty($params['financial_type_id'])) { $errors['financial_type_id'] = ts('Financial Type is required for product having cost.'); } - $fileLocation = $files['uploadFile']['tmp_name']; - if ($fileLocation != "") { - list($width, $height) = getimagesize($fileLocation); - - if (($width < 80 || $width > 500) || ($height < 80 || $height > 500)) { - //$errors ['uploadFile'] = "Please Enter files with dimensions between 80 x 80 and 500 x 500," . " Dimensions of this file is ".$width."X".$height; - } - } if (!$params['period_type']) { if ($params['fixed_period_start_day'] || $params['duration_unit'] || $params['duration_interval'] || From 6c094ca6503eaf7f88650101f850a36bb1749cb2 Mon Sep 17 00:00:00 2001 From: Sean Madsen Date: Fri, 21 Jul 2017 10:53:16 -0600 Subject: [PATCH 6/9] CRM-20821 - Use smarter logic to adjust img URL This change is the crux of CRM-20821. Before this change, editing a premium product would automatically break the image URL. After this change, premium products can be edited while retaining the image URL. Functionality is preserved which changes the URL to a local URL when possible. This commit adds two new Utils functions (along with tests) to handle the logic of changing a supplied URL to a local URL. --- CRM/Contribute/BAO/ManagePremiums.php | 12 +-- CRM/Utils/String.php | 74 ++++++++++++++ tests/phpunit/CRM/Utils/StringTest.php | 136 +++++++++++++++++++++++++ 3 files changed, 213 insertions(+), 9 deletions(-) diff --git a/CRM/Contribute/BAO/ManagePremiums.php b/CRM/Contribute/BAO/ManagePremiums.php index 30f17ad3de0e..40de859a9af2 100644 --- a/CRM/Contribute/BAO/ManagePremiums.php +++ b/CRM/Contribute/BAO/ManagePremiums.php @@ -106,15 +106,9 @@ public static function add(&$params, &$ids) { ); $params = array_merge($defaults, $params); - // CRM-14283 - strip protocol and domain from image URLs - $image_type = array('image', 'thumbnail'); - foreach ($image_type as $key) { - if (isset($params[$key]) && $params[$key]) { - $parsedURL = explode('/', $params[$key]); - $pathComponents = array_slice($parsedURL, 3); - $params[$key] = '/' . implode('/', $pathComponents); - } - } + // Use local URLs for images when possible + $params['image'] = CRM_Utils_String::simplifyURL($params['image'], TRUE); + $params['thumbnail'] = CRM_Utils_String::simplifyURL($params['thumbnail'], TRUE); // Save and return $premium = new CRM_Contribute_DAO_Product(); diff --git a/CRM/Utils/String.php b/CRM/Utils/String.php index fc28c0f000b7..17aed7f626f1 100644 --- a/CRM/Utils/String.php +++ b/CRM/Utils/String.php @@ -789,6 +789,80 @@ public static function unstupifyUrl($htmlUrl) { return str_replace('&', '&', $htmlUrl); } + /** + * When a user supplies a URL (e.g. to an image), we'd like to: + * - Remove the protocol and domain name if the URL points to the current + * site. + * - Keep the domain name for remote URLs. + * - Optionally, force remote URLs to use https instead of http (which is + * useful for images) + * + * @param string $url + * The URL to simplify. Examples: + * "https://example.org/sites/default/files/coffee-mug.jpg" + * "sites/default/files/coffee-mug.jpg" + * "http://i.stack.imgur.com/9jb2ial01b.png" + * @param bool $forceHttps = FALSE + * If TRUE, ensure that remote URLs use https. If a URL with + * http is supplied, then we'll change it to https. + * This is useful for situations like showing a premium product on a + * contribution, because (as reported in CRM-14283) if the user gets a + * browser warning like "page contains insecure elements" on a contribution + * page, that's a very bad thing. Thus, even if changing http to https + * breaks the image, that's better than leaving http content in a + * contribution page. + * + * @return string + * The simplified URL. Examples: + * "/sites/default/files/coffee-mug.jpg" + * "https://i.stack.imgur.com/9jb2ial01b.png" + */ + public static function simplifyURL($url, $forceHttps = FALSE) { + $config = CRM_Core_Config::singleton(); + $siteURLParts = self::simpleParseUrl($config->userFrameworkBaseURL); + $urlParts = self::simpleParseUrl($url); + + // If the image is locally hosted, then only give the path to the image + $urlIsLocal + = ($urlParts['host+port'] == '') + | ($urlParts['host+port'] == $siteURLParts['host+port']); + if ($urlIsLocal) { + // and make sure it begins with one forward slash + return preg_replace('_^/*(?=.)_', '/', $urlParts['path+query']); + } + + // If the URL is external, then keep the full URL as supplied + else { + return $forceHttps ? preg_replace('_^http://_', 'https://', $url) : $url; + } + } + + /** + * A simplified version of PHP's parse_url() function. + * + * @param string $url + * e.g. "https://example.com:8000/foo/bar/?id=1#fragment" + * + * @return array + * Will always contain keys 'host+port' and 'path+query', even if they're + * empty strings. Example: + * [ + * 'host+port' => "example.com:8000", + * 'path+query' => "/foo/bar/?id=1", + * ] + */ + public static function simpleParseUrl($url) { + $parts = parse_url($url); + $host = isset($parts['host']) ? $parts['host'] : ''; + $port = isset($parts['port']) ? ':' . $parts['port'] : ''; + $path = isset($parts['path']) ? $parts['path'] : ''; + $query = isset($parts['query']) ? '?' . $parts['query'] : ''; + return array( + 'host+port' => "$host$port", + 'path+query' => "$path$query", + ); + } + /** * Formats a string of attributes for insertion in an html tag. * diff --git a/tests/phpunit/CRM/Utils/StringTest.php b/tests/phpunit/CRM/Utils/StringTest.php index ddecfb22a99a..5271cb922d61 100644 --- a/tests/phpunit/CRM/Utils/StringTest.php +++ b/tests/phpunit/CRM/Utils/StringTest.php @@ -227,4 +227,140 @@ public function testFilterByWildCards($patterns, $expectedResults) { $this->assertEquals(array_merge($expectedResults, array('noise')), $actualResults); } + /** + * CRM-20821 + * CRM-14283 + * + * @param string $imageURL + * @param book $forceHttps + * @param string $expected + * + * @dataProvider simplifyURLProvider + */ + public function testSimplifyURL($imageURL, $forceHttps, $expected) { + $this->assertEquals( + $expected, + CRM_Utils_String::simplifyURL($imageURL, $forceHttps) + ); + } + + /** + * Used for testNormalizeImageURL above + * + * @return array + */ + public function simplifyURLProvider() { + + $config = CRM_Core_Config::singleton(); + $localDomain = parse_url($config->userFrameworkBaseURL)['host']; + $externalDomain = 'example.org'; + + // Ensure that $externalDomain really is different from $localDomain + if ($externalDomain == $localDomain) { + $externalDomain = 'example.net'; + } + + return array( + + 'prototypical example' => + array( + "https://$localDomain/sites/default/files/coffee-mug.jpg", + FALSE, + '/sites/default/files/coffee-mug.jpg', + ), + + 'external domain with https' => + array( + "https://$externalDomain/sites/default/files/coffee-mug.jpg", + FALSE, + "https://$externalDomain/sites/default/files/coffee-mug.jpg", + ), + + 'external domain with http forced to https' => + array( + "http://$externalDomain/sites/default/files/coffee-mug.jpg", + TRUE, + "https://$externalDomain/sites/default/files/coffee-mug.jpg", + ), + + 'external domain with http not forced' => + array( + "http://$externalDomain/sites/default/files/coffee-mug.jpg", + FALSE, + "http://$externalDomain/sites/default/files/coffee-mug.jpg", + ), + + 'local URL' => + array( + "/sites/default/files/coffee-mug.jpg", + FALSE, + "/sites/default/files/coffee-mug.jpg", + ), + + 'local URL without a forward slash' => + array( + "sites/default/files/coffee-mug.jpg", + FALSE, + "/sites/default/files/coffee-mug.jpg", + ), + + 'empty input' => + array( + '', + FALSE, + '', + ), + ); + } + + /** + * @param string $url + * @param array $expected + * + * @dataProvider parseURLProvider + */ + public function testSimpleParseUrl($url, $expected) { + $this->assertEquals( + $expected, + CRM_Utils_String::simpleParseUrl($url) + ); + } + + /** + * Used for testSimpleParseUrl above + * + * @return array + */ + public function parseURLProvider() { + return array( + + "prototypical example" => + array( + "https://example.com:8000/foo/bar/?id=1#fragment", + array( + 'host+port' => "example.com:8000", + 'path+query' => "/foo/bar/?id=1", + ), + ), + + "empty" => + array( + "", + array( + 'host+port' => "", + 'path+query' => "", + ), + ), + + "path only" => + array( + "/foo/bar/image.png", + array( + 'host+port' => "", + 'path+query' => "/foo/bar/image.png", + ), + ), + ); + } + } From ae969bc19ef7b31eb472f9fb0e7e2e9bc8275e14 Mon Sep 17 00:00:00 2001 From: Sean Madsen Date: Fri, 21 Jul 2017 11:01:39 -0600 Subject: [PATCH 7/9] CRM-20938 - Maintain aspect ratio in resizeImage() Make CRM_Utils_File::resizeImage() preserve image aspect ratio by default. --- CRM/Utils/File.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/CRM/Utils/File.php b/CRM/Utils/File.php index 3c3ae27b154a..2ac57eb3b802 100644 --- a/CRM/Utils/File.php +++ b/CRM/Utils/File.php @@ -929,6 +929,11 @@ public static function getImageURL($imageURL) { * If supplied, the image will be renamed to include this suffix. For * example if the original file name is "foo.png" and $suffix = "_bar", * then the final file name will be "foo_bar.png". + * @param bool $preserveAspect = TRUE + * When TRUE $width and $height will be used as a bounding box, outside of + * which the resized image will not extend. + * When FALSE, the image will be resized exactly to $width and $height, even + * if it means stretching it. * * @return string * Path to image @@ -937,7 +942,7 @@ public static function getImageURL($imageURL) { * - When GD is not available. * - When the source file is not an image. */ - public static function resizeImage($sourceFile, $targetWidth, $targetHeight, $suffix = "") { + public static function resizeImage($sourceFile, $targetWidth, $targetHeight, $suffix = "", $preserveAspect = TRUE) { // Check if GD is installed $gdSupport = CRM_Utils_System::getModuleSetting('gd', 'GD Support'); @@ -964,6 +969,18 @@ public static function resizeImage($sourceFile, $targetWidth, $targetHeight, $su $sourceWidth = $sourceInfo[0]; $sourceHeight = $sourceInfo[1]; + // Adjust target width/height if preserving aspect ratio + if ($preserveAspect) { + $sourceAspect = $sourceWidth / $sourceHeight; + $targetAspect = $targetWidth / $targetHeight; + if ($sourceAspect > $targetAspect) { + $targetHeight = $targetWidth / $sourceAspect; + } + if ($sourceAspect < $targetAspect) { + $targetWidth = $targetHeight * $sourceAspect; + } + } + // figure out the new filename $pathParts = pathinfo($sourceFile); $targetFile = $pathParts['dirname'] . DIRECTORY_SEPARATOR From e0d4afb7421e3451979d0354b9e255275918ee68 Mon Sep 17 00:00:00 2001 From: Sean Madsen Date: Fri, 21 Jul 2017 13:13:31 -0600 Subject: [PATCH 8/9] Don't use array dereferencing (for PHP 5.3 compat) --- tests/phpunit/CRM/Utils/StringTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/CRM/Utils/StringTest.php b/tests/phpunit/CRM/Utils/StringTest.php index 5271cb922d61..98a674e7c217 100644 --- a/tests/phpunit/CRM/Utils/StringTest.php +++ b/tests/phpunit/CRM/Utils/StringTest.php @@ -252,7 +252,8 @@ public function testSimplifyURL($imageURL, $forceHttps, $expected) { public function simplifyURLProvider() { $config = CRM_Core_Config::singleton(); - $localDomain = parse_url($config->userFrameworkBaseURL)['host']; + $urlParts = parse_url($config->userFrameworkBaseURL); + $localDomain = $urlParts['host']; $externalDomain = 'example.org'; // Ensure that $externalDomain really is different from $localDomain From bb80d2f93cca41f4ceb725a2302f9c860183626e Mon Sep 17 00:00:00 2001 From: "deb.monish" Date: Mon, 24 Jul 2017 17:39:52 +0530 Subject: [PATCH 9/9] additional fix --- CRM/Contribute/BAO/ManagePremiums.php | 15 ++++------ CRM/Contribute/Form/ManagePremiums.php | 8 ++--- tests/phpunit/CRM/Utils/StringTest.php | 41 +++++++------------------- 3 files changed, 18 insertions(+), 46 deletions(-) diff --git a/CRM/Contribute/BAO/ManagePremiums.php b/CRM/Contribute/BAO/ManagePremiums.php index 40de859a9af2..4b00a93734e2 100644 --- a/CRM/Contribute/BAO/ManagePremiums.php +++ b/CRM/Contribute/BAO/ManagePremiums.php @@ -96,19 +96,14 @@ public static function setIsActive($id, $is_active) { * @return CRM_Contribute_DAO_Product */ public static function add(&$params, &$ids) { - $defaults = array( + $params = array_merge(array( 'id' => CRM_Utils_Array::value('premium', $ids), - 'image' => '', - 'thumbnail' => '', - 'is_active' => FALSE, + 'image' => CRM_Utils_String::simplifyURL(CRM_Utils_Array::value('image', $params, ''), TRUE), + 'thumbnail' => CRM_Utils_String::simplifyURL(CRM_Utils_Array::value('thumbnail', $params, ''), TRUE), + 'is_active' => 0, 'is_deductible' => FALSE, 'currency' => CRM_Core_Config::singleton()->defaultCurrency, - ); - $params = array_merge($defaults, $params); - - // Use local URLs for images when possible - $params['image'] = CRM_Utils_String::simplifyURL($params['image'], TRUE); - $params['thumbnail'] = CRM_Utils_String::simplifyURL($params['thumbnail'], TRUE); + ), $params); // Save and return $premium = new CRM_Contribute_DAO_Product(); diff --git a/CRM/Contribute/Form/ManagePremiums.php b/CRM/Contribute/Form/ManagePremiums.php index 437505d8349f..2cb9c4fa6c80 100644 --- a/CRM/Contribute/Form/ManagePremiums.php +++ b/CRM/Contribute/Form/ManagePremiums.php @@ -209,16 +209,14 @@ public function buildQuickForm() { public static function formRule($params, $files) { // If choosing to upload an image, then an image must be provided - if ( - isset($params['imageOption']) && - $params['imageOption'] == 'image' && - empty($files['uploadFile']['name']) + if (CRM_Utils_Array::value('imageOption', $params['imageOption']) == 'image' + && empty($files['uploadFile']['name']) ) { $errors['uploadFile'] = ts('A file must be selected'); } // If choosing to use image URLs, then both URLs must be present - if (isset($params['imageOption']) && $params['imageOption'] == 'thumbnail') { + if (CRM_Utils_Array::value('imageOption', $params['imageOption']) == 'thumbnail') { if (!$params['imageUrl']) { $errors['imageUrl'] = ts('Image URL is Required'); } diff --git a/tests/phpunit/CRM/Utils/StringTest.php b/tests/phpunit/CRM/Utils/StringTest.php index 98a674e7c217..ba04a54f31ce 100644 --- a/tests/phpunit/CRM/Utils/StringTest.php +++ b/tests/phpunit/CRM/Utils/StringTest.php @@ -250,7 +250,6 @@ public function testSimplifyURL($imageURL, $forceHttps, $expected) { * @return array */ public function simplifyURLProvider() { - $config = CRM_Core_Config::singleton(); $urlParts = parse_url($config->userFrameworkBaseURL); $localDomain = $urlParts['host']; @@ -262,51 +261,37 @@ public function simplifyURLProvider() { } return array( - - 'prototypical example' => - array( + 'prototypical example' => array( "https://$localDomain/sites/default/files/coffee-mug.jpg", FALSE, '/sites/default/files/coffee-mug.jpg', ), - - 'external domain with https' => - array( + 'external domain with https' => array( "https://$externalDomain/sites/default/files/coffee-mug.jpg", FALSE, "https://$externalDomain/sites/default/files/coffee-mug.jpg", ), - - 'external domain with http forced to https' => - array( + 'external domain with http forced to https' => array( "http://$externalDomain/sites/default/files/coffee-mug.jpg", TRUE, "https://$externalDomain/sites/default/files/coffee-mug.jpg", ), - - 'external domain with http not forced' => - array( + 'external domain with http not forced' => array( "http://$externalDomain/sites/default/files/coffee-mug.jpg", FALSE, "http://$externalDomain/sites/default/files/coffee-mug.jpg", ), - - 'local URL' => - array( + 'local URL' => array( "/sites/default/files/coffee-mug.jpg", FALSE, "/sites/default/files/coffee-mug.jpg", ), - - 'local URL without a forward slash' => - array( + 'local URL without a forward slash' => array( "sites/default/files/coffee-mug.jpg", FALSE, "/sites/default/files/coffee-mug.jpg", ), - - 'empty input' => - array( + 'empty input' => array( '', FALSE, '', @@ -334,27 +319,21 @@ public function testSimpleParseUrl($url, $expected) { */ public function parseURLProvider() { return array( - - "prototypical example" => - array( + "prototypical example" => array( "https://example.com:8000/foo/bar/?id=1#fragment", array( 'host+port' => "example.com:8000", 'path+query' => "/foo/bar/?id=1", ), ), - - "empty" => - array( + "empty" => array( "", array( 'host+port' => "", 'path+query' => "", ), ), - - "path only" => - array( + "path only" => array( "/foo/bar/image.png", array( 'host+port' => "",