| Server IP : 104.26.5.103 / Your IP : 216.73.217.51 Web Server : nginx/1.27.1 System : Linux in-5 5.15.0-191-generic #201-Ubuntu SMP Fri Aug 7 18:39:04 UTC 2026 x86_64 User : arabianexpress ( 1872) PHP Version : 8.0.30 Disable Function : exec,passthru,shell_exec,system,proc_open,popen,parse_ini_file,show_source MySQL : OFF | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /storage/v9321/buzzbitesv3/public_html/wp-content/plugins/metform/utils/banner/ |
Upload File : |
<?php
namespace Wpmet\Libs;
defined( 'ABSPATH' ) || exit;
if(!class_exists('\Wpmet\Libs\Banner')):
class Banner {
protected $script_version = '2.2.0';
protected $key = 'wpmet_banner';
protected $data;
protected $last_check;
protected $check_interval = (3600 * 6);
protected $plugin_screens;
protected $text_domain;
protected $filter_string;
protected $filter_array = [];
protected $api_url;
/**
* Content types the remote feed is allowed to ask us to render.
*/
const ALLOWED_TYPES = ['banner', 'notice'];
/**
* Max length for values that end up as option/transient/user-meta keys.
*/
const MAX_KEY_LENGTH = 64;
public function get_version(){
return $this->script_version;
}
public function get_script_location(){
return __FILE__;
}
/**
* URL schemes accepted from the remote feed.
*
* Deliberately narrower than wp_allowed_protocols() -- a promo banner has
* no reason to emit mailto:, tel: or feed: links.
*/
public static function allowed_protocols() {
return ['http', 'https'];
}
/**
* HTML the remote feed is allowed to emit.
*
* Intentionally different from \MetForm\Utils\Util::kses(): no iframe, no
* form elements, no data-* passthrough, but style allowed on every tag so
* campaign headlines keep their formatting. Everything here arrives from a
* remote endpoint, so it is untrusted input -- if that endpoint is ever
* compromised the worst it can produce is broken markup, never script
* execution. Event handler attributes (on*) are dropped automatically
* because wp_kses() strips every attribute not listed.
*/
public static function allowed_html() {
$common = [
'class' => [],
'style' => [],
'title' => [],
];
return [
'a' => array_merge($common, ['href' => [], 'target' => [], 'rel' => []]),
'abbr' => $common,
'b' => $common,
'br' => [],
'div' => $common,
'em' => $common,
'h1' => $common,
'h2' => $common,
'h3' => $common,
'h4' => $common,
'h5' => $common,
'h6' => $common,
'i' => $common,
'img' => array_merge($common, ['src' => [], 'alt' => [], 'width' => [], 'height' => []]),
'li' => $common,
'ol' => $common,
'p' => $common,
'small' => $common,
'span' => $common,
'strong' => $common,
'u' => $common,
'ul' => $common,
];
}
public function call(){
add_action( 'admin_head', [$this, 'display_content'] );
}
public function display_content(){
$this->get_data();
if(empty($this->data)) {
return;
}
$screen = get_current_screen();
if(is_null($screen)) {
return;
}
if(!class_exists('\Oxaim\Libs\Notice')) {
return;
}
foreach($this->data as $content) {
if(!empty($this->filter_array) && $this->in_blacklist($content, $this->filter_array)) {
continue;
}
if($content->start > time() || time() > $content->end) {
continue;
}
if(!$this->is_correct_screen_to_show($content->screen, $screen->id)) {
continue;
}
$inline_css = '';
$banner_unique_id = ($content->data->unique_key !== '' ? $content->data->unique_key : $content->id);
if($content->data->style_css !== '') {
$inline_css = ' style="' . esc_attr($content->data->style_css) . '"';
}
$instance = \Oxaim\Libs\Notice::instance('wpmet-jhanda', $banner_unique_id)
->set_dismiss('global', (3600 * 24 * 15));
if($content->type == 'banner'){
$this->init_banner($content, $instance, $inline_css);
}
if($content->type == 'notice'){
$this->init_notice($content, $instance, $inline_css);
}
}
}
private function init_notice($content, $instance, $inline_css){
$instance->set_message($content->data->notice_body);
if($content->data->notice_image !== ''){
$instance->set_logo($content->data->notice_image);
}
if($content->data->button_text !== '' && $content->data->button_link !== ''){
$instance->set_button([
'default_class' => 'button',
'class' => 'button-secondary button-small', // button-primary button-secondary button-small button-large button-link
'text' => $content->data->button_text,
'url' => $content->data->button_link,
]);
}
$instance->call();
}
private function init_banner($content, $instance, $inline_css){
if($content->data->banner_link === '' || $content->data->banner_image === ''){
return;
}
$html = sprintf(
'<a target="_blank" rel="noopener noreferrer"%1$s class="wpmet-jhanda-href" href="%2$s"><img style="display: block;margin: 0 auto;" src="%3$s" alt="%4$s" /></a>',
$inline_css, // already escaped in display_content()
esc_url($content->data->banner_link, self::allowed_protocols()),
esc_url($content->data->banner_image, self::allowed_protocols()),
esc_attr($content->title)
);
$instance->set_gutter(false)
->set_html($html)
->call();
}
private function in_whitelist($conf, $list) {
$match = $conf->data->whitelist;
if(empty($match)) {
return true;
};
$match_arr = explode(',', $match);
foreach($list as $word) {
if(in_array($word, $match_arr)) {
return true;
}
}
return false;
}
private function in_blacklist($conf, $list) {
$match = $conf->data->blacklist;
if(empty($match)) {
return false;
};
$match_arr = explode(',', $match);
foreach($match_arr as $idx => $item) {
$match_arr[$idx] = trim($item);
}
foreach($list as $word) {
if(in_array($word, $match_arr)) {
return true;
}
}
return false;
}
public function is_test($is_test = false) {
if($is_test === true){
$this->check_interval = 1;
}
return $this;
}
public function set_text_domain($text_domain) {
$this->text_domain = $text_domain;
return $this;
}
public function set_filter($filter_string) {
$this->filter_string = $filter_string;
if(!empty($filter_string)) {
$filter = explode(',', $this->filter_string);
foreach ($filter as $id => $item) {
$this->filter_array[$id] = trim($item);
}
}
return $this;
}
public function set_api_url($url) {
$this->api_url = $url;
return $this;
}
public function set_plugin_screens($screen) {
$this->plugin_screens[] = $screen;
return $this;
}
/**
* Normalise one remote feed entry into a known-shape, fully escaped object.
*
* Everything the feed sends that is not on this list is dropped, so a
* poisoned response cannot introduce new fields for later code to trip on.
*
* @param mixed $content Raw decoded entry.
* @return object|null Sanitized entry, or null if it is not renderable.
*/
private function sanitize_content($content) {
if(!is_object($content) && !is_array($content)) {
return null;
}
$content = (object) $content;
$type = isset($content->type) ? sanitize_key((string) $content->type) : '';
if(!in_array($type, self::ALLOWED_TYPES, true)) {
return null;
}
$data = (isset($content->data) && (is_object($content->data) || is_array($content->data))) ? (object) $content->data : new \stdClass();
$item = new \stdClass();
$item->id = isset($content->id) ? $this->sanitize_id($content->id) : '';
$item->title = isset($content->title) ? sanitize_text_field((string) $content->title) : '';
$item->type = $type;
$item->screen = isset($content->screen) ? sanitize_key((string) $content->screen) : '';
$item->start = isset($content->start) ? intval($content->start) : 0;
$item->end = isset($content->end) ? intval($content->end) : 0;
$item->data = (object) [
'unique_key' => isset($data->unique_key) ? $this->sanitize_id($data->unique_key) : '',
'style_css' => isset($data->style_css) ? $this->sanitize_style($data->style_css) : '',
'blacklist' => isset($data->blacklist) ? sanitize_text_field((string) $data->blacklist) : '',
'whitelist' => isset($data->whitelist) ? sanitize_text_field((string) $data->whitelist) : '',
'banner_link' => isset($data->banner_link) ? $this->sanitize_url($data->banner_link) : '',
'banner_image' => isset($data->banner_image) ? $this->sanitize_url($data->banner_image) : '',
'notice_body' => isset($data->notice_body) ? $this->sanitize_html($data->notice_body) : '',
'notice_image' => isset($data->notice_image) ? $this->sanitize_url($data->notice_image) : '',
'button_text' => isset($data->button_text) ? sanitize_text_field((string) $data->button_text) : '',
'button_link' => isset($data->button_link) ? $this->sanitize_url($data->button_link) : '',
];
if($item->id === '' && $item->data->unique_key === '') {
return null;
}
return $item;
}
/**
* Run every entry of a decoded feed through sanitize_content().
*/
private function sanitize_response($response) {
if(!is_object($response) && !is_array($response)) {
return [];
}
$clean = [];
foreach((array) $response as $content) {
$item = $this->sanitize_content($content);
if(!is_null($item)) {
$clean[] = $item;
}
}
return $clean;
}
/**
* Values that become HTML ids, transient names and user-meta keys.
*/
private function sanitize_id($value) {
if(!is_scalar($value)) {
return '';
}
return substr(sanitize_key((string) $value), 0, self::MAX_KEY_LENGTH);
}
/**
* Inline CSS from the feed, filtered through core's CSS property allowlist.
*/
private function sanitize_style($css) {
if(!is_scalar($css) || (string) $css === '') {
return '';
}
return (string) safecss_filter_attr((string) $css);
}
/**
* Links and image sources from the feed. Anything that is not plain
* http(s) -- javascript:, data:, protocol-relative tricks -- comes back
* as an empty string and the caller skips rendering it.
*
* The explicit scheme requirement matters beyond protocol filtering:
* esc_url_raw() prepends http:// to a bare string, so an attribute-
* breakout attempt like `x" onerror="..."` would otherwise survive as a
* loadable URL pointing wherever the feed liked. Every URL the endpoint
* actually serves is already absolute https, so nothing legitimate is lost.
*/
private function sanitize_url($url) {
if(!is_scalar($url)) {
return '';
}
$url = trim((string) $url);
if(!preg_match('#^https?://#i', $url)) {
return '';
}
return esc_url_raw($url, self::allowed_protocols());
}
/**
* Rich-text bodies from the feed.
*/
private function sanitize_html($html) {
if(!is_scalar($html)) {
return '';
}
return wp_kses((string) $html, self::allowed_html(), self::allowed_protocols());
}
private function get_data() {
// Sanitize on read as well as on write: installs that already cached an
// unsanitized (or poisoned) payload get cleaned up on the next render
// without waiting for the refresh interval.
$this->data = $this->sanitize_response(get_option($this->text_domain . '__banner_data'));
$this->last_check = get_option($this->text_domain . '__banner_last_check');
$this->last_check = $this->last_check == '' ? 0 : $this->last_check;
if(($this->check_interval + $this->last_check) >= time()){
return;
}
$response = wp_remote_get( $this->api_url . '/cache/'.$this->text_domain.'.json?nocache='.time(),
[
'timeout' => 10,
'httpversion' => '1.1',
]
);
// Record the attempt whatever the outcome. The old code only stamped
// last_check after a successful, non-empty response, so an endpoint
// that was down or slow meant a 10 second blocking request on every
// single admin page load.
update_option($this->text_domain . '__banner_last_check', time());
if(is_wp_error($response) || 200 !== (int) wp_remote_retrieve_response_code($response)){
return;
}
$decoded = json_decode(wp_remote_retrieve_body($response));
if(JSON_ERROR_NONE !== json_last_error()){
return;
}
// An empty-but-valid response is accepted and stored. That is what
// makes a bad payload revocable: serving [] from the endpoint clears
// it everywhere instead of leaving the last cached copy in place.
$this->data = $this->sanitize_response($decoded);
update_option($this->text_domain . '__banner_data', $this->data);
}
public function is_correct_screen_to_show($b_screen, $screen_id) {
if(in_array($b_screen, [$screen_id, 'all_page'])) {
return true;
}
if($b_screen == 'plugin_page') {
return in_array($screen_id, (array) $this->plugin_screens);
}
return false;
}
private static $instance;
public static function instance($text_domain = '') {
self::$instance = new static();
return self::$instance->set_text_domain($text_domain);
}
}
endif;