554 lines
17 KiB
PHP
554 lines
17 KiB
PHP
|
<?php
|
||
|
namespace ShortPixel\Controller;
|
||
|
|
||
|
if ( ! defined( 'ABSPATH' ) ) {
|
||
|
exit; // Exit if accessed directly.
|
||
|
}
|
||
|
|
||
|
use ShortPixel\Notices\NoticeController as Notices;
|
||
|
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||
|
|
||
|
use ShortPixel\ViewController as ViewController;
|
||
|
|
||
|
use ShortPixel\Model\AccessModel as AccessModel;
|
||
|
|
||
|
// Use ShortPixel\Model\ApiKeyModel as ApiKeyModel
|
||
|
|
||
|
/**
|
||
|
* Controller for automatic Notices about status of the plugin.
|
||
|
* This controller is bound for automatic fire. Regular procedural notices should just be queued using the Notices modules.
|
||
|
* Called in admin_notices.
|
||
|
*/
|
||
|
class AdminNoticesController extends \ShortPixel\Controller
|
||
|
{
|
||
|
protected static $instance;
|
||
|
|
||
|
protected $definedNotices = array( // NoticeModels by Class. This is not optimal but until solution found, workable.
|
||
|
'CompatNotice',
|
||
|
'UnlistedNotice',
|
||
|
'AvifNotice',
|
||
|
'QuotaNoticeMonth',
|
||
|
'QuotaNoticeReached',
|
||
|
'ApiNotice',
|
||
|
'ApiNoticeRepeat',
|
||
|
'ApiNoticeRepeatLong',
|
||
|
'NextgenNotice',
|
||
|
// 'SmartcropNotice',
|
||
|
'LegacyNotice',
|
||
|
'ListviewNotice',
|
||
|
// 'HeicFeatureNotice',
|
||
|
'NewExclusionFormat',
|
||
|
);
|
||
|
protected $adminNotices; // Models
|
||
|
|
||
|
private $remote_message_endpoint = 'https://api.shortpixel.com/v2/notices.php';
|
||
|
private $remote_readme_endpoint = 'https://plugins.svn.wordpress.org/shortpixel-image-optimiser/trunk/readme.txt';
|
||
|
|
||
|
public function __construct()
|
||
|
{
|
||
|
add_action('admin_notices', array($this, 'displayNotices'), 50); // notices occured before page load
|
||
|
add_action('admin_footer', array($this, 'displayNotices')); // called in views.
|
||
|
|
||
|
add_action('in_plugin_update_message-' . plugin_basename(SHORTPIXEL_PLUGIN_FILE), array($this, 'pluginUpdateMessage') , 50, 2 );
|
||
|
|
||
|
// no persistent notifications with this flag set.
|
||
|
if (defined('SHORTPIXEL_SILENT_MODE') && SHORTPIXEL_SILENT_MODE === true)
|
||
|
return;
|
||
|
|
||
|
add_action('admin_notices', array($this, 'check_admin_notices'), 5); // run before the plugin admin notices
|
||
|
|
||
|
$this->initNotices();
|
||
|
}
|
||
|
|
||
|
public static function getInstance()
|
||
|
{
|
||
|
if (is_null(self::$instance))
|
||
|
self::$instance = new AdminNoticesController();
|
||
|
|
||
|
return self::$instance;
|
||
|
}
|
||
|
|
||
|
public static function resetAllNotices()
|
||
|
{
|
||
|
Notices::resetNotices();
|
||
|
}
|
||
|
|
||
|
// Notices no longer in use.
|
||
|
public static function resetOldNotices()
|
||
|
{
|
||
|
Notices::removeNoticeByID('MSG_FEATURE_SMARTCROP');
|
||
|
Notices::removeNoticeByID('MSG_FEATURE_HEIC');
|
||
|
|
||
|
}
|
||
|
|
||
|
/** Triggered when plugin is activated */
|
||
|
public static function resetCompatNotice()
|
||
|
{
|
||
|
Notices::removeNoticeByID('MSG_COMPAT');
|
||
|
}
|
||
|
|
||
|
public static function resetAPINotices()
|
||
|
{
|
||
|
Notices::removeNoticeByID('MSG_NO_APIKEY');
|
||
|
Notices::removeNoticeByID('MSG_NO_APIKEY_REPEAT');
|
||
|
Notices::removeNoticeByID('MSG_NO_APIKEY_REPEAT_LONG');
|
||
|
}
|
||
|
|
||
|
public static function resetQuotaNotices()
|
||
|
{
|
||
|
Notices::removeNoticeByID('MSG_UPGRADE_MONTH');
|
||
|
Notices::removeNoticeByID('MSG_UPGRADE_BULK');
|
||
|
Notices::removeNoticeBYID('MSG_QUOTA_REACHED');
|
||
|
}
|
||
|
|
||
|
public static function resetIntegrationNotices()
|
||
|
{
|
||
|
Notices::removeNoticeByID('MSG_INTEGRATION_NGGALLERY');
|
||
|
}
|
||
|
|
||
|
public static function resetLegacyNotice()
|
||
|
{
|
||
|
Notices::removeNoticeByID('MSG_CONVERT_LEGACY');
|
||
|
}
|
||
|
|
||
|
public function displayNotices()
|
||
|
{
|
||
|
if (! \wpSPIO()->env()->is_screen_to_use)
|
||
|
{
|
||
|
if(get_current_screen()->base !== 'dashboard') // ugly exception for dashboard.
|
||
|
return; // suppress all when not our screen.
|
||
|
}
|
||
|
|
||
|
$access = AccessModel::getInstance();
|
||
|
$screen = get_current_screen();
|
||
|
$screen_id = \wpSPIO()->env()->screen_id;
|
||
|
|
||
|
$noticeControl = Notices::getInstance();
|
||
|
$noticeControl->loadIcons(array(
|
||
|
'normal' => '<img class="short-pixel-notice-icon" src="' . plugins_url('res/img/slider.png', SHORTPIXEL_PLUGIN_FILE) . '">',
|
||
|
'success' => '<img class="short-pixel-notice-icon" src="' . plugins_url('res/img/robo-cool.png', SHORTPIXEL_PLUGIN_FILE) . '">',
|
||
|
'warning' => '<img class="short-pixel-notice-icon" src="' . plugins_url('res/img/robo-scared.png', SHORTPIXEL_PLUGIN_FILE) . '">',
|
||
|
'error' => '<img class="short-pixel-notice-icon" src="' . plugins_url('res/img/robo-scared.png', SHORTPIXEL_PLUGIN_FILE) . '">',
|
||
|
));
|
||
|
|
||
|
if ($noticeControl->countNotices() > 0)
|
||
|
{
|
||
|
|
||
|
$notices = $noticeControl->getNoticesForDisplay();
|
||
|
if (count($notices) > 0)
|
||
|
{
|
||
|
\wpSPIO()->load_style('shortpixel-notices');
|
||
|
\wpSPIO()->load_style('notices-module');
|
||
|
|
||
|
|
||
|
foreach($notices as $notice)
|
||
|
{
|
||
|
|
||
|
if ($notice->checkScreen($screen_id) === false)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
elseif ($access->noticeIsAllowed($notice))
|
||
|
{
|
||
|
echo $notice->getForDisplay();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// @Todo change this to new keys
|
||
|
if ($notice->getID() == 'MSG_QUOTA_REACHED' || $notice->getID() == 'MSG_UPGRADE_MONTH') //|| $notice->getID() == AdminNoticesController::MSG_UPGRADE_BULK
|
||
|
{
|
||
|
// @todo check if this is still needed.
|
||
|
wp_enqueue_script('jquery.knob.min.js');
|
||
|
wp_enqueue_script('shortpixel');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
$noticeControl->update(); // puts views, and updates
|
||
|
}
|
||
|
|
||
|
/* General function to check on Hook for admin notices if there is something to show globally */
|
||
|
public function check_admin_notices()
|
||
|
{
|
||
|
if (! \wpSPIO()->env()->is_screen_to_use)
|
||
|
{
|
||
|
if(get_current_screen()->base !== 'dashboard') // ugly exception for dashboard.
|
||
|
return; // suppress all when not our screen.
|
||
|
}
|
||
|
|
||
|
$this->loadNotices();
|
||
|
}
|
||
|
|
||
|
protected function initNotices()
|
||
|
{
|
||
|
foreach($this->definedNotices as $className)
|
||
|
{
|
||
|
$ns = '\ShortPixel\Model\AdminNotices\\' . $className;
|
||
|
$class = new $ns();
|
||
|
$this->adminNotices[$class->getKey()] = $class;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected function loadNotices()
|
||
|
{
|
||
|
foreach($this->adminNotices as $key => $class)
|
||
|
{
|
||
|
$class->load();
|
||
|
$this->doRemoteNotices();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function getNoticeByKey($key)
|
||
|
{
|
||
|
if (isset($this->adminNotices[$key]))
|
||
|
{
|
||
|
return $this->adminNotices[$key];
|
||
|
}
|
||
|
else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function getAllNotices()
|
||
|
{
|
||
|
return $this->adminNotices;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Called by MediaLibraryModel
|
||
|
public function invokeLegacyNotice()
|
||
|
{
|
||
|
$noticeModel = $this->getNoticeByKey('MSG_CONVERT_LEGACY');
|
||
|
if (! $noticeModel->isDismissed())
|
||
|
{
|
||
|
$noticeModel->addManual();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
protected function doRemoteNotices()
|
||
|
{
|
||
|
$notices = $this->get_remote_notices();
|
||
|
|
||
|
if ($notices == false)
|
||
|
return;
|
||
|
|
||
|
foreach($notices as $remoteNotice)
|
||
|
{
|
||
|
if (! isset($remoteNotice->id) && ! isset($remoteNotice->message))
|
||
|
return;
|
||
|
|
||
|
if (! isset($remoteNotice->type))
|
||
|
$remoteNotice->type = 'notice';
|
||
|
|
||
|
$message = esc_html($remoteNotice->message);
|
||
|
$id = sanitize_text_field($remoteNotice->id);
|
||
|
|
||
|
$noticeController = Notices::getInstance();
|
||
|
$noticeObj = $noticeController->getNoticeByID($id);
|
||
|
|
||
|
// not added to system yet
|
||
|
if ($noticeObj === false)
|
||
|
{
|
||
|
switch ($remoteNotice->type)
|
||
|
{
|
||
|
case 'warning':
|
||
|
$new_notice = Notices::addWarning($message);
|
||
|
break;
|
||
|
case 'error':
|
||
|
$new_notice = Notices::addError($message);
|
||
|
break;
|
||
|
case 'notice':
|
||
|
default:
|
||
|
$new_notice = Notices::addNormal($message);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
Notices::makePersistent($new_notice, $id, MONTH_IN_SECONDS);
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
public function proposeUpgradePopup() {
|
||
|
$view = new ViewController();
|
||
|
$view->loadView('snippets/part-upgrade-options');
|
||
|
}
|
||
|
|
||
|
public function proposeUpgradeRemote()
|
||
|
{
|
||
|
//$stats = $this->countAllIfNeeded($this->_settings->currentStats, 300);
|
||
|
$statsController = StatsController::getInstance();
|
||
|
$apiKeyController = ApiKeyController::getInstance();
|
||
|
$settings = \wpSPIO()->settings();
|
||
|
|
||
|
$webpActive = ($settings->createWebp) ? true : false;
|
||
|
$avifActive = ($settings->createAvif) ? true : false;
|
||
|
|
||
|
$args = array(
|
||
|
'method' => 'POST',
|
||
|
'timeout' => 10,
|
||
|
'redirection' => 5,
|
||
|
'httpversion' => '1.0',
|
||
|
'blocking' => true,
|
||
|
'headers' => array(),
|
||
|
'body' => array("params" => json_encode(array(
|
||
|
'plugin_version' => SHORTPIXEL_IMAGE_OPTIMISER_VERSION,
|
||
|
'key' => $apiKeyController->forceGetApiKey(),
|
||
|
'm1' => $statsController->find('period', 'months', '1'),
|
||
|
'm2' => $statsController->find('period', 'months', '2'),
|
||
|
'm3' => $statsController->find('period', 'months', '3'),
|
||
|
'm4' => $statsController->find('period', 'months', '4'),
|
||
|
'filesTodo' => $statsController->totalImagesToOptimize(),
|
||
|
'estimated' => $settings->optimizeUnlisted || $settings->optimizeRetina ? 'true' : 'false',
|
||
|
'webp' => $webpActive,
|
||
|
'avif' => $avifActive,
|
||
|
/* */
|
||
|
'iconsUrl' => base64_encode(wpSPIO()->plugin_url('res/img'))
|
||
|
))),
|
||
|
'cookies' => array()
|
||
|
|
||
|
);
|
||
|
|
||
|
|
||
|
$proposal = wp_remote_post("https://shortpixel.com/propose-upgrade-frag", $args);
|
||
|
|
||
|
if(is_wp_error( $proposal )) {
|
||
|
$proposal = array('body' => __('Error. Could not contact ShortPixel server for proposal', 'shortpixel-image-optimiser'));
|
||
|
}
|
||
|
die( $proposal['body'] );
|
||
|
|
||
|
}
|
||
|
|
||
|
private function get_remote_notices()
|
||
|
{
|
||
|
$transient_name = 'shortpixel_remote_notice';
|
||
|
$transient_duration = DAY_IN_SECONDS;
|
||
|
|
||
|
if (\wpSPIO()->env()->is_debug)
|
||
|
$transient_duration = 30;
|
||
|
|
||
|
$keyControl = new apiKeyController();
|
||
|
//$keyControl->loadKey();
|
||
|
|
||
|
$notices = get_transient($transient_name);
|
||
|
$url = $this->remote_message_endpoint;
|
||
|
$url = add_query_arg(array( // has url
|
||
|
'key' => $keyControl->forceGetApiKey(),
|
||
|
'version' => SHORTPIXEL_IMAGE_OPTIMISER_VERSION,
|
||
|
'target' => 3,
|
||
|
), $url);
|
||
|
|
||
|
|
||
|
if ( $notices === false ) {
|
||
|
$notices_response = wp_safe_remote_request( $url );
|
||
|
$content = false;
|
||
|
if (! is_wp_error( $notices_response ) )
|
||
|
{
|
||
|
$notices = json_decode($notices_response['body']);
|
||
|
|
||
|
if (! is_array($notices))
|
||
|
$notices = false;
|
||
|
|
||
|
// Save transient anywhere to prevent over-asking when nothing good is there.
|
||
|
set_transient( $transient_name, $notices, $transient_duration );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
set_transient( $transient_name, false, $transient_duration );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $notices;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
public function pluginUpdateMessage($data, $response)
|
||
|
{
|
||
|
// $message = $this->getPluginUpdateMessage($plugin['new_version']);
|
||
|
|
||
|
$message = $this->get_update_notice($data, $response);
|
||
|
|
||
|
if( $message !== false && strlen(trim($message)) > 0) {
|
||
|
$wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
|
||
|
printf(
|
||
|
'<tr class="plugin-update-tr active"><td colspan="%s" class="plugin-update colspanchange"><div class="notice inline notice-warning notice-alt">%s</div></td></tr>',
|
||
|
$wp_list_table->get_column_count(),
|
||
|
wpautop( $message )
|
||
|
);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Stolen from SPAI, Thanks.
|
||
|
*/
|
||
|
private function get_update_notice($data, $response) {
|
||
|
$transient_name = 'shortpixel_update_notice_' . $response->new_version;
|
||
|
|
||
|
$transient_duration = DAY_IN_SECONDS;
|
||
|
|
||
|
if (\wpSPIO()->env()->is_debug)
|
||
|
$transient_duration = 30;
|
||
|
|
||
|
$update_notice = get_transient( $transient_name );
|
||
|
$url = $this->remote_readme_endpoint;
|
||
|
|
||
|
if ( $update_notice === false || strlen( $update_notice ) == 0 ) {
|
||
|
$readme_response = wp_safe_remote_request( $url );
|
||
|
$content = false;
|
||
|
if (! is_wp_error( $readme_response ) )
|
||
|
{
|
||
|
$content = $readme_response['body'];
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( !empty( $readme_response ) ) {
|
||
|
$update_notice = $this->parse_update_notice( $content, $response );
|
||
|
set_transient( $transient_name, $update_notice, $transient_duration );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $update_notice;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Parse update notice from readme file.
|
||
|
*
|
||
|
* @param string $content ShortPixel AI readme file content
|
||
|
* @param object $response WordPress response
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
private function parse_update_notice( $content, $response ) {
|
||
|
|
||
|
$new_version = $response->new_version;
|
||
|
|
||
|
$update_notice = '';
|
||
|
|
||
|
// foreach ( $check_for_notices as $id => $check_version ) {
|
||
|
|
||
|
if ( version_compare( SHORTPIXEL_IMAGE_OPTIMISER_VERSION, $new_version, '>' ) ) {
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
$result = $this->parse_readme_content( $content, $new_version, $response );
|
||
|
|
||
|
if ( !empty( $result ) ) {
|
||
|
$update_notice = $result;
|
||
|
}
|
||
|
// }
|
||
|
|
||
|
return wp_kses_post( $update_notice );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
* Parses readme file's content to find notice related to passed version
|
||
|
*
|
||
|
* @param string $content Readme file content
|
||
|
* @param string $version Checked version
|
||
|
* @param object $response WordPress response
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
|
||
|
private function parse_readme_content( $content, $new_version, $response ) {
|
||
|
|
||
|
$notices_pattern = '/==.*Upgrade Notice.*==(.*)$|==/Uis';
|
||
|
|
||
|
$notice = '';
|
||
|
$matches = null;
|
||
|
|
||
|
if ( preg_match( $notices_pattern, $content, $matches ) ) {
|
||
|
|
||
|
if (! isset($matches[1]))
|
||
|
return ''; // no update texts.
|
||
|
|
||
|
$match = str_replace('\n', '', $matches[1]);
|
||
|
$lines = str_split(trim($match));
|
||
|
|
||
|
$versions = array();
|
||
|
$inv = false;
|
||
|
foreach($lines as $char)
|
||
|
{
|
||
|
//if (count($versions) == 0)
|
||
|
if ($char == '=' && ! $inv) // = and not recording version, start one.
|
||
|
{
|
||
|
$curver = '';
|
||
|
$inv = true;
|
||
|
}
|
||
|
elseif ($char == ' ' && $inv) // don't record spaces in version
|
||
|
continue;
|
||
|
elseif ($char == '=' && $inv) // end of version line
|
||
|
{ $versions[trim($curver)] = '';
|
||
|
$inv = false;
|
||
|
}
|
||
|
elseif($inv) // record the version string
|
||
|
{
|
||
|
$curver .= $char;
|
||
|
}
|
||
|
elseif(! $inv) // record the message
|
||
|
{
|
||
|
$versions[trim($curver)] .= $char;
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
foreach($versions as $version => $line)
|
||
|
{
|
||
|
if (version_compare(SHORTPIXEL_IMAGE_OPTIMISER_VERSION, $version, '<') && version_compare($version, $new_version, '<='))
|
||
|
{
|
||
|
$notice .= '<span>';
|
||
|
$notice .= $this->markdown2html( $line );
|
||
|
$notice .= '</span>';
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return $notice;
|
||
|
}
|
||
|
|
||
|
/*private function replace_readme_constants( $content, $response ) {
|
||
|
$constants = [ '{{ NEW VERSION }}', '{{ CURRENT VERSION }}', '{{ PHP VERSION }}', '{{ REQUIRED PHP VERSION }}' ];
|
||
|
$replacements = [ $response->new_version, SHORTPIXEL_IMAGE_OPTIMISER_VERSION, PHP_VERSION, $response->requires_php ];
|
||
|
|
||
|
return str_replace( $constants, $replacements, $content );
|
||
|
} */
|
||
|
|
||
|
private function markdown2html( $content ) {
|
||
|
$patterns = [
|
||
|
'/\*\*(.+)\*\*/U', // bold
|
||
|
'/__(.+)__/U', // italic
|
||
|
'/\[([^\]]*)\]\(([^\)]*)\)/U', // link
|
||
|
];
|
||
|
|
||
|
$replacements = [
|
||
|
'<strong>${1}</strong>',
|
||
|
'<em>${1}</em>',
|
||
|
'<a href="${2}" target="_blank">${1}</a>',
|
||
|
];
|
||
|
|
||
|
$prepared_content = preg_replace( $patterns, $replacements, $content );
|
||
|
|
||
|
return isset( $prepared_content ) ? $prepared_content : $content;
|
||
|
}
|
||
|
|
||
|
|
||
|
} // class
|