wp_back/wp-content/plugins/shortpixel-image-optimiser/class/Controller/QuotaController.php
2024-05-20 15:37:46 +03:00

364 lines
14 KiB
PHP

<?php
namespace ShortPixel\Controller;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
class QuotaController
{
protected static $instance;
const CACHE_NAME = 'quotaData';
protected $quotaData;
/** Singleton instance
* @return Object QuotaController object
*/
public static function getInstance()
{
if (is_null(self::$instance))
self::$instance = new QuotaController();
return self::$instance;
}
/**
* Return if account has any quota left
* @return boolean Has quota left
*/
public function hasQuota()
{
$settings = \wpSPIO()->settings();
if ($settings->quotaExceeded)
{
return false;
}
return true;
}
/**
* Retrieves QuotaData object from cache or from remote source
* @return array The quotadata array (remote format)
*/
protected function getQuotaData()
{
if (! is_null($this->quotaData))
return $this->quotaData;
$cache = new CacheController();
$cacheData = $cache->getItem(self::CACHE_NAME);
if (! $cacheData->exists() )
{
$quotaData = $this->getRemoteQuota();
if (! $this->hasQuota())
$timeout = MINUTE_IN_SECONDS;
else {
$timeout = HOUR_IN_SECONDS;
}
$cache->storeItem(self::CACHE_NAME, $quotaData, $timeout);
}
else
{
$quotaData = $cacheData->getValue();
}
return $quotaData;
}
/**
* Retrieve quota information for this account
* @return object quotadata SPIO format
*/
public function getQuota()
{
$quotaData = $this->getQuotaData();
$DateNow = time();
$DateSubscription = strtotime($quotaData['APILastRenewalDate']);
$DaysToReset = 30 - ( (int) ( ( $DateNow - $DateSubscription) / 84600) % 30);
$quota = (object) [
'unlimited' => isset($quotaData['Unlimited']) ? $quotaData['Unlimited'] : false,
'monthly' => (object) [
'text' => sprintf(__('%s/month', 'shortpixel-image-optimiser'), $quotaData['APICallsQuota']),
'total' => $quotaData['APICallsQuotaNumeric'],
'consumed' => $quotaData['APICallsMadeNumeric'],
'remaining' => max($quotaData['APICallsQuotaNumeric'] - $quotaData['APICallsMadeNumeric'], 0),
'renew' => $DaysToReset,
],
'onetime' => (object) [
'text' => $quotaData['APICallsQuotaOneTime'],
'total' => $quotaData['APICallsQuotaOneTimeNumeric'],
'consumed' => $quotaData['APICallsMadeOneTimeNumeric'],
'remaining' => $quotaData['APICallsQuotaOneTimeNumeric'] - $quotaData['APICallsMadeOneTimeNumeric'],
],
];
$quota->total = (object) [
'total' => $quota->monthly->total + $quota->onetime->total,
'consumed' => $quota->monthly->consumed + $quota->onetime->consumed,
'remaining' =>$quota->monthly->remaining + $quota->onetime->remaining,
];
return $quota;
}
/**
* Get available remaining - total - credits
* @return int Total remaining credits
*/
public function getAvailableQuota()
{
$quota = $this->getQuota();
return $quota->total->remaining;
}
/**
* Force to get quotadata from API, even if cache is still active ( use very sparingly )
* Does not actively fetches it, but invalidates cache, so next call will trigger a remote call
* @return void
*/
public function forceCheckRemoteQuota()
{
$cache = new CacheController();
$cacheData = $cache->getItem(self::CACHE_NAME);
$cacheData->delete();
$this->quotaData = null;
}
/**
* Validate account key in the API via quota check
* @param string $key User account key
* @return array Quotadata array (remote format) with validated key
*/
public function remoteValidateKey($key)
{
// Remove the cache before checking.
$this->forceCheckRemoteQuota();
return $this->getRemoteQuota($key, true);
}
/**
* Called when plugin detects the remote quota has been exceeded.
* Triggers various call to actions to customer
*/
public function setQuotaExceeded()
{
$settings = \wpSPIO()->settings();
$settings->quotaExceeded = 1;
$this->forceCheckRemoteQuota(); // remove the previous cache.
}
/**
* When quota is detected again via remote check, reset all call to actions
*/
private function resetQuotaExceeded()
{
$settings = \wpSPIO()->settings();
AdminNoticesController::resetAPINotices();
// Only reset after a quotaExceeded situation, otherwise it keeps popping.
if ($settings->quotaExceeded == 1)
{
AdminNoticesController::resetQuotaNotices();
}
// Log::addDebug('Reset Quota Exceeded and reset Notices');
$settings->quotaExceeded = 0;
}
/**
* [getRemoteQuota description]
* @param string $apiKey User account key
* @param boolean $validate Api should also validate key or not
* @return array Quotadata array (remote format) [with validated key]
*/
private function getRemoteQuota($apiKey = false, $validate = false)
{
if (! $apiKey && ! $validate) // validation is done by apikeymodel, might result in a loop.
{
$keyControl = ApiKeyController::getInstance();
$apiKey = $keyControl->forceGetApiKey();
}
$settings = \wpSPIO()->settings();
if($settings->httpProto != 'https' && $settings->httpProto != 'http') {
$settings->httpProto = 'https';
}
$requestURL = $settings->httpProto . '://' . SHORTPIXEL_API . '/v2/api-status.php';
$args = array(
'timeout'=> 15, // wait for 15 secs.
'body' => array('key' => $apiKey)
);
$argsStr = "?key=".$apiKey;
$serverAgent = isset($_SERVER['HTTP_USER_AGENT']) ? urlencode(sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT']))) : '';
$args['body']['useragent'] = "Agent" . $serverAgent;
$argsStr .= "&useragent=Agent".$args['body']['useragent'];
// Only used for keyValidation
if($validate) {
$statsController = StatsController::getInstance();
$imageCount = $statsController->find('media', 'itemsTotal');
$thumbsCount = $statsController->find('media', 'thumbsTotal');
$args['body']['DomainCheck'] = get_site_url();
$args['body']['Info'] = get_bloginfo('version') . '|' . phpversion();
$args['body']['ImagesCount'] = $imageCount; //$imageCount['mainFiles'];
$args['body']['ThumbsCount'] = $thumbsCount; // $imageCount['totalFiles'] - $imageCount['mainFiles'];
$argsStr .= "&DomainCheck={$args['body']['DomainCheck']}&Info={$args['body']['Info']}&ImagesCount=$imageCount&ThumbsCount=$thumbsCount";
}
$args['body']['host'] = parse_url(get_site_url(),PHP_URL_HOST);
$argsStr .= "&host={$args['body']['host']}";
if (defined('SHORTPIXEL_HTTP_AUTH_USER') && defined('SHORTPIXEL_HTTP_AUTH_PASSWORD'))
{
$args['body']['user'] = stripslashes(SHORTPIXEL_HTTP_AUTH_USER);
$args['body']['pass'] = stripslashes(SHORTPIXEL_HTTP_AUTH_PASSWORD);
$argsStr .= '&user=' . urlencode($args['body']['user']) . '&pass=' . urlencode($args['body']['pass']);
}
elseif(! is_null($settings->siteAuthUser) && strlen($settings->siteAuthUser)) {
$args['body']['user'] = stripslashes($settings->siteAuthUser);
$args['body']['pass'] = stripslashes($settings->siteAuthPass);
$argsStr .= '&user=' . urlencode($args['body']['user']) . '&pass=' . urlencode($args['body']['pass']);
}
if($settings !== false) {
$args['body']['Settings'] = $settings;
}
$time = microtime(true);
$comm = array();
//Try first HTTPS post. add the sslverify = false if https
if($settings->httpProto === 'https') {
$args['sslverify'] = apply_filters('shortpixel/system/sslverify', true);
}
$response = wp_remote_post($requestURL, $args);
$comm['A: ' . (number_format(microtime(true) - $time, 2))] = array("sent" => "POST: " . $requestURL, "args" => $args, "received" => $response);
//some hosting providers won't allow https:// POST connections so we try http:// as well
if(is_wp_error( $response )) {
$requestURL = $settings->httpProto == 'https' ?
str_replace('https://', 'http://', $requestURL) :
str_replace('http://', 'https://', $requestURL);
// add or remove the sslverify
if($settings->httpProto === 'https') {
$args['sslverify'] = apply_filters('shortpixel/system/sslverify', true);
} else {
unset($args['sslverify']);
}
$response = wp_remote_post($requestURL, $args);
$comm['B: ' . (number_format(microtime(true) - $time, 2))] = array("sent" => "POST: " . $requestURL, "args" => $args, "received" => $response);
if(!is_wp_error( $response )){
$settings->httpProto = ($settings->httpProto == 'https' ? 'http' : 'https');
} else {
}
}
//Second fallback to HTTP get
if(is_wp_error( $response )){
$args['body'] = null;
$requestURL .= $argsStr;
$response = wp_remote_get($requestURL, $args);
$comm['C: ' . (number_format(microtime(true) - $time, 2))] = array("sent" => "POST: " . $requestURL, "args" => $args, "received" => $response);
}
Log::addInfo("API STATUS COMM: " . json_encode($comm));
$defaultData = array(
"APIKeyValid" => false,
"Message" => __('API Key could not be validated due to a connectivity error.<BR>Your firewall may be blocking us. Please contact your hosting provider and ask them to allow connections from your site to api.shortpixel.com (IP 176.9.21.94).<BR> If you still cannot validate your API Key after this, please <a href="https://shortpixel.com/contact" target="_blank">contact us</a> and we will try to help. ','shortpixel-image-optimiser'),
"APICallsMade" => __('Information unavailable. Please check your API key.','shortpixel-image-optimiser'),
"APICallsQuota" => __('Information unavailable. Please check your API key.','shortpixel-image-optimiser'),
"APICallsMadeOneTime" => 0,
"APICallsQuotaOneTime" => 0,
"APICallsMadeNumeric" => 0,
"APICallsQuotaNumeric" => 0,
"APICallsMadeOneTimeNumeric" => 0,
"APICallsQuotaOneTimeNumeric" => 0,
"APICallsRemaining" => 0,
"APILastRenewalDate" => 0,
"DomainCheck" => 'NOT Accessible');
$defaultData = is_array($settings->currentStats) ? array_merge( $settings->currentStats, $defaultData) : $defaultData;
if(is_object($response) && get_class($response) == 'WP_Error') {
$urlElements = parse_url($requestURL);
$portConnect = @fsockopen($urlElements['host'],8,$errno,$errstr,15);
if(!$portConnect) {
$defaultData['Message'] .= "<BR>Debug info: <i>$errstr</i>";
}
return $defaultData;
}
if($response['response']['code'] != 200) {
return $defaultData;
}
$data = $response['body'];
$data = json_decode($data);
if(empty($data)) { return $defaultData; }
if($data->Status->Code != 2) {
$defaultData['Message'] = $data->Status->Message;
return $defaultData;
}
$dataArray = array(
"APIKeyValid" => true,
"APICallsMade" => number_format($data->APICallsMade) . __(' credits','shortpixel-image-optimiser'),
"APICallsQuota" => number_format($data->APICallsQuota) . __(' credits','shortpixel-image-optimiser'),
"APICallsMadeOneTime" => number_format($data->APICallsMadeOneTime) . __(' credits','shortpixel-image-optimiser'),
"APICallsQuotaOneTime" => number_format($data->APICallsQuotaOneTime) . __(' credits','shortpixel-image-optimiser'),
"APICallsMadeNumeric" => (int) max($data->APICallsMade, 0),
"APICallsQuotaNumeric" => (int) max($data->APICallsQuota, 0),
"APICallsMadeOneTimeNumeric" => (int) max($data->APICallsMadeOneTime, 0),
"APICallsQuotaOneTimeNumeric" => (int) max($data->APICallsQuotaOneTime, 0),
"Unlimited" => (property_exists($data, 'Unlimited') && $data->Unlimited == 'true') ? true : false,
"APILastRenewalDate" => $data->DateSubscription,
"DomainCheck" => (isset($data->DomainCheck) ? $data->DomainCheck : null)
);
// My Eyes! Basically : ApiCalls - ApiCalls used, both for monthly and onetime. Max of each is 0. Negative quota seems possible, but should not be substracted from one or the other.
$dataArray["APICallsRemaining"] = max($dataArray['APICallsQuotaNumeric'] - $dataArray['APICallsMadeNumeric'], 0) + max($dataArray['APICallsQuotaOneTimeNumeric'] - $dataArray['APICallsMadeOneTimeNumeric'],0);
//reset quota exceeded flag -> user is allowed to process more images.
if ( $dataArray['APICallsRemaining'] > 0 || $dataArray['Unlimited'])
{
$this->resetQuotaExceeded();
}
else
{
//activate quota limiting
$this->setQuotaExceeded();
}
// Log::addDebug('GetQuotaInformation Result ', $dataArray);
return $dataArray;
}
}