Current File : /home/pacjaorg/www/cop29/wp-content/plugins/duplicator-pro/src/Core/Views/Notifications.php |
<?php
namespace Duplicator\Core\Views;
use DUP_PRO_Global_Entity;
use Duplicator\Addons\ProBase\License\License;
use Duplicator\Core\CapMng;
use Duplicator\Core\Upgrade\UpgradePlugin;
use Duplicator\Core\Views\TplMng;
/**
* Notifications.
*/
class Notifications
{
/**
* Source of notifications content.
*
* @var string
*/
const SOURCE_URL = 'https://notifications.duplicator.com/dp-notifications.json';
/**
* WordPress option key containing notification data
*
* @var string
*/
const DUPLICATOR_PRO_NOTIFICATIONS_OPT_KEY = 'duplicator_pro_notifications';
/**
* WordPress option key containing notification data
*
* @var string
*/
const DUPLICATOR_PRO_BEFORE_PACKAGES_HOOK = 'duplicator_pro_before_packages_table_action';
/**
* Duplicator notifications dismiss nonce key
*
* @var string
*/
const DUPLICATOR_NOTIFICATION_NONCE_KEY = 'duplicator-notification-dismiss';
/**
* Option value.
*
* @var bool|array{update: int, feed: mixed[], events: mixed[], dismissed: mixed[]}
*/
private static $option = false;
/**
* Initialize class.
*
* @return void
*/
public static function init()
{
if (
!CapMng::can(CapMng::CAP_LICENSE, false) ||
!DUP_PRO_Global_Entity::getInstance()->isAmNoticesEnabled()
) {
return;
}
// Add notification count to menu label.
add_filter('duplicator_menu_label_duplicator-pro', function ($label) {
if (self::getCount() === 0) {
return $label;
}
return $label . '<span class="awaiting-mod">' . self::getCount() . '</span>';
});
self::update();
add_action(self::DUPLICATOR_PRO_BEFORE_PACKAGES_HOOK, array(__CLASS__, 'output'));
}
/**
* Dismis notification.
*
* @param string $id Notification id.
*
* @return bool
*/
public static function dismiss($id)
{
$type = is_numeric($id) ? 'feed' : 'events';
$option = self::getOption();
$option['dismissed'][] = $id;
$option['dismissed'] = array_unique($option['dismissed']);
// Remove notification.
if (!is_array($option[$type]) || empty($option[$type])) {
throw new \Exception('Notification type not set.');
}
foreach ($option[$type] as $key => $notification) {
if ((string)$notification['id'] === (string)$id) {
unset($option[$type][$key]);
break;
}
}
return update_option(self::DUPLICATOR_PRO_NOTIFICATIONS_OPT_KEY, $option);
}
/**
* Get option value.
*
* @param bool $cache Reference property cache if available.
*
* @return array{update: int, feed: mixed[], events: mixed[], dismissed: mixed[]}
*/
private static function getOption($cache = true)
{
if (self::$option && $cache) {
return self::$option;
}
self::$option = get_option(self::DUPLICATOR_PRO_NOTIFICATIONS_OPT_KEY, [
'update' => 0,
'feed' => [],
'events' => [],
'dismissed' => [],
]);
return self::$option;
}
/**
* Fetch notifications from feed.
*
* @return mixed[]
*/
private static function fetchFeed()
{
$response = wp_remote_get(
self::SOURCE_URL,
array(
'timeout' => 10,
'user-agent' => self::getUserAgent(),
)
);
if (is_wp_error($response)) {
return array();
}
$body = wp_remote_retrieve_body($response);
if (empty($body)) {
return array();
}
return self::verify(json_decode($body, true));
}
/**
* Verify notification data before it is saved.
*
* @param mixed[] $notifications Array of notifications items to verify.
*
* @return mixed[]
*/
private static function verify($notifications)
{
$data = array();
if (!is_array($notifications) || empty($notifications)) {
return $data;
}
foreach ($notifications as $notification) {
// Ignore if one of the conditional checks is true:
//
// 1. notification message is empty.
// 2. license type does not match.
// 3. notification is expired.
// 4. notification has already been dismissed.
// 5. notification existed before installing Duplicator.
// (Prevents bombarding the user with notifications after activation).
if (
empty($notification['content']) ||
!self::isLicenseTypeMatch($notification) ||
self::isExpired($notification) ||
self::isDismissed($notification) ||
self::isExisted($notification)
) {
continue;
}
$data[] = $notification;
}
return $data;
}
/**
* Verify saved notification data for active notifications.
*
* @param mixed[] $notifications Array of notifications items to verify.
*
* @return mixed[]
*/
private static function verifyActive($notifications)
{
if (!is_array($notifications) || empty($notifications)) {
return array();
}
$current_timestamp = time();
// Remove notifications that are not active.
foreach ($notifications as $key => $notification) {
if (
(!empty($notification['start']) && $current_timestamp < strtotime($notification['start'])) ||
(!empty($notification['end']) && $current_timestamp > strtotime($notification['end']))
) {
unset($notifications[$key]);
}
}
return $notifications;
}
/**
* Get notification data.
*
* @return mixed[]
*/
private static function get()
{
$option = self::getOption();
$feed = !empty($option['feed']) ? self::verifyActive($option['feed']) : array();
$events = !empty($option['events']) ? self::verifyActive($option['events']) : array();
return array_merge($feed, $events);
}
/**
* Get notification count.
*
* @return int
*/
private static function getCount()
{
return count(self::get());
}
/**
* Add a new Event Driven notification.
*
* @param mixed[] $notification Notification data.
*
* @return void
*/
public static function add($notification)
{
if (!self::isValid($notification)) {
return;
}
$option = self::getOption();
// Notification ID already exists.
if (!empty($option['events'][$notification['id']])) {
return;
}
$notification = self::verify(array($notification));
update_option(
self::DUPLICATOR_PRO_NOTIFICATIONS_OPT_KEY,
array(
'update' => $option['update'],
'feed' => $option['feed'],
'events' => array_merge($notification, $option['events']),
'dismissed' => $option['dismissed'],
)
);
}
/**
* Determine if notification data is valid.
*
* @param mixed[] $notification Notification data.
*
* @return bool
*/
private static function isValid($notification)
{
if (empty($notification['id'])) {
return false;
}
return count(self::verify(array($notification))) > 0;
}
/**
* Determine if notification has already been dismissed.
*
* @param mixed[] $notification Notification data.
*
* @return bool
*/
private static function isDismissed($notification)
{
$option = self::getOption();
return !empty($option['dismissed']) && in_array($notification['id'], $option['dismissed']);
}
/**
* Determine if license type is match.
*
* @param mixed[] $notification Notification data.
*
* @return bool
*/
private static function isLicenseTypeMatch($notification)
{
// A specific license type is not required.
if (is_scalar($notification['type'])) {
$notification['type'] = [$notification['type']];
}
if (empty($notification['type'])) {
return false;
}
if (in_array('any', $notification['type']) || in_array('pro', $notification['type'])) {
return true;
}
return in_array(self::getLicenseType(), $notification['type'], true);
}
/**
* Determine if notification is expired.
*
* @param mixed[] $notification Notification data.
*
* @return bool
*/
private static function isExpired($notification)
{
return !empty($notification['end']) && time() > strtotime($notification['end']);
}
/**
* Determine if notification existed before installing Duplicator Pro.
*
* @param mixed[] $notification Notification data.
*
* @return bool
*/
private static function isExisted($notification)
{
$installInfo = UpgradePlugin::getInstallInfo();
return $installInfo['time'] > strtotime($notification['start']);
}
/**
* Update notification data from feed.
*
* @return void
*/
private static function update()
{
$option = self::getOption();
//Only update twice daily
if (time() < $option['update'] + DAY_IN_SECONDS / 2) {
return;
}
$data = array(
'update' => time(),
'feed' => self::fetchFeed(),
'events' => $option['events'],
'dismissed' => $option['dismissed'],
);
/**
* Allow changing notification data before it will be updated in database.
*
* @param array $data New notification data.
*/
$data = (array)apply_filters('duplicator_admin_notifications_update_data', $data);
update_option(self::DUPLICATOR_PRO_NOTIFICATIONS_OPT_KEY, $data);
}
/**
* Enqueue assets on Form Overview admin page.
*
* @return void
*/
private static function enqueues()
{
if (!self::getCount()) {
return;
}
wp_enqueue_script(
'dup-admin-notifications',
DUPLICATOR_PRO_PLUGIN_URL . "assets/js/admin-notifications.js",
array('jquery'),
DUPLICATOR_PRO_VERSION,
true
);
wp_localize_script(
'dup-admin-notifications',
'dup_admin_notifications',
array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce(self::DUPLICATOR_NOTIFICATION_NONCE_KEY),
)
);
}
/**
* Output notifications on Form Overview admin area.
*
* @return void
*/
public static function output()
{
$notificationsData = self::get();
if (empty($notificationsData)) {
return;
}
$content_allowed_tags = array(
'br' => array(),
'em' => array(),
'strong' => array(),
'span' => array(
'style' => array(),
),
'p' => array(
'id' => array(),
'class' => array(),
),
'a' => array(
'href' => array(),
'target' => array(),
'rel' => array(),
),
);
$notifications = [];
foreach ($notificationsData as $notificationData) {
// Prepare required arguments.
$notificationData = wp_parse_args(
$notificationData,
array(
'id' => 0,
'title' => '',
'content' => '',
'video' => '',
)
);
$title = self::getComponentData($notificationData['title']);
$content = self::getComponentData($notificationData['content']);
if (!$title && !$content) {
continue;
}
$notifications[] = array(
'id' => $notificationData['id'],
'title' => $title,
'btns' => self::getButtonsData($notificationData),
'content' => wp_kses(wpautop($content), $content_allowed_tags),
'video_url' => wp_http_validate_url(self::getComponentData($notificationData['video'])),
);
}
self::enqueues();
TplMng::getInstance()->render(
'parts/Notifications/main',
array('notifications' => $notifications)
);
}
/**
* Retrieve notification's buttons.
*
* @param array<string, mixed> $notification Notification data.
*
* @return array<int, mixed>
*/
private static function getButtonsData($notification)
{
if (empty($notification['btn']) || !is_array($notification['btn'])) {
return [];
}
$buttons = [];
if (!empty($notification['btn']['main_text']) && !empty($notification['btn']['main_url'])) {
$buttons[] = array(
'class' => 'secondary',
'text' => $notification['btn']['main_text'],
'url' => self::prepareBtnUrl($notification['btn']['main_url']),
'target' => '_blank',
);
}
if (!empty($notification['btn']['alt_text']) && !empty($notification['btn']['alt_url'])) {
$buttons[] = array(
'class' => 'secondary hollow',
'text' => $notification['btn']['alt_text'],
'url' => self::prepareBtnUrl($notification['btn']['alt_url']),
'target' => '_blank',
);
}
return $buttons;
}
/**
* Retrieve notification's component data by a license type.
*
* @param mixed $data Component data.
*
* @return false|mixed
*/
private static function getComponentData($data)
{
if (empty($data['license'])) {
return $data;
}
if (!empty($data['license']['pro'])) {
return $data['license']['pro'];
}
$license_type = self::getLicenseType();
return !empty($data['license'][$license_type]) ? $data['license'][$license_type] : false;
}
/**
* Retrieve the current installation license type (always lowercase).
*
* @return string
*/
private static function getLicenseType()
{
return strtolower(License::getLicenseToString());
}
/**
* Prepare button URL.
*
* @param string $btnUrl Button url.
*
* @return string
*/
private static function prepareBtnUrl($btnUrl)
{
if (empty($btnUrl)) {
return '';
}
$replace_tags = array(
'{admin_url}' => admin_url(),
);
return wp_http_validate_url(str_replace(array_keys($replace_tags), array_values($replace_tags), $btnUrl));
}
/**
* User agent that will be used for the request
*
* @return string
*/
private static function getUserAgent()
{
return 'WordPress/' . get_bloginfo('version') . '; ' . get_bloginfo('url') . '; Duplicator/Lite-' . DUPLICATOR_PRO_VERSION;
}
}