From eef20220d91ed102bb8d55394c334a8778f22136 Mon Sep 17 00:00:00 2001 From: Gabriel Jenik Date: Tue, 10 Nov 2020 06:41:45 -0300 Subject: [PATCH] Bug/16733 when importing a theme bigger in size than the allowed php.ini settings, there is no proper description of the error (#1633) * Fixed issue #16733: When importing a theme bigger in size than the allowed PHP.ini settings, there is no proper description of the error Adding helper functions for checking uploads filesize * Fixed issue #16733: When importing a theme bigger in size than the allowed PHP.ini settings, there is no proper description of the error Applying fix on - Theme import (Themes list and Theme editor) - File upload in Theme editor (uploading files to a theme) - Image upload (in global theme options) - Image upload (in survey theme options * Fixed issue #16733: When importing a theme bigger in size than the allowed PHP.ini settings, there is no proper description of the error Creating Uploadhelper and moving helper functions to class methods. --- application/controllers/admin/surveyadmin.php | 26 +- application/controllers/admin/themes.php | 343 ++++++++++-------- application/core/LSUploadHelper.php | 99 +++++ .../views/admin/themeoptions/import_modal.php | 2 +- .../views/admin/themeoptions/update.php | 2 +- .../views/admin/themes/importform_view.php | 2 +- themes/survey/bootswatch/options/options.twig | 2 +- themes/survey/fruity/options/options.twig | 2 +- themes/survey/vanilla/options/options.twig | 2 +- 9 files changed, 301 insertions(+), 179 deletions(-) create mode 100644 application/core/LSUploadHelper.php diff --git a/application/controllers/admin/surveyadmin.php b/application/controllers/admin/surveyadmin.php index c9e33ca0f21..afd2c05d201 100644 --- a/application/controllers/admin/surveyadmin.php +++ b/application/controllers/admin/surveyadmin.php @@ -2336,9 +2336,14 @@ public function applythemeoptions($iSurveyID = 0) */ public function uploadimagefile() { + $debug = [$_FILES]; + // Check file size and render JSON on error. + // This is done before checking the survey permissions because, if the max POST size was exceeded, + // there is no Survey ID to check for permissions, so the error could be misleading. + LSUploadHelper::checkUploadedFileSizeAndRenderJson('file', $debug); + $iSurveyID = Yii::app()->request->getPost('surveyid'); $success = false; - $debug = []; if(!Permission::model()->hasSurveyPermission($iSurveyID, 'surveycontent', 'update')) { return Yii::app()->getController()->renderPartial( '/admin/super/_renderJson', @@ -2347,25 +2352,6 @@ public function uploadimagefile() false ); } - $debug[] = $_FILES; - if(empty($_FILES)) { - $uploadresult = gT("No file was uploaded."); - return Yii::app()->getController()->renderPartial( - '/admin/super/_renderJson', - array('data' => ['success' => $success, 'message' => $uploadresult, 'debug' => $debug]), - false, - false - ); - } - if ($_FILES['file']['error'] == 1 || $_FILES['file']['error'] == 2) { - $uploadresult = sprintf(gT("Sorry, this file is too large. Only files up to %01.2f MB are allowed."), getMaximumFileUploadSize() / 1024 / 1024); - return Yii::app()->getController()->renderPartial( - '/admin/super/_renderJson', - array('data' => ['success' => $success, 'message' => $uploadresult, 'debug' => $debug]), - false, - false - ); - } $checkImage = LSYii_ImageValidator::validateImage($_FILES["file"]); if ($checkImage['check'] === false) { return Yii::app()->getController()->renderPartial( diff --git a/application/controllers/admin/themes.php b/application/controllers/admin/themes.php index e7505c47f58..24d00b92668 100755 --- a/application/controllers/admin/themes.php +++ b/application/controllers/admin/themes.php @@ -165,169 +165,204 @@ public function tmp($id) } /** - * Responsible to import a template archive. + * Render the Upload theme view * * @access public * @return void */ public function upload() { - $action = returnGlobal('action'); - if ($action == 'templateuploadimagefile' && Yii::app()->request->getPost('surveyid') ) { - Yii::app()->getController()->forward("/admin/survey/sa/uploadimagefile/"); + $sTemplateName = Yii::app()->request->getPost('templatename'); + if (Permission::model()->hasGlobalPermission('templates', 'import') || Permission::model()->hasTemplatePermission($sTemplateName)) { + Yii::app()->loadHelper('admin/template'); + $lid = returnGlobal('lid'); + + $aViewUrls = 'importform_view'; + $aData = array('lid' => $lid); + + $this->_renderWrappedTemplate('themes', $aViewUrls, $aData); + } else { + Yii::app()->setFlashMessage(gT("We are sorry but you don't have permissions to do this."), 'error'); + $this->getController()->redirect(array("admin/themeoptions")); + } + } + + /** + * Responsible to import a template image file. + * + * Called from Theme Options + * + * @access public + * @return void + */ + public function templateuploadimagefile() + { + if (Yii::app()->request->getPost('surveyid') ) { + Yii::app()->getController()->forward("/admin/survey/sa/uploadimagefile"); Yii::app()->end(); } $sTemplateName = Yii::app()->request->getPost('templatename'); if (Permission::model()->hasGlobalPermission('templates', 'import') || Permission::model()->hasTemplatePermission($sTemplateName)) { Yii::app()->loadHelper('admin/template'); - $lid = returnGlobal('lid'); $uploadresult = ""; $success = false; $debug = []; - if ($action == 'templateuploadimagefile') { - // $iTemplateConfigurationId = Yii::app()->request->getPost('templateconfig'); - // $oTemplateConfiguration = TemplateConfiguration::getInstanceFromConfigurationId($iTemplateConfigurationId); - $oTemplateConfiguration = Template::getInstance($sTemplateName); - - $debug[] = $sTemplateName; - $debug[] = $oTemplateConfiguration; - if (Yii::app()->getConfig('demoMode')) { - $uploadresult = gT("Demo mode: Uploading images is disabled."); - return Yii::app()->getController()->renderPartial( - '/admin/super/_renderJson', - array('data' => ['success' => $success, 'message' => $uploadresult, 'debug' => $debug]), - false, - false - ); - } - $debug[] = $_FILES; - if ($_FILES['file']['error'] == 1 || $_FILES['file']['error'] == 2) { - $uploadresult = sprintf(gT("Sorry, this file is too large. Only files up to %01.2f MB are allowed."), getMaximumFileUploadSize() / 1024 / 1024); - return Yii::app()->getController()->renderPartial( - '/admin/super/_renderJson', - array('data' => ['success' => $success, 'message' => $uploadresult, 'debug' => $debug]), - false, - false - ); - } - $checkImageContent = LSYii_ImageValidator::validateImage($_FILES["file"]); - $checkImageFilename = LSYii_ImageValidator::validateImage($_FILES["file"]['name']); - if ($checkImageContent['check'] === false || $checkImageFilename['check'] === false) { - $message = $checkImageContent['check'] === false - ? $checkImageContent['uploadresult'] - : ($checkImageFilename['check'] === false ? $checkImageFilename['uploadresult']: null); - $debug = $checkImageContent['check'] === false - ? $checkImageContent['debug'] - : ($checkImageFilename['check'] === false ? $checkImageFilename['debug']: null); - return Yii::app()->getController()->renderPartial( - '/admin/super/_renderJson', - array('data' => ['success' => $success, 'message' => $message, 'debug' => $debug]), - false, - false - ); - } - - $destdir = $oTemplateConfiguration->filesPath; - if(Template::isStandardTemplate($oTemplateConfiguration->sTemplateName)){ - $destdir = $oTemplateConfiguration->generalFilesPath; - } - - $filename = sanitize_filename($_FILES['file']['name'], false, false, false); // Don't force lowercase or alphanumeric - $fullfilepath = $destdir.$filename; - $debug[] = $destdir; - $debug[] = $filename; - $debug[] = $fullfilepath; - if (!@move_uploaded_file($_FILES['file']['tmp_name'], $fullfilepath)) { - $uploadresult = gT("An error occurred uploading your file. This may be caused by incorrect permissions for the application /tmp folder."); - } else { - $uploadresult = sprintf(gT("File %s uploaded"), $filename); - Yii::app()->user->setFlash('success', "Data saved!"); - $success = true; - }; + + $oTemplateConfiguration = Template::getInstance($sTemplateName); + $debug[] = $sTemplateName; + $debug[] = $oTemplateConfiguration; + if (Yii::app()->getConfig('demoMode')) { + $uploadresult = gT("Demo mode: Uploading images is disabled."); return Yii::app()->getController()->renderPartial( '/admin/super/_renderJson', array('data' => ['success' => $success, 'message' => $uploadresult, 'debug' => $debug]), false, false ); + } - } else if ($action == 'templateupload') { + $debug[] = $_FILES; + + // Check file size and render JSON on error + LSUploadHelper::checkUploadedFileSizeAndRenderJson('file', $debug); + + $checkImageContent = LSYii_ImageValidator::validateImage($_FILES["file"]); + $checkImageFilename = LSYii_ImageValidator::validateImage($_FILES["file"]['name']); + if ($checkImageContent['check'] === false || $checkImageFilename['check'] === false) { + $message = $checkImageContent['check'] === false + ? $checkImageContent['uploadresult'] + : ($checkImageFilename['check'] === false ? $checkImageFilename['uploadresult']: null); + $debug = $checkImageContent['check'] === false + ? $checkImageContent['debug'] + : ($checkImageFilename['check'] === false ? $checkImageFilename['debug']: null); + return Yii::app()->getController()->renderPartial( + '/admin/super/_renderJson', + array('data' => ['success' => $success, 'message' => $message, 'debug' => $debug]), + false, + false + ); + } + + $destdir = $oTemplateConfiguration->filesPath; + if(Template::isStandardTemplate($oTemplateConfiguration->sTemplateName)){ + $destdir = $oTemplateConfiguration->generalFilesPath; + } + + $filename = sanitize_filename($_FILES['file']['name'], false, false, false); // Don't force lowercase or alphanumeric + $fullfilepath = $destdir.$filename; + $debug[] = $destdir; + $debug[] = $filename; + $debug[] = $fullfilepath; + if (!@move_uploaded_file($_FILES['file']['tmp_name'], $fullfilepath)) { + $uploadresult = gT("An error occurred uploading your file. This may be caused by incorrect permissions for the application /tmp folder."); + } else { + $uploadresult = sprintf(gT("File %s uploaded"), $filename); + Yii::app()->user->setFlash('success', "Data saved!"); + $success = true; + }; + + return Yii::app()->getController()->renderPartial( + '/admin/super/_renderJson', + array('data' => ['success' => $success, 'message' => $uploadresult, 'debug' => $debug]), + false, + false + ); - Yii::app()->loadLibrary('admin.pclzip'); + } else { + Yii::app()->setFlashMessage(gT("We are sorry but you don't have permissions to do this."), 'error'); + $this->getController()->redirect(array("admin/themeoptions")); + } + } - // Redirect back if demo mode is set. - $this->checkDemoMode(); + /** + * Responsible to import a template archive. + * + * Called from theme list and editor + * + * @access public + * @return void + */ + public function templateupload() + { + $sTemplateName = Yii::app()->request->getPost('templatename'); + if (Permission::model()->hasGlobalPermission('templates', 'import') || Permission::model()->hasTemplatePermission($sTemplateName)) { + Yii::app()->loadHelper('admin/template'); + $lid = returnGlobal('lid'); + $uploadresult = ""; + $success = false; + $debug = []; + + Yii::app()->loadLibrary('admin.pclzip'); - // Redirect back at file size error. - $this->checkFileSizeError(); + // Redirect back if demo mode is set. + $this->checkDemoMode(); - $sNewDirectoryName = sanitize_dirname(pathinfo($_FILES['the_file']['name'], PATHINFO_FILENAME)); - $destdir = Yii::app()->getConfig('userthemerootdir').DIRECTORY_SEPARATOR.$sNewDirectoryName; + // Redirect back at file size error. + $this->checkFileSizeError(); - // Redirect back if $destdir is not writable OR if it already exists. - $this->checkDestDir($destdir, $sNewDirectoryName); + $sNewDirectoryName = sanitize_dirname(pathinfo($_FILES['the_file']['name'], PATHINFO_FILENAME)); + $destdir = Yii::app()->getConfig('userthemerootdir').DIRECTORY_SEPARATOR.$sNewDirectoryName; - // All OK if we're here. - mkdir($destdir); + // Redirect back if $destdir is not writable OR if it already exists. + $this->checkDestDir($destdir, $sNewDirectoryName); - $aImportedFilesInfo = array(); - $aErrorFilesInfo = array(); + // All OK if we're here. + mkdir($destdir); - if (is_file($_FILES['the_file']['tmp_name'])) { - $zip = new PclZip($_FILES['the_file']['tmp_name']); - $aExtractResult = $zip->extract(PCLZIP_OPT_PATH, $destdir, PCLZIP_CB_PRE_EXTRACT, 'templateExtractFilter'); + $aImportedFilesInfo = array(); + $aErrorFilesInfo = array(); - if ($aExtractResult === 0) { - Yii::app()->user->setFlash('error', gT("This file is not a valid ZIP file archive. Import failed.")); - rmdirr($destdir); - $this->getController()->redirect(array("admin/themes/sa/upload")); - } else { - // Successfully unpacked - foreach ($aExtractResult as $sFile) { - if ($sFile['status'] == 'skipped' && !$sFile['folder']) { - $aErrorFilesInfo[] = array( - "filename" => $sFile['stored_filename'], - ); - } else { - $aImportedFilesInfo[] = array( - "filename" => $sFile['stored_filename'], - "status" => gT("OK"), - 'is_folder' => $sFile['folder'] - ); - } - } + if (is_file($_FILES['the_file']['tmp_name'])) { + $zip = new PclZip($_FILES['the_file']['tmp_name']); + $aExtractResult = $zip->extract(PCLZIP_OPT_PATH, $destdir, PCLZIP_CB_PRE_EXTRACT, 'templateExtractFilter'); - if (Template::checkIfTemplateExists($sNewDirectoryName)) { - Yii::app()->user->setFlash('error', gT("Can not import a theme that already exists!")); - rmdirr($destdir); - $this->getController()->redirect(array("admin/themes/sa/upload")); + if ($aExtractResult === 0) { + Yii::app()->user->setFlash('error', gT("This file is not a valid ZIP file archive. Import failed.")); + rmdirr($destdir); + $this->getController()->redirect(array("admin/themes/sa/upload")); + } else { + // Successfully unpacked + foreach ($aExtractResult as $sFile) { + if ($sFile['status'] == 'skipped' && !$sFile['folder']) { + $aErrorFilesInfo[] = array( + "filename" => $sFile['stored_filename'], + ); + } else { + $aImportedFilesInfo[] = array( + "filename" => $sFile['stored_filename'], + "status" => gT("OK"), + 'is_folder' => $sFile['folder'] + ); } - TemplateManifest::importManifest($sNewDirectoryName, ['extends' => $destdir]); } - if (count($aImportedFilesInfo) == 0) { - Yii::app()->user->setFlash('error', gT("This ZIP archive contains no valid template files. Import failed.")); + if (Template::checkIfTemplateExists($sNewDirectoryName)) { + Yii::app()->user->setFlash('error', gT("Can not import a theme that already exists!")); + rmdirr($destdir); $this->getController()->redirect(array("admin/themes/sa/upload")); } - } else { - Yii::app()->setFlashMessage(gT("An error occurred uploading your file. This may be caused by incorrect permissions for the application /tmp folder."), 'error'); - rmdirr($destdir); - $this->getController()->redirect(array("admin/themes/sa/upload")); + TemplateManifest::importManifest($sNewDirectoryName, ['extends' => $destdir]); } - $aViewUrls = 'importuploaded_view'; - $aData = array( - 'aImportedFilesInfo' => $aImportedFilesInfo, - 'aErrorFilesInfo' => $aErrorFilesInfo, - 'lid' => $lid, - 'newdir' => $sNewDirectoryName, - ); + if (count($aImportedFilesInfo) == 0) { + Yii::app()->user->setFlash('error', gT("This ZIP archive contains no valid template files. Import failed.")); + $this->getController()->redirect(array("admin/themes/sa/upload")); + } } else { - $aViewUrls = 'importform_view'; - $aData = array('lid' => $lid); + Yii::app()->setFlashMessage(gT("An error occurred uploading your file. This may be caused by incorrect permissions for the application /tmp folder."), 'error'); + rmdirr($destdir); + $this->getController()->redirect(array("admin/themes/sa/upload")); } + $aViewUrls = 'importuploaded_view'; + $aData = array( + 'aImportedFilesInfo' => $aImportedFilesInfo, + 'aErrorFilesInfo' => $aErrorFilesInfo, + 'lid' => $lid, + 'newdir' => $sNewDirectoryName, + ); + $this->_renderWrappedTemplate('themes', $aViewUrls, $aData); } else { Yii::app()->setFlashMessage(gT("We are sorry but you don't have permissions to do this."), 'error'); @@ -338,20 +373,29 @@ public function upload() } /** - * Responsible to import a template file. + * Responsible to import a file into a template. + * + * Called from Theme Editor * * @access public * @return void */ public function uploadfile() { + $editfile = App()->request->getPost('editfile'); + $templatename = returnGlobal('templatename'); + $screenname = returnGlobal('screenname'); + if (empty($screenname)) { + $screenname = 'welcome'; + } + + $redirectUrl = array('admin/themes', 'sa'=>'view', 'editfile'=>$editfile, 'screenname'=>$screenname, 'templatename'=>$templatename); + if (Permission::model()->hasGlobalPermission('templates', 'import')) { + // Check file size and redirect on error + LSUploadHelper::checkUploadedFileSizeAndRedirect('upload_file', $redirectUrl); - $action = returnGlobal('action'); - $editfile = App()->request->getPost('editfile'); - $templatename = returnGlobal('templatename'); $oEditedTemplate = Template::getInstance($templatename); - $screenname = returnGlobal('screenname'); $allowedthemeuploads = Yii::app()->getConfig('allowedthemeuploads'); $filename = sanitize_filename($_FILES['upload_file']['name'], false, false, false); // Don't force lowercase or alphanumeric $dirfilepath = $oEditedTemplate->filesPath; @@ -368,30 +412,32 @@ public function uploadfile() $fullfilepath = $dirfilepath.$filename; $status = 'error'; + + if (Yii::app()->getConfig('demoMode')) { + $uploadresult = gT("Demo mode: Uploading template files is disabled."); + Yii::app()->setFlashMessage($uploadresult, $status); + $this->getController()->redirect($redirectUrl); + } - if ($action == "templateuploadfile") { - if (Yii::app()->getConfig('demoMode')) { - $uploadresult = gT("Demo mode: Uploading template files is disabled."); - } elseif ($filename != $_FILES['upload_file']['name']) { - $uploadresult = gT("This filename is not allowed to be uploaded."); - } elseif (!in_array(strtolower(substr(strrchr($filename, '.'), 1)), explode(",", $allowedthemeuploads))) { - $uploadresult = gT("This file type is not allowed to be uploaded."); + if ($filename != $_FILES['upload_file']['name']) { + $uploadresult = gT("This filename is not allowed to be uploaded."); + } elseif (!in_array(strtolower(substr(strrchr($filename, '.'), 1)), explode(",", $allowedthemeuploads))) { + $uploadresult = gT("This file type is not allowed to be uploaded."); + } else { + //Uploads the file into the appropriate directory + if (!@move_uploaded_file($_FILES['upload_file']['tmp_name'], $fullfilepath)) { + $uploadresult = gT("An error occurred uploading your file. This may be caused by incorrect permissions for the application /tmp folder."); } else { - //Uploads the file into the appropriate directory - if (!@move_uploaded_file($_FILES['upload_file']['tmp_name'], $fullfilepath)) { - $uploadresult = gT("An error occurred uploading your file. This may be caused by incorrect permissions for the application /tmp folder."); - } else { - $uploadresult = sprintf(gT("File %s uploaded"), $filename); - Template::model()->findByPk($templatename)->resetAssetVersion(); // Upload a files, asset need to be resetted (maybe) - $status = 'success'; - } + $uploadresult = sprintf(gT("File %s uploaded"), $filename); + Template::model()->findByPk($templatename)->resetAssetVersion(); // Upload a files, asset need to be resetted (maybe) + $status = 'success'; } - Yii::app()->setFlashMessage($uploadresult, $status); } + Yii::app()->setFlashMessage($uploadresult, $status); } else { Yii::app()->setFlashMessage(gT("We are sorry but you don't have permissions to do this."), 'error'); } - $this->getController()->redirect(array('admin/themes', 'sa'=>'view', 'editfile'=>$editfile, 'screenname'=>$screenname, 'templatename'=>$templatename)); + $this->getController()->redirect($redirectUrl); } @@ -1217,16 +1263,7 @@ protected function checkDemoMode() */ protected function checkFileSizeError() { - if ($_FILES['the_file']['error'] == 1 || $_FILES['the_file']['error'] == 2) { - Yii::app()->setFlashMessage( - sprintf( - gT("Sorry, this file is too large. Only files up to %01.2f MB are allowed."), - getMaximumFileUploadSize() / 1024 / 1024 - ), - 'error' - ); - $this->getController()->redirect(array("admin/themes/sa/upload")); - } + LSUploadHelper::checkUploadedFileSizeAndRedirect('the_file', array("admin/themes/sa/upload")); } /** diff --git a/application/core/LSUploadHelper.php b/application/core/LSUploadHelper.php new file mode 100644 index 00000000000..fb473e67de9 --- /dev/null +++ b/application/core/LSUploadHelper.php @@ -0,0 +1,99 @@ + $iMaximumSize || $_FILES[$sFileName]['error'] == 1 || $_FILES[$sFileName]['error'] == 2) { + throw new \Exception( + sprintf( + gT("Sorry, this file is too large. Only files up to %01.2f MB are allowed."), + $iMaximumSize / 1024 / 1024 + ) + ); + } + } + + /** + * Check uploaded file size. Redirects to the specified URL on failure. + * + * @param string $sFileName the name of the posted file + * @param mixed $redirectUrl the URL to redirect on failure + * @param mixed $customMaxSize maximum file upload size + */ + public static function checkUploadedFileSizeAndRedirect($sFileName, $redirectUrl, $customMaxSize = null) + { + try { + self::checkUploadedFileSize($sFileName, $customMaxSize); + } catch (Exception $ex) { + Yii::app()->setFlashMessage($ex->getMessage(), 'error'); + App()->getController()->redirect($redirectUrl); + } + } + + /** + * Check uploaded file size. Renders JSON on failure. + * + * @param string $sFileName the name of the posted file + * @param array $debugInfo the URL to redirect on failure + * @param mixed $customMaxSize maximum file upload size + */ + public static function checkUploadedFileSizeAndRenderJson($sFileName, $debugInfo = [], $customMaxSize = null) + { + try { + self::checkUploadedFileSize($sFileName, $customMaxSize); + } catch (Exception $ex) { + $error = $ex->getMessage(); + return Yii::app()->getController()->renderPartial( + '/admin/super/_renderJson', + array('data' => ['success' => 'error', 'message' => $error, 'debug' => $debugInfo]), + false, + false + ); + } + } +} diff --git a/application/views/admin/themeoptions/import_modal.php b/application/views/admin/themeoptions/import_modal.php index 49932bdbb7b..dd27085ebbd 100644 --- a/application/views/admin/themeoptions/import_modal.php +++ b/application/views/admin/themeoptions/import_modal.php @@ -7,7 +7,7 @@