Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/enable-media-replace/build/shortpixel/PackageLoader.php
+++ b/enable-media-replace/build/shortpixel/PackageLoader.php
@@ -24,14 +24,14 @@
return $this->composerFile;
}
- public function load($dir)
+ public function load($dir, $prepend = false)
{
$this->dir = $dir;
$composer = $this->getComposerFile();
if(isset($composer["autoload"]["psr-4"])){
- $this->loadPSR4($composer['autoload']['psr-4']);
+ $this->loadPSR4($composer['autoload']['psr-4'], $prepend);
}
if(isset($composer["autoload"]["psr-0"])){
$this->loadPSR0($composer['autoload']['psr-0']);
@@ -50,9 +50,9 @@
}
}
- public function loadPSR4($namespaces)
+ public function loadPSR4($namespaces, $prepend)
{
- $this->loadPSR($namespaces, true);
+ $this->loadPSR($namespaces, true, $prepend);
}
public function loadPSR0($namespaces)
@@ -60,7 +60,7 @@
$this->loadPSR($namespaces, false);
}
- public function loadPSR($namespaces, $psr4)
+ public function loadPSR($namespaces, $psr4, $prepend = false)
{
$dir = $this->dir;
// Foreach namespace specified in the composer, load the given classes
@@ -88,7 +88,7 @@
}
}
}
- });
+ }, true, $prepend);
}
}
}
--- a/enable-media-replace/build/shortpixel/filesystem/src/Controller/FileSystemController.php
+++ b/enable-media-replace/build/shortpixel/filesystem/src/Controller/FileSystemController.php
@@ -70,7 +70,7 @@
if (defined('UPLOADS')) // if this is set, lead.
$abspath = trailingslashit(ABSPATH) . UPLOADS;
- $abspath = apply_filters('shortpixel/filesystem/abspath', $abspath );
+ $abspath = apply_filters('emr/filesystem/abspath', $abspath );
return $this->getDirectory($abspath);
}
--- a/enable-media-replace/build/shortpixel/filesystem/src/Model/File/DirectoryModel.php
+++ b/enable-media-replace/build/shortpixel/filesystem/src/Model/File/DirectoryModel.php
@@ -20,6 +20,7 @@
protected $is_writable = null;
protected $is_readable = null;
protected $is_virtual = null;
+ protected $is_restricted = false;
protected $fields = array();
@@ -48,7 +49,21 @@
$this->exists = true;
}
- if (! $this->is_virtual() && ! is_dir($path) ) // path is wrong, *or* simply doesn't exist.
+ if( false == $this->is_virtual() && true === $this->fileIsRestricted($path) )
+ {
+ $this->exists = false;
+ $this->is_readable = false;
+ $this->is_writable = false;
+ $this->is_restricted = true;
+ }
+ else {
+ $this->is_virtual = false;
+ }
+
+ if (false === $this->is_virtual() &&
+ false === $this->is_restricted &&
+ false === 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.
@@ -64,7 +79,10 @@
$path = dirname($path);
}
- if (! $this->is_virtual() && ! is_dir($path))
+ if (false === $this->is_virtual() &&
+ false === $this->is_restricted &&
+ false === is_dir($path)
+ ) // path is wrong, *or* simply doesn't exist.
{
/* 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
@@ -224,6 +242,37 @@
}
+ /** 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;
+ }
+ }
+
+ // Allow this to be overridden due to specific server configs ( ie symlinks ) might get this flagged falsely.
+ $restricted = apply_filters('emr/file/basedir_check', $restricted);
+
+ return $restricted;
+ }
+
/* Last Resort function to just reduce path to various known WorPress paths. */
private function constructUsualDirectories($path)
@@ -484,6 +533,10 @@
$path = $this->getPath();
$parentPath = dirname($path);
+ if ($path === $parentPath)
+ {
+ return false;
+ }
$parentDir = new DirectoryModel($parentPath);
return $parentDir;
--- a/enable-media-replace/build/shortpixel/filesystem/src/Model/File/FileModel.php
+++ b/enable-media-replace/build/shortpixel/filesystem/src/Model/File/FileModel.php
@@ -2,6 +2,10 @@
namespace EnableMediaReplaceFileSystemModelFile;
use EnableMediaReplaceShortpixelLoggerShortPixelLogger as Log;
+if ( ! defined( 'ABSPATH' ) ) {
+ exit; // Exit if accessed directly.
+}
+
/* FileModel class.
*
*
@@ -17,39 +21,58 @@
// File info
protected $fullpath = null;
- protected $rawfullpath = 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 $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 $is_restricted = false;
+ protected $virtual_status = null;
- protected $status;
-
- protected $backupDirectory;
+ protected $status; // seems unused ?
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();
- $this->fullpath = trim($path);
- $this->rawfullpath = $this->fullpath; // path without any doing.
$fs = $this->getFS();
+
if ($fs->pathIsUrl($path)) // Asap check for URL's to prevent remote wrappers from running.
{
-
$this->UrlToPath($path);
}
}
@@ -90,27 +113,48 @@
public function resetStatus()
{
$this->is_writable = null;
+ $this->is_directory_writable = null;
$this->is_readable = null;
$this->is_file = null;
+ $this->is_restricted = null;
$this->exists = null;
$this->is_virtual = null;
+ $this->filesize = null;
+
+ $this->permissions = null;
}
- public function exists()
+ /**
+ * @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 (is_null($this->exists))
+ if (true === $forceCheck || is_null($this->exists))
{
- $this->exists = (@file_exists($this->fullpath) && is_file($this->fullpath));
+ 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);
+ $this->exists = apply_filters('emr/file/exists', $this->exists, $this->fullpath, $this);
return $this->exists;
}
public function is_writable()
{
- if ($this->is_virtual())
+ // 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
}
@@ -132,6 +176,33 @@
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))
@@ -194,8 +265,6 @@
}
-
-
/** Returns the Directory Model this file resides in
*
* @return DirectoryModel Directorymodel Object
@@ -215,9 +284,14 @@
public function getFileSize()
{
- if ($this->exists() && false === $this->is_virtual() )
+ if (! is_null($this->filesize))
+ {
+ return $this->filesize;
+ }
+ elseif ($this->exists() && false === $this->is_virtual() )
{
- return filesize($this->fullpath);
+ $this->filesize = filesize($this->fullpath);
+ return $this->filesize;
}
elseif (true === $this->is_virtual())
{
@@ -300,7 +374,7 @@
$destination->setFileInfo(); // refresh info.
}
//
- do_action('shortpixel/filesystem/addfile', array($destinationPath, $destination, $this, $is_new));
+ do_action('emr/filesystem/addfile', array($destinationPath, $destination, $this, $is_new));
return $status;
}
@@ -448,6 +522,19 @@
//$path = wp_normalize_path($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;
@@ -466,7 +553,7 @@
$path = $this->relativeToFullPath($path);
}
- $path = apply_filters('shortpixel/filesystem/processFilePath', $path, $original_path);
+ $path = apply_filters('emr/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.
{
@@ -477,7 +564,72 @@
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)
+ {
+ if (! is_null($this->is_restricted))
+ {
+ return $this->is_restricted;
+ }
+
+ $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;
+ }
+ }
+
+ // Allow this to be overridden due to specific server configs ( ie symlinks ) might get this flagged falsely.
+ $restricted = apply_filters('emr/file/basedir_check', $restricted);
+
+ $this->is_restricted = $restricted;
+ return $restricted;
+ }
/** Resolve an URL to a local path
* This partially comes from WordPress functions attempting the same
@@ -488,7 +640,16 @@
{
//$uploadDir = wp_upload_dir();
- $site_url = str_replace('http:', '', home_url('', 'http'));
+ // 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 = $this->getFS();
@@ -507,22 +668,35 @@
$this->is_virtual = true;
- // This filter checks if some supplier will be able to handle the file when needed.
- $path = apply_filters('shortpixel/image/urltopath', false, $url, $this->getRawFullPath());
+ /* 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('emr/image/urltopath', false, $url, $this->getRawFullPath());
- if ($path !== false)
- {
- $this->exists = true;
- $this->is_readable = true;
- $this->is_file = true;
- }
- else
- {
- $this->exists = false;
- $this->is_readable = false;
- $this->is_file = false;
- }
+ 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.
}
@@ -543,7 +717,7 @@
return $path;
// if the file plainly exists, it's usable /**
- if (file_exists($path))
+ if (false === $this->fileIsRestricted($path) && file_exists($path))
{
return $path;
}
--- a/enable-media-replace/build/shortpixel/log/src/ShortPixelLogger.php
+++ b/enable-media-replace/build/shortpixel/log/src/ShortPixelLogger.php
@@ -1,394 +1,461 @@
<?php
+
namespace EnableMediaReplaceShortPixelLogger;
- /*** Logger class
- *
- * Class uses the debug data model for keeping log entries.
- * Logger should not be called before init hook!
- */
- class ShortPixelLogger
- {
- static protected $instance = null;
- protected $start_time;
-
- protected $is_active = false;
- protected $is_manual_request = false;
- protected $show_debug_view = false;
-
- protected $items = array();
- protected $logPath = false;
- protected $logMode = FILE_APPEND;
-
- protected $logLevel;
- protected $format = "[ %%time%% ] %%color%% %%level%% %%color_end%% t %%message%% t %%caller%% ( %%time_passed%% )";
- protected $format_data = "t %%data%% ";
+/*** Logger class
+ *
+ * Class uses the debug data model for keeping log entries.
+ * Logger should not be called before init hook!
+ */
+class ShortPixelLogger
+{
+ static protected $instance = null;
+ protected $start_time;
+ protected $memoryLimit; // to be used for memory logs only.
+
+ protected $is_active = false;
+ protected $is_manual_request = false;
+ protected $show_debug_view = false;
+
+ protected $items = array();
+ protected $logPath = false;
+ protected $logMode = FILE_APPEND;
+
+ protected $logLevel;
+ protected $format = "[ %%time%% ] %%color%% %%level%% %%color_end%% t %%message%% t %%caller%% ( %%time_passed%% )";
+ protected $format_data = "t %%data%% ";
- protected $hooks = array();
+ protected $hooks = array();
- private $logFile; // pointer resource to the logFile.
-/* protected $hooks = array(
+ private $logFile; // pointer resource to the logFile.
+ /* protected $hooks = array(
'shortpixel_image_exists' => array('numargs' => 3),
'shortpixel_webp_image_base' => array('numargs' => 2),
'shortpixel_image_urls' => array('numargs' => 2),
); // @todo monitor hooks, but this should be more dynamic. Do when moving to module via config.
*/
- // utility
- private $namespace;
- private $view;
+ // utility
+ private $namespace;
+ private $view;
- protected $template = 'view-debug-box';
+ protected $template = 'view-debug-box';
- /** Debugger constructor
+ /** Debugger constructor
* Two ways to activate the debugger. 1) Define SHORTPIXEL_DEBUG in wp-config.php. Either must be true or a number corresponding to required LogLevel
* 2) Put SHORTPIXEL_DEBUG in the request. Either true or number.
*/
- public function __construct()
- {
- $this->start_time = microtime(true);
- $this->logLevel = DebugItem::LEVEL_WARN;
-
- $ns = __NAMESPACE__;
- $this->namespace = substr($ns, 0, strpos($ns, '\')); // try to get first part of namespace
-
- // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is not a form
- if (isset($_REQUEST['SHORTPIXEL_DEBUG'])) // manual takes precedence over constants
- {
- $this->is_manual_request = true;
- $this->is_active = true;
-
- // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is not a form
- if ($_REQUEST['SHORTPIXEL_DEBUG'] === 'true')
- {
- $this->logLevel = DebugItem::LEVEL_INFO;
- }
- else {
- // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is not a form
- $this->logLevel = intval($_REQUEST['SHORTPIXEL_DEBUG']);
- }
-
- }
- else if ( (defined('SHORTPIXEL_DEBUG') && SHORTPIXEL_DEBUG > 0) )
- {
- $this->is_active = true;
- if (SHORTPIXEL_DEBUG === true)
- $this->logLevel = DebugItem::LEVEL_INFO;
- else {
- $this->logLevel = intval(SHORTPIXEL_DEBUG);
- }
- }
-
- if (defined('SHORTPIXEL_DEBUG_TARGET') && SHORTPIXEL_DEBUG_TARGET || $this->is_manual_request)
- {
- if (defined('SHORTPIXEL_LOG_OVERWRITE')) // if overwrite, do this on init once.
- file_put_contents($this->logPath,'-- Log Reset -- ' .PHP_EOL);
- }
-
- if ($this->is_active)
- {
- /* On Early init, this function might not exist, then queue it when needed */
- if (! function_exists('wp_get_current_user'))
- add_action('init', array($this, 'initView'));
- else
- $this->initView();
- }
-
- if ($this->is_active && count($this->hooks) > 0)
- $this->monitorHooks();
- }
-
- /** Init the view when needed. Private function ( public because of WP_HOOK )
- * Never call directly */
- public function initView()
- {
- $user_is_administrator = (current_user_can('manage_options')) ? true : false;
-
- if ($this->is_active && $this->is_manual_request && $user_is_administrator )
- {
-
- $logPath = $this->logPath;
- $uploads = wp_get_upload_dir();
-
-
- if ( 0 === strpos( $logPath, $uploads['basedir'] ) ) { // Simple as it should, filepath and basedir share.
- // Replace file location with url location.
- $logLink = str_replace( $uploads['basedir'], $uploads['baseurl'], $logPath );
- }
-
-
- $this->view = new stdClass;
- $this->view->logLink = 'view-source:' . esc_url($logLink);
- add_action('admin_footer', array($this, 'loadView'));
- }
- }
-
- public static function getInstance()
- {
- if ( self::$instance === null)
- {
- self::$instance = new ShortPixelLogger();
- }
- return self::$instance;
- }
-
- public function setLogPath($logPath)
- {
- $this->logPath = $logPath;
- $this->getWriteFile(true); // reset the writeFile here.
- }
- protected function addLog($message, $level, $data = array())
- {
- // $log = self::getInstance();
-
- // don't log anything too low or when not active.
- if ($this->logLevel < $level || ! $this->is_active)
- {
- return;
- }
-
- // Force administrator on manuals.
- if ( $this->is_manual_request )
- {
- if (! function_exists('wp_get_current_user')) // not loaded yet
- return false;
-
- $user_is_administrator = (current_user_can('manage_options')) ? true : false;
- if (! $user_is_administrator)
- return false;
- }
-
- // Check where to log to.
- if ($this->logPath === false)
- {
- $upload_dir = wp_upload_dir(null,false,false);
- $this->logPath = $this->setLogPath($upload_dir['basedir'] . '/' . $this->namespace . ".log");
- }
-
- $arg = array();
- $args['level'] = $level;
- $args['data'] = $data;
-
- $newItem = new DebugItem($message, $args);
- $this->items[] = $newItem;
-
- if ($this->is_active)
- {
- $this->write($newItem);
- }
- }
-
- /** Writes to log File. */
- protected function write($debugItem, $mode = 'file')
- {
- $items = $debugItem->getForFormat();
- $items['time_passed'] = round ( ($items['time'] - $this->start_time), 5);
- $items['time'] = date('Y-m-d H:i:s', (int) $items['time'] );
-
- if ( ($items['caller']) && is_array($items['caller']) && count($items['caller']) > 0)
- {
- $caller = $items['caller'];
- $items['caller'] = $caller['file'] . ' in ' . $caller['function'] . '(' . $caller['line'] . ')';
- }
-
- $line = $this->formatLine($items);
-
- $file = $this->getWriteFile();
-
- // try to write to file. Don't write if directory doesn't exists (leads to notices)
- if ($file )
- {
- fwrite($file, $line);
-// file_put_contents($this->logPath,$line, FILE_APPEND);
- }
+ public function __construct()
+ {
+ $this->start_time = microtime(true);
+ $this->logLevel = DebugItem::LEVEL_WARN;
+
+ $ns = __NAMESPACE__;
+ $this->namespace = substr($ns, 0, strpos($ns, '\')); // try to get first part of namespace
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is not a form
+ if (isset($_REQUEST['SHORTPIXEL_DEBUG']))
+ {
+
+ // Note! User access level is checked in Addlog and Loadview to prevent lower than administrator access. It can't be checked early, because the user functions might not be loaded before first logs
+ $this->is_manual_request = true;
+ $this->is_active = true;
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is not a form
+ if ($_REQUEST['SHORTPIXEL_DEBUG'] === 'true') {
+ $this->logLevel = DebugItem::LEVEL_INFO;
+ } else {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is not a form
+ $this->logLevel = intval($_REQUEST['SHORTPIXEL_DEBUG']);
+ }
+ } else if ((defined('SHORTPIXEL_DEBUG') && SHORTPIXEL_DEBUG > 0)) {
+ $this->is_active = true;
+ if (SHORTPIXEL_DEBUG === true)
+ $this->logLevel = DebugItem::LEVEL_INFO;
else {
- // error_log($line);
- }
- }
-
- protected function getWriteFile($reset = false)
- {
- if (! is_null($this->logFile) && $reset === false)
- {
- return $this->logFile;
- }
- elseif(is_object($this->logFile))
- {
- fclose($this->logFile);
- }
-
- $logDir = dirname($this->logPath);
- if (! is_dir($logDir) || ! is_writable($logDir))
- {
- error_log('ShortpixelLogger: Log Directory is not writable');
- $this->logFile = false;
- return false;
- }
-
- $file = fopen($this->logPath, 'a');
- if ($file === false)
- {
- error_log('ShortpixelLogger: File could not be opened / created: ' . $this->logPath);
- $this->logFile = false;
- return $file;
- }
-
- $this->logFile = $file;
- return $file;
- }
-
- protected function formatLine($args = array() )
- {
- $line= $this->format;
- foreach($args as $key => $value)
- {
- if (! is_array($value) && ! is_object($value))
- $line = str_replace('%%' . $key . '%%', $value, $line);
+ $this->logLevel = intval(SHORTPIXEL_DEBUG);
}
+ }
+ else
+ {
+ return;
+ }
+
+ if (defined('SHORTPIXEL_DEBUG_TARGET') && SHORTPIXEL_DEBUG_TARGET || $this->is_manual_request) {
+ if (defined('SHORTPIXEL_LOG_OVERWRITE')) // if overwrite, do this on init once.
+ file_put_contents($this->logPath, '-- Log Reset -- ' . PHP_EOL);
+ }
+
+ if ($this->is_active) {
+ /* On Early init, this function might not exist, then queue it when needed */
+ if (! function_exists('wp_get_current_user'))
+ add_action('init', array($this, 'initView'));
+ else
+ $this->initView();
+ }
+
+ if ($this->is_active && count($this->hooks) > 0)
+ {
+ $this->monitorHooks();
+ }
+ }
+
+ /** Allow only admin users to manually debug
+ *
+ * @return bool
+ */
+ protected function checkUserLevel()
+ {
+ $user_is_administrator = (current_user_can('manage_options')) ? true : false;
+ return $user_is_administrator;
+ }
- $line .= PHP_EOL;
-
- if (isset($args['data']))
- {
- $data = array_filter($args['data']);
- if (count($data) > 0)
- {
- foreach($data as $item)
- {
- $line .= $item . PHP_EOL;
+ /** Init the view when needed. Private function ( public because of WP_HOOK )
+ * Never call directly */
+ public function initView()
+ {
+ $user_is_administrator = $this->checkUserLevel();
+
+ if ($this->is_active && $this->is_manual_request && $user_is_administrator) {
+
+ $logPath = $logLink = $this->logPath; // default
+ $uploads = wp_get_upload_dir();
+
+
+ if (0 === strpos($logPath, $uploads['basedir'])) { // Simple as it should, filepath and basedir share.
+ // Replace file location with url location.
+ $logLink = str_replace($uploads['basedir'], $uploads['baseurl'], $logPath);
+ }
+
+
+ $this->view = new stdClass;
+ $this->view->logLink = 'view-source:' . esc_url($logLink);
+ add_action('admin_footer', array($this, 'loadView'));
+ }
+ }
+
+ public static function getInstance()
+ {
+ if (self::$instance === null) {
+ self::$instance = new ShortPixelLogger();
+ }
+ return self::$instance;
+ }
+
+ public function setLogPath($logPath)
+ {
+ $this->logPath = $logPath;
+ $this->getWriteFile(true); // reset the writeFile here.
+ }
+ protected function addLog($message, $level, $data = array())
+ {
+ // $log = self::getInstance();
+
+ // don't log anything too low or when not active.
+ if ($this->logLevel < $level || ! $this->is_active) {
+ return;
+ }
+
+ // Force administrator on manuals.
+ if ($this->is_manual_request) {
+ if (! function_exists('wp_get_current_user')) // not loaded yet
+ return false;
+
+ $user_is_administrator = $this->checkUserLevel();
+ if (! $user_is_administrator)
+ return false;
+ }
+
+ // Check where to log to.
+ if ($this->logPath === false) {
+ $upload_dir = wp_upload_dir(null, false, false);
+ $this->logPath = $this->setLogPath($upload_dir['basedir'] . '/' . $this->namespace . ".log");
+ }
+
+ $arg = array();
+ $args['level'] = $level;
+ $args['data'] = $data;
+
+ $newItem = new DebugItem($message, $args);
+ $this->items[] = $newItem;
+
+ if ($this->is_active) {
+ $this->write($newItem);
+ }
+ }
+
+ /** Writes to log File. */
+ protected function write($debugItem, $mode = 'file')
+ {
+ $items = $debugItem->getForFormat();
+ $items['time_passed'] = round(($items['time'] - $this->start_time), 5);
+ $items['time'] = date('Y-m-d H:i:s', (int) $items['time']);
+
+ if (($items['caller']) && is_array($items['caller']) && count($items['caller']) > 0) {
+ $caller = $items['caller'];
+ $items['caller'] = $caller['file'] . ' in ' . $caller['function'] . '(' . $caller['line'] . ')';
+ }
+
+ $line = $this->formatLine($items);
+
+ $file = $this->getWriteFile();
+
+ // try to write to file. Don't write if directory doesn't exists (leads to notices)
+ if ($file) {
+ fwrite($file, $line);
+ // file_put_contents($this->logPath,$line, FILE_APPEND);
+ } else {
+ // error_log($line);
+ }
+ }
+
+ protected function getWriteFile($reset = false)
+ {
+ if (! is_null($this->logFile) && $reset === false) {
+ return $this->logFile;
+ } elseif (is_object($this->logFile)) {
+ fclose($this->logFile);
+ }
+
+ $logDir = dirname($this->logPath);
+ if (! is_dir($logDir) || ! is_writable($logDir)) {
+ error_log('ShortpixelLogger: Log Directory is not writable : ' . $logDir);
+ $this->logFile = false;
+ return false;
+ }
+
+ $file = false;
+ if (file_exists($this->logPath)) {
+ if (! is_writable($this->logPath)) {
+ error_log('ShortPixelLogger: File Exists, but not writable: ' . $this->logPath);
+ $this->logFile = false;
+ return $file;
+ }
+ }
+
+ $file = fopen($this->logPath, 'a');
+
+ if ($file === false) {
+ error_log('ShortpixelLogger: File could not be opened / created: ' . $this->logPath);
+ $this->logFile = false;
+ return $file;
+ }
+
+ $this->logFile = $file;
+ return $file;
+ }
+
+ protected function formatLine($args = array())
+ {
+ $line = $this->format;
+ foreach ($args as $key => $value) {
+ if (! is_array($value) && ! is_object($value))
+ $line = str_replace('%%' . $key . '%%', $value, $line);
+ }
+
+ $line .= PHP_EOL;
+
+ if (isset($args['data'])) {
+ $data = array_filter($args['data']);
+ if (count($data) > 0) {
+ // @todo This should probably be a formatter function to handle multiple stuff?
+ foreach ($data as $item) {
+ if (is_bool($item)) {
+ $item = (true === $item) ? 'true' : 'false';
}
+ $line .= $item . PHP_EOL;
}
}
+ }
- return $line;
- }
+ return $line;
+ }
- protected function setLogLevel($level)
- {
- $this->logLevel = $level;
- }
-
- protected function getEnv($name)
- {
- if (isset($this->{$name}))
- {
- return $this->{$name};
- }
- else {
- return false;
- }
- }
-
- public static function addError($message, $args = array())
- {
- $level = DebugItem::LEVEL_ERROR;
- $log = self::getInstance();
- $log->addLog($message, $level, $args);
- }
- public static function addWarn($message, $args = array())
- {
- $level = DebugItem::LEVEL_WARN;
- $log = self::getInstance();
- $log->addLog($message, $level, $args);
- }
- // Alias, since it goes wrong so often.
- public static function addWarning($message, $args = array())
- {
- self::addWarn($message, $args);
- }
- public static function addInfo($message, $args = array())
- {
- $level = DebugItem::LEVEL_INFO;
- $log = self::getInstance();
- $log->addLog($message, $level, $args);
- }
- public static function addDebug($message, $args = array())
- {
- $level = DebugItem::LEVEL_DEBUG;
- $log = self::getInstance();
- $log->addLog($message, $level, $args);
- }
-
- /** These should be removed every release. They are temporary only for d'bugging the current release */
- public static function addTemp($message, $args = array())
- {
- self::addDebug($message, $args);
- }
-
- public static function logLevel($level)
- {
- $log = self::getInstance();
- static::addInfo('Changing Log level' . $level);
- $log->setLogLevel($level);
- }
-
- public static function getLogLevel()
- {
- $log = self::getInstance();
- return $log->getEnv('logLevel');
- }
-
- public static function isManualDebug()
- {
- $log = self::getInstance();
- return $log->getEnv('is_manual_request');
- }
-
- public static function getLogPath()
- {
- $log = self::getInstance();
- return $log->getEnv('logPath');
- }
+ protected function setLogLevel($level)
+ {
+ $this->logLevel = $level;
+ }
+
+ protected function getEnv($name)
+ {
+ if (isset($this->{$name})) {
+ return $this->{$name};
+ } else {
+ return false;
+ }
+ }
+
+
+ protected function monitorHooks()
+ {
+
+ foreach ($this->hooks as $hook => $data) {
+ $numargs = isset($data['numargs']) ? $data['numargs'] : 1;
+ $prio = isset($data['priority']) ? $data['priority'] : 10;
+
+ add_filter($hook, function ($value) use ($hook) {
+ $args = func_get_args();
+ return $this->logHook($hook, $value, $args);
+ }, $prio, $numargs);
+ }
+ }
+
+ public function logHook($hook, $value, $args)
+ {
+ array_shift($args);
+ self::addInfo('[Hook] - ' . $hook . ' with ' . var_export($value, true), $args);
+ return $value;
+ }
+
+ public function loadView()
+ {
+ // load either param or class template.
+ $template = $this->template;
+
+ $view = $this->view;
+ $view->namespace = $this->namespace;
+ $controller = $this;
+
+ $template_path = __DIR__ . '/' . $this->template . '.php';
+ if (file_exists($template_path)) {
+
+ include($template_path);
+ } else {
+ self::addError(
+ "View $template for ShortPixelLogger could not be found in " . $template_path,
+ array('class' => get_class($this))
+ );
+ }
+ }
+
+ public function addMemoryLog($message, $args = array())
+ {
+ if (is_null($this->memoryLimit)) {
+ $this->memoryLimit = $this->unitToInt(ini_get('memory_limit'));
+ }
+
+ $usage = memory_get_usage();
+ $percentage = round(($usage / $this->memoryLimit) * 100, 2);
+ $memmsg = sprintf(
+ "( %s / %s - %s %%)",
+ $this->formatBytes($usage),
+ $this->formatBytes($this->memoryLimit),
+ $percentage
+ );
+ $level = DebugItem::LEVEL_DEBUG;
+ $this->addLog($message . ' ' . $memmsg, $level, $args);
+ }
+
+ private function unitToInt($s)
+ {
+ return (int)preg_replace_callback('/(-?d+)(.?)/', function ($m) {
+ return $m[1] * pow(1024, strpos('BKMG', $m[2]));
+ }, strtoupper($s));
+ }
+
+ private function formatBytes($size, $precision = 2)
+ {
+ $base = log($size, 1024);
+ $suffixes = array('', 'K', 'M', 'G', 'T');
+
+ if (0 === $size) {
+ return 0;
+ }
+
+ $calculation = pow(1024, $base - floor($base));
+ if (is_nan($calculation)) {
+ return 0;
+ }
+
+ return round($calculation, $precision) . ' ' . $suffixes[floor($base)];
+ }
+
+ public static function addError($message, $args = array())
+ {
+ $level = DebugItem::LEVEL_ERROR;
+ $log = self::getInstance();
+ $log->addLog($message, $level, $args);
+ }
+ public static function addWarn($message, $args = array())
+ {
+ $level = DebugItem::LEVEL_WARN;
+ $log = self::getInstance();
+ $log->addLog($message, $level, $args);
+ }
+ // Alias, since it goes wrong so often.
+ public static function addWarning($message, $args = array())
+ {
+ self::addWarn($message, $args);
+ }
+ public static function addInfo($message, $args = array())
+ {
+ $level = DebugItem::LEVEL_INFO;
+ $log = self::getInstance();
+ $log->addLog($message, $level, $args);
+ }
+ public static function addDebug($message, $args = array())
+ {
+ $level = DebugItem::LEVEL_DEBUG;
+ $log = self::getInstance();
+ $log->addLog($message, $level, $args);
+ }
+
+ /**
+ * Adds a trace for debuggins.
+ * @param String $message Description
+ * @param integer $amount Amount of lines needed.
+ * @param integer $debug_option Debug backtrace ( default IGNORE_ARGS, see docs )
+ */
+ public static function addTrace($message, $amount = 10, $debug_option = 2)
+ {
+ $trace = debug_backtrace($debug_option, $amount);
+ $log = self::getInstance();
+ $log->addLog($message, DebugItem::LEVEL_DEBUG, $trace);
+ }
+
+ public static function addMemory($message, $args = array())
+ {
+ $log = self::getInstance();
+ $log->addMemoryLog($message, $args);
+ }
+
+ /** These should be removed every release. They are temporary only for d'bugging the current release */
+ public static function addTemp($message, $args = array())
+ {
+ self::addDebug($message, $args);
+ }
+
+ public static function logLevel($level)
+ {
+ $log = self::getInstance();
+ static::addInfo('Changing Log level' . $level);
+ $log->setLogLevel($level);
+ }
+
+ public static function getLogLevel()
+ {
+ $log = self::getInstance();
+ return $log->getEnv('logLevel');
+ }
+
+ public static function isManualDebug()
+ {
+ $log = self::getInstance();
+ return $log->getEnv('is_manual_request');
+ }
+
+ public static function getLogPath()
+ {
+ $log = self::getInstance();
+ return $log->getEnv('logPath');
+ }
- /** Function to test if the debugger is active
+ /** Function to test if the debugger is active
* @return boolean true when active.
*/
- public static function debugIsActive()
- {
- $log = self::getInstance();
- return $log->getEnv('is_active');
- }
-
- protected function monitorHooks()
- {
-
- foreach($this->hooks as $hook => $data)
- {
- $numargs = isset($data['numargs']) ? $data['numargs'] : 1;
- $prio = isset($data['priority']) ? $data['priority'] : 10;
-
- add_filter($hook, function($value) use ($hook) {
- $args = func_get_args();
- return $this->logHook($hook, $value, $args); }, $prio, $numargs);
- }
- }
-
- public function logHook($hook, $value, $args)
- {
- array_shift($args);
- self::addInfo('[Hook] - ' . $hook . ' with ' . var_export($value,true), $args);
- return $value;
- }
-
- public function loadView()
- {
- // load either param or class template.
- $template = $this->template;
-
- $view = $this->view;
- $view->namespace = $this->namespace;
- $controller = $this;
-
- $template_path = __DIR__ . '/' . $this->template . '.php';
- if (file_exists($template_path))
- {
-
- include($template_path);
- }
- else {
- self::addError("View $template for ShortPixelLogger could not be found in " . $template_path,
- array('class' => get_class($this)));
- }
- }
-
-
- } // class debugController
+ public static function debugIsActive()
+ {
+ $log = self::getInstance();
+ return $log->getEnv('is_active');
+ }
+} // class debugController
--- a/enable-media-replace/build/shortpixel/notices/src/NoticeController.php
+++ b/enable-media-replace/build/shortpixel/notices/src/NoticeController.php
@@ -7,6 +7,7 @@
protected static $notices = array();
protected static $instance = null;
protected static $cssHookLoaded = false; // prevent css output more than once.
+ protected static $newNotices = []; // notices added this PHP run (to get them if needed)
protected $notice_displayed = array();
@@ -107,6 +108,7 @@
}
}
self::$notices[] = $notice;
+ self::$newNotices[] = $notice;
$this->countNotices();
$this->update();
@@ -151,6 +153,11 @@
return self::$notices;
}
+ public function getNewNotices()
+ {
+ return self::$newNotices;
+ }
+
public function getNoticesForDisplay()
{
$newNotices = array();
@@ -180,6 +187,7 @@
}
+
public function getNoticeByID($id)
{
foreach(self::$notices as $notice)
@@ -217,10 +225,10 @@
if (isset($_POST['plugin_action']) && 'dismiss' == $_POST['plugin_action'] )
{
$id = (isset($_POST['id'])) ? sanitize_text_field( wp_unslash($_POST['id'])) : null;
+ $type = (isset($_POST['dismisstype'])) ? sanitize_text_field($_POST['dismisstype']) : 'dismiss';
if (! is_null($id))
{
-
$notice = $this->getNoticeByID($id);
}
else
@@ -230,9 +238,18 @@
if(false !== $notice)
{
- $notice->dismiss();
- $this->update();
+ if ($type == 'remove')
+ {
+ self::removeNoticeByID($id);
+ $response['removed'] = true;
+ }
+ else {
+ $notice->dismiss();
+ $this->update();
+ $response['dismissed'] = true;
+ }
$response['result'] = true;
+
}
else
{
@@ -287,7 +304,6 @@
$noticeController = self::getInstance();
$notice = $noticeController->addNotice($message, NoticeModel::NOTICE_SUCCESS, $unique);
return $notice;
-
}
public static function addDetail($notice, $detail)
@@ -295,8 +311,6 @@
$noticeController = self::getInstance();
$notice->addDetail($detail);
-// $notice_id = spl_object_id($notice);
-
$noticeController->update();
}
@@ -304,7 +318,7 @@
* @param $notice NoticeModel The Notice to make Persistent
* @param $key String Identifier of the persistent notice.
* @param $suppress Int When dismissed, time to stay dismissed
- * @param $callback Function Callable function
+ * @param $callback Function Callable function - Seems not implemented atm ?
*/
public static function makePersistent($notice, $key, $suppress = -1, $callback = null)
{
--- a/enable-media-replace/build/shortpixel/notices/src/NoticeModel.php
+++ b/enable-media-replace/build/shortpixel/notices/src/NoticeModel.php
@@ -145,7 +145,6 @@
}
-
/** Set a notice persistent. Meaning it shows every page load until dismissed.
* @param $key Unique Key of this message. Required
* @param $suppress When dismissed do not show this message again for X amount of time. When -1 it will just be dropped from the Notices and not suppressed
@@ -268,26 +267,28 @@
}
$id = ! is_null($this->id) ? $this->id : uniqid();
- //'id="' . $this->id . '"'
- $output = "<div id='$id' class='$class'><span class='icon'> " . $icon . "</span> <span class='content'>" . $this->message;
+
+ $output = sprintf('<div id="%s" class="%s"><span class="icon">%s</span><span class="content">%s', $id, $class, $icon,$this->message);
+
if ($this->hasDetails())
{
- $output .= '<div class="details-wrapper">
- <input type="checkbox" name="detailhider" id="check-' . $id .'">
- <label for="check-' . $id . '" class="show-details"><span>' . __('See Details', 'enable-media-replace/') . '</span>
- </label>';
- $output .= "<div class='detail-content-wrapper'><p class='detail-content'>" . $this->parseDetails() . "</p></div>";
- $output .= '<label for="check-' . $id . '" class="hide-details"><span>' . __('Hide Details', 'enable-media-replace/') . '</span></label>';
+ $details = sprintf('<div class="details-wrapper">
+ <input type="checkbox" name="detailhider" id="check-%s">
+ <label for="check-%s" class="show-details"><span>%s</span>
+ </label>
+ <div class="detail-content-wrapper"><p class="detail-content">%s</p></div>
+ <label for="check-%s" class="hide-details"><span>%s</span></label>
+ </div>', $id, $id, __('See Details', 'enable-media-replace/'), $this->parseDetails(), $id, __('Hide Details', 'enable-media-replace/') );
- $output .= '</div>'; // detail wrapper
+ $output .= $details;
}
$output .= "</span>";
if ($this->is_removable)
{
- $output .= '<button type="button" id="button-' . $id . '" class="notice-dismiss" data-dismiss="' . $this->suppress_period . '" ><span class="screen-reader-text">' . __('Dismiss this notice', 'enable-media-replace/') . '</span></button>';
+ $output .= sprintf('<button type="button" id="button-%s" class="notice-dismiss" data-dismiss="%s" ><span class="screen-reader-text">%s</span></button>', $id, $this->suppress_period, __('Dismiss this notice', 'enable-media-replace/') );
if (! $this->is_persistent)
{
@@ -311,7 +312,6 @@
$output .= "<script type='text/javascript'>n" . $this->getDismissJS() . "n</script>";
}
return $output;
-
}
protected function hasDetails()
@@ -337,14 +337,26 @@
$url = wp_json_encode(admin_url('admin-ajax.php'));
$js = "function shortpixel_notice_dismiss(event) {
event.preventDefault();
- var ev = event.detail;
var target = event.target;
- var parent = target.parentElement;
+ if (target.tagName !== 'button') // tricky this
+ {
+ target = target.closest('button');
+ }
+ var parent = target.closest('.shortpixel-notice');
+
+ if (target.hasAttribute('data-dismisstype'))
+ {
+ var type = target.getAttribute('data-dismisstype');
+ }
+ else {
+ var type = 'dismiss';
+ }
var data = {
'plugin_action': 'dismiss',
'action' : '$this->notice_action',
'nonce' : '$nonce',
+ 'dismisstype' : type,
}
data.time = target.getAttribute('data-dismiss');
data.id = parent.getAttribute('id');
@@ -358,7 +370,8 @@
}";
}
- $js .= ' jQuery("#' . $this->id . '").find(".notice-dismiss").on("click", shortpixel_notice_dismiss); ';
+ // This all needs to be formalized in a better script, regardless of class, also in proper scope.
+ $js .= ' jQuery("#' . $this->id . '").find(".notice-dismiss, .notice-dismiss-action").on("click", shortpixel_notice_dismiss); ';
return "n jQuery(document).ready(function(){ n" . $js . "n});";
}
--- a/enable-media-replace/build/shortpixel/replacer/src/Modules/Breakdance.php
+++ b/enable-media-replace/build/shortpixel/replacer/src/Modules/Breakdance.php
@@ -0,0 +1,136 @@
+<?php
+namespace EnableMediaReplaceReplacerModules;
+use EnableMediaReplaceShortPixelLoggerShortPixelLogger as Log;
+
+
+class Breakdance
+{
+ private static $instance;
+ protected $queryKey = 'breakdance';
+
+ public static function getInstance()
+ {
+ if (is_null(self::$instance))
+ self::$instance = new static();
+
+ return self::$instance;
+ }
+
+ public function __construct()
+ {
+ if (has_action('breakdance_loaded')) // elementor is active
+ {
+ if ($this->checkRequiredFunctions())
+ {
+ add_filter('emr/replacer/custom_replace_query', array($this, 'addBreakdance'), 10, 4);
+ add_filter('emr/replacer/load_meta_value', array($this, 'loadContent'),10,3);
+ add_filter('emr/replacer/save_meta_value', array($this, 'saveContent'), 10,3);
+ }
+ else {
+ add_filter('emr/replacer/load_meta_value', array($this, 'abortOnContent'),10,3);
+ }
+ }
+ }
+
+ // This integration uses several Breakdance functions. Don't something if this dance breaks somehow
+ public function checkRequiredFunctions()
+ {
+ $functions = [
+ 'BreakdanceDataget_tree',
+ 'BreakdanceDataencode_before_writing_to_wp',
+ 'BreakdanceDataget_global_option',
+ 'BreakdanceDatasave_document'
+ ];
+
+ foreach($functions as $function)
+ {
+ if (false === function_exists($function))
+ {
+ Log::addWarn('Replacer breakdance module cannot find ' . $function);
+ return false;
+ }
+
+ }
+
+ return true;
+ }
+
+ public function addBreakdance($items, $base_url, $search_urls, $replace_urls)
+ {
+
+ $base_url = $this->addSlash($base_url);
+ $el_search_urls = $search_urls; //array_map(array($this, 'addslash'), $search_urls);
+ $el_replace_urls = $replace_urls; //array_map(array($this, 'addslash'), $replace_urls);
+ //$args = [('json_flags' => 0, 'component' => $this->queryKey];
+ $args = ['component' => $this->queryKey, 'replacer_do_save' => false, 'replace_no_serialize' => true];
+ $items[$this->queryKey] = array('base_url' => $base_url, 'search_urls' => $el_search_urls, 'replace_urls' => $el_replace_urls, 'args' => $args);
+ return $items;
+
+ }
+
+ // @todo This function is duplicated w/ elementor, so possibly at some point needs a Module main class for utils.
+ public function addSlash($value)
+ {
+ global $wpdb;
+ $value= ltrim($value, '/'); // for some reason the left / isn't picked up by Mysql.
+ $value= str_replace('/', '/', $value);
+ $value = $wpdb->esc_like(($value)); //(wp_slash) / str_replace('/', '/', $value);
+
+ return $value;
+ }
+
+ public function loadContent($content, $meta_row, $component)
+ {
+ if ($component !== $this->queryKey)
+ {
+ return $content;
+ }
+
+ Log::addTemp('using tree loader');
+
+ $meta_id = $meta_row['meta_id'];
+ $post_id = $meta_row['post_id'];
+
+ $result = BreakdanceDataget_tree($post_id);
+ if (false === $result)
+ {
+ Log::addWarn("Breakdance integration: Tree returns as false");
+ return null;
+ }
+
+ return $result;
+ }
+
+ public function saveContent($content, $meta_row, $component)
+ {
+ if ($component !== $this->queryKey)
+ {
+ return $content;
+ }
+
+ $global = BreakdanceDataget_global_option('global_settings_json_string');
+
+ $content = json_encode($content, JSON_UNESCAPED_SLASHES);
+ BreakdanceDatasave_document($content, $global, null, $meta_row['post_id']);
+
+ /* return BreakdanceDataencode_before_writing_to_wp([
+ 'tree_json_string' => $content,
+ ], true); */
+
+ return $content;
+ }
+
+ // If something is wrong with breakdance, don't replace content for it since it breaks the whole page content.
+ public function abortOnContent($content, $meta_row, $component)
+ {
+ if ($component !== $this->queryKey)
+ {
+ return $content;
+ }
+
+ return null;
+ }
+
+
+
+} // class
--- a/enable-media-replace/build/shortpixel/replacer/src/Modules/Elementor.php
+++ b/enable-media-replace/build/shortpixel/replacer/src/Modules/Elementor.php
@@ -19,7 +19,7 @@
{
if ($this->elementor_is_active()) // elementor is active
{
- add_filter('shortpixel/replacer/custom_replace_query', array($this, 'addElementor'), 10, 4); // custom query for elementor // problem
+ add_filter('emr/replacer/custom_replace_query', array($this, 'addElementor'), 10, 4); // custom query for elementor // problem
// @todo Fix this for SPIO
//add_action('enable-media-replace-upload-done', array($this, 'removeCache') );
}
--- a/enable-media-replace/build/shortpixel/replacer/src/Modules/WpBakery.php
+++ b/enable-media-replace/build/shortpixel/replacer/src/Modules/WpBakery.php
@@ -19,7 +19,7 @@
{
if ($this->bakery_is_active()) // elementor is active
{
- add_filter('shortpixel/replacer/custom_replace_query', array($this, 'addURLEncoded'), 10, 4); // custom query for elementor // problem
+ add_filter('emr/replacer/custom_replace_query', array($this, 'addURLEncoded'), 10, 4); // custom query for elementor // problem
}
}
--- a/enable-media-replace/build/shortpixel/replacer/src/Modules/YoastSeo.php
+++ b/enable-media-replace/build/shortpixel/replacer/src/Modules/YoastSeo.php
@@ -24,7 +24,7 @@
global $wpdb;
$this->yoastTable = $wpdb->prefix . 'yoast_indexable';
- add_action('shortpixel/replacer/replace_urls', array($this, 'removeIndexes'),10,2);
+ add_action('emr/replacer/replace_urls', array($this, 'removeIndexes'),10,2);
}
}
--- a/enable-media-replace/build/shortpixel/replacer/src/Replacer.php
+++ b/enable-media-replace/build/shortpixel/replacer/src/Replacer.php
@@ -19,6 +19,15 @@
protected $source_metadata = array();
protected $target_metadata = array();
+ private $default_replace_settings = array(
+ 'component' => 'unset',
+ 'json_flags' => JSON_UNESCAPED_SLASHES,
+ 'replacer_do_save' => true,
+ 'replace_no_serialize' => false,
+ );
+
+ private $replace_settings;
+
public function __construct()
{
//$this->source_url = $source_url;
@@ -32,6 +41,7 @@
ModulesElementor::getInstance();
ModulesWpBakery::getInstance();
ModulesYoastSeo::getInstance();
+ ModulesBreakdance::getInstance();
}
public function setSource($url)
@@ -78,23 +88,25 @@
$errors = array();
$args = wp_parse_args($args, $defaults);
+ $this->setReplaceSettings(['component' => 'emr']); // set to defaults.
+
// Search-and-replace filename in post database
// @todo Check this with scaled images.
$base_url = parse_url($this->source_url, PHP_URL_PATH);// emr_get_match_url( $this->source_url);
$base_url = str_replace('.' . pathinfo($base_url, PATHINFO_EXTENSION), '', $base_url);
/** Fail-safe if base_url is a whole directory, don't go search/replace */
- if (is_dir($base_url))
+ if (false === $this->fileIsRestricted($base_url) && is_dir($base_url))
{
Log::addError('Search Replace tried to replace to directory - ' . $base_url);
- $errors[] = __('Fail Safe :: Source Location seems to be a directory.', 'enable-media-replace');
+ $errors[] = __('Fail Safe :: Source Location seems to be a directory.', 'enable-media-replace/');
return $errors;
}
if (strlen(trim($base_url)) == 0)
{
Log::addError('Current Base URL emtpy - ' . $base_url);
- $errors[] = __('Fail Safe :: Source Location returned empty string. Not replacing content','enable-media-replace');
+ $errors[] = __('Fail Safe :: Source Location returned empty string. Not replacing content','enable-media-replace/');
return $errors;
}
@@ -178,16 +190,26 @@
Log::addDebug('Doing meta search and replace -', array($search_urls, $replace_urls) );
Log::addDebug('Searching with BaseuRL ' . $base_url);
- do_action('shortpixel/replacer/replace_urls', $search_urls, $replace_urls);
+ do_action('emr/replacer/replace_urls', $search_urls, $replace_urls);
$updated = 0;
$updated += $this->doReplaceQuery($base_url, $search_urls, $replace_urls);
- $replaceRuns = apply_filters('shortpixel/replacer/custom_replace_query', array(), $base_url, $search_urls, $replace_urls);
- Log::addDebug("REPLACE RUNS", $replaceRuns);
+ $replaceRuns = apply_filters('emr/replacer/custom_replace_query', array(), $base_url, $search_urls, $replace_urls);
+
foreach($replaceRuns as $component => $run)
{
Log::addDebug('Running additional replace for : '. $component, $run);
+
+ // @todo This could perhaps benefit from a more general approach somewhere in class for settings.
+ if (isset($run['args']))
+ {
+ // Update current settings for this run only.
+ $this->setReplaceSettings($run['args']);
+ }
+ else {
+ $this->setReplaceSettings();
+ }
$updated += $this->doReplaceQuery($run['base_url'], $run['search_urls'], $run['replace_urls']);
}
@@ -245,77 +267,128 @@
{
global $wpdb;
- $meta_options = apply_filters('shortpixel/replacer/metadata_tables', array('post', 'comment', 'term', 'user'));
+ $meta_options = apply_filters('emr/replacer/metadata_tables', array('post', 'comment', 'term', 'user', 'options'));
$number_of_updates = 0;
- foreach($meta_options as $type)
- {
- switch($type)
+ $meta_default = [
+ 'id' => 'meta_id',
+ 'value' => 'meta_value',
+ ];
+
+ $table_options = [
+ 'postmeta' => $meta_default,
+ 'commentmeta' => $meta_default,
+ 'termmeta' => $meta_default,
+ 'usermeta' => $meta_default,
+ 'options' => [
+ 'id' => 'option_id',
+ 'value' => 'option_value',
+ ]
+
+ ];
+
+ $table_options = apply_filters('emr/replacer/replacement_tables', $table_options );
+
+ // Exeception in user meta table.
+ $table_options['usermeta']['id'] = 'umeta_id';
+
+ foreach($table_options as $table => $data)
+ {
+ // These must be always defined.
+ $id_field = esc_sql($data['id']);
+ $value_field = esc_sql($data['value']);
+
+ switch($table)
{
- case "post": // special case.
- $sql = 'SELECT meta_id as id, meta_key, meta_value FROM ' . $wpdb->postmeta . '
+ case "postmeta": // special case.
+ $sql = 'SELECT * FROM ' . $wpdb->postmeta . '
WHERE post_id in (SELECT ID from '. $wpdb->posts . ' where post_status in ("publish", "future", "draft", "pending", "private") ) AND meta_value like %s';
$type = 'post';
$update_sql = ' UPDATE ' . $wpdb->postmeta . ' SET meta_value = %s WHERE meta_id = %d';
break;
default:
- $table = $wpdb->{$type . 'meta'}; // termmeta, commentmeta etc
-
- $meta_id = 'meta_id';
- if ($type == 'user')
- $meta_id = 'umeta_id';
+ $wp_table = $wpdb->{$table}