This commit is contained in:
2024-05-20 15:37:46 +03:00
commit 00b7dbd0b7
10404 changed files with 3285853 additions and 0 deletions

View File

@ -0,0 +1,31 @@
{
"name": "shortpixel/notices",
"description": "ShortPixel WordPress Notice System",
"version": "1.5",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Bas",
"email": "bas@weblogmechanic.com"
}
],
"minimum-stability": "dev",
"require": {
"shortpixel/log" : "1.1.*"
},
"repositories": [
{
"packagist.org": false,
"type": "path",
"url": "../modules/",
"options": {
"symlink": true
}
}
],
"autoload": {
"psr-4": { "ShortPixel\\Notices\\" : "src" }
}
}

View File

@ -0,0 +1,372 @@
<?php
namespace ShortPixel\Notices;
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
class NoticeController //extends ShortPixelController
{
protected static $notices = array();
protected static $instance = null;
protected static $cssHookLoaded = false; // prevent css output more than once.
protected $notice_displayed = array();
public $notice_count = 0;
protected $has_stored = false;
protected $notice_option = ''; // The wp_options name for notices here.
/** For backward compat. Never call constructor directly. */
public function __construct()
{
$ns = __NAMESPACE__;
$ns = substr($ns, 0, strpos($ns, '\\')); // try to get first part of namespace
$this->notice_option = $ns . '-notices';
add_action('wp_ajax_' . $this->notice_option, array($this, 'ajax_action'));
$this->loadNotices();
//$this->loadConfig();
}
public static function getInstance()
{
if ( self::$instance === null)
{
self::$instance = new NoticeController();
}
return self::$instance;
}
/** Reset all notices, before loading them, to ensure on updates / activations one starts fresh */
public static function resetNotices()
{
$ns = __NAMESPACE__;
$ns = substr($ns, 0, strpos($ns, '\\')); // try to get first part of namespace
$result = delete_option($ns . '-notices');
}
/** Load Notices Config File, if any
*
* [ Future Use ]
*/
public function loadConfig()
{
return;
if (file_exists('../notice_config.json'))
{
$config = file_get_contents('../notice_config.json');
$json_config = json_decode($config);
}
}
public function loadIcons($icons)
{
foreach($icons as $name => $icon)
NoticeModel::setIcon($name, $icon);
}
protected function loadNotices()
{
$notices = get_option($this->notice_option, false);
$cnotice = (is_array($notices)) ? count($notices) : 0;
if ($notices !== false && is_array($notices))
{
$checked = array();
foreach($notices as $noticeObj)
{
if (is_object($noticeObj) && $noticeObj instanceOf NoticeModel)
{
$checked[] = $noticeObj;
}
}
self::$notices = $checked;
$this->has_stored = true;
}
else {
self::$notices = array();
$this->has_stored = false;
}
$this->countNotices();
}
protected function addNotice($message, $code, $unique)
{
$notice = new NoticeModel($message, $code);
if ($unique)
{
foreach(self::$notices as $nitem)
{
if ($nitem->message == $notice->message && $nitem->code == $notice->code) // same message.
return $nitem; // return the notice with the same message.
}
}
self::$notices[] = $notice;
$this->countNotices();
$this->update();
return $notice;
}
/** Update the notices to store, check what to remove, returns count. */
public function update()
{
if (! is_array(self::$notices) || count(self::$notices) == 0)
{
if ($this->has_stored)
delete_option($this->notice_option);
return 0;
}
$new_notices = array();
foreach(self::$notices as $item)
{
if (! $item->isDone() )
{
$new_notices[] = $item;
}
}
update_option($this->notice_option, $new_notices);
self::$notices = $new_notices;
return $this->countNotices();
}
public function countNotices()
{
$this->notice_count = count(self::$notices);
return $this->notice_count;
}
public function getNotices()
{
return self::$notices;
}
public function getNoticesForDisplay()
{
$newNotices = array();
foreach(self::$notices as $notice)
{
if ($notice->isDismissed()) // dismissed never displays.
continue;
if ($notice->isPersistent())
{
$id = $notice->getID();
if (! is_null($id) && ! in_array($id, $this->notice_displayed))
{
$notice->notice_action = $this->notice_option;
$newNotices[] = $notice;
$this->notice_displayed[] = $id;
}
}
else
$newNotices[] = $notice;
}
return $newNotices;
}
public function getNoticeByID($id)
{
foreach(self::$notices as $notice)
{
if ($notice->getID() == $id)
return $notice;
}
return false;
}
public static function removeNoticeByID($id)
{
$noticeController = self::getInstance();
for($i = 0; $i < count(self::$notices); $i++)
{
$item = self::$notices[$i];
if (is_object($item) && $item->getID() == $id)
{
Log::addDebug('Removing notice with ID ' . $id);
unset(self::$notices[$i]);
}
//if ($notice_item )
}
$noticeController->update();
}
public function ajax_action()
{
$response = array('result' => false, 'reason' => '');
if (isset($_POST['nonce']) && wp_verify_nonce( sanitize_key($_POST['nonce']), 'dismiss') )
{
if (isset($_POST['plugin_action']) && 'dismiss' == $_POST['plugin_action'] )
{
$id = (isset($_POST['id'])) ? sanitize_text_field( wp_unslash($_POST['id'])) : null;
if (! is_null($id))
{
$notice = $this->getNoticeByID($id);
}
else
{
$notice = false;
}
if(false !== $notice)
{
$notice->dismiss();
$this->update();
$response['result'] = true;
}
else
{
Log::addError('Notice not found when dismissing -> ' . $id, self::$notices);
$response['result'] = false;
$response['reason'] = ' Notice ' . $id . ' not found. ';
}
}
}
else
{
Log::addError('Wrong Nonce when dismissed notice. ');
$response['reason'] = 'wrong nonce';
}
wp_send_json($response);
}
/** Adds a notice, quick and fast method
* @param String $message The Message you want to notify
* @param Boolean $unique If unique, check to not repeat notice exact same text in notices. Discard if so
* @param int $code A value of messageType as defined in model
* @returm Object Instance of noticeModel
*/
public static function addNormal($message, $unique = false)
{
$noticeController = self::getInstance();
$notice = $noticeController->addNotice($message, NoticeModel::NOTICE_NORMAL, $unique);
return $notice;
}
public static function addError($message, $unique = false)
{
$noticeController = self::getInstance();
$notice = $noticeController->addNotice($message, NoticeModel::NOTICE_ERROR, $unique);
return $notice;
}
public static function addWarning($message, $unique = false)
{
$noticeController = self::getInstance();
$notice = $noticeController->addNotice($message, NoticeModel::NOTICE_WARNING, $unique);
return $notice;
}
public static function addSuccess($message, $unique = false)
{
$noticeController = self::getInstance();
$notice = $noticeController->addNotice($message, NoticeModel::NOTICE_SUCCESS, $unique);
return $notice;
}
public static function addDetail($notice, $detail)
{
$noticeController = self::getInstance();
$notice->addDetail($detail);
// $notice_id = spl_object_id($notice);
$noticeController->update();
}
/** Make a regular notice persistent across multiple page loads
* @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
*/
public static function makePersistent($notice, $key, $suppress = -1, $callback = null)
{
$noticeController = self::getInstance();
$existing = $noticeController->getNoticeByID($key);
// if this key already exists, don't allow the new notice to be entered into the array. Remove it since it's already created.
if ($existing)
{
for($i = 0; $i < count(self::$notices); $i++)
{
$item = self::$notices[$i];
if ($item->message == $notice->message && $item->getID() == null)
{
if ($item->message != $existing->message) // allow the persistent message to be updated, if something else is served on this ID
{
$existing->message = $item->message;
}
unset(self::$notices[$i]);
}
//if ($notice_item )
}
}
else
{
$notice->setPersistent($key, $suppress, $callback); // set this notice persistent.
}
$noticeController->update();
}
public function admin_notices()
{
if ($this->countNotices() > 0)
{
if (! self::$cssHookLoaded)
{
add_action('admin_print_footer_scripts', array($this, 'printNoticeStyle'));
self::$cssHookLoaded = true;
}
foreach($this->getNoticesForDisplay() as $notice)
{
echo $notice->getForDisplay();
}
}
$this->update(); // puts views, and updates
}
public function printNoticeStyle()
{
if (file_exists(__DIR__ . '/css/notices.css'))
{
echo '<style>' . esc_html(file_get_contents(__DIR__ . '/css/notices.css')) . '</style>';
}
else {
Log::addDebug('Notices : css/notices.css could not be loaded');
}
}
}

View File

@ -0,0 +1,366 @@
<?php
namespace ShortPixel\Notices;
class NoticeModel //extends ShortPixelModel
{
public $message; // The message we want to convey.
public $details = array(); // extra details, like the files involved. Something could be hideable in the future.
public $code;
private $id = null; // used for persistent messages.
protected $viewed = false; // was this notice viewed?
protected $is_persistent = false; // This is a fatal issue, display until something was fixed.
protected $is_dismissed = false; // for persistent notices,
protected $suppress_until = null;
protected $suppress_period = -1;
protected $include_screens = array();
protected $exclude_screens = array();
public $is_removable = true; // if removable, display a notice dialog with red X or so.
public $messageType = self::NOTICE_NORMAL;
public $notice_action; // empty unless for display. Ajax action to talk back to controller.
protected $callback; // empty unless callback is needed
public static $icons = array();
private static $jsDismissLoaded;
const NOTICE_NORMAL = 1;
const NOTICE_ERROR = 2;
const NOTICE_SUCCESS = 3;
const NOTICE_WARNING = 4;
/** Use this model in conjunction with NoticeController, do not call directly */
public function __construct($message, $messageType = self::NOTICE_NORMAL)
{
$this->message = $message;
$this->messageType = $messageType;
}
public function isDone()
{
// check suppressed
if ($this->is_dismissed && ! is_null($this->suppress_until))
{
if (time() >= $this->suppress_until)
{
$this->is_persistent = false; // unpersist, so it will be cleaned and dropped.
}
}
if ($this->viewed && ! $this->is_persistent)
return true;
else
return false;
}
public function getID()
{
return $this->id;
}
public function isPersistent()
{
return $this->is_persistent;
}
public function isDismissed()
{
return $this->is_dismissed;
}
public function dismiss()
{
$this->is_dismissed = true;
$this->suppress_until = time() + $this->suppress_period;
}
public function unDismiss()
{
$this->is_dismissed = false;
}
public function setDismissedUntil($timestamp)
{
$this->suppress_until = $timestamp;
}
/** Support for extra information beyond the message.
* Can help to not overwhelm users w/ the same message but different file /circumstances.
*/
public function addDetail($detail, $clean = false)
{
if ($clean)
$this->details = array();
if (! in_array($detail, $this->details) )
$this->details[] = $detail;
}
/**
* @param $method String Include or Exclude
* @param $includes String|Array Screen Names to Include / Exclude either string, or array
*/
public function limitScreens($method, $screens)
{
if ($method == 'exclude')
{
$var = 'exclude_screens';
}
else {
$var = 'include_screens';
}
if (is_array($screens))
{
$this->$var = array_merge($this->$var, $screens);
}
else {
$this->{$var}[] = $screens; // strange syntax is PHP 5.6 compat.
}
}
/* Checks if Notice is allowed on this screen
* @param @screen_id String The screen Id to check ( most likely current one, via EnvironmentModel)
*/
public function checkScreen($screen_id)
{
if (in_array($screen_id, $this->exclude_screens))
{
return false;
}
if (in_array($screen_id, $this->include_screens))
{
return true;
}
// if include is set, don't show if not screen included.
if (count($this->include_screens) == 0)
{
return true;
}
else {
return false;
}
}
/** 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
*/
public function setPersistent($key, $suppress = -1, $callback = null)
{
$this->id = $key;
$this->is_persistent = true;
$this->suppress_period = $suppress;
if ( ! is_null($callback) && is_callable($callback))
{
$this->callback = $callback;
}
}
public static function setIcon($notice_type, $icon)
{
switch($notice_type)
{
case 'error':
$type = self::NOTICE_ERROR;
break;
case 'success':
$type = self::NOTICE_SUCCESS;
break;
case 'warning':
$type = self::NOTICE_WARNING;
break;
case 'normal':
default:
$type = self::NOTICE_NORMAL;
break;
}
self::$icons[$type] = $icon;
}
public function _debug_getvar($var)
{
if (property_exists($this, $var))
{
return $this->$var;
}
}
private function checkIncomplete($var)
{
return ($var instanceof \__PHP_Incomplete_Class);
}
public function getForDisplay()
{
$this->viewed = true;
$class = 'shortpixel shortpixel-notice ';
$icon = '';
if ($this->callback)
{
if (is_array($this->callback))
{
foreach($this->callback as $part)
{
if ($this->checkIncomplete($part) === true)
{
return false;
}
}
} elseif (is_object($this->callback))
{
if ($this->checkIncomplete($part) === true)
return false;
}
if (! is_callable($this->callback))
{
return;
}
else {
$return = call_user_func($this->callback, $this);
if ($return === false) // don't display is callback returns false explicitly.
return;
}
}
switch($this->messageType)
{
case self::NOTICE_ERROR:
$class .= 'notice-error ';
$icon = isset(self::$icons[self::NOTICE_ERROR]) ? self::$icons[self::NOTICE_ERROR] : '';
//$icon = 'scared';
break;
case self::NOTICE_SUCCESS:
$class .= 'notice-success ';
$icon = isset(self::$icons[self::NOTICE_SUCCESS]) ? self::$icons[self::NOTICE_SUCCESS] : '';
break;
case self::NOTICE_WARNING:
$class .= 'notice-warning ';
$icon = isset(self::$icons[self::NOTICE_WARNING]) ? self::$icons[self::NOTICE_WARNING] : '';
break;
case self::NOTICE_NORMAL:
$class .= 'notice-info ';
$icon = isset(self::$icons[self::NOTICE_NORMAL]) ? self::$icons[self::NOTICE_NORMAL] : '';
break;
default:
$class .= 'notice-info ';
$icon = '';
break;
}
if ($this->is_removable)
{
$class .= 'is-dismissible ';
}
if ($this->is_persistent)
{
$class .= 'is-persistent ';
}
$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;
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', 'shortpixel-image-optimiser') . '</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', 'shortpixel-image-optimiser') . '</span></label>';
$output .= '</div>'; // detail wrapper
}
$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', 'shortpixel-image-optimiser') . '</span></button>';
if (! $this->is_persistent)
{
$output .= "<script type='text/javascript'>\n
document.getElementById('button-$id').onclick = function()
{
var el = document.getElementById('$id');
jQuery(el).fadeTo(100,0,function() {
jQuery(el).slideUp(100, 0, function () {
jQuery(el).remove();
})
});
} </script>";
}
}
$output .= "</div>";
if ($this->is_persistent && $this->is_removable)
{
$output .= "<script type='text/javascript'>\n" . $this->getDismissJS() . "\n</script>";
}
return $output;
}
protected function hasDetails()
{
if (is_array($this->details) && count($this->details) > 0)
return true;
else
return false;
}
protected function parseDetails()
{
return implode('<BR>', $this->details);
}
private function getDismissJS()
{
$js = '';
if (is_null(self::$jsDismissLoaded))
{
$nonce = wp_create_nonce('dismiss');
$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;
var data = {
'plugin_action': 'dismiss',
'action' : '$this->notice_action',
'nonce' : '$nonce',
}
data.time = target.getAttribute('data-dismiss');
data.id = parent.getAttribute('id');
jQuery.post($url,data);
jQuery(parent).fadeTo(100,0,function() {
jQuery(parent).slideUp(100, 0, function () {
jQuery(parent).remove();
})
});
}";
}
$js .= ' jQuery("#' . $this->id . '").find(".notice-dismiss").on("click", shortpixel_notice_dismiss); ';
return "\n jQuery(document).ready(function(){ \n" . $js . "\n});";
}
}

View File

@ -0,0 +1,65 @@
.shortpixel.shortpixel-notice {
min-height: 75px;
padding: 8px;
display: flex;
align-items: center;
background: #fff;
padding: 1px 12px;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
border: 1px solid #c3c4c7;
margin: 15px 0;
border-left-width: 4px;
border-left-color: #72aee6;
position: relative;
}
.shortpixel.shortpixel-notice span {
vertical-align: middle;
}
.shortpixel.shortpixel-notice span.icon {
margin: 0 25px 0 0;
width: 80px;
}
.shortpixel.shortpixel-notice span.content {
padding: 8px 0;
word-wrap: break-word;
overflow: hidden;
}
.shortpixel.shortpixel-notice img {
display: inline-block;
margin: 0 25px 0 0;
max-height: 50px;
}
.shortpixel.shortpixel-notice .notice-dismiss {
margin-top: 6px;
}
.shortpixel.shortpixel-notice.notice-success {
border-left-color: #00a32a;
}
.shortpixel.shortpixel-notice.notice-warning {
border-left-color: #dba617;
}
.shortpixel.shortpixel-notice.notice-error {
border-left-color: #ff0000;
}
.shortpixel.shortpixel-notice.notice-info {
border-left-color: #72aee6;
}
/* In-view notice ( not on top, between the options ) - styled after WP notice */
.view-notice {
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1);
border: 4px solid #fff;
padding: 1px 12px;
}
.view-notice p {
margin: 1em 0 !important;
}
.view-notice.warning {
border-left-color: #ffb900;
}
.view-notice-row {
display: none;
}
/*# sourceMappingURL=notices.css.map */

View File

@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["notices.scss"],"names":[],"mappings":"AACA;EAGC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAEC;;AACA;EACC;EACA;;AAED;EAEC;EACA;EACA;;AAKA;EAEE;EACA;EACA;;AAEF;EAEE;;AAGH;EAEC;;AAED;EAEC;;AAED;EAEC;;AAED;EAEE;;;AAIJ;AACA;EAGE;EACA;EAEA;;AACA;EACE;;AAEF;EAEE;;;AAIJ;EAEE","file":"notices.css"}

View File

@ -0,0 +1,83 @@
.shortpixel.shortpixel-notice
{
min-height: 75px;
padding: 8px;
display: flex;
align-items: center;
background: #fff;
padding: 1px 12px;
box-shadow: 0 1px 1px rgba(0,0,0,0.04);
border: 1px solid #c3c4c7;
margin: 15px 0;
border-left-width: 4px;
border-left-color: #72aee6;
position: relative;
span
{
vertical-align: middle;
&.icon {
margin: 0 25px 0 0;
width: 80px;
}
&.content
{
padding: 8px 0;
word-wrap: break-word;
overflow: hidden;
//display: flex; // magically fixes verticality issues
}
}
img
{
display:inline-block;
margin: 0 25px 0 0;
max-height: 50px;
}
.notice-dismiss
{
margin-top: 6px;
}
&.notice-success
{
border-left-color: #00a32a;
}
&.notice-warning
{
border-left-color: #dba617;
}
&.notice-error
{
border-left-color: #ff0000;
}
&.notice-info
{
border-left-color: #72aee6;
}
}
/* In-view notice ( not on top, between the options ) - styled after WP notice */
.view-notice
{
box-shadow: 0 1px 1px 0 rgba( 0, 0, 0, 0.1 );
border: 4px solid #fff;
padding: 1px 12px;
p {
margin: 1em 0 !important;
}
&.warning
{
border-left-color: #ffb900;
}
}
.view-notice-row
{
display: none;
}