'apiKey', 'cmyk2rgb' => 'CMYKtoRGBconversion', ); protected $display_part = 'settings'; protected $all_display_parts = array('settings', 'adv-settings', 'cloudflare', 'debug', 'tools'); protected $form_action = 'save-settings'; protected static $instance; public function __construct() { $this->model = \wpSPIO()->settings(); //@todo Streamline this mess. Should run through controller mostly. Risk of desync otherwise. $keyControl = ApiKeyController::getInstance(); $this->keyModel = $keyControl->getKeyModel(); //new ApiKeyModel(); // $this->keyModel->loadKey(); $this->is_verifiedkey = $this->keyModel->is_verified(); $this->is_constant_key = $this->keyModel->is_constant(); $this->hide_api_key = $this->keyModel->is_hidden(); parent::__construct(); } // default action of controller public function load() { $this->loadEnv(); $this->checkPost(); // sets up post data $this->model->redirectedSettings = 2; // Prevents any redirects after loading settings if ($this->is_form_submit) { $this->processSave(); } $this->load_settings(); } // this is the nokey form, submitting api key public function action_addkey() { $this->loadEnv(); $this->checkPost(); Log::addDebug('Settings Action - addkey ', array($this->is_form_submit, $this->postData) ); if ($this->is_form_submit && isset($this->postData['apiKey'])) { $apiKey = $this->postData['apiKey']; if (strlen(trim($apiKey)) == 0) // display notice when submitting empty API key { Notice::addError(sprintf(__("The key you provided has %s characters. The API key should have 20 characters, letters and numbers only.",'shortpixel-image-optimiser'), strlen($apiKey) )); } else { $this->keyModel->resetTried(); $this->keyModel->checkKey($this->postData['apiKey']); } } $this->doRedirect(); } public function action_request_new_key() { $this->loadEnv(); $this->checkPost(); $email = isset($_POST['pluginemail']) ? trim(sanitize_text_field($_POST['pluginemail'])) : null; // Not a proper form post. if (is_null($email)) { $this->load(); return; } // Old code starts here. if( $this->keyModel->is_verified() === true) { $this->load(); // already verified? return; } $bodyArgs = array( 'plugin_version' => SHORTPIXEL_IMAGE_OPTIMISER_VERSION, 'email' => $email, 'ip' => isset($_SERVER["HTTP_X_FORWARDED_FOR"]) ? sanitize_text_field($_SERVER["HTTP_X_FORWARDED_FOR"]) : sanitize_text_field($_SERVER['REMOTE_ADDR']), ); $affl_id = false; $affl_id = (defined('SHORTPIXEL_AFFILIATE_ID')) ? SHORTPIXEL_AFFILIATE_ID : false; $affl_id = apply_filters('shortpixel/settings/affiliate', $affl_id); // /af/bla35 if ($affl_id !== false) { $bodyArgs['affiliate'] = $affl_id; } $params = array( 'method' => 'POST', 'timeout' => 10, 'redirection' => 5, 'httpversion' => '1.0', 'blocking' => true, 'sslverify' => false, 'headers' => array(), 'body' => $bodyArgs, ); $newKeyResponse = wp_remote_post("https://shortpixel.com/free-sign-up-plugin", $params); $errorText = __("There was problem requesting a new code. Server response: ", 'shortpixel-image-optimiser'); if ( is_object($newKeyResponse) && get_class($newKeyResponse) == 'WP_Error' ) { //die(json_encode((object)array('Status' => 'fail', 'Details' => '503'))); Notice::addError($errorText . $newKeyResponse->get_error_message() ); $this->doRedirect(); // directly redirect because other data / array is not set. } elseif ( isset($newKeyResponse['response']['code']) && $newKeyResponse['response']['code'] <> 200 ) { //die(json_encode((object)array('Status' => 'fail', 'Details' => Notice::addError($errorText . $newKeyResponse['response']['code']); $this->doRedirect(); // strange http status, redirect with error. } $body = $newKeyResponse['body']; $body = json_decode($body); if($body->Status == 'success') { $key = trim($body->Details); $valid = $this->keyModel->checkKey($key); //$validityData = $this->getQuotaInformation($key, true, true); if($valid === true) { \ShortPixel\Controller\AdminNoticesController::resetAPINotices(); /* Notice::addSuccess(__('Great, you successfully claimed your API Key! Please take a few moments to review the plugin settings below before starting to optimize your images.','shortpixel-image-optimiser')); */ } } elseif($body->Status == 'existing') { Notice::addWarning( sprintf(__('This email address is already in use. Please use your API-key in the "Already have an API key" field. You can obtain your license key via %s your account %s ', 'shortpixel-image-optimiser'), '', '') ); } else { Notice::addError( __('Unexpected error obtaining the ShortPixel key. Please contact support about this:', 'shortpixel-image-optimiser') . ' ' . json_encode($body) ); } $this->doRedirect(); } public function action_debug_redirectBulk() { $this->checkPost(); OptimizeController::resetQueues(); $action = isset($_REQUEST['bulk']) ? sanitize_text_field($_REQUEST['bulk']) : null; if ($action == 'migrate') { $this->doRedirect('bulk-migrate'); } if ($action == 'restore') { $this->doRedirect('bulk-restore'); } if ($action == 'removeLegacy') { $this->doRedirect('bulk-removeLegacy'); } } /** Button in part-debug, routed via custom Action */ public function action_debug_resetStats() { $this->loadEnv(); $this->checkPost(); $statsController = StatsController::getInstance(); $statsController->reset(); $this->doRedirect(); } public function action_debug_resetquota() { $this->loadEnv(); $this->checkPost(); $quotaController = QuotaController::getInstance(); $quotaController->forceCheckRemoteQuota(); $this->doRedirect(); } public function action_debug_resetNotices() { $this->loadEnv(); $this->checkPost(); Notice::resetNotices(); $nControl = new Notice(); // trigger reload. $this->doRedirect(); } public function action_debug_triggerNotice() { $this->checkPost(); $key = isset($_REQUEST['notice_constant']) ? sanitize_text_field($_REQUEST['notice_constant']) : false; if ($key !== false) { $adminNoticesController = AdminNoticesController::getInstance(); if ($key == 'trigger-all') { $notices = $adminNoticesController->getAllNotices(); foreach($notices as $noticeObj) { $noticeObj->addManual(); } } else { $model = $adminNoticesController->getNoticeByKey($key); if ($model) $model->addManual(); } } $this->doRedirect(); } public function action_debug_resetQueue() { $queue = isset($_REQUEST['queue']) ? sanitize_text_field($_REQUEST['queue']) : null; $this->loadEnv(); $this->checkPost(); if (! is_null($queue)) { $opt = new OptimizeController(); $statsMedia = $opt->getQueue('media'); $statsCustom = $opt->getQueue('custom'); $opt->setBulk(true); $bulkMedia = $opt->getQueue('media'); $bulkCustom = $opt->getQueue('custom'); $queues = array('media' => $statsMedia, 'custom' => $statsCustom, 'mediaBulk' => $bulkMedia, 'customBulk' => $bulkCustom); if ( strtolower($queue) == 'all') { foreach($queues as $q) { $q->resetQueue(); } } else { $queues[$queue]->resetQueue(); } if ($queue == 'all') { $message = sprintf(__('All items in the queues have been removed and the process is stopped', 'shortpixel-image-optimiser')); } else { $message = sprintf(__('All items in the %s queue have been removed and the process is stopped', 'shortpixel-image-optimiser'), $queue); } Notice::addSuccess($message); } $this->doRedirect(); } public function action_debug_removePrevented() { $this->loadEnv(); $this->checkPost(); global $wpdb; $sql = 'delete from ' . $wpdb->postmeta . ' where meta_key = %s'; $sql = $wpdb->prepare($sql, '_shortpixel_prevent_optimize'); $wpdb->query($sql); $message = __('Item blocks have been removed. It is recommended to create a backup before trying to optimize image.', 'shortpixel-image-optimiser'); Notice::addSuccess($message); $this->doRedirect(); } public function action_debug_removeProcessorKey() { //$this->loadEnv(); $this->checkPost(); $cacheControl = new CacheController(); $cacheControl->deleteItem('bulk-secret'); exit('reloading settings would cause processorKey to be set again'); } public function processSave() { // Split this in the several screens. I.e. settings, advanced, Key Request IF etc. if (isset($this->postData['includeNextGen']) && $this->postData['includeNextGen'] == 1) { $nextgen = NextGenController::getInstance(); $previous = $this->model->includeNextGen; $nextgen->enableNextGen(true); // Reset any integration notices when updating settings. AdminNoticesController::resetIntegrationNotices(); } $check_key = false; if (isset($this->postData['apiKey'])) { $check_key = $this->postData['apiKey']; unset($this->postData['apiKey']); // unset, since keyModel does the saving. } // If the compression type setting changes, remove all queued items to prevent further optimizing with a wrong type. if (intval($this->postData['compressionType']) !== intval($this->model->compressionType)) { OptimizeController::resetQueues(); } // write checked and verified post data to model. With normal models, this should just be call to update() function foreach($this->postData as $name => $value) { $this->model->{$name} = $value; } // first save all other settings ( like http credentials etc ), then check if (! $this->keyModel->is_constant() && $check_key !== false) // don't allow settings key if there is a constant { $this->keyModel->resetTried(); // reset the tried api keys on a specific post request. $this->keyModel->checkKey($check_key); } // Every save, force load the quota. One reason, because of the HTTP Auth settings refresh. $this->loadQuotaData(true); // end if ($this->do_redirect) $this->doRedirect('bulk'); else { $noticeController = Notice::getInstance(); $notice = Notice::addSuccess(__('Settings Saved', 'shortpixel-image-optimiser')); $notice->is_removable = false; $noticeController->update(); $this->doRedirect(); } } /* Loads the view data and the view */ public function load_settings() { if ($this->is_verifiedkey) // supress quotaData alerts when handing unset API's. $this->loadQuotaData(); else InstallHelper::checkTables(); $keyController = ApiKeyController::getInstance(); $this->view->data = (Object) $this->model->getData(); $this->view->data->apiKey = $keyController->getKeyForDisplay(); $this->loadStatistics(); $this->checkCloudFlare(); $statsControl = StatsController::getInstance(); $this->view->minSizes = $this->getMaxIntermediateImageSize(); $excludeOptions = UtilHelper::getWordPressImageSizes(); $mainOptions = array( 'shortpixel_main_donotuse' => array('nice-name' => __('Main Image', 'shortpixel-image-optimiser')), 'shortpixel_original_donotuse' => array('nice-name' => __('Original Image', 'shortpixel-image-optimiser')), ); $excludeOptions = array_merge($mainOptions, $excludeOptions); $this->view->allThumbSizes = $excludeOptions; $this->view->averageCompression = $statsControl->getAverageCompression(); $this->view->savedBandwidth = UiHelper::formatBytes( intval($this->view->data->savedSpace) * 10000,2); $this->view->cloudflare_constant = defined('SHORTPIXEL_CFTOKEN') ? true : false; $settings = \wpSPIO()->settings(); if ($this->view->data->createAvif == 1) $this->avifServerCheck(); $this->loadView('view-settings'); } protected function avifServerCheck() { $noticeControl = AdminNoticesController::getInstance(); $notice = $noticeControl->getNoticeByKey('MSG_AVIF_ERROR'); $notice->check(); } protected function loadStatistics() { /* $statsControl = StatsController::getInstance(); $stats = new \stdClass; $stats->totalOptimized = $statsControl->find('totalOptimized'); $stats->totalOriginal = $statsControl->find('totalOriginal'); $stats->mainOptimized = $statsControl->find('media', 'images'); // used in part-g eneral $stats->thumbnailsToProcess = $statsControl->thumbNailsToOptimize(); // $totalImages - $totalOptimized; // $stats->totalFiles = $statsControl->find('media', '') $this->view->stats = $stats; */ } /** @todo Remove this check in Version 5.1 including all data on the old CF token */ protected function checkCloudFlare() { $settings = \wpSPIO()->settings(); $authkey = $settings->cloudflareAuthKey; $this->view->hide_cf_global = true; if (strlen($authkey) > 0) { $message = '
' . __('It appears that you are using the Cloudflare Global API key. As it is not as safe as the Cloudflare Token, it will be removed in the next version. Please, switch to the token.', 'shortpixel-image-optimiser') . '
'; $message .= '' . sprintf(__('%s How to set up the Cloudflare Token %s', 'shortpixel-image-optimiser'), '', '') . '
'; Notice::addNormal($message); $this->view->hide_cf_global = false; } } /** Checks on things and set them for information. */ protected function loadEnv() { $env = wpSPIO()->env(); $this->is_nginx = $env->is_nginx; $this->is_gd_installed = $env->is_gd_installed; $this->is_curl_installed = $env->is_curl_installed; $this->is_htaccess_writable = $this->HTisWritable(); $this->is_multisite = $env->is_multisite; $this->is_mainsite = $env->is_mainsite; $this->has_nextgen = $env->has_nextgen; $this->disable_heavy_features = (\wpSPIO()->env()->hasOffload() && false === \wpSPIO()->env()->useVirtualHeavyFunctions()) ? true : false; $this->display_part = (isset($_GET['part']) && in_array($_GET['part'], $this->all_display_parts) ) ? sanitize_text_field($_GET['part']) : 'settings'; } /* Temporary function to check if HTaccess is writable. * HTaccess is writable if it exists *and* is_writable, or can be written if directory is writable. */ private function HTisWritable() { if ($this->is_nginx) return false; $file = \wpSPIO()->filesystem()->getFile(get_home_path() . '.htaccess'); if ($file->is_writable()) { return true; } return false; } protected function getMaxIntermediateImageSize() { global $_wp_additional_image_sizes; $width = 0; $height = 0; $get_intermediate_image_sizes = get_intermediate_image_sizes(); // Create the full array with sizes and crop info if(is_array($get_intermediate_image_sizes)) foreach( $get_intermediate_image_sizes as $_size ) { if ( in_array( $_size, array( 'thumbnail', 'medium', 'large' ) ) ) { $width = max($width, get_option( $_size . '_size_w' )); $height = max($height, get_option( $_size . '_size_h' )); //$sizes[ $_size ]['crop'] = (bool) get_option( $_size . '_crop' ); } elseif ( isset( $_wp_additional_image_sizes[ $_size ] ) ) { $width = max($width, $_wp_additional_image_sizes[ $_size ]['width']); $height = max($height, $_wp_additional_image_sizes[ $_size ]['height']); //'crop' => $_wp_additional_image_sizes[ $_size ]['crop'] } } return array('width' => max(100, $width), 'height' => max(100, $height)); } // @param Force. needed on settings save because it sends off the HTTP Auth protected function loadQuotaData($force = false) { $quotaController = QuotaController::getInstance(); if ($force === true) { $quotaController->forceCheckRemoteQuota(); $this->quotaData = null; } if (is_null($this->quotaData)) $this->quotaData = $quotaController->getQuota(); //$this->shortPixel->checkQuotaAndAlert(); $quotaData = $this->quotaData; $remainingImages = $quotaData->total->remaining; // $quotaData['APICallsRemaining']; $remainingImages = ( $remainingImages < 0 ) ? 0 : $this->formatNumber($remainingImages, 0); $this->view->remainingImages = $remainingImages; } // This is done before handing it off to the parent controller, to sanitize and check against model. protected function processPostData($post) { if (isset($post['display_part']) && strlen($post['display_part']) > 0) { $this->display_part = sanitize_text_field($post['display_part']); } unset($post['display_part']); // analyse the save button if (isset($post['save_bulk'])) { $this->do_redirect = true; } unset($post['save_bulk']); unset($post['save']); // handle 'reverse' checkbox. $keepExif = isset($post['removeExif']) ? 0 : 1; $post['keepExif'] = $keepExif; unset($post['removeExif']); // checkbox overloading $png2jpg = (isset($post['png2jpg']) ? (isset($post['png2jpgForce']) ? 2 : 1): 0); $post['png2jpg'] = $png2jpg; unset($post['png2jpgForce']); // must be an array $post['excludeSizes'] = (isset($post['excludeSizes']) && is_array($post['excludeSizes']) ? $post['excludeSizes']: array()); $post = $this->processWebp($post); $post = $this->processExcludeFolders($post); $post = $this->processCloudFlare($post); parent::processPostData($post); } /** Function for the WebP settings overload * */ protected function processWebP($post) { $deliverwebp = 0; if (! $this->is_nginx) UtilHelper::alterHtaccess(false, false); // always remove the statements. $webpOn = isset($post['createWebp']) && $post['createWebp'] == 1; $avifOn = isset($post['createAvif']) && $post['createAvif'] == 1; if (isset($post['deliverWebp']) && $post['deliverWebp'] == 1) { $type = isset($post['deliverWebpType']) ? $post['deliverWebpType'] : ''; $altering = isset($post['deliverWebpAlteringType']) ? $post['deliverWebpAlteringType'] : ''; if ($type == 'deliverWebpAltered') { if ($altering == 'deliverWebpAlteredWP') { $deliverwebp = 2; } elseif($altering = 'deliverWebpAlteredGlobal') { $deliverwebp = 1; } } elseif ($type == 'deliverWebpUnaltered') { $deliverwebp = 3; } } if (! $this->is_nginx && $deliverwebp == 3) // deliver webp/avif via htaccess, write rules { UtilHelper::alterHtaccess(true, true); } $post['deliverWebp'] = $deliverwebp; unset($post['deliverWebpAlteringType']); unset($post['deliverWebpType']); return $post; } protected function processExcludeFolders($post) { $patterns = array(); if (false === isset($post['exclusions'])) { return $post; } $exclusions = $post['exclusions']; $accepted = array(); foreach($exclusions as $index => $exclusions) { $accepted[] = json_decode(html_entity_decode( stripslashes($exclusions)), true); } foreach($accepted as $index => $pair) { $pattern = $pair['value']; $type = $pair['type']; //$first = substr($pattern, 0,1); if ($type == 'regex-name' || $type == 'regex-path') { if ( @preg_match($pattern, false) === false) { $accepted[$index]['has-error'] = true; Notice::addWarning(sprintf(__('Regular Expression Pattern %s returned an error. Please check if the expression is correct. %s * Special characters should be escaped. %s * A regular expression must be contained between two slashes ', 'shortpixel-image-optimiser'), $pattern, "