first
This commit is contained in:
@ -0,0 +1,140 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
use ShortPixel\Controller\QuotaController as QuotaController;
|
||||
|
||||
// Central place for user / access checking, roles etc.
|
||||
class AccessModel
|
||||
{
|
||||
|
||||
// Instance of class
|
||||
private static $instance;
|
||||
|
||||
// Array of known SPIO Capabilities mapped to WordPress variants
|
||||
private $caps;
|
||||
|
||||
// int . The current user id
|
||||
private $current_user_id;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setDefaultPermissions();
|
||||
}
|
||||
|
||||
protected function setDefaultPermissions()
|
||||
{
|
||||
|
||||
$spioCaps = array(
|
||||
'notices' => 'activate_plugins', // used in AdminNoticesController
|
||||
'quota-warning' => 'manage_options', // used in AdminController
|
||||
'image_all' => 'edit_others_posts',
|
||||
'image_user' => 'edit_post',
|
||||
'custom_all' => 'edit_others_posts',
|
||||
'is_admin_user' => 'manage_options',
|
||||
'actions' => array(),
|
||||
);
|
||||
|
||||
$spioCaps = apply_filters('shortpixel/init/permissions', $spioCaps);
|
||||
// $this->cap_actions = bla.
|
||||
$this->caps = $spioCaps;
|
||||
|
||||
}
|
||||
|
||||
public static function getInstance()
|
||||
{
|
||||
if (is_null(self::$instance))
|
||||
{
|
||||
self::$instance = new AccessModel();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/** Check for allowing a notice
|
||||
* @param $notice Object of type notice.
|
||||
*/
|
||||
public function noticeIsAllowed($notice)
|
||||
{
|
||||
$cap = $this->getCap('notices');
|
||||
return $this->user()->has_cap($cap);
|
||||
}
|
||||
|
||||
/*
|
||||
@param SPIO capability to check again the user WordPress permissions.
|
||||
*/
|
||||
public function userIsAllowed($cap)
|
||||
{
|
||||
$cap = $this->getCap($cap);
|
||||
return $this->user()->has_cap($cap);
|
||||
}
|
||||
|
||||
public function imageIsEditable($mediaItem)
|
||||
{
|
||||
$type = $mediaItem->get('type');
|
||||
if ($type == 'custom' )
|
||||
{
|
||||
return $this->user()->has_cap($this->getCap('custom_all'), $mediaItem->get('id'));
|
||||
}
|
||||
elseif ($type == 'media') // media
|
||||
{
|
||||
if ($this->user()->has_cap($this->getCap('image_all'), $mediaItem->get('id')) || $this->user()->has_cap($this->getCap('image_user'), $mediaItem->get('id')) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isFeatureAvailable($name)
|
||||
{
|
||||
$available = true;
|
||||
|
||||
switch($name)
|
||||
{
|
||||
case 'avif':
|
||||
$quotaControl = QuotaController::getInstance();
|
||||
$quota = $quotaControl->getQuota();
|
||||
|
||||
if (property_exists($quota, 'unlimited') && $quota->unlimited === true)
|
||||
{
|
||||
$available = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'webp':
|
||||
default:
|
||||
|
||||
break;
|
||||
}
|
||||
return $available;
|
||||
}
|
||||
|
||||
|
||||
protected function user()
|
||||
{
|
||||
return wp_get_current_user();
|
||||
}
|
||||
|
||||
/** Find the needed capability
|
||||
*
|
||||
* This translates a SPIO capability into the associated cap that is registered within WordPress.
|
||||
*
|
||||
* @param $cap The required Capability
|
||||
* @param $default The default value if not found. This is defaults to an admin cap to prevent access leaking.
|
||||
*/
|
||||
protected function getCap($cap, $default = 'manage_options')
|
||||
{
|
||||
if (isset($this->caps[$cap]))
|
||||
return $this->caps[$cap];
|
||||
else
|
||||
return $default;
|
||||
}
|
||||
|
||||
|
||||
} // CLASS
|
@ -0,0 +1,155 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
use ShortPixel\Notices\NoticeController as Notice;
|
||||
use ShortPixel\Notices\NoticeController as Notices;
|
||||
|
||||
|
||||
abstract class AdminNoticeModel
|
||||
{
|
||||
protected $key; // abstract
|
||||
protected $notice;
|
||||
|
||||
protected $errorLevel = 'normal';
|
||||
protected $suppress_delay = YEAR_IN_SECONDS;
|
||||
protected $callback;
|
||||
|
||||
protected $include_screens = array();
|
||||
protected $exclude_screens = array();
|
||||
|
||||
protected $data;
|
||||
|
||||
// No stuff loading here, low init
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// The main init, ty.
|
||||
public function load()
|
||||
{
|
||||
$noticeController = Notices::getInstance();
|
||||
$notice = $noticeController->getNoticeByID($this->key);
|
||||
|
||||
if (is_object($notice))
|
||||
{
|
||||
$this->notice = $notice;
|
||||
}
|
||||
|
||||
if (is_object($notice) && $notice->isDismissed())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_null($this->notice) && $this->checkTrigger() === true)
|
||||
{
|
||||
$this->add();
|
||||
}
|
||||
elseif ( is_object($this->notice) && $this->checkReset() === true)
|
||||
{
|
||||
$this->reset();
|
||||
}
|
||||
}
|
||||
|
||||
public function getKey()
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function reset($key = null)
|
||||
{
|
||||
$key = (is_null($key)) ? $this->key : $key;
|
||||
Notices::removeNoticeByID($key);
|
||||
}
|
||||
|
||||
protected function checkReset()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// For when trigger condition is not applicable.
|
||||
public function addManual($args = array())
|
||||
{
|
||||
foreach($args as $key => $val)
|
||||
{
|
||||
$this->addData($key, $val);
|
||||
}
|
||||
$this->add();
|
||||
}
|
||||
|
||||
public function getNoticeObj()
|
||||
{
|
||||
return $this->notice; // can be null!
|
||||
}
|
||||
|
||||
// Proxy for noticeModel dismissed
|
||||
public function isDismissed()
|
||||
{
|
||||
$notice = $this->getNoticeObj();
|
||||
if (is_null($notice) || $notice->isDismissed() === false)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected function add()
|
||||
{
|
||||
|
||||
switch ($this->errorLevel)
|
||||
{
|
||||
case 'warning':
|
||||
$notice = Notices::addWarning($this->getMessage());
|
||||
break;
|
||||
case 'normal';
|
||||
default:
|
||||
$notice = Notices::addNormal($this->getMessage());
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/// Todo implement include / exclude screens here.
|
||||
if (count($this->exclude_screens) > 0)
|
||||
{
|
||||
$notice->limitScreens('exclude', $this->exclude_screens);
|
||||
}
|
||||
|
||||
if (count($this->include_screens) > 0)
|
||||
{
|
||||
$notice->limitScreens('include', $this->include_screens);
|
||||
}
|
||||
|
||||
if (! is_null($this->callback))
|
||||
Notices::makePersistent($notice, $this->key, $this->suppress_delay, $this->callback);
|
||||
else
|
||||
Notices::makePersistent($notice, $this->key, $this->suppress_delay);
|
||||
|
||||
$this->notice = $notice;
|
||||
}
|
||||
|
||||
protected function addData($name, $value)
|
||||
{
|
||||
$this->data[$name] = $value;
|
||||
}
|
||||
|
||||
protected function getData($name)
|
||||
{
|
||||
if (isset($this->data[$name]))
|
||||
{
|
||||
return $this->data[$name];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ?? abstract proteced removeCondition();
|
||||
|
||||
abstract protected function checkTrigger();
|
||||
abstract protected function getMessage();
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\AdminNotices;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class ApiNotice extends \ShortPixel\Model\AdminNoticeModel
|
||||
{
|
||||
protected $key = 'MSG_NO_APIKEY';
|
||||
|
||||
public function load()
|
||||
{
|
||||
$activationDate = \wpSPIO()->settings()->activationDate;
|
||||
if (! $activationDate)
|
||||
{
|
||||
$activationDate = time();
|
||||
\wpSPIO()->settings()->activationDate = $activationDate;
|
||||
}
|
||||
|
||||
parent::load();
|
||||
}
|
||||
|
||||
protected function checkTrigger()
|
||||
{
|
||||
if (\wpSPIO()->settings()->verifiedKey)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If not key is verified.
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getMessage()
|
||||
{
|
||||
$message = "<p>" . __('To start the optimization process, you need to validate your API key on the '
|
||||
. '<a href="options-general.php?page=wp-shortpixel-settings">ShortPixel Settings</a> page in your WordPress admin.','shortpixel-image-optimiser') . "
|
||||
</p>
|
||||
<p>" . __('If you do not have an API key yet, just fill out the form and a key will be created.','shortpixel-image-optimiser') . "</p>";
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\AdminNotices;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\Controller\AdminNoticesController as AdminNoticesController;
|
||||
|
||||
class ApiNoticeRepeat extends \ShortPixel\Model\AdminNoticeModel
|
||||
{
|
||||
protected $key = 'MSG_NO_APIKEY_REPEAT';
|
||||
protected $errorLevel = 'warning';
|
||||
|
||||
protected function checkTrigger()
|
||||
{
|
||||
if (\wpSPIO()->settings()->verifiedKey)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is set by general ApiNotice. If not set, don't bother with the repeat.
|
||||
$activationDate = \wpSPIO()->settings()->activationDate;
|
||||
if (! $activationDate)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$controller = AdminNoticesController::getInstance();
|
||||
|
||||
$firstNotice = $controller->getNoticeByKey('MSG_NO_APIKEY');
|
||||
|
||||
// Check if first notice is there, and not dismissed, then don't repeat.
|
||||
if ($firstNotice->isDismissed() === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// After 6 hours
|
||||
if (time() < $activationDate + (6 * HOUR_IN_SECONDS))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If not key is verified and first one is dismissed, and not this one.
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getMessage()
|
||||
{
|
||||
$message = __("Action required! Please <a href='https://shortpixel.com/wp-apikey' target='_blank'>get your API key</a> to activate your ShortPixel plugin.",'shortpixel-image-optimiser');
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\AdminNotices;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\Controller\AdminNoticesController as AdminNoticesController;
|
||||
|
||||
class ApiNoticeRepeatLong extends \ShortPixel\Model\AdminNoticeModel
|
||||
{
|
||||
protected $key = 'MSG_NO_APIKEY_REPEAT_LONG';
|
||||
protected $errorLevel = 'warning';
|
||||
|
||||
protected function checkTrigger()
|
||||
{
|
||||
if (\wpSPIO()->settings()->verifiedKey)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is set by general ApiNotice. If not set, don't bother with the repeat.
|
||||
$activationDate = \wpSPIO()->settings()->activationDate;
|
||||
if (! $activationDate)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$controller = AdminNoticesController::getInstance();
|
||||
|
||||
// Check the original
|
||||
$firstNotice = $controller->getNoticeByKey('MSG_NO_APIKEY');
|
||||
if ($firstNotice->isDismissed() === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the Repeat.
|
||||
$secondNotice = $controller->getNoticeByKey('MSG_NO_APIKEY_REPEAT');
|
||||
if ($secondNotice->isDismissed() === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// After 3 days.
|
||||
if (time() < $activationDate + (3 * DAY_IN_SECONDS))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If not key is verified and first one is dismissed, and not this one.
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getMessage()
|
||||
{
|
||||
$message = __("Your image gallery is not optimized. It takes 2 minutes to <a href='https://shortpixel.com/wp-apikey' target='_blank'>get your API key</a> and activate your ShortPixel plugin.",'shortpixel-image-optimiser') . "<BR><BR>";
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\AdminNotices;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use \ShortPixel\Controller\CacheController as CacheController;
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
|
||||
|
||||
class AvifNotice extends \ShortPixel\Model\AdminNoticeModel
|
||||
{
|
||||
protected $key = 'MSG_AVIF_ERROR';
|
||||
protected $errorLevel = 'error';
|
||||
|
||||
protected $error_message;
|
||||
protected $error_detail;
|
||||
|
||||
// Remove this.
|
||||
public function __construct()
|
||||
{
|
||||
$this->callback = array($this, 'function_crash');
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/*
|
||||
public function function_crash()
|
||||
{
|
||||
echo 'Yall';
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
protected function checkTrigger()
|
||||
{
|
||||
// No Automatic Trigger.
|
||||
return false;
|
||||
}
|
||||
|
||||
public function check()
|
||||
{
|
||||
$cache = new CacheController();
|
||||
if (apply_filters('shortpixel/avifcheck/override', false) === true)
|
||||
{ return; }
|
||||
|
||||
|
||||
if ($cache->getItem('avif_server_check')->exists() === false)
|
||||
{
|
||||
$url = \WPSPIO()->plugin_url('res/img/test.avif');
|
||||
$headers = get_headers($url);
|
||||
$is_error = true;
|
||||
|
||||
$this->addData('headers', $headers);
|
||||
// Defaults.
|
||||
$this->error_message = __('AVIF server test failed. Your server may not be configured to display AVIF files correctly. Serving AVIF might cause your images not to load. Check your images, disable the AVIF option, or update your web server configuration.', 'shortpixel-image-optimiser');
|
||||
$this->error_detail = __('The request did not return valid HTTP headers. Check if the plugin is allowed to access ' . $url, 'shortpixel-image-optimiser');
|
||||
|
||||
$contentType = null;
|
||||
$response = $headers[0];
|
||||
|
||||
if (is_array($headers) )
|
||||
{
|
||||
foreach($headers as $index => $header)
|
||||
{
|
||||
if ( strpos(strtolower($header), 'content-type') !== false )
|
||||
{
|
||||
// This is another header that can interrupt.
|
||||
if (strpos(strtolower($header), 'x-content-type-options') === false)
|
||||
{
|
||||
$contentType = $header;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// http not ok, redirect etc. Shouldn't happen.
|
||||
if (is_null($response) || strpos($response, '200') === false)
|
||||
{
|
||||
$this->error_detail = sprintf(__('AVIF check could not be completed because the plugin could not retrieve %s %s %s. %s Please check the security/firewall settings and try again', 'shortpixel-image-optimiser'), '<a href="' . $url . '">', $url, '</a>', '<br>');
|
||||
}
|
||||
elseif(is_null($contentType) || strpos($contentType, 'avif') === false)
|
||||
{
|
||||
$this->error_detail = sprintf(__('The required Content-type header for AVIF files was not found. Please check this with your hosting and/or CDN provider. For more details on how to fix this issue, %s see this article %s', 'shortpixel_image_optimiser'), '<a href="https://shortpixel.com/blog/avif-mime-type-delivery-apache-nginx/" target="_blank"> ', '</a>');
|
||||
}
|
||||
else
|
||||
{
|
||||
$is_error = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_error)
|
||||
{
|
||||
if (is_null($this->notice) || $this->notice->isDismissed() === false)
|
||||
{
|
||||
$this->addManual();
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->reset();
|
||||
|
||||
$item = $cache->getItem('avif_server_check');
|
||||
$item->setValue(time());
|
||||
$item->setExpires(MONTH_IN_SECONDS);
|
||||
$cache->storeItemObject($item );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function getMessage()
|
||||
{
|
||||
$headers = $this->getData('headers');
|
||||
$message = '<h4>' . $this->error_message . '</h4><p>' . $this->error_detail . '</p><p class="small">' . __('Returned headers for:<br>', 'shortpixel-image-optimiser') . print_r($headers, true) . '</p>';
|
||||
return $message;
|
||||
}
|
||||
}
|
@ -0,0 +1,219 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\AdminNotices;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class CompatNotice extends \ShortPixel\Model\AdminNoticeModel
|
||||
{
|
||||
protected $key = 'MSG_COMPAT';
|
||||
protected $errorLevel = 'warning';
|
||||
|
||||
protected function checkTrigger()
|
||||
{
|
||||
$conflictPlugins = $this->getConflictingPlugins();
|
||||
if (count($conflictPlugins) > 0)
|
||||
{
|
||||
$this->addData('conflicts', $conflictPlugins);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getMessage()
|
||||
{
|
||||
$conflicts = $this->getData('conflicts');
|
||||
if (! is_array($conflicts))
|
||||
$conflicts = array();
|
||||
|
||||
$message = __("The following plugins are not compatible with ShortPixel and may cause unexpected results: ",'shortpixel-image-optimiser');
|
||||
$message .= '<ul class="sp-conflict-plugins">';
|
||||
foreach($conflicts as $plugin) {
|
||||
//ShortPixelVDD($plugin);
|
||||
$action = $plugin['action'];
|
||||
$link = ( $action == 'Deactivate' )
|
||||
? wp_nonce_url( admin_url( 'admin-post.php?action=shortpixel_deactivate_conflict_plugin&plugin=' . urlencode( $plugin['path'] ) ), 'sp_deactivate_plugin_nonce' )
|
||||
: $plugin['href'];
|
||||
$message .= '<li class="sp-conflict-plugins-list"><strong>' . $plugin['name'] . '</strong>';
|
||||
$message .= '<a href="' . $link . '" class="button button-primary">' . $action . '</a>';
|
||||
|
||||
if($plugin['details']) $message .= '<br>';
|
||||
if($plugin['details']) $message .= '<span>' . $plugin['details'] . '</span>';
|
||||
}
|
||||
$message .= "</ul>";
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
protected function getConflictingPlugins() {
|
||||
$settings = \wpSPIO()->settings();
|
||||
|
||||
$conflictPlugins = array(
|
||||
'WP Smush - Image Optimization'
|
||||
=> array(
|
||||
'action'=>'Deactivate',
|
||||
'data'=>'wp-smushit/wp-smush.php',
|
||||
'page'=>'wp-smush-bulk'
|
||||
),
|
||||
'Imagify Image Optimizer'
|
||||
=> array(
|
||||
'action'=>'Deactivate',
|
||||
'data'=>'imagify/imagify.php',
|
||||
'page'=>'imagify'
|
||||
),
|
||||
'Compress JPEG & PNG images (TinyPNG)'
|
||||
=> array(
|
||||
'action'=>'Deactivate',
|
||||
'data'=>'tiny-compress-images/tiny-compress-images.php',
|
||||
'page'=>'tinify'
|
||||
),
|
||||
'Kraken.io Image Optimizer'
|
||||
=> array(
|
||||
'action'=>'Deactivate',
|
||||
'data'=>'kraken-image-optimizer/kraken.php',
|
||||
'page'=>'wp-krakenio'
|
||||
),
|
||||
'Optimus - WordPress Image Optimizer'
|
||||
=> array(
|
||||
'action'=>'Deactivate',
|
||||
'data'=>'optimus/optimus.php',
|
||||
'page'=>'optimus'
|
||||
),
|
||||
'Phoenix Media Rename' => array(
|
||||
'action' => 'Deactivate',
|
||||
'data' => 'phoenix-media-rename/phoenix-media-rename.php',
|
||||
),
|
||||
'EWWW Image Optimizer'
|
||||
=> array(
|
||||
'action'=>'Deactivate',
|
||||
'data'=>'ewww-image-optimizer/ewww-image-optimizer.php',
|
||||
'page'=>'ewww-image-optimizer%2F'
|
||||
),
|
||||
'EWWW Image Optimizer Cloud'
|
||||
=> array(
|
||||
'action'=>'Deactivate',
|
||||
'data'=>'ewww-image-optimizer-cloud/ewww-image-optimizer-cloud.php',
|
||||
'page'=>'ewww-image-optimizer-cloud%2F'
|
||||
),
|
||||
'ImageRecycle pdf & image compression'
|
||||
=> array(
|
||||
'action'=>'Deactivate',
|
||||
'data'=>'imagerecycle-pdf-image-compression/wp-image-recycle.php',
|
||||
'page'=>'option-image-recycle'
|
||||
),
|
||||
'CheetahO Image Optimizer'
|
||||
=> array(
|
||||
'action'=>'Deactivate',
|
||||
'data'=>'cheetaho-image-optimizer/cheetaho.php',
|
||||
'page'=>'cheetaho'
|
||||
),
|
||||
'Zara 4 Image Compression'
|
||||
=> array(
|
||||
'action'=>'Deactivate',
|
||||
'data'=>'zara-4/zara-4.php',
|
||||
'page'=>'zara-4'
|
||||
),
|
||||
'CW Image Optimizer'
|
||||
=> array(
|
||||
'action'=>'Deactivate',
|
||||
'data'=>'cw-image-optimizer/cw-image-optimizer.php',
|
||||
'page'=>'cw-image-optimizer'
|
||||
),
|
||||
'Simple Image Sizes'
|
||||
=> array(
|
||||
'action'=>'Deactivate',
|
||||
'data'=>'simple-image-sizes/simple_image_sizes.php'
|
||||
),
|
||||
'Regenerate Thumbnails and Delete Unused'
|
||||
=> array(
|
||||
'action' => 'Deactivate',
|
||||
'data' => 'regenerate-thumbnails-and-delete-unused/regenerate_wpregenerate.php',
|
||||
),
|
||||
'Swift Performance'
|
||||
=> array(
|
||||
'action' => 'Deactivate',
|
||||
'data' => 'swift-performance/performance.php',
|
||||
),
|
||||
'Swift AI'
|
||||
=> array(
|
||||
'action' => 'Deactivate',
|
||||
'data' => 'swift-ai/main.php',
|
||||
),
|
||||
'Swift Performance Lite'
|
||||
=> array(
|
||||
'action' => 'Deactivate',
|
||||
'data' => 'swift-performance-lite/performance.php',
|
||||
),
|
||||
//DEACTIVATED TEMPORARILY - it seems that the customers get scared.
|
||||
/* 'Jetpack by WordPress.com - The Speed up image load times Option'
|
||||
=> array(
|
||||
'action'=>'Change Setting',
|
||||
'data'=>'jetpack/jetpack.php',
|
||||
'href'=>'admin.php?page=jetpack#/settings'
|
||||
)
|
||||
*/
|
||||
);
|
||||
if($settings->processThumbnails) {
|
||||
$details = __('Details: recreating image files may require re-optimization of the resulting thumbnails, even if they were previously optimized. Please use <a href="https://wordpress.org/plugins/regenerate-thumbnails-advanced/" target="_blank">reGenerate Thumbnails Advanced</a> instead.','shortpixel-image-optimiser');
|
||||
|
||||
$conflictPlugins = array_merge($conflictPlugins, array(
|
||||
'Regenerate Thumbnails'
|
||||
=> array(
|
||||
'action'=>'Deactivate',
|
||||
'data'=>'regenerate-thumbnails/regenerate-thumbnails.php',
|
||||
'page'=>'regenerate-thumbnails',
|
||||
'details' => $details
|
||||
),
|
||||
'Force Regenerate Thumbnails'
|
||||
=> array(
|
||||
'action'=>'Deactivate',
|
||||
'data'=>'force-regenerate-thumbnails/force-regenerate-thumbnails.php',
|
||||
'page'=>'force-regenerate-thumbnails',
|
||||
'details' => $details
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
$found = array();
|
||||
foreach($conflictPlugins as $name => $path) {
|
||||
$action = ( isset($path['action']) ) ? $path['action'] : null;
|
||||
$data = ( isset($path['data']) ) ? $path['data'] : null;
|
||||
$href = ( isset($path['href']) ) ? $path['href'] : null;
|
||||
$page = ( isset($path['page']) ) ? $path['page'] : null;
|
||||
$details = ( isset($path['details']) ) ? $path['details'] : null;
|
||||
if(is_plugin_active($data)) {
|
||||
|
||||
// Local checks for things. If too much this needs some other impl.
|
||||
if( $data == 'jetpack/jetpack.php' ){
|
||||
$jetPackPhoton = get_option('jetpack_active_modules') ? in_array('photon', get_option('jetpack_active_modules')) : false;
|
||||
if( !$jetPackPhoton ){ continue; }
|
||||
}
|
||||
|
||||
if ($data == 'swift-performance/performance.php' || $data == 'swift-ai/main.php')
|
||||
{
|
||||
if (false === $this->checkSwiftActive())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$found[] = array( 'name' => $name, 'action'=> $action, 'path' => $data, 'href' => $href , 'page' => $page, 'details' => $details);
|
||||
}
|
||||
}
|
||||
return $found;
|
||||
}
|
||||
|
||||
private function checkSwiftActive()
|
||||
{
|
||||
if ( function_exists('swift3_check_option') && true == swift3_check_option('optimize-images', 'on'))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\AdminNotices;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class LegacyNotice extends \ShortPixel\Model\AdminNoticeModel
|
||||
{
|
||||
protected $key = 'MSG_CONVERT_LEGACY';
|
||||
|
||||
protected function checkTrigger()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getMessage()
|
||||
{
|
||||
$message = '<p><strong>' . __('ShortPixel has found items in the media library with an outdated optimization format!', 'shortpixel-image-optimiser') . '</strong></p>';
|
||||
|
||||
$message .= '<p>' . __('Prior to version 5.0, a different format was used to store ShortPixel optimization information. ShortPixel automatically migrates the media library items to the new format when they are opened. %s Please check if your images contain the optimization information after migration. %s Read more %s', 'shortpixel-image-optimiser') . '</p>';
|
||||
|
||||
$message .= '<p>' . __('It is recommended to migrate all items to the modern format by clicking the button below.', 'shortpixel-image-optimser') . '</p>';
|
||||
$message .= '<p><a href="%s" class="button button-primary">%s</a></p>';
|
||||
|
||||
$read_link = esc_url('https://shortpixel.com/knowledge-base/article/539-spio-5-tells-me-to-convert-legacy-data-what-is-this');
|
||||
$action_link = esc_url(admin_url('upload.php?page=wp-short-pixel-bulk&panel=bulk-migrate'));
|
||||
$action_name = __('Migrate optimization data', 'shortpixel-image-optimiser');
|
||||
|
||||
$message = sprintf($message, '<br>', '<a href="' . $read_link . '" target="_blank">', '</a>', $action_link, $action_name);
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\AdminNotices;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
|
||||
class ListviewNotice extends \ShortPixel\Model\AdminNoticeModel
|
||||
{
|
||||
protected $key = 'MSG_LISTVIEW_ACTIVE';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->include_screens[] = 'upload';
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/*public function load()
|
||||
{
|
||||
parent::load();
|
||||
// Reset this notice even when dismissed when condition changed.
|
||||
if ($this->isDismissed() && $this->checkReset() === true)
|
||||
{
|
||||
$this->reset();
|
||||
}
|
||||
} */
|
||||
|
||||
protected function checkTrigger()
|
||||
{
|
||||
// Don't check for this, when not on this screen.
|
||||
$screen_id = \wpSPIO()->env()->screen_id;
|
||||
if ($screen_id !== 'upload')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! function_exists('wp_get_current_user') )
|
||||
{
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
$viewMode = get_user_option('media_library_mode', get_current_user_id() );
|
||||
|
||||
if ($viewMode === "" || strlen($viewMode) == 0)
|
||||
{
|
||||
// If nothing is set, set it for them.
|
||||
update_user_option( get_current_user_id(), 'media_library_mode', 'list' );
|
||||
return false;
|
||||
}
|
||||
elseif ($viewMode !== "list")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (is_object($this->getNoticeObj()))
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function checkReset()
|
||||
{
|
||||
if (! function_exists('wp_get_current_user') )
|
||||
{
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
$current_user = wp_get_current_user();
|
||||
$currentUserID = $current_user->ID;
|
||||
$viewMode = get_user_meta($currentUserID, "wp_media_library_mode", true);
|
||||
|
||||
if ($viewMode == 'list')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
protected function getMessage()
|
||||
{
|
||||
|
||||
$message = sprintf(__('Now you can see ShortPixel Image Optimizer\'s actions and optimization data in Grid view too! Click on any image below and you can see the ShortPixel actions and menus in the popup that opens. However, the list view provides a better experience. Click now to %sswitch to the list view%s. ', 'shortpixel-image-optimiser'), '<a href="' . admin_url('upload.php?mode=list') . '">','</a>');
|
||||
|
||||
return $message;
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\AdminNotices;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class NewExclusionFormat extends \ShortPixel\Model\AdminNoticeModel
|
||||
{
|
||||
|
||||
protected $key = 'MSG_EXCLUSION_WARNING';
|
||||
|
||||
|
||||
protected function checkTrigger()
|
||||
{
|
||||
$patterns = \wpSPIO()->settings()->excludePatterns;
|
||||
|
||||
if (! is_array($patterns))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach($patterns as $index => $pattern)
|
||||
{
|
||||
if (! isset($pattern['apply']))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getMessage()
|
||||
{
|
||||
$message = "<p>" . __('As of version 5.5.0, ShortPixel Image Optimiser also checks thumbnails for exclusions. This can change which images are optimized and which are excluded. Please check your exclusion rules on the '
|
||||
. '<a href="options-general.php?page=wp-shortpixel-settings&part=adv-settings">ShortPixel Settings</a> page.','shortpixel-image-optimiser') . "
|
||||
</p>";
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\AdminNotices;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class NextgenNotice extends \ShortPixel\Model\AdminNoticeModel
|
||||
{
|
||||
protected $key = 'MSG_INTEGRATION_NGGALLERY';
|
||||
|
||||
protected function checkTrigger()
|
||||
{
|
||||
|
||||
$settings = \wpSPIO()->settings();
|
||||
|
||||
if (! $settings->verifiedKey)
|
||||
{
|
||||
return false; // no key, no integrations.
|
||||
}
|
||||
|
||||
if (\wpSPIO()->env()->has_nextgen && ! $settings->includeNextGen)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getMessage()
|
||||
{
|
||||
$url = esc_url(admin_url('options-general.php?page=wp-shortpixel-settings&part=adv-settings'));
|
||||
$message = sprintf(__('You seem to be using NextGen Gallery. You can optimize your galleries with ShortPixel, but this is not currently enabled. To enable it, %sgo to settings and enable%s it!', 'shortpixel_image_optimiser'), '<a href="' . $url . '">', '</a>');
|
||||
|
||||
return $message;
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\AdminNotices;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\Controller\StatsController as StatsController;
|
||||
use ShortPixel\Controller\AdminNoticesController as AdminNoticesController;
|
||||
use ShortPixel\Controller\QuotaController as QuotaController;
|
||||
|
||||
class QuotaNoticeMonth extends \ShortPixel\Model\AdminNoticeModel
|
||||
{
|
||||
protected $key = 'MSG_UPGRADE_MONTH';
|
||||
|
||||
public function load()
|
||||
{
|
||||
$this->callback = array(AdminNoticesController::getInstance(), 'proposeUpgradePopup');
|
||||
parent::load();
|
||||
}
|
||||
|
||||
protected function checkTrigger()
|
||||
{
|
||||
$quotaController = QuotaController::getInstance();
|
||||
|
||||
if ($quotaController->hasQuota() === false)
|
||||
return false;
|
||||
|
||||
$quotaData = $quotaController->getQuota();
|
||||
|
||||
if ($this->monthlyUpgradeNeeded($quotaData) === false)
|
||||
return false;
|
||||
|
||||
$this->addData('average', $this->getMonthAverage());
|
||||
$this->addData('month_total', $quotaData->monthly->total);
|
||||
$this->addData('onetime_remaining', $quotaData->onetime->remaining);
|
||||
|
||||
}
|
||||
|
||||
protected function getMessage()
|
||||
{
|
||||
$quotaController = QuotaController::getInstance();
|
||||
|
||||
$quotaData = $quotaController->getQuota();
|
||||
$average = $this->getMonthAverage(); // $this->getData('average');
|
||||
$month_total = $quotaData->monthly->total;// $this->getData('month_total');
|
||||
$onetime_remaining = $quotaData->onetime->remaining; //$this->getData('onetime_remaining'); */
|
||||
|
||||
$message = '<p>' . sprintf(__("You add an average of <strong>%d images and thumbnails</strong> to your Media Library every month and you have <strong>a plan of %d images/month (and %d one-time images)</strong>.%s"
|
||||
. " You may need to upgrade your plan to have all your images optimized.", 'shortpixel-image-optimiser'), $average, $month_total, $onetime_remaining, '<br>') . '</p>';
|
||||
|
||||
$message .= ' <button class="button button-primary" id="shortpixel-upgrade-advice" onclick="ShortPixel.proposeUpgrade()" style="margin-right:10px;"><strong>' . __('Show me the best available options', 'shortpixel-image-optimiser') . '</strong></button>';
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
protected function CheckUpgradeNeeded($quotaData)
|
||||
{
|
||||
if (isset($quotaData->monthly->total) && !$quotaData->unlimited)
|
||||
{
|
||||
$monthAvg = $this->getMonthAvg($quotaData);
|
||||
// +20 I suspect to not trigger on very low values of monthly use(?)
|
||||
$threshold = $quotaData->monthly->total + ($quotaData->onetime->remaining / 6 ) +20;
|
||||
|
||||
if ($monthAvg > $threshold)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getMonthAverage() {
|
||||
$stats = StatsController::getInstance();
|
||||
|
||||
// Count how many months have some optimized images.
|
||||
for($i = 4, $count = 0; $i>=1; $i--) {
|
||||
if($count == 0 && $stats->find('period', 'months', $i) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$count++;
|
||||
|
||||
}
|
||||
// Sum last 4 months, and divide by number of active months to get number of avg per active month.
|
||||
return ($stats->find('period', 'months', 1) + $stats->find('period', 'months', 2) + $stats->find('period', 'months', 3) + $stats->find('period', 'months', 4) / max(1,$count));
|
||||
}
|
||||
|
||||
protected function monthlyUpgradeNeeded($quotaData)
|
||||
{
|
||||
if (isset($quotaData->monthly->total))
|
||||
{
|
||||
$monthAvg = $this->getMonthAverage($quotaData);
|
||||
// +20 I suspect to not trigger on very low values of monthly use(?)
|
||||
$threshold = $quotaData->monthly->total + ($quotaData->onetime->remaining / 6 ) +20;
|
||||
|
||||
if ($monthAvg > $threshold)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // class
|
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\AdminNotices;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\Controller\StatsController as StatsController;
|
||||
use ShortPixel\Controller\ApiKeyController as ApiKeyController;
|
||||
use ShortPixel\Controller\AdminNoticesController as AdminNoticesController;
|
||||
use ShortPixel\Controller\QuotaController as QuotaController;
|
||||
|
||||
|
||||
class QuotaNoticeReached extends \ShortPixel\Model\AdminNoticeModel
|
||||
{
|
||||
protected $key = 'MSG_QUOTA_REACHED';
|
||||
protected $errorLevel = 'error';
|
||||
|
||||
|
||||
public function load()
|
||||
{
|
||||
$this->callback = array(AdminNoticesController::getInstance(), 'proposeUpgradePopup');
|
||||
parent::load();
|
||||
}
|
||||
|
||||
protected function checkTrigger()
|
||||
{
|
||||
$quotaController = QuotaController::getInstance();
|
||||
|
||||
if ($quotaController->hasQuota() === true)
|
||||
return false;
|
||||
|
||||
// $quotaData = $quotaController->getQuota();
|
||||
|
||||
$this->reset('MSG_UPGRADE_MONTH');
|
||||
$this->reset('MSG_UPGRADE_BULK');
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
protected function getMessage()
|
||||
{
|
||||
$statsControl = StatsController::getInstance();
|
||||
$averageCompression = $statsControl->getAverageCompression();
|
||||
$quotaController = QuotaController::getInstance();
|
||||
|
||||
$keyControl = ApiKeyController::getInstance();
|
||||
|
||||
//$keyModel->loadKey();
|
||||
|
||||
$login_url = 'https://shortpixel.com/login/';
|
||||
$friend_url = $login_url;
|
||||
|
||||
if ($keyControl->getKeyForDisplay())
|
||||
{
|
||||
$login_url .= $keyControl->getKeyForDisplay() . '/spio-unlimited';
|
||||
$friend_url = $login_url . 'tell-a-friend';
|
||||
}
|
||||
|
||||
$message = '<div class="sp-quota-exceeded-alert" id="short-pixel-notice-exceed">';
|
||||
|
||||
if($averageCompression) {
|
||||
|
||||
$message .= '<div style="float:right;">
|
||||
<div class="bulk-progress-indicator" style="height: 110px">
|
||||
<div style="margin-bottom:5px">' . __('Average image<br>reduction until now:','shortpixel-image-optimiser') . '</div>
|
||||
<div id="sp-avg-optimization"><input type="text" id="sp-avg-optimization-dial" value="' . round($averageCompression) . '" class="dial percentDial" data-dialsize="60"></div>
|
||||
<script>
|
||||
jQuery(function() {
|
||||
if (ShortPixel)
|
||||
{
|
||||
ShortPixel.percentDial("#sp-avg-optimization-dial", 60);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
}
|
||||
|
||||
$message .= '<h3>' . __('Quota Exceeded','shortpixel-image-optimiser') . '</h3>';
|
||||
|
||||
$quota = $quotaController->getQuota();
|
||||
|
||||
$creditsUsed = number_format($quota->monthly->consumed + $quota->onetime->consumed);
|
||||
$totalOptimized = $statsControl->find('total', 'images');
|
||||
$totalImagesToOptimize = number_format($statsControl->totalImagesToOptimize());
|
||||
|
||||
$message .= '<p>' . sprintf(__('The plugin has optimized <strong>%s images</strong> and has been stopped because it has reached the available quota limit.','shortpixel-image-optimiser'),
|
||||
$creditsUsed);
|
||||
|
||||
if($totalImagesToOptimize > 0) {
|
||||
|
||||
$message .= sprintf(__('<strong> %s images and thumbnails</strong> have not been optimized by ShortPixel yet.','shortpixel-image-optimiser'), $totalImagesToOptimize );
|
||||
}
|
||||
|
||||
$message .= sprintf('</p>
|
||||
<div>
|
||||
<button class="button button-primary" type="button" id="shortpixel-upgrade-advice" onclick="ShortPixel.proposeUpgrade()" style="margin-right:10px;"><strong>' . __('Show me the best available options', 'shortpixel-image-optimiser') . '</strong></button>
|
||||
<a class="button button-primary" href="%s"
|
||||
title="' . __('Go to My Account and choose a plan','shortpixel-image-optimiser') . '" target="_blank" style="margin-right:10px;">
|
||||
<strong>' . __('Upgrade','shortpixel-image-optimiser') . '</strong>
|
||||
</a>
|
||||
<button type="button" name="checkQuota" class="button" onclick="ShortPixel.checkQuota()">'. __('Confirm new credits','shortpixel-image-optimiser') . '</button>
|
||||
</div>', $login_url);
|
||||
|
||||
$message .= '</div>'; /// closing div
|
||||
return $message;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\AdminNotices;
|
||||
|
||||
class SmartcropNotice extends \ShortPixel\Model\AdminNoticeModel
|
||||
{
|
||||
protected $key = 'MSG_FEATURE_SMARTCROP';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->exclude_screens[] = 'settings_page_wp-shortpixel-settings';
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function checkTrigger()
|
||||
{
|
||||
|
||||
$settings = \wpSPIO()->settings();
|
||||
|
||||
if (! $settings->verifiedKey)
|
||||
{
|
||||
return false; // no key, no integrations.
|
||||
}
|
||||
|
||||
if (! $settings->useSmartcrop)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function checkReset()
|
||||
{
|
||||
$settings = \wpSPIO()->settings();
|
||||
if ($settings->useSmartcrop == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getMessage()
|
||||
{
|
||||
$link = 'https://shortpixel.com/knowledge-base/article/182-what-is-smart-cropping';
|
||||
$link2 = 'https://shortpixel.com/blog/how-to-smart-crop-wordpress-images/#how-to-crop-wordpress-images-automatically-smart-solution';
|
||||
$link3 = esc_url(admin_url('options-general.php?page=wp-shortpixel-settings'));
|
||||
|
||||
$message = sprintf(__('%s With ShortPixel you can now %ssmartly crop%s thumbnails on your website. This is especially useful for eCommerce websites %s(read more)%s. %s %s Enable the option on the %sShortPixel Settings%s page. %s', 'shortpixel-image-optimiser'),
|
||||
'<p>' ,
|
||||
'<a href="' . $link . '" target="_blank">', '</a>',
|
||||
'<a href="' . $link2 . '" target="_blank">', '</a>',
|
||||
'</p>', '<p>',
|
||||
'<a href="' . $link3 . '" >', '</a>',
|
||||
'</p>'
|
||||
);
|
||||
return $message;
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\AdminNotices;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class UnlistedNotice extends \ShortPixel\Model\AdminNoticeModel
|
||||
{
|
||||
|
||||
protected $key = 'MSG_UNLISTED_FOUND';
|
||||
|
||||
protected function checkTrigger()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// @todo This message is not properly stringF'ed.
|
||||
protected function getMessage()
|
||||
{
|
||||
$settings = \wpSPIO()->settings();
|
||||
|
||||
//$unlisted = isset($settings->currentStats['foundUnlistedThumbs']) ? $settings->currentStats['foundUnlistedThumbs'] : null;
|
||||
$unlisted_id = $this->getData('id');
|
||||
$unlisted_name = $this->getData('name');
|
||||
$unlistedFiles = (is_array($this->getData('filelist'))) ? $this->getData('filelist') : array();
|
||||
|
||||
$admin_url = esc_url(admin_url('options-general.php?page=wp-shortpixel-settings&part=adv-settings'));
|
||||
|
||||
|
||||
$message = __("<p>ShortPixel has found thumbnails that are not registered in the metadata, but are present alongside the other thumbnails. These thumbnails could be created and needed by a plugin or the theme. Let ShortPixel optimize them too?</p>", 'shortpixel-image-optimiser');
|
||||
$message .= '<p>' . __("For example, the image", 'shortpixel-image-optimiser') . '
|
||||
<a href="post.php?post=' . $unlisted_id . '&action=edit" target="_blank">
|
||||
' . $unlisted_name . '
|
||||
</a> also has these thumbnails that are not listed in the metadata: ' . (implode(', ', $unlistedFiles)) . '
|
||||
</p>';
|
||||
|
||||
$message .= '<p>' . sprintf(__('You can enable optimizing %s Unlisted Images %s in the %s settings %s', 'shortpixel-image-optimiser'), '<b>', '</b>', '<a href="'. $admin_url . '">','</a>') . '</p>';
|
||||
|
||||
return $message;
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,311 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
use ShortPixel\Notices\NoticeController as Notice;
|
||||
|
||||
use ShortPixel\Controller\AdminNoticesController as AdminNoticesController;
|
||||
use ShortPixel\Controller\QuotaController as QuotaController;
|
||||
|
||||
class ApiKeyModel extends \ShortPixel\Model
|
||||
{
|
||||
|
||||
// variables
|
||||
protected $apiKey;
|
||||
protected $apiKeyTried; // stop retrying the same key over and over if not valid.
|
||||
protected $verifiedKey;
|
||||
protected $redirectedSettings;
|
||||
|
||||
// states
|
||||
// key is verified is set by checkKey *after* checks and validation
|
||||
protected $key_is_verified = false; // this state doesn't have to be the same as the verifiedKey field in DB.
|
||||
protected $key_is_empty = false;
|
||||
protected $key_is_constant = false;
|
||||
protected $key_is_hidden = false;
|
||||
|
||||
protected static $notified = array();
|
||||
|
||||
protected $model = array(
|
||||
'apiKey' => array('s' => 'string',
|
||||
'key' => 'wp-short-pixel-apiKey',
|
||||
),
|
||||
'apiKeyTried' => array('s' => 'string',
|
||||
'key' => 'wp-short-pixel-apiKeyTried'
|
||||
),
|
||||
'verifiedKey' => array('s' => 'boolean',
|
||||
'key' => 'wp-short-pixel-verifiedKey',
|
||||
),
|
||||
'redirectedSettings' => array('s' => 'int',
|
||||
'key' => 'wp-short-pixel-redirected-settings',
|
||||
),
|
||||
);
|
||||
|
||||
public $shortPixel;
|
||||
|
||||
/** Constructor. Check for constants, load the key */
|
||||
public function __construct()
|
||||
{
|
||||
$this->key_is_constant = (defined("SHORTPIXEL_API_KEY")) ? true : false;
|
||||
$this->key_is_hidden = (defined("SHORTPIXEL_HIDE_API_KEY")) ? SHORTPIXEL_HIDE_API_KEY : false;
|
||||
|
||||
}
|
||||
|
||||
/** Load the key from storage. This can be a constant, or the database. Check if key is valid.
|
||||
*
|
||||
*/
|
||||
public function loadKey()
|
||||
{
|
||||
$this->apiKey = get_option($this->model['apiKey']['key'], false);
|
||||
$this->verifiedKey = get_option($this->model['verifiedKey']['key'], false);
|
||||
$this->redirectedSettings = get_option($this->model['redirectedSettings']['key'], false);
|
||||
$this->apiKeyTried = get_option($this->model['apiKeyTried']['key'], false);
|
||||
|
||||
if ($this->key_is_constant)
|
||||
{
|
||||
$key = SHORTPIXEL_API_KEY;
|
||||
}
|
||||
else
|
||||
{
|
||||
$key = $this->apiKey;
|
||||
}
|
||||
|
||||
$valid = $this->checkKey($key);
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
protected function update()
|
||||
{
|
||||
update_option($this->model['apiKey']['key'], trim($this->apiKey));
|
||||
update_option($this->model['verifiedKey']['key'], $this->verifiedKey);
|
||||
update_option($this->model['redirectedSettings']['key'], $this->redirectedSettings);
|
||||
update_option($this->model['apiKeyTried']['key'], $this->apiKeyTried);
|
||||
|
||||
}
|
||||
|
||||
/** Resets the last APIkey that was attempted with validation
|
||||
*
|
||||
* The last apikey tried is saved to prevent server and notice spamming when using a constant key, or a wrong key in the database without updating.
|
||||
*/
|
||||
public function resetTried()
|
||||
{
|
||||
if (is_null($this->apiKeyTried))
|
||||
{
|
||||
return; // if already null, no need for additional activity
|
||||
}
|
||||
$this->apiKeyTried = null;
|
||||
$this->update();
|
||||
Log::addDebug('Reset Tried', $this->apiKeyTried);
|
||||
}
|
||||
|
||||
/** Checks the API key to see if we have a validated situation
|
||||
* @param $key String The 20-character ShortPixel API Key or empty string
|
||||
* @return boolean Returns a boolean indicating valid key or not
|
||||
*
|
||||
* An Api key can be removed from the system by passing an empty string when the key is not hidden.
|
||||
* If the key has changed from stored key, the function will pass a validation request to the server
|
||||
* Failing to key a 20char string, or passing an empty key will result in notices.
|
||||
*/
|
||||
public function checkKey($key)
|
||||
{
|
||||
|
||||
$valid = false;
|
||||
|
||||
if (strlen($key) == 0)
|
||||
{
|
||||
// first-timers, redirect to nokey screen
|
||||
$this->checkRedirect(); // this should be a one-time thing.
|
||||
if($this->key_is_hidden) // hidden empty keys shouldn't be checked
|
||||
{
|
||||
$this->key_is_verified = $this->verifiedKey;
|
||||
return $this->key_is_verified;
|
||||
}
|
||||
elseif ($key != $this->apiKey)
|
||||
{
|
||||
Notice::addWarning(__('Your API Key has been removed', 'shortpixel-image-optimiser'));
|
||||
$this->clearApiKey(); // notice and remove;
|
||||
return false;
|
||||
}
|
||||
$valid = false;
|
||||
|
||||
}
|
||||
elseif (strlen($key) <> 20 && $key != $this->apiKeyTried)
|
||||
{
|
||||
$this->NoticeApiKeyLength($key);
|
||||
Log::addDebug('Key Wrong Length');
|
||||
$valid = $this->verifiedKey; // if we already had a verified key, and a wrong new one is giving keep status.
|
||||
}
|
||||
elseif( ($key != $this->apiKey || ! $this->verifiedKey) && $key != $this->apiKeyTried)
|
||||
{
|
||||
Log::addDebug('Validate Key' . $key);
|
||||
$valid = $this->validateKey($key);
|
||||
}
|
||||
elseif($key == $this->apiKey) // all is fine!
|
||||
{
|
||||
$valid = $this->verifiedKey;
|
||||
}
|
||||
|
||||
// if key is not valid on load, means not valid at all
|
||||
if (! $valid)
|
||||
{
|
||||
$this->verifiedKey = false;
|
||||
$this->key_is_verified = false;
|
||||
$this->apiKeyTried = $key;
|
||||
$this->update();
|
||||
}
|
||||
else {
|
||||
$this->key_is_verified = true;
|
||||
}
|
||||
|
||||
return $this->key_is_verified; // first time this is set! *after* this function
|
||||
}
|
||||
|
||||
public function is_verified()
|
||||
{
|
||||
return $this->key_is_verified;
|
||||
}
|
||||
|
||||
public function is_constant()
|
||||
{
|
||||
return $this->key_is_constant;
|
||||
}
|
||||
|
||||
public function is_hidden()
|
||||
{
|
||||
return $this->key_is_hidden;
|
||||
}
|
||||
|
||||
public function getKey()
|
||||
{
|
||||
return $this->apiKey;
|
||||
}
|
||||
|
||||
public function uninstall()
|
||||
{
|
||||
$this->clearApiKey();
|
||||
}
|
||||
|
||||
protected function clearApiKey()
|
||||
{
|
||||
$this->key_is_empty = true;
|
||||
$this->apiKey = '';
|
||||
$this->verifiedKey = false;
|
||||
$this->apiKeyTried = '';
|
||||
$this->key_is_verified = false;
|
||||
|
||||
AdminNoticesController::resetAPINotices();
|
||||
AdminNoticesController::resetQuotaNotices();
|
||||
AdminNoticesController::resetIntegrationNotices();
|
||||
|
||||
// Remove them all
|
||||
delete_option($this->model['apiKey']['key']);
|
||||
delete_option($this->model['verifiedKey']['key']);
|
||||
delete_option($this->model['redirectedSettings']['key']);
|
||||
delete_option($this->model['apiKeyTried']['key']);
|
||||
|
||||
// $this->update();
|
||||
|
||||
}
|
||||
|
||||
protected function validateKey($key)
|
||||
{
|
||||
Log::addDebug('Validating Key ' . $key);
|
||||
// first, save Auth to satisfy getquotainformation
|
||||
|
||||
$quotaData = $this->remoteValidate($key);
|
||||
|
||||
$checked_key = ($quotaData['APIKeyValid']) ? true : false;
|
||||
|
||||
|
||||
if (! $checked_key)
|
||||
{
|
||||
Log::addError('Key is not validated', $quotaData['Message']);
|
||||
Notice::addError(sprintf(__('Error during verifying API key: %s','shortpixel-image-optimiser'), $quotaData['Message'] ));
|
||||
}
|
||||
elseif ($checked_key) {
|
||||
$this->apiKey = $key;
|
||||
$this->verifiedKey = $checked_key;
|
||||
$this->processNewKey($quotaData);
|
||||
$this->update();
|
||||
}
|
||||
return $this->verifiedKey;
|
||||
}
|
||||
|
||||
|
||||
/** Process some things when key has been added. This is from original wp-short-pixel.php */
|
||||
protected function processNewKey($quotaData)
|
||||
{
|
||||
//display notification
|
||||
$urlParts = explode("/", get_site_url());
|
||||
if( $quotaData['DomainCheck'] == 'NOT Accessible'){
|
||||
$notice = array("status" => "warn", "msg" => __("API Key is valid but your site is not accessible from our servers. Please make sure that your server is accessible from the Internet before using the API or otherwise we won't be able to optimize them.",'shortpixel-image-optimiser'));
|
||||
Notice::addWarning($notice);
|
||||
} else {
|
||||
if ( function_exists("is_multisite") && is_multisite() && !defined("SHORTPIXEL_API_KEY"))
|
||||
$notice = __("Great, your API Key is valid! <br>You seem to be running a multisite, please note that API Key can also be configured in wp-config.php like this:",'shortpixel-image-optimiser')
|
||||
. "<BR> <b>define('SHORTPIXEL_API_KEY', '". $this->apiKey ."');</b>";
|
||||
else
|
||||
$notice = __('Great, your API Key is valid. Please take a few moments to review the plugin settings before starting to optimize your images.','shortpixel-image-optimiser');
|
||||
|
||||
Notice::addSuccess($notice);
|
||||
}
|
||||
|
||||
//test that the "uploads" have the right rights and also we can create the backup dir for ShortPixel
|
||||
if ( \wpSPIO()->filesystem()->checkBackupFolder() === false)
|
||||
{
|
||||
$notice = sprintf(__("There is something preventing us to create a new folder for backing up your original files.<BR>Please make sure that folder <b>%s</b> has the necessary write and read rights.",'shortpixel-image-optimiser'), WP_CONTENT_DIR . '/' . SHORTPIXEL_UPLOADS_NAME );
|
||||
Notice::addError($notice);
|
||||
}
|
||||
|
||||
AdminNoticesController::resetAPINotices();
|
||||
}
|
||||
|
||||
|
||||
protected function NoticeApiKeyLength($key)
|
||||
{
|
||||
// repress double warning.
|
||||
if (isset(self::$notified['apilength']) && self::$notified['apilength'])
|
||||
return;
|
||||
|
||||
$KeyLength = strlen($key);
|
||||
$notice = sprintf(__("The key you provided has %s characters. The API key should have 20 characters, letters and numbers only.",'shortpixel-image-optimiser'), $KeyLength)
|
||||
. "<BR> <b>"
|
||||
. __('Please check that the API key is the same as the one you received in your confirmation email.','shortpixel-image-optimiser')
|
||||
. "</b><BR> "
|
||||
. __('If this problem persists, please contact us at ','shortpixel-image-optimiser')
|
||||
. "<a href='mailto:help@shortpixel.com?Subject=API Key issues' target='_top'>help@shortpixel.com</a>"
|
||||
. __(' or ','shortpixel-image-optimiser')
|
||||
. "<a href='https://shortpixel.com/contact' target='_blank'>" . __('here','shortpixel-image-optimiser') . "</a>.";
|
||||
self::$notified['apilength'] = true;
|
||||
Notice::addError($notice);
|
||||
}
|
||||
|
||||
// Does remote Validation of key. In due time should be replaced with something more lean.
|
||||
private function remoteValidate($key)
|
||||
{
|
||||
$qControl = QuotaController::getInstance();
|
||||
$quotaData = $qControl->remoteValidateKey($key);
|
||||
|
||||
return $quotaData;
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected function checkRedirect()
|
||||
{
|
||||
|
||||
if(! \wpSPIO()->env()->is_ajaxcall && !$this->redirectedSettings && !$this->verifiedKey && (!function_exists("is_multisite") || ! is_multisite())) {
|
||||
$this->redirectedSettings = 1;
|
||||
$this->update();
|
||||
wp_safe_redirect(admin_url("options-general.php?page=wp-shortpixel-settings"));
|
||||
exit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
|
||||
|
||||
/* Model for storing cached data
|
||||
*
|
||||
* Use this in conjunction with cache controller, don't call it stand-alone.
|
||||
*/
|
||||
class CacheModel
|
||||
{
|
||||
|
||||
protected $name;
|
||||
protected $value;
|
||||
protected $expires = HOUR_IN_SECONDS; // This is the expires, when saved without SetExpires! This value is not a representation of any expire time when loading something cache!
|
||||
protected $exists = false;
|
||||
|
||||
|
||||
public function __construct($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->load();
|
||||
}
|
||||
|
||||
/** Set the expiration of this item. In seconds
|
||||
* @param $time Expiration in Seconds
|
||||
*/
|
||||
public function setExpires($time)
|
||||
{
|
||||
$this->expires = $time;
|
||||
}
|
||||
|
||||
public function setValue($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function exists()
|
||||
{
|
||||
return $this->exists;
|
||||
}
|
||||
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
if ($this->expires <= 0)
|
||||
{
|
||||
return; // don't save transients without expiration
|
||||
}
|
||||
$this->exists = set_transient($this->name, $this->value, $this->expires);
|
||||
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
delete_transient($this->name);
|
||||
$this->exists = false;
|
||||
}
|
||||
|
||||
protected function load()
|
||||
{
|
||||
$item = get_transient($this->name);
|
||||
if ($item !== false)
|
||||
{
|
||||
$this->value = $item;
|
||||
$this->exists = true;
|
||||
$this->checkExpiration($this->name);
|
||||
}
|
||||
}
|
||||
|
||||
/** It has been shown that sometimes the expire of the transient is lost, creating a persistent transient. This can be harmful, especially in the case of bulk-secret which can create a situation were no client will optimize due to the hanging transient. */
|
||||
private function checkExpiration($name)
|
||||
{
|
||||
$option = get_option('_transient_timeout_' . $name);
|
||||
|
||||
if (false !== $option && is_numeric($option))
|
||||
{
|
||||
return true; // ok
|
||||
}
|
||||
else {
|
||||
|
||||
// Via object cache the expire info can't be retrieved. Customer is on it's own with this.
|
||||
if (wp_using_ext_object_cache())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->value = '';
|
||||
$this->delete();
|
||||
Log::addError('Found hanging transient with no expiration! ' . $name, $option);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,253 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\Converter;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
|
||||
use ShortPixel\Helper\UtilHelper as UtilHelper;
|
||||
|
||||
class ApiConverter extends MediaLibraryConverter
|
||||
{
|
||||
|
||||
const CONVERTABLE_EXTENSIONS = array( 'heic');
|
||||
|
||||
protected $requestAPIthumbnails = true;
|
||||
|
||||
|
||||
public function isConvertable()
|
||||
{
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
$extension = $this->imageModel->getExtension();
|
||||
|
||||
// If extension is in list of allowed Api Converts.
|
||||
if (in_array($extension, static::CONVERTABLE_EXTENSIONS) && $extension !== 'png')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If file has not been converted in terms of file, but has a placeholder - process ongoing, so continue;
|
||||
if (false === $this->imageModel->getMeta()->convertMeta()->isConverted() && true === $this->imageModel->getMeta()->convertMeta()->hasPlaceHolder())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// File has been converted, not converting again.
|
||||
if (true === $this->imageModel->getMeta()->convertMeta()->isConverted())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create placeholder here.
|
||||
public function convert($args = array())
|
||||
{
|
||||
$defaults = array(
|
||||
'runReplacer' => true, // The replacer doesn't need running when the file is just uploaded and doing in handle upload hook.
|
||||
);
|
||||
|
||||
$args = wp_parse_args($args, $defaults);
|
||||
|
||||
$this->setupReplacer();
|
||||
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
$placeholderFile = $fs->getFile(\wpSPIO()->plugin_path('res/img/fileformat-heic-placeholder.jpg'));
|
||||
|
||||
// Convert runs when putting imageModel to queue format in the Queue classs. This could run without optimization (before) taking place and when accidentally running it more than once results in duplicate files / backups (img-1, img-2 etc). Check placeholder and baseName to prevent this. Assume already done when it has it .
|
||||
if ($this->imageModel->getMeta()->convertMeta()->hasPlaceHolder() && $this->imageModel->getMeta()->convertMeta()->getReplacementImageBase() !== false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// @todo Check replacementpath here. Rename main file - and backup - if numeration is needed.
|
||||
// @todo Also placeholder probably needs to be done each time to block current job in progress.
|
||||
$replacementPath = $this->getReplacementPath();
|
||||
if (false === $replacementPath)
|
||||
{
|
||||
Log::addWarn('ApiConverter replacement path failed');
|
||||
$this->imageModel->getMeta()->convertMeta()->setError(self::ERROR_PATHFAIL);
|
||||
|
||||
return false; // @todo Add ResponseController something here.
|
||||
}
|
||||
|
||||
$replaceFile = $fs->getFile($replacementPath);
|
||||
// If filebase (filename without extension) is not the same, this indicates that a double is there and it's enumerated. Move backup accordingly.
|
||||
|
||||
$destinationFile = $fs->getFile($replacementPath);
|
||||
$copyok = $placeholderFile->copy($destinationFile);
|
||||
|
||||
if ($copyok)
|
||||
{
|
||||
$this->imageModel->getMeta()->convertMeta()->setFileFormat('heic');
|
||||
$this->imageModel->getMeta()->convertMeta()->setPlaceHolder(true);
|
||||
$this->imageModel->getMeta()->convertMeta()->setReplacementImageBase($destinationFile->getFileBase());
|
||||
$this->imageModel->saveMeta();
|
||||
|
||||
|
||||
// @todo Wip . Moved from handleConverted.
|
||||
// Backup basically. Do this first.
|
||||
$conversion_args = array('replacementPath' => $replacementPath);
|
||||
$prepared = $this->imageModel->conversionPrepare($conversion_args);
|
||||
if (false === $prepared)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->setTarget($destinationFile);
|
||||
// $params = array('success' => true, 'generate_metadata' => false);
|
||||
// $this->updateMetaData($params);
|
||||
|
||||
$fs->flushImage($this->imageModel);
|
||||
|
||||
if (true === $args['runReplacer'])
|
||||
{
|
||||
$result = $this->replacer->replace();
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
Log::addError('Failed to copy placeholder');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Restore from original file. Search and replace everything else to death.
|
||||
public function restore()
|
||||
{
|
||||
/*$params = array('restore' => true);
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
$this->setupReplacer();
|
||||
|
||||
$newExtension = $this->imageModel->getMeta()->convertMeta()->getFileFormat();
|
||||
|
||||
$oldFileName = $this->imageModel->getFileName(); // Old File Name, Still .jpg
|
||||
$newFileName = $this->imageModel->getFileBase() . '.' . $newExtension;
|
||||
|
||||
if ($this->imageModel->isScaled())
|
||||
{
|
||||
$oldFileName = $this->imageModel->getOriginalFile()->getFileName();
|
||||
$newFileName = $this->imageModel->getOriginalFile()->getFileBase() . '.' . $newExtension;
|
||||
}
|
||||
|
||||
$fsNewFile = $fs->getFile($this->imageModel->getFileDir() . $newFileName);
|
||||
|
||||
$this->newFile = $fsNewFile;
|
||||
$this->setTarget($fsNewFile);
|
||||
|
||||
$this->updateMetaData($params);
|
||||
// $result = $this->replacer->replace();
|
||||
|
||||
$fs->flushImageCache(); */
|
||||
}
|
||||
|
||||
public function getCheckSum()
|
||||
{
|
||||
return 1; // done or not.
|
||||
}
|
||||
|
||||
public function handleConverted($optimizeData)
|
||||
{
|
||||
$this->setupReplacer();
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
|
||||
/* $replacementPath = $this->getReplacementPath();
|
||||
if (false === $replacementPath)
|
||||
{
|
||||
Log::addWarn('ApiConverter replacement path failed');
|
||||
$this->imageModel->getMeta()->convertMeta()->setError(self::ERROR_PATHFAIL);
|
||||
|
||||
return false; // @todo Add ResponseController something here.
|
||||
}
|
||||
|
||||
$replacementFile = $fs->getFile($replacementPath);
|
||||
*/
|
||||
$replacementBase = $this->imageModel->getMeta()->convertMeta()->getReplacementImageBase();
|
||||
if (false === $replacementBase)
|
||||
{
|
||||
$replacementPath = $this->getReplacementPath();
|
||||
$replacementFile = $fs->getFile($replacementPath);
|
||||
}
|
||||
else {
|
||||
$replacementPath = $replacementBase . '.jpg';
|
||||
$replacementFile = $fs->getFile($this->imageModel->getFileDir() . $replacementPath);
|
||||
}
|
||||
|
||||
// If -sigh- file has a placeholder, then do something with that.
|
||||
if (true === $this->imageModel->getMeta()->convertMeta()->hasPlaceHolder())
|
||||
{
|
||||
$this->imageModel->getMeta()->convertMeta()->setPlaceHolder(false);
|
||||
// $this->imageModel->getMeta()->convertMeta()->setReplacementImageBase(false);
|
||||
|
||||
// $attach_id = $this->imageModel->get('id');
|
||||
|
||||
// ReplacementFile as source should not point to the placeholder file
|
||||
$this->source_url = $fs->pathToUrl($replacementFile);
|
||||
$this->replacer->setSource($this->source_url);
|
||||
|
||||
$replacementFile->delete();
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (isset($optimizeData['files']) && isset($optimizeData['data']))
|
||||
{
|
||||
$files = $optimizeData['files'];
|
||||
$data = $optimizeData['data'];
|
||||
}
|
||||
else {
|
||||
Log::addError('Something went wrong with handleOptimized', $optimizeData);
|
||||
return false;
|
||||
}
|
||||
|
||||
$mainImageKey = $this->imageModel->get('mainImageKey');
|
||||
$mainFile = (isset($files) && isset($files[$mainImageKey])) ? $files[$mainImageKey] : false;
|
||||
|
||||
if (false === $mainFile)
|
||||
{
|
||||
Log::addError('MainFile not set during success Api Conversion');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! isset($mainFile['image']) || ! isset($mainFile['image']['file']))
|
||||
{
|
||||
Log::addError('Optimizer didn\'t return file', $mainFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
$tempFile = $fs->getFile($mainFile['image']['file']);
|
||||
|
||||
$res = $tempFile->copy($replacementFile);
|
||||
|
||||
if (true === $res)
|
||||
{
|
||||
$this->newFile = $replacementFile;
|
||||
$tempFile->delete();
|
||||
|
||||
$params = array('success' => true);
|
||||
$this->updateMetaData($params);
|
||||
|
||||
$result = true;
|
||||
|
||||
// if (true === $args['runReplacer'])
|
||||
// {
|
||||
$result = $this->replacer->replace();
|
||||
// }
|
||||
|
||||
// Conversion done, but backup results.
|
||||
$this->imageModel->conversionSuccess(array('omit_backup' => false));
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // class
|
@ -0,0 +1,172 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\Converter;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\Replacer\Replacer as Replacer;
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
use ShortPixel\Model\File\DirectoryModel as DirectoryModel;
|
||||
use ShortPixel\Model\File\FileModel as FileModel;
|
||||
use ShortPixel\Controller\ResponseController as ResponseController;
|
||||
|
||||
/* ShortPixel Image Optimiser Converters. Unified interface for handling conversion between file types */
|
||||
abstract class Converter
|
||||
{
|
||||
const CONVERTABLE_EXTENSIONS = array('png', 'heic');
|
||||
|
||||
const ERROR_LIBRARY = -1; /// PNG Library error
|
||||
const ERROR_PATHFAIL = -2; // Couldn't create replacement path
|
||||
const ERROR_RESULTLARGER = -3; // Converted file is bigger than the original
|
||||
const ERROR_WRITEERROR = -4; // Result file could not be written
|
||||
const ERROR_BACKUPERROR = -5; // Backup didn't work.
|
||||
const ERROR_TRANSPARENT = -6; // Transparency when it is not allowed.
|
||||
|
||||
protected $imageModel; // The current ImageModel from SPIO
|
||||
|
||||
// Method specific
|
||||
abstract public function convert($args = array());
|
||||
abstract public function isConvertable();
|
||||
abstract public function restore();
|
||||
abstract public function getCheckSum();
|
||||
|
||||
// Media Library specific
|
||||
abstract protected function updateMetaData($params);
|
||||
abstract public function getUpdatedMeta();
|
||||
abstract protected function setupReplacer();
|
||||
abstract protected function setTarget($file);
|
||||
|
||||
public function __construct($imageModel)
|
||||
{
|
||||
$this->imageModel = $imageModel;
|
||||
$this->imageModel->getMeta()->convertMeta()->setFileFormat($imageModel->getExtension());
|
||||
}
|
||||
|
||||
public function isConverterFor($extension)
|
||||
{
|
||||
if ($extension === $this->imageModel->getMeta()->convertMeta()->getFileFormat())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ForConversion: Return empty if file can't be converted or is already converrted
|
||||
public static function getConverter($imageModel, $forConversion = false)
|
||||
{
|
||||
$extension = $imageModel->getExtension();
|
||||
|
||||
$converter = false;
|
||||
|
||||
$converter = self::getConverterByExt($extension, $imageModel);
|
||||
|
||||
// No Support (yet)
|
||||
if ($imageModel->get('type') == 'custom')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Second option for conversion is image who have been placeholdered.
|
||||
if (true === $imageModel->getMeta()->convertMeta()->hasPlaceHolder() && false === $imageModel->getMeta()->convertMeta()->isConverted() && ! is_null($imageModel->getMeta()->convertMeta()->getFileFormat()))
|
||||
{
|
||||
$converter = self::getConverterByExt($imageModel->getMeta()->convertMeta()->getFileFormat(), $imageModel);
|
||||
}
|
||||
|
||||
if (true === $forConversion) // don't check more.
|
||||
{
|
||||
return $converter;
|
||||
}
|
||||
|
||||
if (false === $converter)
|
||||
{
|
||||
if ($imageModel->getMeta()->convertMeta()->isConverted() && ! is_null($imageModel->getMeta()->convertMeta()->getFileFormat()) )
|
||||
{
|
||||
$converter = self::getConverterByExt($imageModel->getMeta()->convertMeta()->getFileFormat(), $imageModel);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $converter;
|
||||
}
|
||||
|
||||
/** Own function to get a unique filename since the WordPress wp_unique_filename seems to not function properly w/ thumbnails */
|
||||
protected function unique_file(DirectoryModel $dir, FileModel $file, $number = 0)
|
||||
{
|
||||
if (! $file->exists())
|
||||
return $file;
|
||||
|
||||
if ($file->is_virtual())
|
||||
{
|
||||
return $file;
|
||||
}
|
||||
|
||||
$number = 0;
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
$base = $file->getFileBase();
|
||||
$ext = $file->getExtension();
|
||||
|
||||
while($file->exists())
|
||||
{
|
||||
$number++;
|
||||
$numberbase = $base . '-' . $number;
|
||||
Log::addDebug('check for unique file -- ' . $dir->getPath() . $numberbase . '.' . $ext);
|
||||
$file = $fs->getFile($dir->getPath() . $numberbase . '.' . $ext);
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
protected function getReplacementPath()
|
||||
{
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
$filename = $this->imageModel->getFileName();
|
||||
$newFileName = $this->imageModel->getFileBase() . '.jpg'; // convert extension to .jpg
|
||||
|
||||
$fsNewFile = $fs->getFile($this->imageModel->getFileDir() . $newFileName);
|
||||
|
||||
$uniqueFile = $this->unique_file( $this->imageModel->getFileDir(), $fsNewFile);
|
||||
$newPath = $uniqueFile->getFullPath(); //(string) $fsFile->getFileDir() . $uniquepath;
|
||||
|
||||
if (! $this->imageModel->getFileDir()->is_writable())
|
||||
{
|
||||
Log::addWarn('Replacement path for PNG not writable ' . $this->imageModel->getFileDir()->getPath());
|
||||
$msg = __('Replacement path for PNG not writable', 'shortpixel-image-optimiser');
|
||||
ResponseController::addData($this->imageModel->get('id'), 'message', $msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->setTarget($uniqueFile);
|
||||
|
||||
return $newPath;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static function getConverterByExt($ext, $imageModel)
|
||||
{
|
||||
$converter = false;
|
||||
switch($ext)
|
||||
{
|
||||
case 'png':
|
||||
$converter = new PNGConverter($imageModel);
|
||||
break;
|
||||
case 'heic':
|
||||
$converter = new ApiConverter($imageModel);
|
||||
break;
|
||||
|
||||
}
|
||||
return $converter;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\Converter;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\Replacer\Replacer as Replacer;
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
|
||||
/* Abstract base to use for image converters. Handles media library related functions ( replacing ) */
|
||||
abstract class MediaLibraryConverter extends Converter
|
||||
{
|
||||
protected $source_url;
|
||||
protected $replacer; // Replacer class Object.
|
||||
protected $newFile; // The newFile Object.
|
||||
|
||||
public function getUpdatedMeta()
|
||||
{
|
||||
$id = $this->imageModel->get('id');
|
||||
$meta = wp_get_attachment_metadata($id); // reset the metadata because we are on the hook.
|
||||
return $meta;
|
||||
}
|
||||
|
||||
protected function setupReplacer()
|
||||
{
|
||||
$this->replacer = new Replacer();
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
$url = $fs->pathToUrl($this->imageModel);
|
||||
|
||||
if ($this->imageModel->isScaled()) // @todo Test these assumptions
|
||||
{
|
||||
$url = $fs->pathToUrl($this->imageModel->getOriginalFile());
|
||||
}
|
||||
|
||||
$this->source_url = $url;
|
||||
$this->replacer->setSource($url);
|
||||
|
||||
$this->replacer->setSourceMeta($this->imageModel->getWPMetaData());
|
||||
|
||||
}
|
||||
|
||||
protected function setTarget($newFile)
|
||||
{
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
$this->newFile = $newFile; // set target newFile.
|
||||
|
||||
$url = $fs->pathToUrl($this->imageModel);
|
||||
$newUrl = str_replace($this->imageModel->getFileName(), $newFile->getFileName(), $url);
|
||||
|
||||
$this->replacer->setTarget($newUrl);
|
||||
}
|
||||
|
||||
protected function updateMetaData($params)
|
||||
{
|
||||
$defaults = array(
|
||||
'success' => false,
|
||||
'restore' => false,
|
||||
'generate_metadata' => true,
|
||||
);
|
||||
|
||||
$params = wp_parse_args($params, $defaults);
|
||||
|
||||
$newFile = $this->newFile;
|
||||
$fullPath = $newFile->getFullPath();
|
||||
|
||||
if (! is_object($newFile))
|
||||
{
|
||||
Log::addError('Update metadata failed. NewFile not properly set', $newFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
$attach_id = $this->imageModel->get('id');
|
||||
|
||||
$WPMLduplicates = $this->imageModel->getWPMLDuplicates();
|
||||
|
||||
$attachment = get_post($attach_id);
|
||||
|
||||
$guid = $attachment->guid;
|
||||
|
||||
// This action prevents images from being regenerated on the thumbnail hook.
|
||||
do_action('shortpixel-thumbnails-before-regenerate', $attach_id );
|
||||
do_action('shortpixel/converter/prevent-offload', $attach_id);
|
||||
|
||||
// Update attached_file
|
||||
$bool = update_attached_file($attach_id, $newFile->getFullPath() );
|
||||
if (false === $bool)
|
||||
return false;
|
||||
|
||||
|
||||
// Update post mime on attachment
|
||||
if (isset($params['success']) && true === $params['success'])
|
||||
{
|
||||
$newExt = $this->imageModel->getMeta()->convertMeta()->getFileFormat();
|
||||
$newGuid = str_replace($guid, $newExt, 'jpg'); // This probable doesn't work bcause doesn't update Guid with this function.
|
||||
$post_ar = array('ID' => $attach_id, 'post_mime_type' => 'image/jpeg', 'guid' => $newGuid);
|
||||
}
|
||||
elseif ( isset($params['restore']) && true === $params['restore'] )
|
||||
{
|
||||
$oldExt = $this->imageModel->getMeta()->convertMeta()->getFileFormat();
|
||||
$newGuid = str_replace($guid, 'jpg', $oldExt);
|
||||
$post_ar = array('ID' => $attach_id, 'post_mime_type' => 'image/png', 'guid' => $newGuid);
|
||||
}
|
||||
|
||||
$result = wp_update_post($post_ar);
|
||||
|
||||
if ($result === 0 || is_wp_error($result))
|
||||
{
|
||||
Log::addError('Issue updating WP Post converter - ' . $attach_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
$metadata = wp_get_attachment_metadata($attach_id);
|
||||
|
||||
if (true === $params['generate_metadata'])
|
||||
{
|
||||
$attachment = get_post( $attach_id );
|
||||
|
||||
$new_metadata = wp_generate_attachment_metadata($attach_id, $newFile->getFullPath());
|
||||
|
||||
}
|
||||
else {
|
||||
$new_metadata = array();
|
||||
}
|
||||
|
||||
// Metadata might not be array when add_attachment is calling this hook via AdminController ( PNG2JPG)
|
||||
if (is_array($metadata))
|
||||
{
|
||||
// Original Image in the new situation can not be there. Don't preserve it.
|
||||
if (isset($metadata['original_image']) && ! isset($new_metadata['original_image']) )
|
||||
{
|
||||
unset($metadata['original_image']);
|
||||
}
|
||||
|
||||
$new_metadata = array_merge($metadata, $new_metadata); // merge to preserve other custom metadata
|
||||
|
||||
}
|
||||
|
||||
if (isset($params['success']) && true === $params['success'])
|
||||
{
|
||||
do_action('shortpixel/converter/prevent-offload-off', $attach_id);
|
||||
}
|
||||
|
||||
if (is_array($new_metadata) && count($new_metadata) > 0)
|
||||
{
|
||||
$bool = wp_update_attachment_metadata($attach_id, $new_metadata);
|
||||
}
|
||||
|
||||
// Restore -sigh- fires off a later signal, because on the succesHandler in MediaLIbraryModel it may copy back backups.
|
||||
if (isset($params['restore']) && true === $params['restore'])
|
||||
{
|
||||
do_action('shortpixel/converter/prevent-offload-off', $attach_id);
|
||||
}
|
||||
|
||||
if (is_array($WPMLduplicates) && count($WPMLduplicates) > 0)
|
||||
{
|
||||
foreach ($WPMLduplicates as $duplicate_id)
|
||||
{
|
||||
update_attached_file($duplicate_id, $newFile->getFullPath() );
|
||||
wp_update_attachment_metadata($duplicate_id, $new_metadata);
|
||||
|
||||
$post_ar["ID"] = $duplicate_id;
|
||||
wp_update_post($post_ar);
|
||||
}
|
||||
}
|
||||
|
||||
$this->replacer->setTargetMeta($new_metadata);
|
||||
|
||||
}
|
||||
|
||||
|
||||
} // class
|
@ -0,0 +1,427 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\Converter;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
use ShortPixel\Model\Image\ImageModel as ImageModel;
|
||||
use ShortPixel\Model\File\DirectoryModel as DirectoryModel;
|
||||
use ShortPixel\Model\File\FileModel as FileModel;
|
||||
use ShortPixel\Notices\NoticeController as Notices;
|
||||
|
||||
use ShortPixel\Controller\ResponseController as ResponseController;
|
||||
|
||||
use ShortPixel\Helper\DownloadHelper as DownloadHelper;
|
||||
|
||||
|
||||
class PNGConverter extends MediaLibraryConverter
|
||||
{
|
||||
protected $instance;
|
||||
|
||||
protected $current_image; // The current PHP image resource in memory
|
||||
protected $virtual_filesize;
|
||||
protected $replacer; // Replacer class Object.
|
||||
|
||||
protected $converterActive = false;
|
||||
protected $forceConvertTransparent = false;
|
||||
|
||||
protected $lastError;
|
||||
|
||||
protected $settingCheckSum;
|
||||
|
||||
|
||||
public function __construct($imageModel)
|
||||
{
|
||||
parent::__construct($imageModel);
|
||||
|
||||
$settings = \wpSPIO()->settings();
|
||||
$env = \wpSPIO()->env();
|
||||
|
||||
|
||||
$this->converterActive = (intval($settings->png2jpg) > 0) ? true : false;
|
||||
|
||||
if ($env->is_gd_installed === false)
|
||||
{
|
||||
$this->converterActive = false;
|
||||
$this->lastError = __('GD library is not active on this installation. Can\'t convert images to PNG', 'shortpixel-image-optimiser');
|
||||
}
|
||||
|
||||
$this->forceConvertTransparent = ($settings->png2jpg == 2) ? true : false;
|
||||
|
||||
// If conversion is tried, but failed somehow, it will never try it again, even after changing settings. This should prevent that switch.
|
||||
$this->settingCheckSum = intval($settings->png2jpg) + intval($settings->backupImages);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function isConvertable()
|
||||
{
|
||||
$imageModel = $this->imageModel;
|
||||
|
||||
// Settings
|
||||
if ($this->converterActive === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extension
|
||||
if ($imageModel->getExtension() !== 'png') // not a png ext. fail silently.
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Existence
|
||||
if (! $imageModel->exists())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (true === $imageModel->getMeta()->convertMeta()->isConverted() || true === $this->hasTried($imageModel->getMeta()->convertMeta()->didTry()) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function hasTried($checksum)
|
||||
{
|
||||
if ( intval($checksum) === $this->getCheckSum())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function convert($args = array())
|
||||
{
|
||||
if (! $this->isConvertable($this->imageModel))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
$defaults = array(
|
||||
'runReplacer' => true, // The replacer doesn't need running when the file is just uploaded and doing in handle upload hook.
|
||||
);
|
||||
|
||||
$conversionArgs = array('checksum' => $this->getCheckSum());
|
||||
|
||||
$this->setupReplacer();
|
||||
$this->raiseMemoryLimit();
|
||||
|
||||
$replacementPath = $this->getReplacementPath();
|
||||
if (false === $replacementPath)
|
||||
{
|
||||
Log::addWarn('ApiConverter replacement path failed');
|
||||
$this->imageModel->getMeta()->convertMeta()->setError(self::ERROR_PATHFAIL);
|
||||
|
||||
return false; // @todo Add ResponseController something here.
|
||||
}
|
||||
|
||||
$replaceFile = $fs->getFile($replacementPath);
|
||||
Log::addDebug('Image replacement base : ' . $replaceFile->getFileBase());
|
||||
$this->imageModel->getMeta()->convertMeta()->setReplacementImageBase($replaceFile->getFileBase());
|
||||
|
||||
$prepared = $this->imageModel->conversionPrepare($conversionArgs);
|
||||
if (false === $prepared)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$args = wp_parse_args($args, $defaults);
|
||||
|
||||
if ($this->forceConvertTransparent === false && $this->isTransparent())
|
||||
{
|
||||
$this->imageModel->getMeta()->convertMeta()->setError(self::ERROR_TRANSPARENT);
|
||||
$this->imageModel->conversionFailed($conversionArgs);
|
||||
return false;
|
||||
}
|
||||
|
||||
Log::addDebug('Starting PNG conversion of #' . $this->imageModel->get('id'));
|
||||
$bool = $this->run();
|
||||
|
||||
if (true === $bool)
|
||||
{
|
||||
$params = array('success' => true);
|
||||
$this->updateMetaData($params);
|
||||
|
||||
$result = true;
|
||||
if (true === $args['runReplacer'])
|
||||
{
|
||||
$result = $this->replacer->replace();
|
||||
}
|
||||
|
||||
if (is_array($result))
|
||||
{
|
||||
foreach($result as $error)
|
||||
Notices::addError($error);
|
||||
}
|
||||
|
||||
|
||||
$this->imageModel->conversionSuccess($conversionArgs);
|
||||
|
||||
// new hook.
|
||||
do_action('shortpixel/image/convertpng2jpg_success', $this->imageModel);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->imageModel->conversionFailed($conversionArgs);
|
||||
|
||||
//legacy. Note at this point metadata has not been updated.
|
||||
do_action('shortpixel/image/convertpng2jpg_after', $this->imageModel, $args);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getCheckSum()
|
||||
{
|
||||
return intval($this->settingCheckSum);
|
||||
}
|
||||
|
||||
|
||||
protected function run()
|
||||
{
|
||||
do_action('shortpixel/image/convertpng2jpg_before', $this->imageModel);
|
||||
|
||||
$img = $this->getPNGImage();
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
$width = $this->imageModel->get('width');
|
||||
$height = $this->imageModel->get('height');
|
||||
|
||||
// If imageModel doesn't have proper width / height set. This can happen with remote files.
|
||||
if (! is_int($width) && ! $width > 0)
|
||||
{
|
||||
$width = imagesx($img);
|
||||
}
|
||||
if (! is_int($height) && ! $height > 0)
|
||||
{
|
||||
$height = imagesy($img);
|
||||
}
|
||||
|
||||
Log::addDebug("PNG2JPG doConvert width $width height $height", memory_get_usage());
|
||||
$bg = imagecreatetruecolor($width, $height);
|
||||
|
||||
if(false === $bg || false === $img)
|
||||
{
|
||||
Log::addError('ImageCreateTrueColor failed');
|
||||
if (false === $bg)
|
||||
{
|
||||
$msg = __('Creating an TrueColor Image failed - Possible library error', 'shortpixel-image-optimiser');
|
||||
}
|
||||
elseif (false === $img)
|
||||
{
|
||||
$msg = __('Image source failed - Check if source image is PNG and library is working', 'shortpixel-image-optimiser');
|
||||
}
|
||||
|
||||
$this->imageModel->getMeta()->convertMeta()->setError(self::ERROR_LIBRARY);
|
||||
ResponseController::addData($this->imageModel->get('id'), 'message', $msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
imagefill($bg, 0, 0, imagecolorallocate($bg, 255, 255, 255));
|
||||
imagealphablending($bg, 1);
|
||||
imagecopy($bg, $img, 0, 0, 0, 0, $width, $height);
|
||||
|
||||
// $fsFile = $fs->getFile($image); // the original png file
|
||||
// @todo Add ResponseController support to here and getReplacementPath.
|
||||
$replacementPath = $this->getReplacementPath();
|
||||
if (false === $replacementPath)
|
||||
{
|
||||
Log::addWarn('Png2Jpg replacement path failed');
|
||||
$this->imageModel->getMeta()->convertMeta()->setError(self::ERROR_PATHFAIL);
|
||||
|
||||
return false; // @todo Add ResponseController something here.
|
||||
}
|
||||
|
||||
// check old filename, replace with uniqued filename.
|
||||
|
||||
/** Quality is set to 90 and not using WP defaults (or filter) for good reason. Lower settings very quickly degrade the libraries output quality. Better to leave this hardcoded at 90 and let the ShortPixel API handle the optimization **/
|
||||
if ($bool = imagejpeg($bg, $replacementPath, 90)) {
|
||||
Log::addDebug("PNG2JPG doConvert created JPEG at $replacementPath");
|
||||
$newSize = filesize($replacementPath); // This might invoke wrapper but ok
|
||||
|
||||
if (! is_null($this->virtual_filesize))
|
||||
{
|
||||
$origSize = $this->virtual_filesize;
|
||||
}
|
||||
else {
|
||||
$origSize = $this->imageModel->getFileSize();
|
||||
}
|
||||
|
||||
// Reload the file we just wrote.
|
||||
$newFile = $fs->getFile($replacementPath);
|
||||
|
||||
|
||||
if($newSize > $origSize * 0.95 || $newSize == 0) {
|
||||
//if the image is not 5% smaller, don't bother.
|
||||
//if the size is 0, a conversion (or disk write) problem happened, go on with the PNG
|
||||
Log::addDebug("PNG2JPG converted image is larger ($newSize vs. $origSize), keeping the PNG");
|
||||
$msg = __('Converted file is larger. Keeping original file', 'shortpixel-image-optimiser');
|
||||
ResponseController::addData($this->imageModel->get('id'), 'message', $msg);
|
||||
$newFile->delete();
|
||||
$this->imageModel->getMeta()->convertMeta()->setError(self::ERROR_RESULTLARGER);
|
||||
|
||||
return false;
|
||||
}
|
||||
elseif (! $newFile->exists())
|
||||
{
|
||||
Log::addWarn('PNG imagejpeg file not written!', $uniqueFile->getFileName() );
|
||||
$msg = __('Error - PNG file not written', 'shortpixel-image-optimiser');
|
||||
ResponseController::addData($this->imageModel->get('id'), 'message', $msg);
|
||||
$this->imageModel->getMeta()->convertMeta()->setError(self::ERROR_WRITEERROR);
|
||||
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
$this->newFile = $newFile;
|
||||
}
|
||||
|
||||
|
||||
Log::addDebug('PNG2jPG Converted');
|
||||
}
|
||||
|
||||
$fs->flushImageCache();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function restore()
|
||||
{
|
||||
$params = array(
|
||||
'restore' => true,
|
||||
);
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
$this->setupReplacer();
|
||||
|
||||
$oldFileName = $this->imageModel->getFileName(); // Old File Name, Still .jpg
|
||||
$newFileName = $this->imageModel->getFileBase() . '.png';
|
||||
|
||||
if ($this->imageModel->isScaled())
|
||||
{
|
||||
$oldFileName = $this->imageModel->getOriginalFile()->getFileName();
|
||||
$newFileName = $this->imageModel->getOriginalFile()->getFileBase() . '.png';
|
||||
}
|
||||
|
||||
$fsNewFile = $fs->getFile($this->imageModel->getFileDir() . $newFileName);
|
||||
|
||||
$this->newFile = $fsNewFile;
|
||||
$this->setTarget($fsNewFile);
|
||||
|
||||
$this->updateMetaData($params);
|
||||
$result = $this->replacer->replace();
|
||||
|
||||
$fs->flushImageCache();
|
||||
|
||||
}
|
||||
|
||||
protected function isTransparent() {
|
||||
$isTransparent = false;
|
||||
$transparent_pixel = $bg = false;
|
||||
|
||||
$imagePath = $this->imageModel->getFullPath();
|
||||
|
||||
// Check for transparency at the bit path.
|
||||
if(ord(file_get_contents($imagePath, false, null, 25, 1)) & 4) {
|
||||
Log::addDebug("PNG2JPG: 25th byte has third bit 1 - transparency");
|
||||
$isTransparent = true;
|
||||
// return true;
|
||||
} else {
|
||||
|
||||
$contents = file_get_contents($imagePath);
|
||||
if (stripos($contents, 'PLTE') !== false && stripos($contents, 'tRNS') !== false) {
|
||||
$isTransparent = true;
|
||||
}
|
||||
if (false === $isTransparent) {
|
||||
|
||||
$width = $this->imageModel->get('width');
|
||||
$height = $this->imageModel->get('height');
|
||||
Log::addDebug("PNG2JPG Image width: " . $width . " height: " . $height . " aprox. size: " . round($width*$height*5/1024/1024) . "M memory limit: " . ini_get('memory_limit') . " USED: " . memory_get_usage());
|
||||
|
||||
$image = $this->getPNGImage();
|
||||
if (false === $image)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Log::addDebug("PNG2JPG width $width height $height. Now checking pixels.");
|
||||
//run through pixels until transparent pixel is found:
|
||||
for ($i = 0; $i < $width; $i++) {
|
||||
for ($j = 0; $j < $height; $j++) {
|
||||
$rgba = imagecolorat($image, $i, $j);
|
||||
if (($rgba & 0x7F000000) >> 24) {
|
||||
$isTransparent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // non-transparant.
|
||||
|
||||
Log::addDebug("PNG2JPG is " . (false === $isTransparent ? " not" : "") . " transparent");
|
||||
|
||||
return $isTransparent;
|
||||
|
||||
}
|
||||
|
||||
/** Try to load resource and an PNG via library */
|
||||
protected function getPNGImage()
|
||||
{
|
||||
if (is_object($this->current_image))
|
||||
{
|
||||
return $this->current_image;
|
||||
}
|
||||
|
||||
if ($this->imageModel->isScaled())
|
||||
{
|
||||
$imagePath = $this->imageModel->getOriginalFile()->getFullPath();
|
||||
}
|
||||
else {
|
||||
$imagePath = $this->imageModel->getFullPath();
|
||||
}
|
||||
|
||||
if (true === $this->imageModel->is_virtual())
|
||||
{
|
||||
$downloadHelper = DownloadHelper::getInstance();
|
||||
Log::addDebug('PNG converter: Item is remote, attempting to download');
|
||||
|
||||
$tempFile = $downloadHelper->downloadFile($this->imageModel->getURL());
|
||||
if (is_object($tempFile))
|
||||
{
|
||||
$imagePath = $tempFile->getFullPath();
|
||||
$this->virtual_filesize = $tempFile->getFileSize();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$image = @imagecreatefrompng($imagePath);
|
||||
if (! $image)
|
||||
{
|
||||
$this->current_image = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->current_image = $image;
|
||||
}
|
||||
|
||||
return $this->current_image;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Try to increase limits when doing heavy processing
|
||||
private function raiseMemoryLimit()
|
||||
{
|
||||
if(function_exists('wp_raise_memory_limit')) {
|
||||
wp_raise_memory_limit( 'image' );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // class
|
@ -0,0 +1,328 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
|
||||
/** Loads a few environment variables handy to have nearby
|
||||
*
|
||||
* Notice - This is meant to be loaded via the plugin class. Easy access with wpSPIO()->getEnv().
|
||||
*/
|
||||
class EnvironmentModel extends \ShortPixel\Model
|
||||
{
|
||||
// Server and PHP
|
||||
public $is_nginx;
|
||||
public $is_apache;
|
||||
public $is_gd_installed;
|
||||
public $is_curl_installed;
|
||||
private $disabled_functions = array();
|
||||
|
||||
// MultiSite
|
||||
public $is_multisite;
|
||||
public $is_mainsite;
|
||||
|
||||
// Integrations
|
||||
public $has_nextgen;
|
||||
|
||||
// WordPress
|
||||
public $is_front = false;
|
||||
public $is_admin = false;
|
||||
public $is_ajaxcall = false;
|
||||
|
||||
private $screen_is_set = false;
|
||||
public $is_screen_to_use = false; // where shortpixel optimizer loads
|
||||
public $is_our_screen = false; // where shortpixel hooks in more complicated functions.
|
||||
public $is_gutenberg_editor = false;
|
||||
public $is_bulk_page = false; // ShortPixel bulk screen.
|
||||
public $screen_id = false;
|
||||
|
||||
// Debug flag
|
||||
public $is_debug = false;
|
||||
// Is the plugin configured to automatically optimize on upload hook?
|
||||
public $is_autoprocess = false;
|
||||
|
||||
protected static $instance;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setServer();
|
||||
$this->setWordPress();
|
||||
add_action('plugins_loaded', array($this, 'setIntegrations') ); // not set on construct.
|
||||
add_action('current_screen', array($this, 'setScreen') ); // Not set on construct
|
||||
}
|
||||
|
||||
public static function getInstance()
|
||||
{
|
||||
if (is_null(self::$instance))
|
||||
self::$instance = new EnvironmentModel();
|
||||
|
||||
/*if (! self::$instance->screen_is_set)
|
||||
self::$instance->setScreen(); */
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/** Check ENV is a specific function is allowed. Use this with functions that might be turned off on configurations
|
||||
* @param $function String The name of the function being tested
|
||||
* Note: In future this function can be extended with other function edge cases.
|
||||
*/
|
||||
public function is_function_usable($function)
|
||||
{
|
||||
if (count($this->disabled_functions) == 0)
|
||||
{
|
||||
$disabled = ini_get('disable_functions');
|
||||
$this->disabled_functions = explode(',', $disabled);
|
||||
}
|
||||
|
||||
if (isset($this->disabled_functions[$function]))
|
||||
return false;
|
||||
|
||||
if (function_exists($function))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkPHPVersion($needed)
|
||||
{
|
||||
|
||||
if (version_compare(PHP_VERSION, $needed) >= 0 )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function plugin_active($name)
|
||||
{
|
||||
switch($name)
|
||||
{
|
||||
case 'wpml':
|
||||
$plugin = 'sitepress-multilingual-cms/sitepress.php';
|
||||
break;
|
||||
case 'polylang':
|
||||
$plugin = 'polylang/polylang.php';
|
||||
break;
|
||||
case 'spai':
|
||||
$plugin = 'shortpixel-adaptive-images/short-pixel-ai.php';
|
||||
break;
|
||||
case 's3-offload':
|
||||
$plugin = 'amazon-s3-and-cloudfront/wordpress-s3.php';
|
||||
break;
|
||||
case 'woocommerce':
|
||||
$plugin = 'woocommerce/woocommerce.php';
|
||||
break;
|
||||
default:
|
||||
$plugin = 'none';
|
||||
break;
|
||||
}
|
||||
|
||||
if (!function_exists('is_plugin_active')) {
|
||||
include_once(ABSPATH . 'wp-admin/includes/plugin.php');
|
||||
}
|
||||
|
||||
return \is_plugin_active($plugin);
|
||||
}
|
||||
|
||||
//https://www.php.net/manual/en/function.sys-getloadavg.php
|
||||
public function getSystemLoad()
|
||||
{
|
||||
$load = sys_getloadavg();
|
||||
|
||||
}
|
||||
|
||||
/* https://github.com/WordPress/WordPress/blob/master/wp-includes/class-wp-image-editor-imagick.php */
|
||||
public function hasImagick()
|
||||
{
|
||||
$editor = wp_get_image_editor(\wpSPIO()->plugin_path('res/img/test.jpg'));
|
||||
$className = get_class($editor);
|
||||
|
||||
if ($className == 'WP_Image_Editor_Imagick')
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasOffload()
|
||||
{
|
||||
$off = \ShortPixel\External\Offload\Offloader::getInstance();
|
||||
$name = $off->getOffloadName();
|
||||
if (is_null($name))
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getOffloadName()
|
||||
{
|
||||
$off = \ShortPixel\External\Offload\Offloader::getInstance();
|
||||
$name = $off->getOffloadName();
|
||||
return $name;
|
||||
}
|
||||
|
||||
public function useVirtualHeavyFunctions()
|
||||
{
|
||||
$bool = apply_filters('shortpixel/file/virtual/heavy_features', true);
|
||||
return $bool;
|
||||
}
|
||||
|
||||
private function setServer()
|
||||
{
|
||||
$this->is_nginx = ! empty($_SERVER["SERVER_SOFTWARE"]) && strpos(strtolower(wp_unslash($_SERVER["SERVER_SOFTWARE"])), 'nginx') !== false ? true : false;
|
||||
$this->is_apache = ! empty($_SERVER["SERVER_SOFTWARE"]) && strpos(strtolower(wp_unslash($_SERVER["SERVER_SOFTWARE"])), 'apache') !== false ? true : false;
|
||||
$this->is_gd_installed = function_exists('imagecreatefrompng') && function_exists('imagejpeg');
|
||||
$this->is_curl_installed = function_exists('curl_init');
|
||||
}
|
||||
|
||||
|
||||
private function setWordPress()
|
||||
{
|
||||
$this->is_multisite = (function_exists("is_multisite") && is_multisite()) ? true : false;
|
||||
$this->is_mainsite = (function_exists('is_main_site') && true === is_main_site()) ? true : false;
|
||||
|
||||
$this->determineFrontBack();
|
||||
|
||||
if (wp_doing_ajax())
|
||||
{
|
||||
$this->is_ajaxcall = true;
|
||||
}
|
||||
|
||||
$this->is_debug = Log::debugIsActive();
|
||||
|
||||
if (\wpSPIO()->settings()->autoMediaLibrary == 1)
|
||||
$this->is_autoprocess = true;
|
||||
|
||||
}
|
||||
|
||||
// check if this request is front or back.
|
||||
protected function determineFrontBack()
|
||||
{
|
||||
if ( is_admin() || wp_doing_ajax() )
|
||||
$this->is_admin = true;
|
||||
else
|
||||
$this->is_front = true;
|
||||
|
||||
}
|
||||
|
||||
public function setScreen($screen)
|
||||
{
|
||||
// WordPress pages where we'll be active on.
|
||||
// https://codex.wordpress.org/Plugin_API/Admin_Screen_Reference
|
||||
$use_screens = array(
|
||||
'edit-post_tag', // edit tags
|
||||
'upload', // media library
|
||||
'attachment', // edit media
|
||||
'post', // post screen
|
||||
'page', // page editor
|
||||
'edit-post', // edit post
|
||||
'new-post', // new post
|
||||
'edit-page', // all pages
|
||||
'media', // add new item screen
|
||||
);
|
||||
$use_screens = apply_filters('shortpixel/init/optimize_on_screens', $use_screens);
|
||||
|
||||
$this->screen_id = $screen->id;
|
||||
if(is_array($use_screens) && in_array($screen->id, $use_screens)) {
|
||||
$this->is_screen_to_use = true;
|
||||
}
|
||||
|
||||
// Our pages.
|
||||
$pages = \wpSPIO()->get_admin_pages();
|
||||
// the main WP pages where SPIO hooks a lot of functions into, our operating area.
|
||||
$wp_pages = array('upload', 'attachment');
|
||||
$pages = array_merge($pages, $wp_pages);
|
||||
|
||||
/* pages can be null in certain cases i.e. plugin activation.
|
||||
* treat those cases as improper screen set.
|
||||
*/
|
||||
if (is_null($pages))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( in_array($screen->id, $pages))
|
||||
{
|
||||
$this->is_screen_to_use = true;
|
||||
$this->is_our_screen = true;
|
||||
|
||||
// Strpos instead of full screen id, because the first page (media_page) is not reliable and can change.
|
||||
if ( strpos($screen->id, 'wp-short-pixel-bulk') !== false)
|
||||
$this->is_bulk_page = true;
|
||||
}
|
||||
elseif (is_object($screen) && method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor() ) {
|
||||
$this->is_screen_to_use = true;
|
||||
$this->is_gutenberg_editor = true;
|
||||
}
|
||||
|
||||
$this->screen_is_set = true;
|
||||
}
|
||||
|
||||
public function setIntegrations()
|
||||
{
|
||||
$ng = \ShortPixel\NextGenController::getInstance();
|
||||
$this->has_nextgen = $ng->has_nextgen();
|
||||
}
|
||||
|
||||
//set default move as "list". only set once, it won't try to set the default mode again.
|
||||
public function setDefaultViewModeList()
|
||||
{
|
||||
$settings = \wpSPIO()->settings();
|
||||
if( $settings->mediaLibraryViewMode == false)
|
||||
{
|
||||
$settings->mediaLibraryViewMode = 1;
|
||||
$currentUserID = false;
|
||||
if ( function_exists('wp_get_current_user') ) {
|
||||
$current_user = wp_get_current_user();
|
||||
$currentUserID = $current_user->ID;
|
||||
update_user_meta($currentUserID, "wp_media_library_mode", "list");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getRelativePluginSlug()
|
||||
{
|
||||
$dir = SHORTPIXEL_PLUGIN_DIR;
|
||||
$file = SHORTPIXEL_PLUGIN_FILE;
|
||||
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
$plugins_dir = $fs->getDirectory($dir)->getParent();
|
||||
|
||||
$slug = str_replace($plugins_dir->getPath(), '', $file);
|
||||
|
||||
return $slug;
|
||||
}
|
||||
|
||||
public function useDoubleWebpExtension()
|
||||
{
|
||||
if (defined('SHORTPIXEL_USE_DOUBLE_WEBP_EXTENSION') && SHORTPIXEL_USE_DOUBLE_WEBP_EXTENSION)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function useDoubleAvifExtension()
|
||||
{
|
||||
if (defined('SHORTPIXEL_USE_DOUBLE_AVIF_EXTENSION') && SHORTPIXEL_USE_DOUBLE_AVIF_EXTENSION)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function useTrustedMode()
|
||||
{
|
||||
if (defined('SHORTPIXEL_TRUSTED_MODE') && true === SHORTPIXEL_TRUSTED_MODE)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,557 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\File;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
|
||||
use ShortPixel\Helper\UtilHelper as UtilHelper;
|
||||
|
||||
/* Model for Directories
|
||||
*
|
||||
* For all low-level operations on directories
|
||||
* Private model of FileSystemController. Please get your directories via there.
|
||||
*
|
||||
*/
|
||||
|
||||
class DirectoryModel extends \ShortPixel\Model
|
||||
{
|
||||
// Directory info
|
||||
protected $path;
|
||||
protected $name;
|
||||
|
||||
// Directory status
|
||||
protected $exists = null;
|
||||
protected $is_writable = null;
|
||||
protected $is_readable = null;
|
||||
protected $is_virtual = null;
|
||||
|
||||
protected $fields = array();
|
||||
|
||||
protected $new_directory_permission = 0755;
|
||||
|
||||
public static $TRUSTED_MODE = false;
|
||||
|
||||
|
||||
/** Creates a directory model object. DirectoryModel directories don't need to exist on FileSystem
|
||||
*
|
||||
* When a filepath is given, it will remove the file part.
|
||||
* @param $path String The Path
|
||||
*/
|
||||
public function __construct($path)
|
||||
{
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
// Test if path actually has someting, otherwise just bail.
|
||||
if (strlen(trim($path)) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($fs->pathIsUrl($path))
|
||||
{
|
||||
$pathinfo = pathinfo($path);
|
||||
if (isset($pathinfo['extension'])) // check if this is a file, remove the file information.
|
||||
{
|
||||
$path = $pathinfo['dirname'];
|
||||
}
|
||||
|
||||
$this->is_virtual = true;
|
||||
$this->is_readable = true; // assume
|
||||
$this->exists = true;
|
||||
}
|
||||
|
||||
// When in trusted mode prevent filesystem checks as much as possible.
|
||||
if (true === self::$TRUSTED_MODE)
|
||||
{
|
||||
$this->exists = true;
|
||||
$this->is_writable = true;
|
||||
$this->is_readable = true;
|
||||
}
|
||||
|
||||
// On virtual situation this would remove the slashes on :// , causing issues with offload et al.
|
||||
if (false === $this->is_virtual)
|
||||
$path = UtilHelper::spNormalizePath($path);
|
||||
|
||||
if (! $this->is_virtual() && ! is_dir($path) ) // path is wrong, *or* simply doesn't exist.
|
||||
{
|
||||
/* Test for file input.
|
||||
* If pathinfo is fed a fullpath, it rips of last entry without setting extension, don't further trust.
|
||||
* If it's a file extension is set, then trust.
|
||||
*/
|
||||
$pathinfo = pathinfo($path);
|
||||
|
||||
|
||||
if (isset($pathinfo['extension']))
|
||||
{
|
||||
$path = $pathinfo['dirname'];
|
||||
}
|
||||
elseif (is_file($path))
|
||||
$path = dirname($path);
|
||||
}
|
||||
|
||||
if (! $this->is_virtual() && ! is_dir($path))
|
||||
{
|
||||
/* Check if realpath improves things. We support non-existing paths, which realpath fails on, so only apply on result.
|
||||
Moved realpath to check after main pathinfo is set. Reason is that symlinked directories which don't include the WordPress upload dir will start to fail in file_model on processpath ( doesn't see it as a wp path, starts to try relative path). Not sure if realpath should be used anyhow in this model /BS
|
||||
*/
|
||||
$testpath = realpath($path);
|
||||
if ($testpath)
|
||||
$path = $testpath;
|
||||
}
|
||||
|
||||
$this->path = trailingslashit($path);
|
||||
|
||||
// Basename doesn't work properly on non-latin ( cyrillic, greek etc ) directory names, returning the parent path instead.
|
||||
$dir = new \SplFileInfo($path);
|
||||
//basename($this->path);
|
||||
$this->name = $dir->getFileName();
|
||||
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return (string) $this->path;
|
||||
}
|
||||
|
||||
/** Returns path *with* trailing slash
|
||||
*
|
||||
* @return String Path with trailing slash
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function getModified()
|
||||
{
|
||||
return filemtime($this->path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get basename of the directory. Without path
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function exists()
|
||||
{
|
||||
if (is_null($this->exists))
|
||||
{
|
||||
$this->exists = file_exists($this->path) && is_dir($this->path);
|
||||
}
|
||||
return $this->exists;
|
||||
}
|
||||
|
||||
public function is_writable()
|
||||
{
|
||||
if (is_null($this->is_writable))
|
||||
{
|
||||
$this->is_writable = is_writable($this->path);
|
||||
}
|
||||
return $this->is_writable;
|
||||
}
|
||||
|
||||
|
||||
public function is_readable()
|
||||
{
|
||||
if (is_null($this->is_readable))
|
||||
{
|
||||
$this->is_readable = is_readable($this->path);
|
||||
}
|
||||
|
||||
return $this->is_readable;
|
||||
}
|
||||
|
||||
public function is_virtual()
|
||||
{
|
||||
return $this->is_virtual;
|
||||
}
|
||||
/** Try to obtain the path, minus the installation directory.
|
||||
* @return Mixed False if this didn't work, Path as string without basedir if it did. With trailing slash, without starting slash.
|
||||
*/
|
||||
public function getRelativePath()
|
||||
{
|
||||
// not used anywhere in directory.
|
||||
// $upload_dir = wp_upload_dir(null, false);
|
||||
|
||||
$install_dir = get_home_path();
|
||||
if($install_dir == '/') {
|
||||
$install_dir = \wpSPIO()->filesystem()->getWPAbsPath();
|
||||
}
|
||||
|
||||
$install_dir = trailingslashit($install_dir);
|
||||
|
||||
$path = $this->getPath();
|
||||
// try to build relativePath without first slash.
|
||||
$relativePath = str_replace($install_dir, '', $path);
|
||||
|
||||
if (is_dir( $install_dir . $relativePath) === false)
|
||||
{
|
||||
$test_path = $this->reverseConstructPath($path, $install_dir);
|
||||
if ($test_path !== false)
|
||||
{
|
||||
$relativePath = $test_path;
|
||||
}
|
||||
else {
|
||||
if($test_path = $this->constructUsualDirectories($path))
|
||||
{
|
||||
$relativePath = $test_path;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// If relativePath has less amount of characters, changes are this worked.
|
||||
if (strlen($path) > strlen($relativePath))
|
||||
{
|
||||
return ltrim(trailingslashit($relativePath), '/');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private function reverseConstructPath($path, $install_path)
|
||||
{
|
||||
// Array value to reset index
|
||||
$pathar = array_values(array_filter(explode('/', $path)));
|
||||
$parts = array();
|
||||
|
||||
if (is_array($pathar))
|
||||
{
|
||||
// Reverse loop the structure until solid ground is found.
|
||||
for ($i = (count($pathar)); $i > 0; $i--)
|
||||
{
|
||||
$parts[] = $pathar[$i - 1];
|
||||
$testpath = implode('/', array_reverse($parts));
|
||||
// if the whole thing exists
|
||||
if (is_dir($install_path . $testpath) === true)
|
||||
{
|
||||
return $testpath;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Last Resort function to just reduce path to various known WorPress paths. */
|
||||
private function constructUsualDirectories($path)
|
||||
{
|
||||
$pathar = array_values(array_filter(explode('/', $path))); // array value to reset index
|
||||
$testpath = false;
|
||||
if ( ($key = array_search('wp-content', $pathar)) !== false)
|
||||
{
|
||||
$testpath = implode('/', array_slice($pathar, $key));
|
||||
}
|
||||
elseif ( ($key = array_search('uploads', $pathar)) !== false)
|
||||
{
|
||||
$testpath = implode('/', array_slice($pathar, $key));
|
||||
}
|
||||
|
||||
return $testpath;
|
||||
}
|
||||
|
||||
/** Checks the directory into working order
|
||||
* Tries to create directory if it doesn't exist
|
||||
* Tries to fix file permission if writable is needed
|
||||
* @param $check_writable Boolean Directory should be writable
|
||||
*/
|
||||
public function check($check_writable = false)
|
||||
{
|
||||
$permission = $this->getPermissionRecursive();
|
||||
|
||||
if ($permission === false) // if something wrong, return to default.
|
||||
{
|
||||
$permission = $this->new_directory_permission;
|
||||
}
|
||||
|
||||
if (! $this->exists())
|
||||
{
|
||||
|
||||
Log::addInfo('Directory does not exists. Try to create recursive ' . $this->path . ' with ' . $permission);
|
||||
|
||||
|
||||
$result = @mkdir($this->path, $permission , true);
|
||||
chmod ($this->path, $permission );
|
||||
|
||||
if (! $result)
|
||||
{
|
||||
$error = error_get_last();
|
||||
Log::addWarn('MkDir failed: ' . $error['message'], array($error));
|
||||
}
|
||||
// reset.
|
||||
$this->exists = null;
|
||||
$this->is_readable = null;
|
||||
$this->is_writable = null;
|
||||
|
||||
}
|
||||
if ($this->exists() && $check_writable && ! $this->is_writable())
|
||||
{
|
||||
chmod($this->path, $permission);
|
||||
if (! $this->is_writable()) // perhaps parent permission is no good.
|
||||
{
|
||||
chmod($this->path, $this->new_directory_permission);
|
||||
}
|
||||
}
|
||||
|
||||
if (! $this->exists())
|
||||
{
|
||||
Log::addInfo('Directory does not exist :' . $this->path);
|
||||
return false;
|
||||
}
|
||||
if ($check_writable && !$this->is_writable())
|
||||
{
|
||||
Log::addInfo('Directory not writable :' . $this->path);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getPermissionRecursive()
|
||||
{
|
||||
$parent = $this->getParent();
|
||||
if (! $parent->exists())
|
||||
{
|
||||
return $parent->getPermissionRecursive();
|
||||
}
|
||||
else
|
||||
{
|
||||
return $parent->getPermissions();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Get files from directory
|
||||
* @returns Array|boolean Returns false if something wrong w/ directory, otherwise a files array of FileModel Object.
|
||||
*/
|
||||
public function getFiles($args = array())
|
||||
{
|
||||
|
||||
$defaults = array(
|
||||
'date_newer' => null,
|
||||
'exclude_files' => null,
|
||||
'include_files' => null,
|
||||
);
|
||||
$args = wp_parse_args($args, $defaults);
|
||||
|
||||
// if all filters are set to null, so point in checking those.
|
||||
$has_filters = (count(array_filter($args)) > 0) ? true : false;
|
||||
|
||||
if ( ! $this->exists() || ! $this->is_readable() )
|
||||
return false;
|
||||
|
||||
$fileArray = array();
|
||||
|
||||
if ($handle = opendir($this->path)) {
|
||||
while (false !== ($entry = readdir($handle))) {
|
||||
if ( ($entry != "." && $entry != "..") && ! is_dir($this->path . $entry) ) {
|
||||
|
||||
|
||||
$fileObj = new FileModel($this->path . $entry);
|
||||
if ($has_filters)
|
||||
{
|
||||
if ($this->fileFilter($fileObj,$args) === false)
|
||||
{
|
||||
$fileObj = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (! is_null($fileObj))
|
||||
$fileArray[] = $fileObj;
|
||||
}
|
||||
}
|
||||
closedir($handle);
|
||||
}
|
||||
|
||||
/*
|
||||
if ($has_filters)
|
||||
{
|
||||
$fileArray = array_filter($fileArray, function ($file) use ($args) {
|
||||
return $this->fileFilter($file, $args);
|
||||
} );
|
||||
} */
|
||||
return $fileArray;
|
||||
}
|
||||
|
||||
// @return boolean true if it should be kept in array, false if not.
|
||||
private function fileFilter(FileModel $file, $args)
|
||||
{
|
||||
$filter = true;
|
||||
|
||||
if (! is_null($args['include_files']))
|
||||
{
|
||||
foreach($args['include_files'] as $inc)
|
||||
{
|
||||
// If any in included is true, filter is good for us.
|
||||
$filter = false;
|
||||
if (strpos( strtolower($file->getRawFullPath()), strtolower($inc) ) !== false)
|
||||
{
|
||||
$filter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! is_null($args['date_newer']))
|
||||
{
|
||||
$modified = $file->getModified();
|
||||
if ($modified < $args['date_newer'] )
|
||||
$filter = false;
|
||||
}
|
||||
if (! is_null($args['exclude_files']))
|
||||
{
|
||||
foreach($args['exclude_files'] as $ex)
|
||||
{
|
||||
if (strpos( strtolower($file->getRawFullPath()), strtolower($ex) ) !== false)
|
||||
$filter = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/** Get subdirectories from directory
|
||||
* * @returns Array|boolean Returns false if something wrong w/ directory, otherwise a files array of DirectoryModel Object.
|
||||
*/
|
||||
public function getSubDirectories()
|
||||
{
|
||||
|
||||
if (! $this->exists() || ! $this->is_readable())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$dirIt = new \DirectoryIterator($this->path);
|
||||
$dirArray = array();
|
||||
foreach ($dirIt as $fileInfo)
|
||||
{ // IsDot must go first here, or there is possiblity to run into openbasedir restrictions.
|
||||
if (! $fileInfo->isDot() && $fileInfo->isDir() && $fileInfo->isReadable())
|
||||
{
|
||||
if ('ShortPixel\Model\File\DirectoryOtherMediaModel' == get_called_class())
|
||||
{
|
||||
$dir = new DirectoryOtherMediaModel($fileInfo->getRealPath());
|
||||
}
|
||||
else
|
||||
{
|
||||
$dir = new DirectoryModel($fileInfo->getRealPath());
|
||||
}
|
||||
|
||||
if ($dir->exists())
|
||||
$dirArray[] = $dir;
|
||||
}
|
||||
|
||||
}
|
||||
return $dirArray;
|
||||
}
|
||||
|
||||
/** Check if this dir is a subfolder
|
||||
* @param DirectoryModel The directoryObject that is tested as the parent */
|
||||
public function isSubFolderOf(DirectoryModel $dir)
|
||||
{
|
||||
// the same path, is not a subdir of.
|
||||
if ($this->getPath() === $dir->getPath())
|
||||
return false;
|
||||
|
||||
// the main path must be followed from the beginning to be a subfolder.
|
||||
if (strpos($this->getPath(), $dir->getPath() ) === 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//** Note, use sparingly, recursive function
|
||||
public function getFolderSize()
|
||||
{
|
||||
// \wpSPIO()->filesystem()->getFilesRecursive($this)
|
||||
$size = 0;
|
||||
$files = $this->getFiles();
|
||||
|
||||
// GetFiles can return Boolean false on missing directory.
|
||||
if (! is_array($files))
|
||||
{
|
||||
return $size;
|
||||
}
|
||||
|
||||
foreach($files as $fileObj)
|
||||
{
|
||||
$size += $fileObj->getFileSize();
|
||||
}
|
||||
unset($files); //attempt at performance.
|
||||
|
||||
$subdirs = $this->getSubDirectories();
|
||||
|
||||
foreach($subdirs as $subdir)
|
||||
{
|
||||
$size += $subdir->getFolderSize();
|
||||
}
|
||||
|
||||
return $size;
|
||||
}
|
||||
|
||||
/** Get this paths parent */
|
||||
public function getParent()
|
||||
{
|
||||
$path = $this->getPath();
|
||||
$parentPath = dirname($path);
|
||||
|
||||
$parentDir = new DirectoryModel($parentPath);
|
||||
|
||||
return $parentDir;
|
||||
}
|
||||
|
||||
public function getPermissions()
|
||||
{
|
||||
if (! $this->exists())
|
||||
{
|
||||
Log::addWarning('Directory not existing (fileperms): '. $this->getPath() );
|
||||
return false;
|
||||
}
|
||||
$perms = fileperms($this->getPath());
|
||||
|
||||
if ($perms !== false)
|
||||
{
|
||||
return $perms;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
return rmdir($this->getPath());
|
||||
}
|
||||
|
||||
/** This will try to remove the whole structure. Use with care.
|
||||
* This is mostly used to clear the backups.
|
||||
*/
|
||||
public function recursiveDelete()
|
||||
{
|
||||
if (! $this->exists() || ! $this->is_writable())
|
||||
return false;
|
||||
|
||||
// This is a security measure to prevent unintended wipes.
|
||||
$wpdir = \wpSPIO()->filesystem()->getWPUploadBase();
|
||||
if (! $this->isSubFolderOf($wpdir))
|
||||
return false;
|
||||
|
||||
$files = $this->getFiles();
|
||||
$subdirs = $this->getSubDirectories();
|
||||
|
||||
foreach($files as $file)
|
||||
$file->delete();
|
||||
|
||||
foreach($subdirs as $subdir)
|
||||
$subdir->recursiveDelete();
|
||||
|
||||
$this->delete();
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,637 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\File;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
use ShortPixel\Notices\NoticeController as Notice;
|
||||
|
||||
use \ShortPixel\Model\File\DirectoryModel as DirectoryModel;
|
||||
use \ShortPixel\Model\Image\ImageModel as ImageModel;
|
||||
|
||||
use ShortPixel\Controller\OptimizeController as OptimizeController;
|
||||
use ShortPixel\Controller\OtherMediaController as OtherMediaController;
|
||||
|
||||
// extends DirectoryModel. Handles ShortPixel_meta database table
|
||||
// Replacing main parts of shortpixel-folder
|
||||
class DirectoryOtherMediaModel extends DirectoryModel
|
||||
{
|
||||
|
||||
protected $id = -1; // if -1, this might not exist yet in Dbase. Null is not used, because that messes with isset
|
||||
|
||||
protected $name;
|
||||
protected $status = 0;
|
||||
protected $fileCount = 0; // inherent onreliable statistic in dbase. When insert / batch insert the folder count could not be updated, only on refreshFolder which is a relative heavy function to use on every file upload. Totals are better gotten from a stat-query, on request.
|
||||
protected $updated = 0;
|
||||
protected $created = 0;
|
||||
protected $checked = 0;
|
||||
protected $path_md5;
|
||||
|
||||
protected $is_nextgen = false;
|
||||
protected $in_db = false;
|
||||
protected $is_removed = false;
|
||||
|
||||
protected $last_message;
|
||||
|
||||
//protected $stats;
|
||||
|
||||
protected static $stats;
|
||||
|
||||
const DIRECTORY_STATUS_REMOVED = -1;
|
||||
const DIRECTORY_STATUS_NORMAL = 0;
|
||||
const DIRECTORY_STATUS_NEXTGEN = 1;
|
||||
|
||||
/** Path or Folder Object, from SpMetaDao
|
||||
*
|
||||
*/
|
||||
public function __construct($path)
|
||||
{
|
||||
|
||||
if (is_object($path)) // Load directly via Database object, this saves a query.
|
||||
{
|
||||
$folder = $path;
|
||||
$path = $folder->path;
|
||||
|
||||
parent::__construct($path);
|
||||
$this->loadFolder($folder);
|
||||
}
|
||||
else
|
||||
{
|
||||
parent::__construct($path);
|
||||
$this->loadFolderbyPath($path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function get($name)
|
||||
{
|
||||
if (property_exists($this, $name))
|
||||
return $this->$name;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function set($name, $value)
|
||||
{
|
||||
if (property_exists($this, $name))
|
||||
{
|
||||
$this->$name = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getAllStats()
|
||||
{
|
||||
if (is_null(self::$stats))
|
||||
{
|
||||
global $wpdb;
|
||||
$sql = 'SELECT SUM(CASE WHEN status = 2 OR status = -11 THEN 1 ELSE 0 END) optimized, SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) waiting, count(*) total, folder_id FROM ' . $wpdb->prefix . 'shortpixel_meta GROUP BY folder_id';
|
||||
|
||||
$result = $wpdb->get_results($sql, ARRAY_A);
|
||||
|
||||
$stats = array();
|
||||
foreach($result as $rawdata)
|
||||
{
|
||||
$folder_id = $rawdata['folder_id'];
|
||||
|
||||
$data = array(
|
||||
'optimized' => (int) $rawdata['optimized'],
|
||||
'waiting' => (int) $rawdata['waiting'],
|
||||
'total' => (int) $rawdata['total'],
|
||||
);
|
||||
$stats[$folder_id] = $data;
|
||||
}
|
||||
|
||||
self::$stats = $stats;
|
||||
}
|
||||
|
||||
return self::$stats;
|
||||
}
|
||||
|
||||
public function getStats()
|
||||
{
|
||||
$stats = self::getAllStats(); // Querying all stats is more efficient than one-by-one
|
||||
|
||||
if (isset($stats[$this->id]))
|
||||
{
|
||||
return $stats[$this->id];
|
||||
}
|
||||
else {
|
||||
global $wpdb;
|
||||
$sql = "SELECT SUM(CASE WHEN status = 2 OR status = -11 THEN 1 ELSE 0 END) optimized, "
|
||||
. "SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) waiting, count(*) total "
|
||||
. "FROM " . $wpdb->prefix . "shortpixel_meta "
|
||||
. "WHERE folder_id = %d";
|
||||
$sql = $wpdb->prepare($sql, $this->id);
|
||||
$res = $wpdb->get_row($sql, ARRAY_A);
|
||||
|
||||
if (is_array($res))
|
||||
{
|
||||
$result = array(
|
||||
'optimized' => (int) $res['optimized'],
|
||||
'waiting' => (int) $res['waiting'],
|
||||
'total' => (int) $res['total'],
|
||||
|
||||
);
|
||||
return $result;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
// Simple Update
|
||||
global $wpdb;
|
||||
$data = array(
|
||||
// 'id' => $this->id,
|
||||
'status' => $this->status,
|
||||
'file_count' => $this->fileCount,
|
||||
'ts_updated' => $this->timestampToDB($this->updated),
|
||||
'ts_checked' => $this->timestampToDB($this->checked),
|
||||
'name' => $this->name,
|
||||
'path' => $this->getPath(),
|
||||
);
|
||||
$format = array('%d', '%d', '%s', '%s', '%s', '%s');
|
||||
$table = $wpdb->prefix . 'shortpixel_folders';
|
||||
|
||||
$is_new = false;
|
||||
$result = false;
|
||||
|
||||
if ($this->in_db) // Update
|
||||
{
|
||||
$result = $wpdb->update($table, $data, array('id' => $this->id), $format);
|
||||
}
|
||||
else // Add new
|
||||
{
|
||||
// Fallback. In case some other process adds it. This happens with Nextgen.
|
||||
if (true === $this->loadFolderByPath($this->getPath()))
|
||||
{
|
||||
$result = $wpdb->update($table, $data, array('id' => $this->id), $format);
|
||||
}
|
||||
else
|
||||
{
|
||||
$data['ts_created'] = $this->timestampToDB(time());
|
||||
$this->id = $wpdb->insert($table, $data);
|
||||
if ($this->id !== false)
|
||||
{
|
||||
$is_new = true;
|
||||
$result = $this->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reloading because action can create a new DB-entry, which will not be reflected (in id )
|
||||
if ($is_new)
|
||||
{
|
||||
$this->loadFolderByPath($this->getPath());
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$id = $this->id;
|
||||
if (! $this->in_db)
|
||||
{
|
||||
Log::addError('Trying to remove Folder without being in the database (in_db false) ' . $id, $this->getPath());
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
$otherMedia = OtherMediaController::getInstance();
|
||||
|
||||
// Remove all files from this folder that are not optimized.
|
||||
$sql = "DELETE FROM " . $otherMedia->getMetaTable() . ' WHERE status <> 2 and folder_id = %d';
|
||||
$sql = $wpdb->prepare($sql, $this->id);
|
||||
$wpdb->query($sql);
|
||||
|
||||
// Check if there are any images left.
|
||||
$sql = 'SELECT count(id) FROM ' . $otherMedia->getMetaTable() . ' WHERE folder_id = %d';
|
||||
$sql = $wpdb->prepare($sql, $this->id);
|
||||
$numImages = $wpdb->get_var($sql);
|
||||
|
||||
if ($numImages > 0)
|
||||
{
|
||||
$sql = 'UPDATE ' . $otherMedia->getFolderTable() . ' SET status = -1 where id = %d';
|
||||
$sql = $wpdb->prepare($sql, $this->id);
|
||||
$result = $wpdb->query($sql);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sql = 'DELETE FROM ' . $otherMedia->getFolderTable() . ' where id = %d';
|
||||
$sql = $wpdb->prepare($sql, $this->id);
|
||||
$result = $wpdb->query($sql);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function isRemoved()
|
||||
{
|
||||
if ($this->is_removed)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Updates the updated variable on folder to indicating when the last file change was made
|
||||
* @return boolean True if file were changed since last update, false if not
|
||||
*/
|
||||
public function updateFileContentChange()
|
||||
{
|
||||
if (! $this->exists() )
|
||||
return false;
|
||||
|
||||
$old_time = $this->updated;
|
||||
|
||||
$time = $this->recurseLastChangeFile();
|
||||
$this->updated = $time;
|
||||
$this->save();
|
||||
|
||||
if ($old_time !== $time)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Crawls the folder and check for files that are newer than param time, or folder updated
|
||||
* Note - last update timestamp is not updated here, needs to be done separately.
|
||||
*/
|
||||
public function refreshFolder($force = false)
|
||||
{
|
||||
if ($force === false)
|
||||
{
|
||||
$time = $this->updated;
|
||||
}
|
||||
else
|
||||
{
|
||||
$time = 0; //force refresh of the whole.
|
||||
}
|
||||
|
||||
$stats = $this->getStats();
|
||||
$total_before = $stats['total'];
|
||||
|
||||
if (! $this->checkDirectory(true))
|
||||
{
|
||||
Log::addWarn('Refreshing directory, something wrong in checkDirectory ' . $this->getPath());
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->id <= 0)
|
||||
{
|
||||
Log::addWarn('FolderObj from database is not there, while folder seems ok ' . $this->getPath() );
|
||||
return false;
|
||||
}
|
||||
elseif (! $this->exists())
|
||||
{
|
||||
$message = sprintf(__('Folder %s does not exist! ', 'shortpixel-image-optimiser'), $this->getPath());
|
||||
$this->last_message = $message;
|
||||
Notice::addError( $message );
|
||||
return false;
|
||||
}
|
||||
elseif (! $this->is_writable())
|
||||
{
|
||||
$message = sprintf(__('Folder %s is not writeable. Please check permissions and try again.','shortpixel-image-optimiser'),$this->getPath());
|
||||
$this->last_message = $message;
|
||||
Notice::addWarning( $message );
|
||||
return false;
|
||||
}
|
||||
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
$filter = ($time > 0) ? array('date_newer' => $time) : array();
|
||||
$filter['exclude_files'] = array('.webp', '.avif');
|
||||
$filter['include_files'] = ImageModel::PROCESSABLE_EXTENSIONS;
|
||||
|
||||
$files = $fs->getFilesRecursive($this, $filter);
|
||||
|
||||
\wpSPIO()->settings()->hasCustomFolders = time(); // note, check this against bulk when removing. Custom Media Bulk depends on having a setting.
|
||||
|
||||
$result = $this->addImages($files);
|
||||
|
||||
// Reset stat.
|
||||
unset(self::$stats[$this->id]);
|
||||
|
||||
$stats = $this->getStats();
|
||||
$this->fileCount = $stats['total'];
|
||||
|
||||
$this->checked = time();
|
||||
$this->save();
|
||||
|
||||
$stats['new'] = $stats['total'] - $total_before;
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
|
||||
/** Check if a directory is allowed. Directory can't be media library, outside of root, or already existing in the database
|
||||
* @param $silent If not allowed, don't generate notices.
|
||||
*
|
||||
*/
|
||||
public function checkDirectory($silent = false)
|
||||
{
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
$rootDir = $fs->getWPFileBase();
|
||||
$backupDir = $fs->getDirectory(SHORTPIXEL_BACKUP_FOLDER);
|
||||
$otherMediaController = OtherMediaController::getInstance();
|
||||
|
||||
if (! $this->exists())
|
||||
{
|
||||
$message = __('Could not be added, directory not found: ' . $path ,'shortpixel-image-optimiser');
|
||||
$this->last_message = $message;
|
||||
|
||||
if (false === $silent)
|
||||
{
|
||||
Notice::addError($message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
elseif (! $this->isSubFolderOf($rootDir) && $this->getPath() != $rootDir->getPath() )
|
||||
{
|
||||
$message = sprintf(__('The %s folder cannot be processed as it\'s not inside the root path of your website (%s).','shortpixel-image-optimiser'),$this->getPath(), $rootDir->getPath());
|
||||
$this->last_message = $message;
|
||||
|
||||
if (false === $silent)
|
||||
{
|
||||
Notice::addError( $message );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
elseif($this->isSubFolderOf($backupDir) || $this->getPath() == $backupDir->getPath() )
|
||||
{
|
||||
$message = __('This folder contains the ShortPixel Backups. Please select a different folder.','shortpixel-image-optimiser');
|
||||
$this->last_message = $message;
|
||||
|
||||
if (false === $silent)
|
||||
{
|
||||
Notice::addError( $message );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
elseif( $otherMediaController->checkIfMediaLibrary($this) )
|
||||
{
|
||||
$message = __('This folder contains Media Library images. To optimize Media Library images please go to <a href="upload.php?mode=list">Media Library list view</a> or to <a href="upload.php?page=wp-short-pixel-bulk">ShortPixel Bulk page</a>.','shortpixel-image-optimiser');
|
||||
$this->last_message = $message;
|
||||
|
||||
if (false === $silent)
|
||||
{
|
||||
Notice::addError($message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
elseif (! $this->is_writable())
|
||||
{
|
||||
$message = sprintf(__('Folder %s is not writeable. Please check permissions and try again.','shortpixel-image-optimiser'),$this->getPath());
|
||||
$this->last_message = $message;
|
||||
|
||||
if (false === $silent)
|
||||
{
|
||||
Notice::addError( $message );
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$folders = $otherMediaController->getAllFolders();
|
||||
|
||||
foreach($folders as $folder)
|
||||
{
|
||||
if ($this->isSubFolderOf($folder))
|
||||
{
|
||||
|
||||
if (false === $silent)
|
||||
{
|
||||
Notice::addError(sprintf(__('This folder is a subfolder of an already existing Other Media folder. Folder %s can not be added', 'shortpixel-image-optimiser'), $this->getPath() ));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
public function getFiles($args = array())
|
||||
{
|
||||
// Check if this directory if not forbidden.
|
||||
if (! $this->checkDirectory(true))
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
return parent::getFiles($args);
|
||||
}
|
||||
*/
|
||||
/* public function getSubDirectories()
|
||||
{
|
||||
$dirs = parent::getSubDirectories();
|
||||
$checked = array();
|
||||
foreach($dirs as $dir)
|
||||
{
|
||||
if ($dir->checkDirectory(false))
|
||||
{
|
||||
$checked[] = $dir;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::addDebug('Illegal directory' . $dir->getPath());
|
||||
}
|
||||
}
|
||||
|
||||
return $checked;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
private function recurseLastChangeFile($mtime = 0)
|
||||
{
|
||||
$ignore = array('.','..');
|
||||
|
||||
// Directories without read rights should not be checked at all.
|
||||
if (! $this->is_readable())
|
||||
return $mtime;
|
||||
|
||||
$path = $this->getPath();
|
||||
|
||||
$files = scandir($path);
|
||||
|
||||
// no files, nothing to update.
|
||||
if (! is_array($files))
|
||||
{
|
||||
return $mtime;
|
||||
}
|
||||
|
||||
$files = array_diff($files, $ignore);
|
||||
|
||||
$mtime = max($mtime, filemtime($path));
|
||||
|
||||
foreach($files as $file) {
|
||||
|
||||
|
||||
$filepath = $path . $file;
|
||||
|
||||
if (is_dir($filepath)) {
|
||||
$mtime = max($mtime, filemtime($filepath));
|
||||
$subDirObj = new DirectoryOtherMediaModel($filepath);
|
||||
$subdirtime = $subDirObj->recurseLastChangeFile($mtime);
|
||||
if ($subdirtime > $mtime)
|
||||
$mtime = $subdirtime;
|
||||
}
|
||||
}
|
||||
return $mtime;
|
||||
}
|
||||
|
||||
private function timestampToDB($timestamp)
|
||||
{
|
||||
if ($timestamp == 0) // when adding / or empty.
|
||||
$timestamp = time();
|
||||
return date("Y-m-d H:i:s", $timestamp);
|
||||
}
|
||||
|
||||
private function DBtoTimestamp($date)
|
||||
{
|
||||
if (is_null($date))
|
||||
{
|
||||
$timestamp = time();
|
||||
}
|
||||
else {
|
||||
$timestamp =strtotime($date);
|
||||
}
|
||||
return $timestamp;
|
||||
}
|
||||
|
||||
/** This function is called by OtherMediaController / RefreshFolders. Other scripts should not call it
|
||||
* @public
|
||||
* @param Array of CustomMediaImageModel stubs.
|
||||
*/
|
||||
public function addImages($files) {
|
||||
|
||||
global $wpdb;
|
||||
if ( apply_filters('shortpixel/othermedia/addfiles', true, $files, $this) === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$values = array();
|
||||
|
||||
$optimizeControl = new OptimizeController();
|
||||
$otherMediaControl = OtherMediaController::getInstance();
|
||||
$activeFolders = $otherMediaControl->getActiveDirectoryIDS();
|
||||
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
|
||||
foreach($files as $fileObj)
|
||||
{
|
||||
// Note that load is set to false here.
|
||||
$imageObj = $fs->getCustomStub($fileObj->getFullPath(), false);
|
||||
|
||||
// image already exists
|
||||
if ($imageObj->get('in_db') == true)
|
||||
{
|
||||
// Load meta to make it check the folder_id.
|
||||
$imageObj->loadMeta();
|
||||
|
||||
// Check if folder id is something else. This might indicate removed or inactive folders.
|
||||
// If in inactive folder, move to current active.
|
||||
if ($imageObj->get('folder_id') !== $this->id)
|
||||
{
|
||||
if (! in_array($imageObj->get('folder_id'), $activeFolders) )
|
||||
{
|
||||
$imageObj->setFolderId($this->id);
|
||||
$imageObj->saveMeta();
|
||||
}
|
||||
}
|
||||
|
||||
// If in Db, but not optimized and autoprocess is on; add to queue for optimizing
|
||||
if (\wpSPIO()->env()->is_autoprocess && $imageObj->isProcessable())
|
||||
{
|
||||
$optimizeControl->addItemToQueue($imageObj);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
elseif ($imageObj->isProcessable()) // Check strict on Processable here.
|
||||
{
|
||||
$imageObj->setFolderId($this->id);
|
||||
$imageObj->saveMeta();
|
||||
|
||||
if (\wpSPIO()->env()->is_autoprocess)
|
||||
{
|
||||
$optimizeControl->addItemToQueue($imageObj);
|
||||
}
|
||||
}
|
||||
else {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function loadFolderByPath($path)
|
||||
{
|
||||
//$folders = self::getFolders(array('path' => $path));
|
||||
global $wpdb;
|
||||
|
||||
$sql = 'SELECT * FROM ' . $wpdb->prefix . 'shortpixel_folders where path = %s ';
|
||||
$sql = $wpdb->prepare($sql, $path);
|
||||
|
||||
$folder = $wpdb->get_row($sql);
|
||||
if (! is_object($folder))
|
||||
return false;
|
||||
else
|
||||
{
|
||||
$this->loadFolder($folder);
|
||||
$this->in_db = true; // exists in database
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Loads from database into model, the extra data of this model. */
|
||||
private function loadFolder($folder)
|
||||
{
|
||||
// $class = get_class($folder);
|
||||
// Setters before action
|
||||
$this->id = $folder->id;
|
||||
|
||||
if ($this->id > 0)
|
||||
$this->in_db = true;
|
||||
|
||||
$this->updated = property_exists($folder,'ts_updated') ? $this->DBtoTimestamp($folder->ts_updated) : time();
|
||||
$this->created = property_exists($folder,'ts_created') ? $this->DBtoTimestamp($folder->ts_created) : time();
|
||||
$this->checked = property_exists($folder,'ts_checked') ? $this->DBtoTimestamp($folder->ts_checked) : time();
|
||||
$this->fileCount = property_exists($folder,'file_count') ? $folder->file_count : 0; // deprecated, do not rely on.
|
||||
|
||||
$this->status = $folder->status;
|
||||
|
||||
if (strlen($folder->name) == 0)
|
||||
$this->name = basename($folder->path);
|
||||
else
|
||||
$this->name = $folder->name;
|
||||
|
||||
do_action('shortpixel/othermedia/folder/load', $this->id, $this);
|
||||
|
||||
// Making conclusions after action.
|
||||
if ($this->status == -1)
|
||||
$this->is_removed = true;
|
||||
|
||||
if ($this->status == self::DIRECTORY_STATUS_NEXTGEN)
|
||||
{
|
||||
$this->is_nextgen = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,889 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\File;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
use ShortPixel\Helper\UtilHelper as UtilHelper;
|
||||
|
||||
|
||||
/* FileModel class.
|
||||
*
|
||||
*
|
||||
* - Represents a -single- file.
|
||||
* - Can handle any type
|
||||
* - Usually controllers would use a collection of files
|
||||
* - Meant for all low-level file operations and checks.
|
||||
* - Every file can have a backup counterpart.
|
||||
*
|
||||
*/
|
||||
class FileModel extends \ShortPixel\Model
|
||||
{
|
||||
|
||||
// File info
|
||||
protected $fullpath = null;
|
||||
protected $rawfullpath = null;
|
||||
protected $filename = null; // filename + extension
|
||||
protected $filebase = null; // filename without extension
|
||||
protected $directory = null;
|
||||
protected $extension = null;
|
||||
protected $mime = null;
|
||||
protected $permissions = null;
|
||||
protected $filesize = null;
|
||||
|
||||
// File Status
|
||||
protected $exists = null;
|
||||
protected $is_writable = null;
|
||||
protected $is_directory_writable = null;
|
||||
protected $is_readable = null;
|
||||
protected $is_file = null;
|
||||
protected $is_virtual = false;
|
||||
protected $virtual_status = null;
|
||||
|
||||
protected $status;
|
||||
|
||||
protected $backupDirectory;
|
||||
|
||||
const FILE_OK = 1;
|
||||
const FILE_UNKNOWN_ERROR = 2;
|
||||
|
||||
public static $TRUSTED_MODE = false;
|
||||
|
||||
// Constants for is_virtual . Virtual Remote is truly a remote file, not writable from machine. Stateless means it looks remote, but it's a protocol-based filesystem remote or not - that will accept writes / is_writable. Stateless also mean performance issue since it can't be 'translated' to a local path. All communication happens over http wrapper, so check should be very limited.
|
||||
public static $VIRTUAL_REMOTE = 1;
|
||||
public static $VIRTUAL_STATELESS = 2;
|
||||
|
||||
/** Creates a file model object. FileModel files don't need to exist on FileSystem */
|
||||
public function __construct($path)
|
||||
{
|
||||
$this->rawfullpath = $path;
|
||||
|
||||
if (is_null($path))
|
||||
{
|
||||
Log::addWarn('FileModel: Loading null path! ');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strlen($path) > 0)
|
||||
$path = trim($path);
|
||||
|
||||
$this->fullpath = $path;
|
||||
|
||||
$this->checkTrustedMode();
|
||||
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
if ($fs->pathIsUrl($path)) // Asap check for URL's to prevent remote wrappers from running.
|
||||
{
|
||||
$this->UrlToPath($path);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get a string representation of file, the fullpath
|
||||
* Note - this might be risky, without processedpath, in cases.
|
||||
* @return String Full path processed or unprocessed.
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return (string) $this->fullpath;
|
||||
}
|
||||
|
||||
protected function setFileInfo()
|
||||
{
|
||||
$processed_path = $this->processPath($this->fullpath);
|
||||
if ($processed_path !== false)
|
||||
$this->fullpath = $processed_path; // set processed path if that went alright
|
||||
|
||||
|
||||
$info = $this->mb_pathinfo($this->fullpath);
|
||||
// Todo, maybe replace this with splFileINfo.
|
||||
if ($this->is_file()) // only set fileinfo when it's an actual file.
|
||||
{
|
||||
$this->filename = isset($info['basename']) ? $info['basename'] : null; // filename + extension
|
||||
$this->filebase = isset($info['filename']) ? $info['filename'] : null; // only filename
|
||||
$this->extension = isset($info['extension']) ? strtolower($info['extension']) : null; // only (last) extension
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Call when file status changed, so writable / readable / exists are not reliable anymore */
|
||||
public function resetStatus()
|
||||
{
|
||||
$this->is_writable = null;
|
||||
$this->is_directory_writable = null;
|
||||
$this->is_readable = null;
|
||||
$this->is_file = null;
|
||||
$this->exists = null;
|
||||
$this->is_virtual = null;
|
||||
$this->filesize = null;
|
||||
$this->permissions = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $forceCheck Forces a filesystem check instead of using cached. Use very sparingly. Implemented for retina on trusted mode.
|
||||
*/
|
||||
public function exists($forceCheck = false)
|
||||
{
|
||||
if (true === $forceCheck || is_null($this->exists))
|
||||
{
|
||||
if (true === $this->fileIsRestricted($this->fullpath))
|
||||
{
|
||||
$this->exists = false;
|
||||
}
|
||||
else {
|
||||
$this->exists = (@file_exists($this->fullpath) && is_file($this->fullpath));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->exists = apply_filters('shortpixel_image_exists', $this->exists, $this->fullpath, $this); //legacy
|
||||
$this->exists = apply_filters('shortpixel/file/exists', $this->exists, $this->fullpath, $this);
|
||||
return $this->exists;
|
||||
}
|
||||
|
||||
public function is_writable()
|
||||
{
|
||||
// Return when already asked / Stateless might set this
|
||||
if (! is_null($this->is_writable))
|
||||
{
|
||||
return $this->is_writable;
|
||||
}
|
||||
elseif ($this->is_virtual())
|
||||
{
|
||||
$this->is_writable = false; // can't write to remote files
|
||||
}
|
||||
elseif (is_null($this->is_writable))
|
||||
{
|
||||
if ($this->exists())
|
||||
{
|
||||
$this->is_writable = @is_writable($this->fullpath);
|
||||
}
|
||||
else // quite expensive check to see if file is writable.
|
||||
{
|
||||
$res = $this->create();
|
||||
$this->delete();
|
||||
$this->is_writable = $res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $this->is_writable;
|
||||
}
|
||||
|
||||
public function is_directory_writable()
|
||||
{
|
||||
// Return when already asked / Stateless might set this
|
||||
if (! is_null($this->is_directory_writable))
|
||||
{
|
||||
return $this->is_directory_writable;
|
||||
}
|
||||
elseif ($this->is_virtual())
|
||||
{
|
||||
$this->is_directory_writable = false; // can't write to remote files
|
||||
}
|
||||
elseif (is_null($this->is_directory_writable))
|
||||
{
|
||||
$directory = $this->getFileDir();
|
||||
if (is_object($directory) && $directory->exists())
|
||||
{
|
||||
$this->is_directory_writable = $directory->is_writable();
|
||||
}
|
||||
else {
|
||||
$this->is_directory_writable = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $this->is_directory_writable;
|
||||
}
|
||||
|
||||
public function is_readable()
|
||||
{
|
||||
if (is_null($this->is_readable))
|
||||
$this->is_readable = @is_readable($this->fullpath);
|
||||
|
||||
return $this->is_readable;
|
||||
}
|
||||
|
||||
// A file is virtual when the file is remote with URL and no local alternative is present.
|
||||
public function is_virtual()
|
||||
{
|
||||
if ( is_null($this->is_virtual))
|
||||
$this->is_virtual = false; // return bool
|
||||
return $this->is_virtual;
|
||||
}
|
||||
|
||||
/* Function checks if path is actually a file. This can be used to check possible confusion if a directory path is given to filemodel */
|
||||
public function is_file()
|
||||
{
|
||||
if ($this->is_virtual()) // don't look further when virtual
|
||||
{
|
||||
$this->is_file = true;
|
||||
return $this->is_file;
|
||||
}
|
||||
elseif (is_null($this->is_file))
|
||||
{
|
||||
if ($this->exists())
|
||||
{
|
||||
if (basename($this->fullpath) == '..' || basename($this->fullpath) == '.')
|
||||
$this->is_file = false;
|
||||
else
|
||||
$this->is_file = is_file($this->fullpath);
|
||||
}
|
||||
else // file can not exist, but still have a valid filepath format. In that case, if file should return true.
|
||||
{
|
||||
|
||||
/* if file does not exist on disk, anything can become a file ( with/ without extension, etc). Meaning everything non-existing is a potential file ( or directory ) until created. */
|
||||
|
||||
if (basename($this->fullpath) == '..' || basename($this->fullpath) == '.') // don't see this as file.
|
||||
{
|
||||
$this->is_file = false;
|
||||
}
|
||||
else if (! file_exists($this->fullpath) && ! is_dir($this->fullpath))
|
||||
{
|
||||
$this->is_file = true;
|
||||
}
|
||||
else //if (! is_file($this->fullpath)) // can be a non-existing directory. /
|
||||
{
|
||||
$this->is_file = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return $this->is_file;
|
||||
}
|
||||
|
||||
public function getModified()
|
||||
{
|
||||
return filemtime($this->fullpath);
|
||||
}
|
||||
|
||||
public function hasBackup()
|
||||
{
|
||||
$directory = $this->getBackupDirectory();
|
||||
if (! $directory)
|
||||
return false;
|
||||
|
||||
$backupFile = $directory . $this->getBackupFileName();
|
||||
|
||||
if (file_exists($backupFile) && ! is_dir($backupFile) )
|
||||
return true;
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Tries to retrieve an *existing* BackupFile. Returns false if not present.
|
||||
* This file might not be writable.
|
||||
* To get writable directory reference to backup, use FileSystemController
|
||||
*/
|
||||
public function getBackupFile()
|
||||
{
|
||||
if ($this->hasBackup())
|
||||
return new FileModel($this->getBackupDirectory() . $this->getBackupFileName() );
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Function returns the filename for the backup. This is an own function so it's possible to manipulate backup file name if needed, i.e. conversion or enumeration */
|
||||
public function getBackupFileName()
|
||||
{
|
||||
return $this->getFileName();
|
||||
}
|
||||
|
||||
/** Returns the Directory Model this file resides in
|
||||
*
|
||||
* @return DirectoryModel Directorymodel Object
|
||||
*/
|
||||
public function getFileDir()
|
||||
{
|
||||
$fullpath = $this->getFullPath(); // triggers a file lookup if needed.
|
||||
// create this only when needed.
|
||||
if (is_null($this->directory) && strlen($fullpath) > 0)
|
||||
{
|
||||
// Feed to full path to DirectoryModel since it checks if input is file, or dir. Using dirname here would cause errors when fullpath is already just a dirpath ( faulty input )
|
||||
$this->directory = new DirectoryModel($fullpath);
|
||||
}
|
||||
|
||||
return $this->directory;
|
||||
}
|
||||
|
||||
public function getFileSize()
|
||||
{
|
||||
if (! is_null($this->filesize))
|
||||
{
|
||||
return $this->filesize;
|
||||
}
|
||||
elseif ($this->exists() && false === $this->is_virtual() )
|
||||
{
|
||||
$this->filesize = filesize($this->fullpath);
|
||||
return $this->filesize;
|
||||
}
|
||||
elseif (true === $this->is_virtual())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Creates an empty file
|
||||
public function create()
|
||||
{
|
||||
if (! $this->exists() )
|
||||
{
|
||||
$fileDir = $this->getFileDir();
|
||||
|
||||
if (! is_null($fileDir) && $fileDir->exists())
|
||||
{
|
||||
$res = @touch($this->fullpath);
|
||||
$this->exists = $res;
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
else
|
||||
Log::addWarn('Could not create/write file: ' . $this->fullpath);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function append($message)
|
||||
{
|
||||
if (! $this->exists() )
|
||||
$this->create();
|
||||
|
||||
if (! $this->is_writable() )
|
||||
{
|
||||
Log::addWarn('File append failed on ' . $this->getFullPath() . ' - not writable');
|
||||
return false;
|
||||
}
|
||||
$handle = fopen($this->getFullPath(), 'a');
|
||||
fwrite($handle, $message);
|
||||
fclose($handle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/** Copy a file to somewhere
|
||||
*
|
||||
* @param $destination String Full Path to new file.
|
||||
*/
|
||||
public function copy(FileModel $destination)
|
||||
{
|
||||
$sourcePath = $this->getFullPath();
|
||||
$destinationPath = $destination->getFullPath();
|
||||
Log::addDebug("Copy from $sourcePath to $destinationPath ");
|
||||
|
||||
if (! strlen($sourcePath) > 0 || ! strlen($destinationPath) > 0)
|
||||
{
|
||||
Log::addWarn('Attempted Copy on Empty Path', array($sourcePath, $destinationPath));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->exists())
|
||||
{
|
||||
Log::addWarn('Tried to copy non-existing file - ' . $sourcePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
$is_new = ($destination->exists()) ? false : true;
|
||||
$status = @copy($sourcePath, $destinationPath);
|
||||
|
||||
if (! $status)
|
||||
{
|
||||
Log::addWarn('Could not copy file ' . $sourcePath . ' to' . $destinationPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
$destination->resetStatus();
|
||||
$destination->setFileInfo(); // refresh info.
|
||||
}
|
||||
//
|
||||
do_action('shortpixel/filesystem/addfile', array($destinationPath, $destination, $this, $is_new));
|
||||
return $status;
|
||||
}
|
||||
|
||||
/** Move a file to somewhere
|
||||
* This uses copy and delete functions and will fail if any of those fail.
|
||||
* @param $destination String Full Path to new file.
|
||||
*/
|
||||
public function move(FileModel $destination)
|
||||
{
|
||||
$result = false;
|
||||
if ($this->copy($destination))
|
||||
{
|
||||
$result = $this->delete();
|
||||
if ($result == false)
|
||||
{
|
||||
Log::addError('Move can\'t remove file ' . $this->getFullPath());
|
||||
}
|
||||
|
||||
$this->resetStatus();
|
||||
$destination->resetStatus();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/** Deletes current file
|
||||
* This uses the WP function since it has a filter that might be useful
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
if ($this->exists())
|
||||
{
|
||||
\wp_delete_file($this->fullpath); // delete file hook via wp_delete_file
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::addWarn('Trying to remove non-existing file: ' . $this->getFullPath());
|
||||
}
|
||||
|
||||
if (! file_exists($this->fullpath))
|
||||
{
|
||||
$this->resetStatus();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
$writable = ($this->is_writable()) ? 'true' : 'false';
|
||||
Log::addWarn('File seems not removed - ' . $this->getFullPath() . ' (writable:' . $writable . ')');
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getContents()
|
||||
{
|
||||
return file_get_contents($this->getFullPath());
|
||||
}
|
||||
|
||||
public function getFullPath()
|
||||
{
|
||||
// filename here since fullpath is set unchecked in constructor, but might be a different take
|
||||
if (is_null($this->filename))
|
||||
{
|
||||
$this->setFileInfo();
|
||||
}
|
||||
|
||||
return $this->fullpath;
|
||||
}
|
||||
|
||||
// Testing this. Principle is that when the plugin is absolutely sure this is a file, not something remote, not something non-existing, get the fullpath without any check.
|
||||
// This function should *only* be used when processing mega amounts of files while not doing optimization or any processing.
|
||||
// So far, testing use for file Filter */
|
||||
public function getRawFullPath()
|
||||
{
|
||||
return $this->rawfullpath;
|
||||
}
|
||||
|
||||
public function getFileName()
|
||||
{
|
||||
if (is_null($this->filename))
|
||||
$this->setFileInfo();
|
||||
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
public function getFileBase()
|
||||
{
|
||||
if (is_null($this->filebase))
|
||||
$this->setFileInfo();
|
||||
|
||||
return $this->filebase;
|
||||
}
|
||||
|
||||
|
||||
public function getExtension()
|
||||
{
|
||||
if (is_null($this->extension))
|
||||
$this->setFileInfo();
|
||||
|
||||
return $this->extension;
|
||||
}
|
||||
|
||||
public function getMime()
|
||||
{
|
||||
if (is_null($this->mime))
|
||||
$this->setFileInfo();
|
||||
|
||||
if ($this->exists() && ! $this->is_virtual() )
|
||||
{
|
||||
$this->mime = wp_get_image_mime($this->fullpath);
|
||||
if (false === $this->mime)
|
||||
{
|
||||
$image_data = wp_check_filetype_and_ext($this->getFullPath(), $this->getFileName());
|
||||
if (is_array($image_data) && isset($image_data['type']) && strlen($image_data['type']) > 0)
|
||||
{
|
||||
$this->mime = $image_data['type'];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
$this->mime = false;
|
||||
|
||||
return $this->mime;
|
||||
}
|
||||
/* Util function to get location of backup Directory.
|
||||
* @param Create - If true will try to create directory if it doesn't exist.
|
||||
* @return Boolean | DirectModel Returns false if directory is not properly set, otherwhise with a new directoryModel
|
||||
*/
|
||||
protected function getBackupDirectory($create = false)
|
||||
{
|
||||
|
||||
if (is_null($this->getFileDir()))
|
||||
{
|
||||
Log::addWarn('Could not establish FileDir ' . $this->fullpath);
|
||||
return false;
|
||||
}
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
if (is_null($this->backupDirectory))
|
||||
{
|
||||
$directory = $fs->getBackupDirectory($this, $create);
|
||||
|
||||
if ($directory === false || ! $directory->exists()) // check if exists. FileModel should not attempt to create.
|
||||
{
|
||||
//Log::addWarn('Backup Directory not existing ' . $directory-);
|
||||
return false;
|
||||
}
|
||||
elseif ($directory !== false)
|
||||
{
|
||||
$this->backupDirectory = $directory;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->backupDirectory;
|
||||
}
|
||||
|
||||
/* Internal function to check if path is a real path
|
||||
* - Test for URL's based on http / https
|
||||
* - Test if given path is absolute, from the filesystem root.
|
||||
* @param $path String The file path
|
||||
* @param String The Fixed filepath.
|
||||
*/
|
||||
protected function processPath($path)
|
||||
{
|
||||
$original_path = $path;
|
||||
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
if ($fs->pathIsUrl($path))
|
||||
{
|
||||
$path = $this->UrlToPath($path);
|
||||
}
|
||||
|
||||
if ($path === false) // don't process further
|
||||
return false;
|
||||
|
||||
$path = UtilHelper::spNormalizePath($path);
|
||||
$abspath = $fs->getWPAbsPath();
|
||||
|
||||
|
||||
// Prevent file operation below if trusted.
|
||||
if (true === self::$TRUSTED_MODE)
|
||||
{
|
||||
return $path;
|
||||
}
|
||||
|
||||
// Check if some openbasedir is active.
|
||||
if (true === $this->fileIsRestricted($path))
|
||||
{
|
||||
$path = $this->relativeToFullPath($path);
|
||||
}
|
||||
|
||||
if ( is_file($path) && ! is_dir($path) ) // if path and file exist, all should be okish.
|
||||
{
|
||||
return $path;
|
||||
}
|
||||
// If attempted file does not exist, but the file is in a dir that exists, that is good enough.
|
||||
elseif ( ! is_dir($path) && is_dir(dirname($path)) )
|
||||
{
|
||||
return $path;
|
||||
}
|
||||
// If path is not in the abspath, it might be relative.
|
||||
elseif (strpos($path, $abspath->getPath()) === false)
|
||||
{
|
||||
// if path does not contain basepath.
|
||||
//$uploadDir = $fs->getWPUploadBase();
|
||||
//$abspath = $fs->getWPAbsPath();
|
||||
|
||||
$path = $this->relativeToFullPath($path);
|
||||
}
|
||||
$path = apply_filters('shortpixel/filesystem/processFilePath', $path, $original_path);
|
||||
/* This needs some check here on malformed path's, but can't be test for existing since that's not a requirement.
|
||||
if (file_exists($path) === false) // failed to process path to something workable.
|
||||
{
|
||||
// Log::addInfo('Failed to process path', array($path));
|
||||
$path = false;
|
||||
} */
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
protected function checkTrustedMode()
|
||||
{
|
||||
// When in trusted mode prevent filesystem checks as much as possible.
|
||||
if (true === self::$TRUSTED_MODE)
|
||||
{
|
||||
|
||||
// At this point file info might not be loaded, because it goes w/ construct -> processpath -> urlToPath etc on virtual files. And called via getFileInfo. Using any of the file info functions can trigger a loop.
|
||||
if (is_null($this->extension))
|
||||
{
|
||||
$extension = pathinfo($this->fullpath, PATHINFO_EXTENSION);
|
||||
}
|
||||
else {
|
||||
$extension = $this->getExtension();
|
||||
}
|
||||
|
||||
$this->exists = true;
|
||||
$this->is_writable = true;
|
||||
$this->is_directory_writable = true;
|
||||
$this->is_readable = true;
|
||||
$this->is_file = true;
|
||||
// Set mime to prevent lookup in IsImage
|
||||
$this->mime = 'image/' . $extension;
|
||||
|
||||
if (is_null($this->filesize))
|
||||
{
|
||||
$this->filesize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Check if path is allowed within openbasedir restrictions. This is an attempt to limit notices in file funtions if so. Most likely the path will be relative in that case.
|
||||
* @param String Path as String
|
||||
*/
|
||||
private function fileIsRestricted($path)
|
||||
{
|
||||
$basedir = ini_get('open_basedir');
|
||||
|
||||
if (false === $basedir || strlen($basedir) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$restricted = true;
|
||||
$basedirs = preg_split('/:|;/i', $basedir);
|
||||
|
||||
foreach($basedirs as $basepath)
|
||||
{
|
||||
if (strpos($path, $basepath) !== false)
|
||||
{
|
||||
$restricted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $restricted;
|
||||
}
|
||||
|
||||
/** Resolve an URL to a local path
|
||||
* This partially comes from WordPress functions attempting the same
|
||||
* @param String $url The URL to resolve
|
||||
* @return String/Boolean - False is this seems an external domain, otherwise resolved path.
|
||||
*/
|
||||
private function UrlToPath($url)
|
||||
{
|
||||
|
||||
// If files is present, high chance that it's WPMU old style, which doesn't have in home_url the /files/ needed to properly replace and get the filepath . It would result in a /files/files path which is incorrect.
|
||||
if (strpos($url, '/files/') !== false)
|
||||
{
|
||||
$uploadDir = wp_upload_dir();
|
||||
$site_url = str_replace(array('http:', 'https:'), '', $uploadDir['baseurl']);
|
||||
}
|
||||
else {
|
||||
$site_url = str_replace('http:', '', home_url('', 'http'));
|
||||
}
|
||||
|
||||
$url = str_replace(array('http:', 'https:'), '', $url);
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
// The site URL domain is included in the URL string
|
||||
if (strpos($url, $site_url) !== false)
|
||||
{
|
||||
// try to replace URL for Path
|
||||
$abspath = \wpSPIO()->filesystem()->getWPAbsPath();
|
||||
$path = str_replace($site_url, rtrim($abspath->getPath(),'/'), $url);
|
||||
|
||||
if (! $fs->pathIsUrl($path)) // test again.
|
||||
{
|
||||
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
$this->is_virtual = true;
|
||||
|
||||
/* This filter checks if some supplier will be able to handle the file when needed.
|
||||
* Use translate filter to correct filepath when needed.
|
||||
* Return could be true, or fileModel virtual constant
|
||||
*/
|
||||
$result = apply_filters('shortpixel/image/urltopath', false, $url);
|
||||
|
||||
if ($result === false)
|
||||
{
|
||||
$this->exists = false;
|
||||
$this->is_readable = false;
|
||||
$this->is_file = false;
|
||||
}
|
||||
else {
|
||||
$this->exists = true;
|
||||
$this->is_readable = true;
|
||||
$this->is_file = true;
|
||||
}
|
||||
|
||||
// If return is a stateless server, assume that it's writable and all that.
|
||||
if ($result === self::$VIRTUAL_STATELESS)
|
||||
{
|
||||
$this->is_writable = true;
|
||||
$this->is_directory_writable = true;
|
||||
$this->virtual_status = self::$VIRTUAL_STATELESS;
|
||||
}
|
||||
elseif ($result === self::$VIRTUAL_REMOTE)
|
||||
{
|
||||
$this->virtual_status = self::$VIRTUAL_REMOTE;
|
||||
}
|
||||
|
||||
return false; // seems URL from other server, use virtual mode.
|
||||
}
|
||||
|
||||
/** Tries to find the full path for a perceived relative path.
|
||||
*
|
||||
* Relative path is detected on basis of WordPress ABSPATH. If this doesn't appear in the file path, it might be a relative path.
|
||||
* Function checks for expections on this rule ( tmp path ) and returns modified - or not - path.
|
||||
* @param $path The path for the file_exists
|
||||
* @returns String The updated path, if that was possible.
|
||||
*/
|
||||
private function relativeToFullPath($path)
|
||||
{
|
||||
$originalPath = $path; // for safe-keeping
|
||||
|
||||
// A file with no path, can never be created to a fullpath.
|
||||
if (strlen($path) == 0)
|
||||
return $path;
|
||||
|
||||
// if the file plainly exists, it's usable /**
|
||||
if (false === $this->fileIsRestricted($path) && file_exists($path))
|
||||
{
|
||||
return $path;
|
||||
}
|
||||
|
||||
// Test if our 'relative' path is not a path to /tmp directory.
|
||||
|
||||
// This ini value might not exist.
|
||||
$tempdirini = ini_get('upload_tmp_dir');
|
||||
if ( (strlen($tempdirini) > 0) && strpos($path, $tempdirini) !== false)
|
||||
return $path;
|
||||
|
||||
$tempdir = sys_get_temp_dir();
|
||||
if ( (strlen($tempdir) > 0) && strpos($path, $tempdir) !== false)
|
||||
return $path;
|
||||
|
||||
// Path contains upload basedir. This happens when upload dir is outside of usual WP.
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
$uploadDir = $fs->getWPUploadBase();
|
||||
$abspath = $fs->getWPAbsPath();
|
||||
|
||||
if (strpos($path, $uploadDir->getPath()) !== false) // If upload Dir is feature in path, consider it ok.
|
||||
{
|
||||
return $path;
|
||||
}
|
||||
elseif (file_exists($abspath->getPath() . $path)) // If upload dir is abspath plus return path. Exceptions.
|
||||
{
|
||||
return $abspath->getPath() . $path;
|
||||
}
|
||||
elseif(file_exists($uploadDir->getPath() . $path)) // This happens when upload_dir is not properly prepended in get_attachment_file due to WP errors
|
||||
{
|
||||
return $uploadDir->getPath() . $path;
|
||||
}
|
||||
|
||||
// this is probably a bit of a sharp corner to take.
|
||||
// if path starts with / remove it due to trailingslashing ABSPATH
|
||||
$path = ltrim($path, '/');
|
||||
$fullpath = $abspath->getPath() . $path;
|
||||
|
||||
// We can't test for file_exists here, since file_model allows non-existing files.
|
||||
// Test if directory exists, perhaps. Otherwise we are in for a failure anyhow.
|
||||
//if (is_dir(dirname($fullpath)))
|
||||
return $fullpath;
|
||||
//else
|
||||
// return $originalPath;
|
||||
}
|
||||
|
||||
public function getPermissions()
|
||||
{
|
||||
if (is_null($this->permissions))
|
||||
$this->permissions = fileperms($this->getFullPath()) & 0777;
|
||||
|
||||
return $this->permissions;
|
||||
}
|
||||
|
||||
// @tozo Lazy IMplementation / copy, should be brought in line w/ other attributes.
|
||||
public function setPermissions($permissions)
|
||||
{
|
||||
@chmod($this->fullpath, $permissions);
|
||||
}
|
||||
|
||||
|
||||
/** Fix for multibyte pathnames and pathinfo which doesn't take into regard the locale.
|
||||
* This snippet taken from PHPMailer.
|
||||
*/
|
||||
private function mb_pathinfo($path, $options = null)
|
||||
{
|
||||
$ret = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''];
|
||||
$pathinfo = [];
|
||||
if (preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^.\\\\/]+?)|))[\\\\/.]*$#m', $path, $pathinfo)) {
|
||||
if (array_key_exists(1, $pathinfo)) {
|
||||
$ret['dirname'] = $pathinfo[1];
|
||||
}
|
||||
if (array_key_exists(2, $pathinfo)) {
|
||||
$ret['basename'] = $pathinfo[2];
|
||||
}
|
||||
if (array_key_exists(5, $pathinfo)) {
|
||||
$ret['extension'] = $pathinfo[5];
|
||||
}
|
||||
if (array_key_exists(3, $pathinfo)) {
|
||||
$ret['filename'] = $pathinfo[3];
|
||||
}
|
||||
}
|
||||
switch ($options) {
|
||||
case PATHINFO_DIRNAME:
|
||||
case 'dirname':
|
||||
return $ret['dirname'];
|
||||
case PATHINFO_BASENAME:
|
||||
case 'basename':
|
||||
return $ret['basename'];
|
||||
case PATHINFO_EXTENSION:
|
||||
case 'extension':
|
||||
return $ret['extension'];
|
||||
case PATHINFO_FILENAME:
|
||||
case 'filename':
|
||||
return $ret['filename'];
|
||||
default:
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
|
||||
public function __debuginfo()
|
||||
{
|
||||
return [
|
||||
'fullpath' => $this->fullpath,
|
||||
'filename' => $this->filename,
|
||||
'filebase' => $this->filebase,
|
||||
'exists' => $this->exists,
|
||||
'is_writable' => $this->is_writable,
|
||||
'is_readable' => $this->is_readable,
|
||||
'is_virtual' => $this->is_virtual,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
} // FileModel Class
|
@ -0,0 +1,349 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model;
|
||||
|
||||
use ShortPixel\Model\Image\ImageModel as ImageModel;
|
||||
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
|
||||
|
||||
class FrontImage
|
||||
{
|
||||
protected $raw;
|
||||
protected $image_loaded = false;
|
||||
protected $is_parsable = false;
|
||||
protected $imageBase; // directory path of this image.
|
||||
|
||||
protected $id; // HTML ID of image
|
||||
protected $alt;
|
||||
protected $src; // original src of image
|
||||
protected $srcset; // orginal srcset of image
|
||||
protected $class;
|
||||
protected $width;
|
||||
protected $height;
|
||||
protected $style;
|
||||
protected $sizes;
|
||||
|
||||
// Array of all other attributes.
|
||||
protected $attributes;
|
||||
|
||||
// Parsed items of src /srcset / sizes
|
||||
protected $dataTags = array();
|
||||
|
||||
public function __construct($raw_html)
|
||||
{
|
||||
$this->raw = $raw_html;
|
||||
$this->loadImageDom();
|
||||
}
|
||||
|
||||
public function loadImageDom()
|
||||
{
|
||||
if (function_exists("mb_convert_encoding")) {
|
||||
$this->raw = mb_encode_numericentity($this->raw, [0x80, 0x10FFFF, 0, ~0], 'UTF-8');
|
||||
}
|
||||
|
||||
$dom = new \DOMDocument();
|
||||
libxml_use_internal_errors(true); // disable error emit from libxml
|
||||
|
||||
$result = $dom->loadHTML($this->raw, LIBXML_NOWARNING);
|
||||
|
||||
// HTML failed loading
|
||||
if (false === $result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$image = $dom->getElementsByTagName('img')->item(0);
|
||||
$attributes = array();
|
||||
|
||||
/* This can happen with mismatches, or extremely malformed HTML.
|
||||
In customer case, a javascript that did for (i<imgDefer) --- </script> */
|
||||
if (! is_object($image))
|
||||
{
|
||||
$this->is_parsable = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($image->attributes as $attr) {
|
||||
// Skip is no value
|
||||
if (strlen($attr->nodeValue) == 0)
|
||||
continue;
|
||||
|
||||
if (property_exists($this, $attr->nodeName))
|
||||
{
|
||||
$this->{$attr->nodeName} = $attr->nodeValue;
|
||||
}
|
||||
|
||||
$this->attributes[$attr->nodeName] = $attr->nodeValue;
|
||||
}
|
||||
|
||||
|
||||
// Parse the directory path and other sources
|
||||
$result = $this->setupSources();
|
||||
|
||||
|
||||
if (true === $result)
|
||||
$this->image_loaded = true;
|
||||
}
|
||||
|
||||
public function hasBackground()
|
||||
{
|
||||
if (! is_null($this->style) && strpos($this->style, 'background') !== false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasPreventClasses()
|
||||
{
|
||||
// no class, no prevent.
|
||||
if (is_null($this->class))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$preventArray = apply_filters('shortpixel/front/preventclasses', array('sp-no-webp', 'rev-sildebg') );
|
||||
|
||||
foreach($preventArray as $classname)
|
||||
{
|
||||
if (false !== strpos($this->class, $classname) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasSource()
|
||||
{
|
||||
if (is_null($this->src) && is_null($this->srcset))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isParseable()
|
||||
{
|
||||
if (
|
||||
false === $this->hasPreventClasses() &&
|
||||
false === $this->hasBackground() &&
|
||||
true === $this->hasSource() &&
|
||||
true === $this->image_loaded
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getImageData()
|
||||
{
|
||||
if (! is_null($this->srcset))
|
||||
{
|
||||
$data = $this->getLazyData('srcset');
|
||||
$data = explode(',', $data); // srcset is multiple images, split.
|
||||
}
|
||||
else {
|
||||
$data = $this->getLazyData('src');
|
||||
$data = array($data); // single item, wrap in array
|
||||
}
|
||||
|
||||
$this->getLazyData('sizes'); // sets the sizes.
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getImageBase()
|
||||
{
|
||||
if (! is_null($this->imageBase))
|
||||
return $this->imageBase->getPath();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function parseReplacement($args)
|
||||
{
|
||||
if (is_null($this->class))
|
||||
{
|
||||
$this->class = '';
|
||||
}
|
||||
|
||||
$this->class .= ' sp-no-webp';
|
||||
|
||||
$output = "<picture>";
|
||||
|
||||
if (isset($args['avif']) && count($args['avif']) > 0)
|
||||
{
|
||||
$output .= $this->buildSource($args['avif'], 'avif');
|
||||
}
|
||||
|
||||
if (isset($args['webp']) && count($args['webp']) > 0)
|
||||
{
|
||||
$output .= $this->buildSource($args['webp'], 'webp');
|
||||
}
|
||||
|
||||
$output .= $this->buildImage();
|
||||
|
||||
$output .= "</picture>";
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
protected function setupSources()
|
||||
{
|
||||
$src = null;
|
||||
|
||||
if (! is_null($this->src))
|
||||
{
|
||||
$src = $this->src;
|
||||
}
|
||||
elseif (! is_null($this->srcset))
|
||||
{
|
||||
$parts = preg_split('/\s+/', trim($this->srcset));
|
||||
$image_url = $parts[0];
|
||||
$src = $image_url;
|
||||
}
|
||||
|
||||
if (is_null($src))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter out extension that are not for us.
|
||||
if (false === $this->checkExtensionConvertable($src))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
$fileObj = $fs->getFile($src);
|
||||
$fileDir = $fileObj->getFileDir();
|
||||
$this->imageBase = $fileObj->getFileDir();
|
||||
|
||||
return true;
|
||||
// If (! is_hnull $srcset)
|
||||
// Get first item from srcset ( remove the size ? , then feed it to FS, get directory from it.
|
||||
}
|
||||
|
||||
/*** Check if the extension is something we want to check
|
||||
* @param String The URL source of the image.
|
||||
**/
|
||||
private function checkExtensionConvertable($source)
|
||||
{
|
||||
$extension = substr($source, strrpos($source, '.') + 1);
|
||||
if (in_array($extension, ImageModel::PROCESSABLE_EXTENSIONS))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
protected function buildSource($sources, $fileFormat)
|
||||
{
|
||||
|
||||
$prefix = (isset($this->dataTags['srcset'])) ? $this->dataTags['srcset'] : $this->dataTags['src'];
|
||||
$srcset = implode(',', $sources);
|
||||
|
||||
$sizeOutput = '';
|
||||
if (! is_null($this->sizes))
|
||||
{
|
||||
$sizeOutput = $this->dataTags['sizes'] . 'sizes="' . $this->sizes . '"';
|
||||
}
|
||||
|
||||
$output = '<source ' . $prefix . 'srcset="' . $srcset . '" ' . $sizeOutput . ' type="image/' . $fileFormat . '">';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
protected function buildImage()
|
||||
{
|
||||
$src = $this->src;
|
||||
$output = '<img src="' . $src . '" ';
|
||||
|
||||
// Get this from set attributes on class.
|
||||
$attrs = array('id', 'height', 'width', 'srcset', 'sizes', 'class');
|
||||
foreach($attrs as $attr)
|
||||
{
|
||||
if (! is_null($this->{$attr}))
|
||||
{
|
||||
$output .= $attr . '="' . $this->{$attr} . '" ';
|
||||
}
|
||||
}
|
||||
|
||||
// Always output alt tag, because it's important to screen readers and otherwise.
|
||||
$output .= 'alt="' . $this->alt . '" ';
|
||||
|
||||
// Left over attributes that should be harmless, ie extra image data or other custom tags.
|
||||
$leftAttrs = $this->getImageAttributes();
|
||||
foreach($leftAttrs as $name => $value)
|
||||
{
|
||||
$output .= $name . '="' . $value . '" ';
|
||||
}
|
||||
|
||||
$output .= ' > '; // ending image.
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
|
||||
protected function getImageAttributes()
|
||||
{
|
||||
|
||||
$dontuse = array(
|
||||
'src', 'data-src', 'data-lazy-src', 'srcset', 'sizes'
|
||||
|
||||
);
|
||||
$dontuse = array_merge($dontuse, array('id', 'alt', 'height', 'width', 'srcset', 'sizes', 'class'));
|
||||
|
||||
$attributes = $this->attributes;
|
||||
|
||||
$leftAttrs = array();
|
||||
foreach($attributes as $name => $value)
|
||||
{
|
||||
if (! in_array($name, $dontuse ))
|
||||
{
|
||||
$leftAttrs[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $leftAttrs;
|
||||
}
|
||||
|
||||
protected function getLazyData($type)
|
||||
{
|
||||
$attributes = $this->attributes;
|
||||
$value = $prefix = false;
|
||||
|
||||
if (isset($attributes['data-lazy-' . $type]) && strlen($attributes['data-lazy-' . $type]) > 0)
|
||||
{
|
||||
$value = $attributes['data-lazy-' . $type];
|
||||
$prefix = 'data-lazy-';
|
||||
}
|
||||
elseif( isset($attributes['data-' . $type]) && strlen($attributes['data-' . $type]) > 0)
|
||||
{
|
||||
$value = $attributes['data-' . $type];
|
||||
$prefix = 'data-';
|
||||
}
|
||||
elseif(isset($attributes[$type]) && strlen($attributes[$type]) > 0)
|
||||
{
|
||||
$value = $attributes[$type];
|
||||
$prefix = '';
|
||||
}
|
||||
|
||||
$this->dataTags[$type] = $prefix;
|
||||
|
||||
return $value;
|
||||
}
|
||||
} // class FrontImage
|
@ -0,0 +1,684 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\Image;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
use ShortPixel\Controller\OptimizeController as OptimizeController;
|
||||
use ShortPixel\Helper\UtilHelper as UtilHelper;
|
||||
|
||||
use ShortPixel\Controller\ApiController as API;
|
||||
|
||||
|
||||
// @todo Custom Model for adding files, instead of meta DAO.
|
||||
class CustomImageModel extends \ShortPixel\Model\Image\ImageModel
|
||||
{
|
||||
|
||||
protected $folder_id;
|
||||
protected $path_md5;
|
||||
|
||||
protected $type = 'custom';
|
||||
|
||||
protected $thumbnails = array(); // placeholder, should return empty.
|
||||
protected $retinas = array(); // placeholder, should return empty.
|
||||
|
||||
protected $in_db = false;
|
||||
protected $is_stub = false;
|
||||
|
||||
protected $is_main_file = true;
|
||||
|
||||
/** @var array */
|
||||
protected $forceSettings = array(); // option derives from setting or otherwise, request to be forced upon via UI to use specific value.
|
||||
|
||||
|
||||
// @param int $id
|
||||
public function __construct($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
|
||||
if ($id > 0)
|
||||
{
|
||||
$bool = $this->loadMeta();
|
||||
/*if ($bool)
|
||||
{
|
||||
$this->setWebp();
|
||||
$this->setAvif();
|
||||
} */
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->fullpath = ''; // stub
|
||||
$this->image_meta = new ImageMeta();
|
||||
$this->is_stub = true;
|
||||
}
|
||||
parent::__construct($this->fullpath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $folder_id;
|
||||
*/
|
||||
public function setFolderId($folder_id)
|
||||
{
|
||||
$this->folder_id = $folder_id;
|
||||
}
|
||||
|
||||
|
||||
public function getOptimizeUrls()
|
||||
{
|
||||
|
||||
$data = $this->getOptimizeData();
|
||||
return array_values($data['urls']);
|
||||
|
||||
}
|
||||
|
||||
protected function getExcludePatterns()
|
||||
{
|
||||
$args = array(
|
||||
'filter' => true,
|
||||
'is_custom' => true,
|
||||
);
|
||||
|
||||
$patterns = UtilHelper::getExclusions($args);
|
||||
return $patterns;
|
||||
}
|
||||
|
||||
public function getOptimizeData()
|
||||
{
|
||||
$parameters = array(
|
||||
'urls' => array(),
|
||||
'params' => array(),
|
||||
'returnParams' => array(),
|
||||
);
|
||||
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
if ($this->is_virtual())
|
||||
$url = $this->getFullPath();
|
||||
else
|
||||
$url = $this->getURL();
|
||||
|
||||
$settings = \wpSPIO()->settings();
|
||||
$isSmartCrop = ($settings->useSmartcrop == true && $this->getExtension() !== 'pdf') ? true : false;
|
||||
$paramListArgs = array(); // args for the params, yes.
|
||||
|
||||
if (isset($this->forceSettings['smartcrop']) && $this->getExtension() !== 'pdf')
|
||||
{
|
||||
$isSmartCrop = ($this->forceSettings['smartcrop'] == ImageModel::ACTION_SMARTCROP) ? true : false;
|
||||
}
|
||||
$paramListArgs['smartcrop'] = $isSmartCrop;
|
||||
$paramListArgs['main_url'] = $url;
|
||||
$paramListArgs['url'] = $url;
|
||||
|
||||
if ($this->isProcessable(true) || $this->isProcessableAnyFileType())
|
||||
{
|
||||
$parameters['urls'][0] = $url;
|
||||
$parameters['paths'][0] = $this->getFullPath();
|
||||
$parameters['params'][0] = $this->createParamList($paramListArgs);
|
||||
$parameters['returnParams']['sizes'][0] = $this->getFileName();
|
||||
|
||||
if ($isSmartCrop )
|
||||
{
|
||||
$parameters['returnParams']['fileSizes'][0] = $this->getFileSize();
|
||||
}
|
||||
}
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
public function doSetting($setting, $value)
|
||||
{
|
||||
$this->forceSettings[$setting] = $value;
|
||||
}
|
||||
|
||||
public function getURL()
|
||||
{
|
||||
return \wpSPIO()->filesystem()->pathToUrl($this);
|
||||
}
|
||||
|
||||
public function count($type)
|
||||
{
|
||||
// everything is 1 on 1 in the customModel
|
||||
switch($type)
|
||||
{
|
||||
case 'thumbnails':
|
||||
return 0;
|
||||
break;
|
||||
case 'webps':
|
||||
$count = count($this->getWebps());
|
||||
break;
|
||||
case 'avifs':
|
||||
$count = count($this->getAvifs());
|
||||
break;
|
||||
case 'retinas':
|
||||
$count = count($this->getRetinas());
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return $count; // 0 or 1
|
||||
}
|
||||
|
||||
/* Check if an image in theory could be processed. Check only exclusions, don't check status etc */
|
||||
public function isProcessable($strict = false)
|
||||
{
|
||||
$bool = parent::isProcessable();
|
||||
|
||||
if($strict)
|
||||
{
|
||||
return $bool;
|
||||
}
|
||||
|
||||
// The exclude size on the image - via regex - if fails, prevents the whole thing from optimization.
|
||||
if ($this->processable_status == ImageModel::P_EXCLUDE_SIZE || $this->processable_status == ImageModel::P_EXCLUDE_PATH)
|
||||
{
|
||||
return $bool;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* if ($bool === false && $strict === false)
|
||||
{
|
||||
// Todo check if Webp / Acif is active, check for unoptimized items
|
||||
if ($this->isProcessableFileType('webp'))
|
||||
{
|
||||
$bool = true;
|
||||
}
|
||||
if ($this->isProcessableFileType('avif'))
|
||||
{
|
||||
$bool = true;
|
||||
}
|
||||
|
||||
} */
|
||||
|
||||
// From above to below was implemented because it could not detect file not writable / directory not writable issues if there was any option to generate webp in the settings. Should check for all those file issues first.
|
||||
|
||||
// First test if this file isn't unprocessable for any other reason, then check.
|
||||
if (($this->isProcessable(true) || $this->isOptimized() ) && $this->isProcessableAnyFileType() === true)
|
||||
{
|
||||
if (false === $this->is_directory_writable())
|
||||
{
|
||||
$bool = false;
|
||||
}
|
||||
else {
|
||||
$bool = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $bool;
|
||||
}
|
||||
|
||||
public function isRestorable()
|
||||
{
|
||||
|
||||
$bool = parent::isRestorable();
|
||||
|
||||
// If fine, all fine.
|
||||
if ($bool == true)
|
||||
{
|
||||
return $bool;
|
||||
}
|
||||
|
||||
// If not, check this..
|
||||
if ($this->hasBackup() && $this->getMeta('status') == self::FILE_STATUS_PREVENT)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $bool;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getWebps()
|
||||
{
|
||||
$webp = array($this->getWebp());
|
||||
return array_filter($webp);
|
||||
}
|
||||
|
||||
protected function getAvifs()
|
||||
{
|
||||
$avif = array($this->getAvif());
|
||||
return array_filter($avif);
|
||||
}
|
||||
|
||||
/** Get FileTypes that might be optimized. Checking for setting should go via isProcessableFileType! */
|
||||
public function getOptimizeFileType($type = 'webp')
|
||||
{
|
||||
// Pdf files can't have special images.
|
||||
if ($this->getExtension() == 'pdf')
|
||||
return array();
|
||||
|
||||
if ($type == 'webp')
|
||||
{
|
||||
$types = $this->getWebps();
|
||||
}
|
||||
elseif ($type == 'avif')
|
||||
{
|
||||
$types = $this->getAvifs();
|
||||
}
|
||||
|
||||
$toOptimize = array();
|
||||
$fs = \WPSPIO()->filesystem();
|
||||
|
||||
// The file must not exist yet.
|
||||
if (count($types) == 0 && ($this->isProcessable(true) || $this->isOptimized()) )
|
||||
return array($fs->pathToUrl($this));
|
||||
else
|
||||
return array();
|
||||
|
||||
}
|
||||
|
||||
public function restore($args = array())
|
||||
{
|
||||
do_action('shortpixel_before_restore_image', $this->get('id'));
|
||||
do_action('shortpixel/image/before_restore', $this);
|
||||
|
||||
$defaults = array(
|
||||
'keep_in_queue' => false, // used for bulk restore.
|
||||
);
|
||||
|
||||
$args = wp_parse_args($args, $defaults);
|
||||
|
||||
$bool = parent::restore();
|
||||
|
||||
$return = true;
|
||||
if ($bool)
|
||||
{
|
||||
$this->setMeta('status', ImageModel::FILE_STATUS_UNPROCESSED);
|
||||
$this->setMeta('compressedSize', 0);
|
||||
$this->setMeta('compressionType', null);
|
||||
|
||||
|
||||
$this->saveMeta();
|
||||
|
||||
$webps = $this->getWebps();
|
||||
foreach($webps as $webpFile)
|
||||
$webpFile->delete();
|
||||
|
||||
$avifs = $this->getAvifs();
|
||||
foreach($avifs as $avifFile)
|
||||
$avifFile->delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
$return = false;
|
||||
}
|
||||
|
||||
if ($args['keep_in_queue'] === false)
|
||||
{
|
||||
$this->dropFromQueue();
|
||||
}
|
||||
do_action('shortpixel/image/after_restore', $this, $this->id, $bool);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function handleOptimized($optimizeData, $args = array())
|
||||
{
|
||||
$bool = true;
|
||||
|
||||
if (isset($optimizeData['files']) && isset($optimizeData['data']))
|
||||
{
|
||||
$files = $optimizeData['files'];
|
||||
$data = $optimizeData['data'];
|
||||
}
|
||||
else {
|
||||
Log::addError('Something went wrong with handleOptimized', $optimizeData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (! $this->isOptimized() ) // main file might not be contained in results
|
||||
{
|
||||
$bool = parent::handleOptimized($files[0]);
|
||||
}
|
||||
|
||||
$this->handleOptimizedFileType($files[0]);
|
||||
|
||||
if ($bool)
|
||||
{
|
||||
$this->setMeta('customImprovement', parent::getImprovement());
|
||||
$this->saveMeta();
|
||||
}
|
||||
|
||||
// $this->deleteTempFiles($files);
|
||||
|
||||
return $bool;
|
||||
}
|
||||
|
||||
public function loadMeta()
|
||||
{
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$sql = 'SELECT * FROM ' . $wpdb->prefix . 'shortpixel_meta where id = %d';
|
||||
$sql = $wpdb->prepare($sql, $this->id);
|
||||
|
||||
$imagerow = $wpdb->get_row($sql);
|
||||
|
||||
$metaObj = new ImageMeta();
|
||||
$this->image_meta = $metaObj; // even if not found, load an empty imageMeta.
|
||||
|
||||
if (! is_object($imagerow))
|
||||
return false;
|
||||
|
||||
$this->in_db = true; // record found.
|
||||
|
||||
$this->fullpath = $imagerow->path;
|
||||
$this->folder_id = $imagerow->folder_id;
|
||||
$this->path_md5 = $imagerow->path_md5;
|
||||
|
||||
$status = intval($imagerow->status);
|
||||
$metaObj->status = $status;
|
||||
|
||||
if ($status == ImageModel::FILE_STATUS_SUCCESS)
|
||||
{
|
||||
$metaObj->customImprovement = $imagerow->message;
|
||||
}
|
||||
|
||||
|
||||
$metaObj->compressedSize = intval($imagerow->compressed_size);
|
||||
// The null check is important, otherwise it will always optimize wrongly.
|
||||
$metaObj->compressionType = (is_null($imagerow->compression_type)) ? null : intval($imagerow->compression_type);
|
||||
|
||||
if (! is_numeric($imagerow->message) && ! is_null($imagerow->message))
|
||||
$metaObj->errorMessage = $imagerow->message;
|
||||
|
||||
$metaObj->did_keepExif = (intval($imagerow->keep_exif) == 1) ? true : false;
|
||||
|
||||
$metaObj->did_cmyk2rgb = (intval($imagerow->cmyk2rgb) == 1) ? true : false;
|
||||
|
||||
$metaObj->resize = (intval($imagerow->resize) > 1) ? true : false;
|
||||
|
||||
if (intval($imagerow->resize_width) > 0)
|
||||
$metaObj->resizeWidth = intval($imagerow->resize_width);
|
||||
|
||||
if (intval($imagerow->resize_height) > 0)
|
||||
$metaObj->resizeHeight = intval($imagerow->resize_height);
|
||||
|
||||
//$metaObj->has_backup = (intval($imagerow->backup) == 1) ? true : false;
|
||||
|
||||
$addedDate = UtilHelper::DBtoTimestamp($imagerow->ts_added);
|
||||
$metaObj->tsAdded = $addedDate;
|
||||
|
||||
$optimizedDate = UtilHelper::DBtoTimestamp($imagerow->ts_optimized);
|
||||
$metaObj->tsOptimized = $optimizedDate;
|
||||
|
||||
$extraInfo = property_exists($imagerow, 'extra_info') ? $imagerow->extra_info : null;
|
||||
|
||||
if (! is_null($extraInfo))
|
||||
{
|
||||
$data = json_decode($extraInfo, true);
|
||||
|
||||
if (isset($data['webpStatus']))
|
||||
{
|
||||
$this->setMeta('webp', $data['webpStatus']);
|
||||
}
|
||||
if (isset($data['avifStatus']))
|
||||
{
|
||||
$this->setMeta('avif', $data['avifStatus']);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
$this->image_meta = $metaObj;
|
||||
}
|
||||
|
||||
public function getParent()
|
||||
{
|
||||
return false; // no parents here
|
||||
}
|
||||
|
||||
/** Load a CustomImageModel as Stub ( to be added ) . Checks if the image is already added as well
|
||||
*
|
||||
* @param String $path
|
||||
* @param Boolean $load
|
||||
*/
|
||||
public function setStub($path, $load = true)
|
||||
{
|
||||
$this->fullpath = $path;
|
||||
$this->path_md5 = md5($this->fullpath);
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$sql = 'SELECT id from ' . $wpdb->prefix . 'shortpixel_meta where path = %s';
|
||||
$sql = $wpdb->prepare($sql, $path);
|
||||
|
||||
$result = $wpdb->get_var($sql);
|
||||
if ( ! is_null($result) )
|
||||
{
|
||||
$this->in_db = true;
|
||||
$this->id = $result;
|
||||
if ($load)
|
||||
$this->loadMeta();
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->image_meta = new ImageMeta();
|
||||
$this->image_meta->compressedSize = 0;
|
||||
$this->image_meta->tsOptimized = 0;
|
||||
$this->image_meta->tsAdded = time();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function preventNextTry($reason = '', $status = self::FILE_STATUS_PREVENT)
|
||||
{
|
||||
$this->setMeta('errorMessage', $reason);
|
||||
$this->setMeta('status', $status);
|
||||
$this->saveMeta();
|
||||
}
|
||||
|
||||
public function markCompleted($reason, $status)
|
||||
{
|
||||
return $this->preventNextTry($reason, $status);
|
||||
}
|
||||
|
||||
public function isOptimizePrevented()
|
||||
{
|
||||
$status = $this->getMeta('status');
|
||||
|
||||
if ($status == self::FILE_STATUS_PREVENT || $status == self::FILE_STATUS_MARKED_DONE )
|
||||
{
|
||||
$this->processable_status = self::P_OPTIMIZE_PREVENTED;
|
||||
$this->optimizePreventedReason = $this->getMeta('errorMessage');
|
||||
|
||||
return $this->getMeta('errorMessage');
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only one item for now, so it's equal
|
||||
public function isSomethingOptimized()
|
||||
{
|
||||
return $this->isOptimized();
|
||||
}
|
||||
|
||||
public function resetPrevent()
|
||||
{
|
||||
|
||||
if ($this->hasBackup())
|
||||
$this->setMeta('status', self::FILE_STATUS_SUCCESS);
|
||||
else
|
||||
$this->setMeta('status', self::FILE_STATUS_UNPROCESSED);
|
||||
|
||||
$this->setMeta('errorMessage', '');
|
||||
$this->saveMeta();
|
||||
}
|
||||
|
||||
public function saveMeta()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'shortpixel_meta';
|
||||
$where = array('id' => $this->id);
|
||||
|
||||
$metaObj = $this->image_meta;
|
||||
|
||||
if (! is_null($metaObj->customImprovement) && is_numeric($metaObj->customImprovement))
|
||||
$message = $metaObj->customImprovement;
|
||||
elseif (! is_null($metaObj->errorMessage))
|
||||
$message = $metaObj->errorMessage;
|
||||
else
|
||||
$message = null;
|
||||
|
||||
$optimized = new \DateTime();
|
||||
$optimized->setTimestamp($metaObj->tsOptimized);
|
||||
|
||||
$added = new \DateTime();
|
||||
$added->setTimeStamp($metaObj->tsAdded);
|
||||
|
||||
$extra_info = array();
|
||||
if ($this->getMeta('webp') === self::FILETYPE_BIGGER)
|
||||
{
|
||||
$extra_info['webpStatus'] = self::FILETYPE_BIGGER;
|
||||
}
|
||||
if ($this->getMeta('avif') === self::FILETYPE_BIGGER)
|
||||
{
|
||||
$extra_info['avifStatus'] = self::FILETYPE_BIGGER;
|
||||
}
|
||||
|
||||
if (count($extra_info) > 0)
|
||||
{
|
||||
$extra_info = json_encode($extra_info);
|
||||
}
|
||||
else {
|
||||
$extra_info = null;
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'folder_id' => $this->folder_id,
|
||||
'compressed_size' => $metaObj->compressedSize,
|
||||
'compression_type' => $metaObj->compressionType,
|
||||
'keep_exif' => ($metaObj->did_keepExif) ? 1 : 0,
|
||||
'cmyk2rgb' => ($metaObj->did_cmyk2rgb) ? 1 : 0,
|
||||
'resize' => ($metaObj->resize) ? 1 : 0,
|
||||
'resize_width' => $metaObj->resizeWidth,
|
||||
'resize_height' => $metaObj->resizeHeight,
|
||||
'backup' => ($this->hasBackup()) ? 1 : 0,
|
||||
'status' => $metaObj->status,
|
||||
'retries' => 0, // this is unused / legacy
|
||||
'message' => $message, // this is used for improvement line.
|
||||
'ts_added' => UtilHelper::timestampToDB($metaObj->tsAdded),
|
||||
'ts_optimized' => UtilHelper::timestampToDB($metaObj->tsOptimized),
|
||||
'path' => $this->getFullPath(),
|
||||
'name' => $this->getFileName(),
|
||||
'path_md5' => md5($this->getFullPath()), // this is legacy
|
||||
'extra_info' => $extra_info,
|
||||
);
|
||||
// The keys are just for readability.
|
||||
$format = array(
|
||||
'folder_id' => '%d',
|
||||
'compressed_size' => '%d',
|
||||
'compression_type' => '%d' ,
|
||||
'keep_exif' => '%d' ,
|
||||
'cmyk2rgb' => '%d' ,
|
||||
'resize' => '%d' ,
|
||||
'resize_width' => '%d',
|
||||
'resize_height' => '%d',
|
||||
'backup' => '%d',
|
||||
'status' => '%d',
|
||||
'retries' => '%d', // this is unused / legacy
|
||||
'message' => '%s', // this is used for improvement line.
|
||||
'ts_added' => '%s',
|
||||
'ts_optimized' => '%s' ,
|
||||
'path' => '%s',
|
||||
'name' => '%s',
|
||||
'path_md5' => '%s' , // this is legacy
|
||||
'extra_info' => '%s',
|
||||
);
|
||||
|
||||
|
||||
$is_new = false;
|
||||
|
||||
if ($this->in_db)
|
||||
{
|
||||
$res = $wpdb->update($table, $data, $where, $format); // result is amount rows updated.
|
||||
}
|
||||
else
|
||||
{
|
||||
$is_new = true;
|
||||
$res = $wpdb->insert($table, $data, $format); // result is new inserted id
|
||||
}
|
||||
|
||||
if ($is_new)
|
||||
{
|
||||
$this->id = $wpdb->insert_id;
|
||||
}
|
||||
|
||||
if ($res !== false)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public function deleteMeta()
|
||||
{
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . 'shortpixel_meta';
|
||||
$where = array('id' => $this->id);
|
||||
|
||||
$result = $wpdb->delete($table, $where, array('%d'));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function onDelete()
|
||||
{
|
||||
parent::onDelete();
|
||||
$this->deleteMeta();
|
||||
$this->dropfromQueue();
|
||||
}
|
||||
|
||||
public function dropFromQueue()
|
||||
{
|
||||
$optimizeController = new OptimizeController();
|
||||
|
||||
$q = $optimizeController->getQueue($this->type);
|
||||
$q->dropItem($this->get('id'));
|
||||
|
||||
// Drop also from bulk if there.
|
||||
|
||||
$optimizeController->setBulk(true);
|
||||
|
||||
$q = $optimizeController->getQueue($this->type);
|
||||
$q->dropItem($this->get('id'));
|
||||
}
|
||||
|
||||
public function getImprovement($int = false)
|
||||
{
|
||||
return $this->getMeta('customImprovement');
|
||||
}
|
||||
|
||||
public function getImprovements()
|
||||
{
|
||||
$improvements = array();
|
||||
/*$totalsize = $totalperc = $count = 0;
|
||||
if ($this->isOptimized())
|
||||
{
|
||||
$perc = $this->getImprovement();
|
||||
$size = $this->getImprovement(true);
|
||||
$totalsize += $size;
|
||||
$totalperc += $perc;
|
||||
$improvements['main'] = array($perc, $size);
|
||||
$count++;
|
||||
} */
|
||||
$improvement = $this->getImprovement();
|
||||
if (is_null($improvement)) // getImprovement can return null.
|
||||
{
|
||||
$improvement = 0;
|
||||
}
|
||||
$improvements['main'] = array($improvement, 0);
|
||||
$improvements['totalpercentage'] = round($improvement); // the same.
|
||||
|
||||
return $improvements;
|
||||
|
||||
// return $improvements; // we have no thumbnails.
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\Image;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
|
||||
class ImageConvertMeta
|
||||
{
|
||||
|
||||
protected $fileFormat; // png / heic etc
|
||||
protected $isConverted = false;
|
||||
protected $placeholder = false;
|
||||
protected $replacementImageBase = false;
|
||||
// protected $doConversion = false;
|
||||
protected $triedConversion = false;
|
||||
protected $errorReason = false;
|
||||
protected $omitBackup = true; // Don't backup the converted image (again), keeping only the original format. if not, make a backup of the converted file and treat that as the default backup/restore
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function isConverted()
|
||||
{
|
||||
return $this->isConverted;
|
||||
}
|
||||
|
||||
public function didTry()
|
||||
{
|
||||
return $this->triedConversion;
|
||||
}
|
||||
|
||||
public function setTried($value)
|
||||
{
|
||||
$this->triedConversion = $value;
|
||||
}
|
||||
|
||||
public function setConversionDone($omitBackup = true)
|
||||
{
|
||||
$this->isConverted = true;
|
||||
$this->omitBackup = $omitBackup;
|
||||
}
|
||||
|
||||
public function setError($code)
|
||||
{
|
||||
$this->errorReason = $code;
|
||||
}
|
||||
|
||||
public function getError()
|
||||
{
|
||||
return $this->errorReason;
|
||||
}
|
||||
|
||||
public function setFileFormat($ext)
|
||||
{
|
||||
if (is_null($this->fileFormat))
|
||||
$this->fileFormat = $ext;
|
||||
}
|
||||
|
||||
public function getFileFormat()
|
||||
{
|
||||
return $this->fileFormat;
|
||||
}
|
||||
|
||||
public function omitBackup()
|
||||
{
|
||||
return $this->omitBackup;
|
||||
}
|
||||
|
||||
// bool for now, otherwise if needed.
|
||||
public function setPlaceHolder($placeholder = true)
|
||||
{
|
||||
$this->placeholder = $placeholder;
|
||||
}
|
||||
|
||||
public function hasPlaceHolder()
|
||||
{
|
||||
return $this->placeholder;
|
||||
}
|
||||
|
||||
public function setReplacementImageBase($name)
|
||||
{
|
||||
$this->replacementImageBase = $name;
|
||||
|
||||
}
|
||||
|
||||
public function getReplacementImageBase()
|
||||
{
|
||||
return $this->replacementImageBase;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function fromClass($object)
|
||||
{
|
||||
foreach($object as $property => $value)
|
||||
{
|
||||
if (property_exists($this, $property))
|
||||
{
|
||||
$this->$property = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function toClass()
|
||||
{
|
||||
$class = new \stdClass;
|
||||
$vars = get_object_vars($this);
|
||||
|
||||
foreach($vars as $property => $value) // only used by media library.
|
||||
{
|
||||
$class->$property = $this->$property;
|
||||
}
|
||||
return $class;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\Image;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
// Base Class for ImageMeta
|
||||
class ImageMeta extends ImageThumbnailMeta
|
||||
{
|
||||
|
||||
public $errorMessage;
|
||||
public $wasConverted = false; // Was converted from legacy format
|
||||
|
||||
protected $convertMeta;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->convertMeta = new ImageConvertMeta();
|
||||
|
||||
}
|
||||
|
||||
public function fromClass($object)
|
||||
{
|
||||
if (property_exists($object, 'convertMeta'))
|
||||
{
|
||||
|
||||
$this->convertMeta->fromClass($object->convertMeta);
|
||||
unset($object->convertMeta);
|
||||
}
|
||||
// legacy.
|
||||
if (property_exists($object, 'tried_png2jpg') && $object->tried_png2jpg)
|
||||
{
|
||||
$this->convertMeta()->setTried($object->tried_png2jpg);
|
||||
}
|
||||
elseif (property_exists($object, 'did_png2jpg') && $object->did_png2jpg)
|
||||
{
|
||||
$this->convertMeta()->setFileFormat('png');
|
||||
$this->convertMeta()->setConversionDone();
|
||||
|
||||
}
|
||||
|
||||
parent::fromClass($object);
|
||||
}
|
||||
|
||||
|
||||
public function convertMeta()
|
||||
{
|
||||
return $this->convertMeta;
|
||||
}
|
||||
|
||||
} // class
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\Image;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
|
||||
class ImageThumbnailMeta
|
||||
{
|
||||
/** @var int */
|
||||
public $databaseID = null;
|
||||
/** @var int */
|
||||
public $status = 0;
|
||||
/** @var int */
|
||||
public $compressionType;
|
||||
/** @var int */
|
||||
public $compressedSize;
|
||||
/** @var int */
|
||||
public $originalSize;
|
||||
// public $improvement;
|
||||
|
||||
/** @var boolean */
|
||||
public $did_keepExif = false;
|
||||
|
||||
/** @var boolean */
|
||||
public $did_cmyk2rgb = false;
|
||||
|
||||
/** @var int */
|
||||
public $resize;
|
||||
/** @var int */
|
||||
public $resizeWidth;
|
||||
/** @var int */
|
||||
public $resizeHeight;
|
||||
/** @var int */
|
||||
public $resizeType;
|
||||
/** @var int */
|
||||
public $originalWidth;
|
||||
/** @var int */
|
||||
public $originalHeight;
|
||||
|
||||
public $tsAdded;
|
||||
public $tsOptimized;
|
||||
public $webp;
|
||||
public $avif;
|
||||
|
||||
public $file; // **Only for unlisted images. This defines an unlisted image */
|
||||
|
||||
// Only for customImageModel! Exception to prevent having to create a whole class. Second var here, warrants a subclass.
|
||||
public $customImprovement;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->tsAdded = time(); // default
|
||||
}
|
||||
|
||||
|
||||
/** Load data from basic class to prevent issues when class definitions changes over time */
|
||||
public function fromClass($object)
|
||||
{
|
||||
|
||||
foreach($object as $property => $value)
|
||||
{
|
||||
if ($property == 'customImprovement')
|
||||
{ continue; }
|
||||
|
||||
|
||||
if (property_exists($this, $property))
|
||||
{
|
||||
$this->$property = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Save data as basic class to prevent issues when class definitions changes over time */
|
||||
public function toClass()
|
||||
{
|
||||
$class = new \stdClass;
|
||||
$vars = get_object_vars($this);
|
||||
|
||||
foreach($vars as $property => $value) // only used by media library.
|
||||
{
|
||||
if ($property == 'customImprovement')
|
||||
{ continue; }
|
||||
|
||||
if ($property == 'convertMeta' && is_null($this->convertMeta))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
elseif ($property == 'convertMeta') {
|
||||
$class->$property = $this->$property->toClass();
|
||||
continue;
|
||||
}
|
||||
// if (is_null($value)) // don't save default / values without init.
|
||||
// continue;
|
||||
|
||||
|
||||
$class->$property = $this->$property;
|
||||
}
|
||||
|
||||
return $class;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,591 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\Image;
|
||||
|
||||
use ShortPixel\Helper\DownloadHelper as DownloadHelper;
|
||||
use ShortPixel\Helper\UtilHelper as UtilHelper;
|
||||
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
|
||||
use \ShortPixel\Model\File\FileModel as FileModel;
|
||||
|
||||
// Represent a thumbnail image / limited image in mediaLibrary.
|
||||
class MediaLibraryThumbnailModel extends \ShortPixel\Model\Image\ImageModel
|
||||
{
|
||||
|
||||
public $name;
|
||||
|
||||
/* public $width;
|
||||
public $height;
|
||||
public $mime; */
|
||||
protected $prevent_next_try = false;
|
||||
protected $is_main_file = false;
|
||||
protected $is_retina = false; // diffentiate from thumbnail / retina.
|
||||
protected $id; // this is the parent attachment id
|
||||
protected $size; // size name of image in WP, if applicable.
|
||||
protected $sizeDefinition; // size width / height / crop according to WordPress
|
||||
|
||||
public function __construct($path, $id, $size)
|
||||
{
|
||||
|
||||
parent::__construct($path);
|
||||
$this->image_meta = new ImageThumbnailMeta();
|
||||
$this->id = $id;
|
||||
$this->imageType = self::IMAGE_TYPE_THUMB;
|
||||
$this->size = $size;
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected function loadMeta()
|
||||
{
|
||||
}
|
||||
|
||||
protected function saveMeta()
|
||||
{
|
||||
}
|
||||
|
||||
public function __debugInfo() {
|
||||
return array(
|
||||
'image_meta' => $this->image_meta,
|
||||
'name' => $this->name,
|
||||
'path' => $this->getFullPath(),
|
||||
'size' => $this->size,
|
||||
'width' => $this->get('width'),
|
||||
'height' => $this->get('height'),
|
||||
'exists' => ($this->exists()) ? 'yes' : 'no',
|
||||
'is_virtual' => ($this->is_virtual()) ? 'yes' : 'no',
|
||||
'wordpress_size' => $this->sizeDefinition,
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
/** Set the meta name of thumbnail. */
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function setSizeDefinition($sizedef)
|
||||
{
|
||||
$this->sizeDefinition = $sizedef;
|
||||
}
|
||||
|
||||
public function setImageType($type)
|
||||
{
|
||||
$this->imageType = $type;
|
||||
}
|
||||
|
||||
public function getRetina()
|
||||
{
|
||||
if ($this->is_virtual())
|
||||
{
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
$filepath = apply_filters('shortpixel/file/virtual/translate', $this->getFullPath(), $this);
|
||||
$virtualFile = $fs->getFile($filepath);
|
||||
|
||||
$filebase = $virtualFile->getFileBase();
|
||||
$filepath = (string) $virtualFile->getFileDir();
|
||||
$extension = $virtualFile->getExtension();
|
||||
|
||||
// This function needs an hard check on file exists, which might not be wanted.
|
||||
if (false === \wpSPIO()->env()->useVirtualHeavyFunctions())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
$filebase = $this->getFileBase();
|
||||
$filepath = (string) $this->getFileDir();
|
||||
$extension = $this->getExtension();
|
||||
}
|
||||
|
||||
$retina = new MediaLibraryThumbnailModel($filepath . $filebase . '@2x.' . $extension, $this->id, $this->size); // mind the dot in after 2x
|
||||
$retina->setName($this->size);
|
||||
$retina->setImageType(self::IMAGE_TYPE_RETINA);
|
||||
|
||||
$retina->is_retina = true;
|
||||
|
||||
$forceCheck = true;
|
||||
if ($retina->exists($forceCheck))
|
||||
return $retina;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isFileTypeNeeded($type = 'webp')
|
||||
{
|
||||
// pdf extension can be optimized, but don't come with these filetypes
|
||||
if ($this->getExtension() == 'pdf')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($type == 'webp')
|
||||
$file = $this->getWebp();
|
||||
elseif ($type == 'avif')
|
||||
$file = $this->getAvif();
|
||||
|
||||
if ( ($this->isThumbnailProcessable() || $this->isOptimized()) && $file === false) // if no file, it can be optimized.
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
// @param FileDelete can be false. I.e. multilang duplicates might need removal of metadata, but not images.
|
||||
public function onDelete($fileDelete = true)
|
||||
{
|
||||
if ($fileDelete == true)
|
||||
$bool = parent::onDelete();
|
||||
else {
|
||||
$bool = true;
|
||||
}
|
||||
|
||||
// minimally reset all the metadata.
|
||||
if ($this->is_main_file)
|
||||
{
|
||||
$this->image_meta = new ImageMeta();
|
||||
}
|
||||
else {
|
||||
$this->image_meta = new ImageThumbnailMeta();
|
||||
}
|
||||
|
||||
return $bool;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected function setMetaObj($metaObj)
|
||||
{
|
||||
$this->image_meta = clone $metaObj;
|
||||
}
|
||||
|
||||
protected function getMetaObj()
|
||||
{
|
||||
return $this->image_meta;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// get_path param see MediaLibraryModel
|
||||
// This should be unused at the moment!
|
||||
public function getOptimizeUrls()
|
||||
{
|
||||
if (! $this->isProcessable() )
|
||||
return false;
|
||||
|
||||
$url = $this->getURL();
|
||||
|
||||
if (! $url)
|
||||
{
|
||||
return false; //nothing
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function getURL()
|
||||
{
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
if ($this->size == 'original' && ! $this->get('is_retina'))
|
||||
{
|
||||
$url = wp_get_original_image_url($this->id);
|
||||
}
|
||||
elseif ($this->isUnlisted())
|
||||
{
|
||||
$url = $fs->pathToUrl($this);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We can't trust higher lever function, or any WP functions. I.e. Woocommerce messes with the URL's if they like so.
|
||||
// So get it from intermediate and if that doesn't work, default to pathToUrl - better than nothing.
|
||||
// https://app.asana.com/0/1200110778640816/1202589533659780
|
||||
$size_array = image_get_intermediate_size($this->id, $this->size);
|
||||
|
||||
if ($size_array === false || ! isset($size_array['url']))
|
||||
{
|
||||
$url = $fs->pathToUrl($this);
|
||||
}
|
||||
elseif (isset($size_array['url']))
|
||||
{
|
||||
$url = $size_array['url'];
|
||||
// Even this can go wrong :/
|
||||
if (strpos($url, $this->getFileName() ) === false)
|
||||
{
|
||||
// Taken from image_get_intermediate_size if somebody still messes with the filters.
|
||||
$mainurl = wp_get_attachment_url( $this->id);
|
||||
$url = path_join( dirname( $mainurl ), $this->getFileName() );
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $this->fs()->checkURL($url);
|
||||
}
|
||||
|
||||
// Just a placeholder for abstract, shouldn't do anything.
|
||||
public function getImprovements()
|
||||
{
|
||||
return parent::getImprovements();
|
||||
}
|
||||
|
||||
|
||||
public function getBackupFileName()
|
||||
{
|
||||
$mainFile = ($this->is_main_file) ? $this : $this->getMainFile();
|
||||
if (false == $mainFile)
|
||||
{
|
||||
return parent::getBackupFileName();
|
||||
}
|
||||
|
||||
if ($mainFile->getMeta()->convertMeta()->getReplacementImageBase() !== false)
|
||||
{
|
||||
if ($this->is_main_file)
|
||||
return $mainFile->getMeta()->convertMeta()->getReplacementImageBase() . '.' . $this->getExtension();
|
||||
else {
|
||||
// $fileBaseNoSize =
|
||||
$name = str_replace($mainFile->getFileBase(), $mainFile->getMeta()->convertMeta()->getReplacementImageBase(), $this->getFileName());
|
||||
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::getBackupFileName();
|
||||
}
|
||||
|
||||
|
||||
protected function preventNextTry($reason = '')
|
||||
{
|
||||
$this->prevent_next_try = $reason;
|
||||
}
|
||||
|
||||
// Don't ask thumbnails this, only the main image
|
||||
public function isOptimizePrevented()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't ask thumbnails this, only the main image
|
||||
public function resetPrevent()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function isThumbnailProcessable()
|
||||
{
|
||||
// if thumbnail processing is off, thumbs are never processable.
|
||||
// This is also used by main file, so check for that!
|
||||
|
||||
if ( $this->excludeThumbnails() && $this->is_main_file === false && $this->get('imageType') !== self::IMAGE_TYPE_ORIGINAL)
|
||||
{
|
||||
$this->processable_status = self::P_EXCLUDE_SIZE;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$bool = parent::isProcessable();
|
||||
|
||||
return $bool;
|
||||
}
|
||||
}
|
||||
|
||||
/** Function to check if said thumbnail is a WP-native or something SPIO added as unlisted
|
||||
*
|
||||
*
|
||||
*/
|
||||
protected function isUnlisted()
|
||||
{
|
||||
if (! is_null($this->getMeta('file')))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// !Important . This doubles as checking excluded image sizes.
|
||||
protected function isSizeExcluded()
|
||||
{
|
||||
|
||||
$excludeSizes = \wpSPIO()->settings()->excludeSizes;
|
||||
|
||||
if (is_array($excludeSizes) && in_array($this->name, $excludeSizes))
|
||||
{
|
||||
$this->processable_status = self::P_EXCLUDE_SIZE;
|
||||
return true;
|
||||
}
|
||||
|
||||
$bool = parent::isSizeExcluded();
|
||||
|
||||
return $bool;
|
||||
}
|
||||
|
||||
public function isProcessableFileType($type = 'webp')
|
||||
{
|
||||
// Prevent webp / avif processing for thumbnails if this is off. Exclude main file
|
||||
if ($this->excludeThumbnails() === true && $this->is_main_file === false )
|
||||
return false;
|
||||
|
||||
return parent::isProcessableFileType($type);
|
||||
}
|
||||
|
||||
protected function getExcludePatterns()
|
||||
{
|
||||
$args = array(
|
||||
'filter' => true,
|
||||
'thumbname' => $this->name,
|
||||
'is_thumbnail' => (true === $this->is_main_file) ? false : true,
|
||||
);
|
||||
|
||||
// @todo Find a way to cache IsProcessable perhaps due to amount of checks being done. Should be release in flushOptimizeCache or elsewhere (?)
|
||||
|
||||
// $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
|
||||
|
||||
$patterns = UtilHelper::getExclusions($args);
|
||||
// echo "<PRE>"; print_r($args); print_r($patterns); echo "</PRE>";
|
||||
return $patterns;
|
||||
}
|
||||
|
||||
protected function excludeThumbnails()
|
||||
{
|
||||
return (! \wpSPIO()->settings()->processThumbnails);
|
||||
}
|
||||
|
||||
public function hasBackup($args = array())
|
||||
{
|
||||
$defaults = array(
|
||||
'forceConverted' => false,
|
||||
'noConversionCheck' => false, // do not check on mainfile, this loops when used in loadMeta / legacyConversion
|
||||
);
|
||||
$args = wp_parse_args($args, $defaults);
|
||||
|
||||
// @todo This can probably go.
|
||||
if (true === $args['noConversionCheck'])
|
||||
{
|
||||
return parent::hasBackup();
|
||||
}
|
||||
|
||||
$mainFile = ($this->is_main_file) ? $this : $this->getMainFile();
|
||||
if (false == $mainFile)
|
||||
{
|
||||
return parent::hasBackup();
|
||||
|
||||
}
|
||||
|
||||
// When main file and converted and omitBackup is true ( only original backup ) and not forced.
|
||||
$loadRegular= (false === $mainFile->getMeta()->convertMeta()->isConverted() ||
|
||||
false === $mainFile->getMeta()->convertMeta()->omitBackup()) && false === $args['forceConverted'];
|
||||
|
||||
if (true === $loadRegular)
|
||||
{
|
||||
return parent::hasBackup();
|
||||
}
|
||||
else
|
||||
{
|
||||
$directory = $this->getBackupDirectory();
|
||||
$converted_ext = $mainFile->getMeta()->convertMeta()->getFileFormat();
|
||||
|
||||
if (! $directory)
|
||||
return false;
|
||||
|
||||
|
||||
$backupFile = $directory . $this->getFileBase() . '.' . $converted_ext;
|
||||
|
||||
// Issue with PNG not being scaled on the main file.
|
||||
if (! file_exists($backupFile) && $mainFile->isScaled())
|
||||
{
|
||||
$backupFile = $directory . $mainFile->getOriginalFile()->getFileBase() . '.' . $converted_ext;
|
||||
}
|
||||
if (file_exists($backupFile) && ! is_dir($backupFile) )
|
||||
return true;
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function hasDBRecord()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$sql = 'SELECT id FROM ' . $wpdb->prefix . 'shortpixel_postmeta WHERE attach_id = %d AND size = %s';
|
||||
$sql = $wpdb->prepare($sql, $this->id, $this->size);
|
||||
|
||||
$id = $wpdb->get_var($sql);
|
||||
|
||||
if (is_null($id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
elseif (is_numeric($id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function restore()
|
||||
{
|
||||
if ($this->is_virtual())
|
||||
{
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
$filepath = apply_filters('shortpixel/file/virtual/translate', $this->getFullPath(), $this);
|
||||
|
||||
$this->setVirtualToReal($filepath);
|
||||
}
|
||||
|
||||
$bool = parent::restore();
|
||||
|
||||
if ($bool === true)
|
||||
{
|
||||
if ($this->is_main_file)
|
||||
{
|
||||
// If item is converted and will not be moved back to original format ( but converted ) , keep the convert metadata
|
||||
if (true === $this->getMeta()->convertMeta()->isConverted() && false === $this->getMeta()->convertMeta()->omitBackup() )
|
||||
{
|
||||
$convertMeta = clone $this->getMeta()->convertMeta();
|
||||
$imageMeta = new ImageMeta();
|
||||
$imageMeta->convertMeta()->fromClass($convertMeta);
|
||||
$bool = false; // Prevent cleanRestore from deleting the metadata.
|
||||
}
|
||||
else {
|
||||
$imageMeta = new ImageMeta();
|
||||
}
|
||||
|
||||
$this->image_meta = $imageMeta;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->image_meta = new ImageThumbNailMeta();
|
||||
}
|
||||
}
|
||||
|
||||
return $bool;
|
||||
}
|
||||
|
||||
/** Tries to retrieve an *existing* BackupFile. Returns false if not present.
|
||||
* This file might not be writable.
|
||||
* To get writable directory reference to backup, use FileSystemController
|
||||
*/
|
||||
public function getBackupFile($args = array())
|
||||
{
|
||||
|
||||
$defaults = array(
|
||||
'forceConverted' => false,
|
||||
'noConversionCheck' => false, // do not check on mainfile, this loops when used in loadMeta / legacyConversion
|
||||
);
|
||||
$args = wp_parse_args($args, $defaults);
|
||||
|
||||
if (true === $args['noConversionCheck'])
|
||||
{
|
||||
return parent::getBackupFile();
|
||||
}
|
||||
|
||||
$mainFile = ($this->is_main_file) ? $this : $this->getMainFile();
|
||||
if (false == $mainFile)
|
||||
{
|
||||
return parent::getBackupFile();
|
||||
|
||||
}
|
||||
// When main file and converted and omitBackup is true ( only original backup ) and not forced.
|
||||
$loadRegular= (false === $mainFile->getMeta()->convertMeta()->isConverted() ||
|
||||
false === $mainFile->getMeta()->convertMeta()->omitBackup()) && false === $args['forceConverted'];
|
||||
|
||||
if (true === $loadRegular )
|
||||
{
|
||||
return parent::getBackupFile();
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($this->hasBackup($args))
|
||||
{
|
||||
|
||||
$directory = $this->getBackupDirectory();
|
||||
$converted_ext = $mainFile->getMeta()->convertMeta()->getFileFormat();
|
||||
|
||||
$backupFile = $directory . $this->getFileBase() . '.' . $converted_ext;
|
||||
|
||||
/* Because WP doesn't support big PNG with scaled for some reason, it's possible it doesn't create them. Which means we end up with a scaled images without backup */
|
||||
if (! file_exists($backupFile) && $mainFile->isScaled())
|
||||
{
|
||||
$backupFile = $directory . $mainFile->getOriginalFile()->getFileBase() . '.' . $converted_ext;
|
||||
}
|
||||
|
||||
return new FileModel($backupFile);
|
||||
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected function createBackup()
|
||||
{
|
||||
if ($this->is_virtual()) // download remote file to backup.
|
||||
{
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
$filepath = apply_filters('shortpixel/file/virtual/translate', $this->getFullPath(), $this);
|
||||
|
||||
$result = false;
|
||||
if ($this->virtual_status == self::$VIRTUAL_REMOTE)
|
||||
{
|
||||
// filepath is translated. Check if this exists as a local copy, if not remotely download.
|
||||
if ($filepath !== $this->getFullPath())
|
||||
{
|
||||
$fileObj = $fs->getFile($filepath);
|
||||
$fileExists = $fileObj->exists();
|
||||
}
|
||||
else {
|
||||
$fileExists = false;
|
||||
}
|
||||
|
||||
if (false === $fileExists)
|
||||
{
|
||||
$downloadHelper = DownloadHelper::getInstance();
|
||||
$url = $this->getURL();
|
||||
$result = $downloadHelper->downloadFile($url, array('destinationPath' => $filepath));
|
||||
}
|
||||
|
||||
}
|
||||
elseif ($this->virtual_status == self::$VIRTUAL_STATELESS)
|
||||
{
|
||||
$result = $filepath;
|
||||
}
|
||||
else {
|
||||
Log::addWarning('Virtual Status not set. Trying to blindly download vv DownloadHelper');
|
||||
$downloadHelper = DownloadHelper::getInstance();
|
||||
$url = $this->getURL();
|
||||
$result = $downloadHelper->downloadFile($url, array('destinationPath' => $filepath));
|
||||
}
|
||||
|
||||
if ($result == false)
|
||||
{
|
||||
$this->preventNextTry(__('Fatal Issue: Remote virtual file could not be downloaded for backup', 'shortpixel-image-optimiser'));
|
||||
Log::addError('Remote file download failed from : ' . $url . ' to: ' . $filepath, $this->getURL());
|
||||
$this->error_message = __('Remote file could not be downloaded' . $this->getFullPath(), 'shortpixel-image-optimiser');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->setVirtualToReal($filepath);
|
||||
}
|
||||
|
||||
return parent::createBackup();
|
||||
}
|
||||
|
||||
// @todo This is a breach of pattern to realize checking for changes to the main image path on conversion / duplicates.
|
||||
private function getMainFile()
|
||||
{
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
return $fs->getMediaImage($this->id, true, true);
|
||||
}
|
||||
|
||||
} // class
|
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\Controller\ResponseController as ResponseController;
|
||||
|
||||
|
||||
class ResponseModel
|
||||
{
|
||||
|
||||
// Identification for Item.
|
||||
public $item_id;
|
||||
public $item_type; // set by queue
|
||||
|
||||
// General item variables
|
||||
public $fileName;
|
||||
public $is_error;
|
||||
public $is_done;
|
||||
|
||||
public $apiStatus;
|
||||
public $fileStatus;
|
||||
|
||||
// Images being processed variables. From APIController
|
||||
public $tries;
|
||||
public $images_done;
|
||||
public $images_waiting;
|
||||
public $images_total;
|
||||
|
||||
public $issue_type; // Optional - if there is any issue to report.
|
||||
public $message; // This can be base text, but decision textually is within responsecontroller.
|
||||
public $action; // Custom Operations use this ( i.e. migrate )
|
||||
|
||||
// public $queueName;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param $item_id int The attachment_id of the item in process
|
||||
* @param $item_type string item type: media or custom.
|
||||
*
|
||||
**/
|
||||
public function __construct($item_id, $item_type)
|
||||
{
|
||||
$this->item_id = $item_id;
|
||||
$this->item_type = $item_type; // media or custum
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,216 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
|
||||
class SettingsModel
|
||||
{
|
||||
protected static $instance;
|
||||
|
||||
private $option_name = 'spio_settings';
|
||||
private $state_name = 'spio_states';
|
||||
|
||||
protected $model = array(
|
||||
'apiKey' => array('s' => 'string'), // string
|
||||
'verifiedKey' => array('s' => 'int'), // string
|
||||
'compressionType' => array('s' => 'int'), // int
|
||||
'resizeWidth' => array('s' => 'int'), // int
|
||||
'resizeHeight' => array('s' => 'int'), // int
|
||||
'processThumbnails' => array('s' => 'boolean'), // checkbox
|
||||
'useSmartcrop' => array('s' => 'boolean'),
|
||||
'backupImages' => array('s' => 'boolean'), // checkbox
|
||||
'keepExif' => array('s' => 'int'), // checkbox
|
||||
'resizeImages' => array('s' => 'boolean'),
|
||||
'resizeType' => array('s' => 'string'),
|
||||
'includeNextGen' => array('s' => 'boolean'), // checkbox
|
||||
'png2jpg' => array('s' => 'int'), // checkbox
|
||||
'CMYKtoRGBconversion' => array('s' => 'boolean'), //checkbox
|
||||
'createWebp' => array('s' => 'boolean'), // checkbox
|
||||
'createAvif' => array('s' => 'boolean'), // checkbox
|
||||
'deliverWebp' => array('s' => 'int'), // checkbox
|
||||
'optimizeRetina' => array('s' => 'boolean'), // checkbox
|
||||
'optimizeUnlisted' => array('s' => 'boolean'), // $checkbox
|
||||
'optimizePdfs' => array('s' => 'boolean'), //checkbox
|
||||
'excludePatterns' => array('s' => 'exception'), // - processed, multi-layer, so skip
|
||||
'siteAuthUser' => array('s' => 'string'), // string
|
||||
'siteAuthPass' => array('s' => 'string'), // string
|
||||
'frontBootstrap' => array('s' =>'boolean'), // checkbox
|
||||
'autoMediaLibrary' => array('s' => 'boolean'), // checkbox
|
||||
'excludeSizes' => array('s' => 'array'), // Array
|
||||
'cloudflareEmail' => array('s' => 'string'), // string
|
||||
'cloudflareAuthKey' => array('s' => 'string'), // string
|
||||
'cloudflareZoneID' => array('s' => 'string'), // string
|
||||
'cloudflareToken' => array('s' => 'string'),
|
||||
'savedSpace' => array('s' => 'skip'),
|
||||
'fileCount' => array('s' => 'skip'), // int
|
||||
'under5Percent' => array('s' => 'skip'), // int
|
||||
);
|
||||
|
||||
protected $state = array(
|
||||
|
||||
);
|
||||
|
||||
protected $settings;
|
||||
protected $states;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->checkLegacy();
|
||||
$this->load();
|
||||
|
||||
}
|
||||
|
||||
public static function getInstance()
|
||||
{
|
||||
if (is_null(self::$instance))
|
||||
{
|
||||
self::$instance = new SettingsModel;
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
protected function load()
|
||||
{
|
||||
$settings = get_option($this->option_name);
|
||||
}
|
||||
|
||||
protected function save()
|
||||
{
|
||||
update_option($this->option_name, $this->settings);
|
||||
}
|
||||
|
||||
public function __get($name)
|
||||
{
|
||||
if (isset($this->settings[$name]))
|
||||
{
|
||||
return $this->sanitize($name, $this->settings[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkLegacy()
|
||||
{
|
||||
$this->deleteLegacy(); // very legacy, unused
|
||||
// $this->convertLegacy(); // legacy, move to new format.
|
||||
}
|
||||
|
||||
public function convertLegacy()
|
||||
{
|
||||
$options = array(
|
||||
//optimization options
|
||||
'apiKey' => array('key' => 'wp-short-pixel-apiKey'),
|
||||
'verifiedKey' => array('key' => 'wp-short-pixel-verifiedKey'),
|
||||
'compressionType' => array('key' => 'wp-short-pixel-compression'),
|
||||
'processThumbnails' => array('key' => 'wp-short-process_thumbnails'),
|
||||
'useSmartcrop' => array('key' => 'wpspio-usesmartcrop'),
|
||||
'keepExif' => array('key' => 'wp-short-pixel-keep-exif'),
|
||||
'CMYKtoRGBconversion' => array('key' => 'wp-short-pixel_cmyk2rgb'),
|
||||
'createWebp' => array('key' => 'wp-short-create-webp'),
|
||||
'createAvif' => array('key' => 'wp-short-create-avif'),
|
||||
'deliverWebp' => array('key' => 'wp-short-pixel-create-webp-markup'),
|
||||
'optimizeRetina' => array('key' => 'wp-short-pixel-optimize-retina'),
|
||||
'optimizeUnlisted' => array('key' => 'wp-short-pixel-optimize-unlisted'),
|
||||
'backupImages' => array('key' => 'wp-short-backup_images'),
|
||||
'resizeImages' => array('key' => 'wp-short-pixel-resize-images'),
|
||||
'resizeType' => array('key' => 'wp-short-pixel-resize-type'),
|
||||
'resizeWidth' => array('key' => 'wp-short-pixel-resize-width'),
|
||||
'resizeHeight' => array('key' => 'wp-short-pixel-resize-height'),
|
||||
'siteAuthUser' => array('key' => 'wp-short-pixel-site-auth-user'),
|
||||
'siteAuthPass' => array('key' => 'wp-short-pixel-site-auth-pass'),
|
||||
'autoMediaLibrary' => array('key' => 'wp-short-pixel-auto-media-library'),
|
||||
'optimizePdfs' => array('key' => 'wp-short-pixel-optimize-pdfs'),
|
||||
'excludePatterns' => array('key' => 'wp-short-pixel-exclude-patterns'),
|
||||
'png2jpg' => array('key' => 'wp-short-pixel-png2jpg'),
|
||||
'excludeSizes' => array('key' => 'wp-short-pixel-excludeSizes'),
|
||||
'currentVersion' => array('key' => 'wp-short-pixel-currentVersion'),
|
||||
|
||||
//CloudFlare
|
||||
'cloudflareEmail' => array( 'key' => 'wp-short-pixel-cloudflareAPIEmail'),
|
||||
'cloudflareAuthKey' => array( 'key' => 'wp-short-pixel-cloudflareAuthKey'),
|
||||
'cloudflareZoneID' => array( 'key' => 'wp-short-pixel-cloudflareAPIZoneID'),
|
||||
'cloudflareToken' => array( 'key' => 'wp-short-pixel-cloudflareToken'),
|
||||
|
||||
//optimize other images than the ones in Media Library
|
||||
'includeNextGen' => array('key' => 'wp-short-pixel-include-next-gen'),
|
||||
'hasCustomFolders' => array('key' => 'wp-short-pixel-has-custom-folders'),
|
||||
'customBulkPaused' => array('key' => 'wp-short-pixel-custom-bulk-paused'),
|
||||
|
||||
//stats, notices, etc.
|
||||
// @todo Most of this can go. See state machine comment.
|
||||
|
||||
'currentStats' => array('key' => 'wp-short-pixel-current-total-files'),
|
||||
'fileCount' => array('key' => 'wp-short-pixel-fileCount'),
|
||||
'thumbsCount' => array('key' => 'wp-short-pixel-thumbnail-count'),
|
||||
'under5Percent' => array('key' => 'wp-short-pixel-files-under-5-percent'),
|
||||
'savedSpace' => array('key' => 'wp-short-pixel-savedSpace'),
|
||||
'apiRetries' => array('key' => 'wp-short-pixel-api-retries'),
|
||||
'totalOptimized' => array('key' => 'wp-short-pixel-total-optimized'),
|
||||
'totalOriginal' => array('key' => 'wp-short-pixel-total-original'),
|
||||
'quotaExceeded' => array('key' => 'wp-short-pixel-quota-exceeded'),
|
||||
'httpProto' => array('key' => 'wp-short-pixel-protocol'),
|
||||
'downloadProto' => array('key' => 'wp-short-pixel-download-protocol'),
|
||||
|
||||
'downloadArchive' => array('key' => 'wp-short-pixel-download-archive'),
|
||||
|
||||
'activationDate' => array('key' => 'wp-short-pixel-activation-date'),
|
||||
'mediaLibraryViewMode' => array('key' => 'wp-short-pixel-view-mode'),
|
||||
'redirectedSettings' => array('key' => 'wp-short-pixel-redirected-settings'),
|
||||
'convertedPng2Jpg' => array('key' => 'wp-short-pixel-converted-png2jpg'),
|
||||
);
|
||||
}
|
||||
|
||||
private function deleteLegacy()
|
||||
{
|
||||
delete_option('wp-short-pixel-activation-notice');
|
||||
delete_option('wp-short-pixel-bulk-last-status'); // legacy shizzle
|
||||
delete_option('wp-short-pixel-current-total-files');
|
||||
delete_option('wp-short-pixel-remove-settings-on-delete-plugin');
|
||||
|
||||
// Bulk State machine legacy
|
||||
$bulkLegacyOptions = array(
|
||||
'wp-short-pixel-bulk-type',
|
||||
'wp-short-pixel-bulk-last-status',
|
||||
'wp-short-pixel-query-id-start',
|
||||
'wp-short-pixel-query-id-stop',
|
||||
'wp-short-pixel-bulk-count',
|
||||
'wp-short-pixel-bulk-previous-percent',
|
||||
'wp-short-pixel-bulk-processed-items',
|
||||
'wp-short-pixel-bulk-done-count',
|
||||
'wp-short-pixel-last-bulk-start-time',
|
||||
'wp-short-pixel-last-bulk-success-time',
|
||||
'wp-short-pixel-bulk-running-time',
|
||||
'wp-short-pixel-cancel-pointer',
|
||||
'wp-short-pixel-skip-to-custom',
|
||||
'wp-short-pixel-bulk-ever-ran',
|
||||
'wp-short-pixel-flag-id',
|
||||
'wp-short-pixel-failed-imgs',
|
||||
'bulkProcessingStatus',
|
||||
'wp-short-pixel-prioritySkip',
|
||||
);
|
||||
|
||||
$removedStats = array(
|
||||
'wp-short-pixel-helpscout-optin',
|
||||
'wp-short-pixel-activation-notice',
|
||||
'wp-short-pixel-dismissed-notices',
|
||||
'wp-short-pixel-media-alert',
|
||||
);
|
||||
|
||||
$removedOptions = array(
|
||||
'wp-short-pixel-remove-settings-on-delete-plugin',
|
||||
'wp-short-pixel-custom-bulk-paused',
|
||||
'wp-short-pixel-last-back-action',
|
||||
'wp-short-pixel-front-bootstrap',
|
||||
);
|
||||
|
||||
$toRemove = array_merge($bulkLegacyOptions, $removedStats, $removedOptions);
|
||||
|
||||
foreach($toRemove as $option)
|
||||
{
|
||||
delete_option($option);
|
||||
}
|
||||
}
|
||||
|
||||
} // class
|
@ -0,0 +1,467 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
|
||||
use ShortPixel\Controller\OtherMediaController as OtherMediaController;
|
||||
use ShortPixel\Model\Image\ImageModel as ImageModel;
|
||||
use ShortPixel\Model\Image\MediaLibraryModel as MediaLibraryModel;
|
||||
|
||||
|
||||
use ShortPixel\Helper\UtilHelper as UtilHelper;
|
||||
use ShortPixel\Helper\InstallHelper as InstallHelper;
|
||||
|
||||
|
||||
class StatsModel
|
||||
{
|
||||
|
||||
// Below are counted and saved in settings
|
||||
protected $totalOptimized; // combined filesize of optimized images
|
||||
protected $totalOriginal; // combined filesize of original images
|
||||
|
||||
// There are gotten via SQL and saved in stats
|
||||
//protected $totalImages;
|
||||
//protected $totalThumbnails
|
||||
|
||||
protected $lastUpdate;
|
||||
protected $path = array();
|
||||
|
||||
protected $currentStat; // used for chaining it.
|
||||
|
||||
protected $refreshStatTime;
|
||||
|
||||
|
||||
// Commented out stats were dropped.
|
||||
// Note: the difference in items / images including thumbs and the counts don't . This is due to technical difference in acquiring the data.
|
||||
protected $defaults = array(
|
||||
'media' => array('items' => -1, // total optimized media items found
|
||||
'images' => -1, // total optimized images (+thumbs) found
|
||||
'thumbs' => -1, // Optimized thumbs - SQL does thumbs, but queue doesn't. (imprecise query)
|
||||
'itemsTotal' => -1, // Total items in media ( sql )
|
||||
'thumbsTotal' => -1, // Total thumbs in media ( sql ) - imprecise query
|
||||
'isLimited' => false,
|
||||
/* 'lossy' => 0, // processed x compression
|
||||
'lossy_thumbs' => 0, // main / thumbs
|
||||
'lossless' => 0, // main /thumbs
|
||||
'lossless_thumbs' => 0,
|
||||
'glossy' => 0,
|
||||
'glossy_thumbs' => 0, */
|
||||
),
|
||||
'custom' => array('items' => -1, // total optimized custom items
|
||||
'images' => -1, // total optimized custom images
|
||||
'itemsTotal' => -1,
|
||||
|
||||
/* 'lossy' => 0, // process x compression
|
||||
'lossless' => 0,
|
||||
'glossy' => 0, */
|
||||
),
|
||||
'period' => array('months' => // amount of images compressed in x month
|
||||
array('1' => -1, /// count x months ago what was done.
|
||||
'2' => -1,
|
||||
'3' => -1,
|
||||
'4' => -1,
|
||||
),
|
||||
),
|
||||
'total' => array('items' => -1,
|
||||
'images' => -1,
|
||||
'thumbs' => -1,
|
||||
'itemsTotal' => -1,
|
||||
'thumbsTotal' => -1,
|
||||
),
|
||||
|
||||
/* 'total' => array('items' => 0, // total items found
|
||||
'images' => 0, // total images found
|
||||
), */
|
||||
);
|
||||
|
||||
protected $stats; // loaded as defaults, or from dbase.
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->refreshStatTime = apply_filters('shortpixel/statistics/refresh', WEEK_IN_SECONDS);
|
||||
$this->load();
|
||||
}
|
||||
|
||||
public function load()
|
||||
{
|
||||
$settings = \wpSPIO()->settings();
|
||||
|
||||
$this->totalOptimized = $settings->totalOptimized;
|
||||
$this->totalOriginal = $settings->totalOriginal;
|
||||
|
||||
$stats = $settings->currentStats;
|
||||
|
||||
// Legacy. Stats from < 5.0 are loaded somehow. Don't load them.
|
||||
if (isset($stats['APIKeyValid']))
|
||||
$stats = $this->defaults;
|
||||
|
||||
$this->lastUpdate = (isset($stats['time'])) ? $stats['time'] : 0;
|
||||
|
||||
if ( ($this->lastUpdate + $this->refreshStatTime) >= time())
|
||||
{
|
||||
$this->stats = $stats;
|
||||
}
|
||||
else
|
||||
$this->stats = $this->defaults;
|
||||
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$settings = \wpSPIO()->settings();
|
||||
$stats = $this->stats;
|
||||
$stats['time'] = time();
|
||||
|
||||
$settings->currentStats = $stats;
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->stats = $this->defaults;
|
||||
\wpSPIO()->settings()->deleteOption('currentStats');
|
||||
|
||||
// $this->save();
|
||||
}
|
||||
|
||||
// @todo This is not functional
|
||||
public function add($stat)
|
||||
{
|
||||
if (property_exists($stat, 'images'))
|
||||
$this->stats[$stat->type][$images] += $stats->images;
|
||||
if (property_exists($stat, 'items'))
|
||||
$this->stats[$stat->type][$items] += $stats->items;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function get($name)
|
||||
{
|
||||
if (property_exists($this, $name))
|
||||
return $this->$name;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getStat($type)
|
||||
{
|
||||
$this->currentStat = null;
|
||||
|
||||
if (isset($this->stats[$type]))
|
||||
{
|
||||
$this->currentStat = $this->stats[$type];
|
||||
$this->path = [$type];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function grab($data)
|
||||
{
|
||||
|
||||
if (is_null($this->currentStat))
|
||||
return null;
|
||||
|
||||
if (array_key_exists($data, $this->currentStat))
|
||||
{
|
||||
$this->currentStat = $this->currentStat[$data];
|
||||
$this->path[] = $data;
|
||||
}
|
||||
|
||||
|
||||
if (! is_array($this->currentStat))
|
||||
{
|
||||
if ($this->currentStat === -1)
|
||||
{
|
||||
$this->currentStat = $this->fetchStatdata(); // if -1 stat might not be loaded, load.
|
||||
}
|
||||
|
||||
return $this->currentStat;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
private function fetchStatData()
|
||||
{
|
||||
$path = $this->path;
|
||||
$data = -1;
|
||||
|
||||
if ($path[0] == 'period' && $path[1] == 'months' && isset($path[2]))
|
||||
{
|
||||
$month = $path[2];
|
||||
|
||||
$data = $this->countMonthlyOptimized(intval($month));
|
||||
|
||||
if ($data >= 0)
|
||||
{
|
||||
$this->stats['period']['months'][$month] = $data;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
}
|
||||
if ($path[0] == 'media')
|
||||
{
|
||||
switch($path[1])
|
||||
{
|
||||
case 'items':
|
||||
$data = $this->countMediaItems(['optimizedOnly' => true]);
|
||||
break;
|
||||
case 'thumbs': // unrealiable if certain thumbs are not optimized, but the main image is.
|
||||
$data = $this->countMediaThumbnails(['optimizedOnly' => true]);
|
||||
break;
|
||||
case 'images':
|
||||
$data = $this->getStat('media')->grab('items') + $this->getStat('media')->grab('thumbs');
|
||||
break;
|
||||
case 'itemsTotal':
|
||||
$data = $this->countMediaItems();
|
||||
break;
|
||||
case 'thumbsTotal':
|
||||
$data = $this->countMediaThumbnails();
|
||||
break;
|
||||
case 'isLimited':
|
||||
$data = $this->stats['media']['isLimited'];
|
||||
break;
|
||||
}
|
||||
|
||||
if ($data >= 0)
|
||||
{
|
||||
if (is_numeric($data))
|
||||
{
|
||||
$data = max($data, 0);
|
||||
}
|
||||
$this->stats['media'][$path[1]] = $data; // never allow any data below zero.
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($path[0] == 'custom')
|
||||
{
|
||||
switch($path[1])
|
||||
{
|
||||
case 'items':
|
||||
$data = $this->customItems(['optimizedOnly' => true]);
|
||||
break;
|
||||
case 'itemsTotal':
|
||||
$data = $this->customItems();
|
||||
break;
|
||||
}
|
||||
|
||||
if ($data >= 0)
|
||||
{
|
||||
$this->stats['custom'][$path[1]] = $data;
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
|
||||
if ($path[0] == 'total')
|
||||
{
|
||||
switch($path[1])
|
||||
{
|
||||
|
||||
case 'items':
|
||||
$media = $this->getStat('media')->grab('items');
|
||||
$custom = $this->getStat('custom')->grab('items');
|
||||
$data = $media + $custom;
|
||||
break;
|
||||
case 'images':
|
||||
$media = $this->getStat('media')->grab('images');
|
||||
$custom = $this->getStat('custom')->grab('items'); // items == images
|
||||
$data = $media + $custom;
|
||||
break;
|
||||
case 'thumbs':
|
||||
$data = $this->getStat('media')->grab('thumbs');
|
||||
break;
|
||||
case 'itemsTotal':
|
||||
$media = $this->getStat('media')->grab('itemsTotal');
|
||||
$custom = $this->getStat('custom')->grab('itemsTotal');
|
||||
$data = $media + $custom;
|
||||
break;
|
||||
case 'thumbsTotal':
|
||||
$data = $this->getStat('media')->grab('thumbsTotal');
|
||||
break;
|
||||
|
||||
}
|
||||
if ($data >= 0)
|
||||
{
|
||||
$this->stats['total'][$path[1]] = $data;
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// suboptimal over full stats implementation, but faster.
|
||||
private function countMediaThumbnails($args = array())
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$defaults = array(
|
||||
'optimizedOnly' => false,
|
||||
'limit' => 50000,
|
||||
);
|
||||
|
||||
$args = wp_parse_args($args,$defaults);
|
||||
$prepare = array();
|
||||
|
||||
if ($args['optimizedOnly'] == true)
|
||||
{
|
||||
$sql = ' SELECT count(id) as thumbcount FROM ' . UtilHelper::getPostMetaTable() . ' WHERE status = %d AND (image_type = %d or image_type = %d)';
|
||||
$prepare = array(ImageModel::FILE_STATUS_SUCCESS, MediaLibraryModel::IMAGE_TYPE_THUMB, MediaLibraryModel::IMAGE_TYPE_ORIGINAL);
|
||||
}
|
||||
else {
|
||||
// This query will return 2 positions after the thumbnail array declaration. Value can be up to two positions ( 0-100 thumbnails) . If positions is 1-10 intval will filter out the string part.
|
||||
$sql = "SELECT meta_id, post_id, substr(meta_value, instr(meta_value,'sizes')+9,2) as thumbcount, LOCATE('original_image', meta_value) as originalImage FROM " . $wpdb->postmeta . " WHERE meta_key = '_wp_attachment_metadata' ";
|
||||
|
||||
$sql .= " AND post_id NOT IN ( SELECT post_id FROM " . $wpdb->postmeta . " where meta_key = '_shortpixel_prevent_optimize' )"; // exclude 'crashed items'
|
||||
|
||||
$sql .= " limit 0," . $args['limit'];
|
||||
}
|
||||
|
||||
|
||||
if (count($prepare) > 0)
|
||||
{
|
||||
$sql = $wpdb->prepare($sql, $prepare);
|
||||
}
|
||||
|
||||
$results = $wpdb->get_results($sql);
|
||||
|
||||
//og::addDebug('Limit and count results' . $args['limit'] . ' ' . count($results));
|
||||
if ($args['limit'] <= count($results))
|
||||
{
|
||||
$this->stats['media']['isLimited']= true;
|
||||
}
|
||||
|
||||
$thumbCount = 0;
|
||||
|
||||
foreach($results as $row)
|
||||
{
|
||||
$count = intval($row->thumbcount);
|
||||
if ($count > 0)
|
||||
$thumbCount += $count;
|
||||
if (property_exists($row, 'originalImage') && $row->originalImage > 0) // add to count, return value is string pos
|
||||
$thumbCount++;
|
||||
}
|
||||
|
||||
return intval($thumbCount);
|
||||
}
|
||||
|
||||
private function countMediaItems($args = array())
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$defaults = array(
|
||||
'optimizedOnly' => false,
|
||||
);
|
||||
|
||||
$args = wp_parse_args($args,$defaults);
|
||||
$prepare = array();
|
||||
|
||||
if ($args['optimizedOnly'] == true)
|
||||
{
|
||||
//$sql .= ' AND post_id IN ( SELECT post_id FROM ' . $wpdb->postmeta . ' WHERE meta_key = "_shortpixel_optimized")';
|
||||
$sql = ' SELECT count(id) as count FROM ' . UtilHelper::getPostMetaTable() . ' WHERE status = %d AND parent = %d';
|
||||
$prepare = array(ImageModel::FILE_STATUS_SUCCESS, MediaLibraryModel::IMAGE_TYPE_MAIN);
|
||||
}
|
||||
else {
|
||||
$sql = 'SELECT count(meta_id) FROM ' . $wpdb->postmeta . ' WHERE meta_key = "_wp_attached_file"';
|
||||
$sql .= " AND post_id NOT IN ( SELECT post_id FROM " . $wpdb->postmeta . " where meta_key = '_shortpixel_prevent_optimize' )"; // exclude 'crashed items'
|
||||
}
|
||||
|
||||
if (count($prepare) > 0)
|
||||
$sql = $wpdb->prepare($sql, $prepare);
|
||||
|
||||
$count = $wpdb->get_var($sql);
|
||||
|
||||
|
||||
if (is_null($count) && strpos($wpdb->last_error, 'exist') !== false)
|
||||
{
|
||||
InstallHelper::checkTables();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return intval($count);
|
||||
}
|
||||
|
||||
private function countMonthlyOptimized($monthsAgo = 1)
|
||||
{
|
||||
global $wpdb;
|
||||
//$monthsAgo = 0 - $monthsAgo; // minus it for the sub.
|
||||
/*$sql = "select meta_id from wp_postmeta where meta_key = '_shortpixel_meta' HAVING substr(meta_value, instr(meta_value, 'tsOptimized')+15,10) as stamp >= %d and stamp <= %d"; */
|
||||
|
||||
$date = new \DateTime();
|
||||
$date->sub( new \DateInterval('P' . $monthsAgo . 'M'));
|
||||
|
||||
$dateUntil = new \DateTime();
|
||||
$dateUntil->sub( new \DateInterval('P' . ($monthsAgo-1). 'M'));
|
||||
|
||||
$sql = 'SELECT count(id) FROM ' . $wpdb->prefix . 'shortpixel_postmeta WHERE tsOptimized >= %s and tsOptimized <= %s';
|
||||
$sql = $wpdb->prepare($sql, $date->format('Y-m-d H:i:s'), $dateUntil->format('Y-m-d H:i:s') );
|
||||
$count_media = $wpdb->get_var($sql);
|
||||
|
||||
// Custom
|
||||
$sql = 'SELECT count(id) FROM ' . $wpdb->prefix . 'shortpixel_meta WHERE ts_optimized >= %s and ts_optimized <= %s';
|
||||
$sql = $wpdb->prepare($sql, $date->format('Y-m-d H:i:s'), $dateUntil->format('Y-m-d H:i:s') );
|
||||
$count_custom = $wpdb->get_var($sql);
|
||||
|
||||
$count = 0;
|
||||
if (! is_null($count_media) && is_numeric($count_media))
|
||||
$count += $count_media;
|
||||
|
||||
if (! is_null($count_custom) && is_numeric($count_custom))
|
||||
$count += $count_custom;
|
||||
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
private function customItems($args = array())
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$defaults = array(
|
||||
'optimizedOnly' => false,
|
||||
);
|
||||
|
||||
$args = wp_parse_args($args,$defaults);
|
||||
|
||||
$otherMediaController = OtherMediaController::getInstance();
|
||||
if (! $otherMediaController->hasCustomImages() )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
$activeDirectories = $otherMediaController->getActiveDirectoryIDS();
|
||||
// $foldersids = implode(',', $activeDirectories );
|
||||
|
||||
if (count($activeDirectories) == 0)
|
||||
return 0; // no active folders
|
||||
|
||||
$in_str_arr = array_fill( 0, count( $activeDirectories ), '%s' );
|
||||
$in_str = join( ',', $in_str_arr );
|
||||
|
||||
$sql = 'SELECT COUNT(id) as count FROM ' . $wpdb->prefix . 'shortpixel_meta WHERE folder_id in (' . $in_str . ')';
|
||||
$sql = $wpdb->prepare($sql, $activeDirectories);
|
||||
|
||||
if ($args['optimizedOnly'] == true)
|
||||
{
|
||||
$sql .= ' AND status = %d';
|
||||
$sql = $wpdb->prepare($sql, ImageModel::FILE_STATUS_SUCCESS);
|
||||
}
|
||||
|
||||
$count = $wpdb->get_var($sql);
|
||||
return $count;
|
||||
|
||||
}
|
||||
|
||||
|
||||
} // class
|
Reference in New Issue
Block a user