|
Server : Apache System : Linux s1230 5.15.0-139-generic #149~20.04.1 SMP Tue Jul 14 11:21:49 UTC 2026 x86_64 User : p141464 ( 418825) PHP Version : 7.4.33.12 Disable Function : NONE Directory : /html/relaunch-kmu/wp-content/plugins/real-cookie-banner/inc/view/ |
Upload File : |
<?php
namespace DevOwl\RealCookieBanner\view;
use DevOwl\RealCookieBanner\Vendor\DevOwl\CacheInvalidate\CacheInvalidator;
use DevOwl\RealCookieBanner\Vendor\DevOwl\Customize\Utils;
use DevOwl\RealCookieBanner\Vendor\DevOwl\HeadlessContentBlocker\AttributesHelper;
use DevOwl\RealCookieBanner\Vendor\DevOwl\HeadlessContentBlocker\Constants;
use DevOwl\RealCookieBanner\Vendor\DevOwl\Multilingual\AbstractOutputBufferPlugin;
use DevOwl\RealCookieBanner\base\UtilsProvider;
use DevOwl\RealCookieBanner\Core;
use DevOwl\RealCookieBanner\settings\Consent;
use DevOwl\RealCookieBanner\settings\General;
use DevOwl\RealCookieBanner\Utils as RealCookieBannerUtils;
use DevOwl\RealCookieBanner\view\customize\banner\BasicLayout;
use DevOwl\RealCookieBanner\view\customize\banner\CustomCss;
use DevOwl\RealCookieBanner\view\customize\banner\FooterDesign;
use DevOwl\RealCookieBanner\view\customize\banner\Texts;
use DevOwl\RealCookieBanner\Vendor\MatthiasWeb\Utils\Constants as UtilsConstants;
use WP_Admin_Bar;
// @codeCoverageIgnoreStart
\defined('ABSPATH') or die('No script kiddies please!');
// Avoid direct file request
// @codeCoverageIgnoreEnd
/**
* Main banner.
* @internal
*/
class Banner
{
use UtilsProvider;
/**
* The design version is incremented each time, we need to distinguish between some
* UI elements within our cookie banner and content blockers. For example, in a recent
* version of Real Cookie Banner we removed the footer for content blockers completely, but
* to be compliant with our documentation we still want to show footers for older consents
* in our "List of consents".
*
* For the chronically changes have a look at the `@devowl-wp/react-cookie-banner` package.
*/
const DESIGN_VERSION = 12;
const ACTION_CLEAR_CURRENT_COOKIE = 'rcb-clear-current-cookie';
/**
* Example:
*
* ```html
* <script unique-write-name="gtag">console.log("this gets written to DOM");</script>
* <script unique-write-name="gtag">console.log("this gets skipped");</script>
* ```
*/
const HTML_ATTRIBUTE_UNIQUE_WRITE_NAME = 'unique-write-name';
/**
* The customize handler
*
* @var BannerCustomize
*/
private $customize;
/**
* C'tor.
*/
private function __construct()
{
$this->customize = \DevOwl\RealCookieBanner\view\BannerCustomize::instance();
}
/**
* Show a "Show banner again" button in the admin toolbar in frontend.
*
* @param WP_Admin_Bar $admin_bar
*/
public function admin_bar_menu($admin_bar)
{
if (!\is_admin() && $this->shouldLoadAssets(UtilsConstants::ASSETS_TYPE_FRONTEND) && \current_user_can(Core::MANAGE_MIN_CAPABILITY)) {
if (isset($_GET[self::ACTION_CLEAR_CURRENT_COOKIE])) {
RealCookieBannerUtils::setCookie(Core::getInstance()->getCookieConsentManagement()->getFrontend()->getCookieName(), '', -1, \constant('COOKIEPATH'), \constant('COOKIE_DOMAIN'), \is_ssl(), \false, 'None');
\wp_safe_redirect(\esc_url_raw(\add_query_arg(self::ACTION_CLEAR_CURRENT_COOKIE, \false)));
exit;
}
$admin_bar->add_menu(['parent' => Core::getInstance()->getConfigPage()->ensureAdminBarTopLevelNode(), 'id' => self::ACTION_CLEAR_CURRENT_COOKIE, 'title' => Consent::getInstance()->isBannerLessConsent() ? \__('Reset cookie banner consent to default', 'real-cookie-banner') : \__('Show cookie banner again', 'real-cookie-banner'), 'href' => \esc_url_raw(\add_query_arg(self::ACTION_CLEAR_CURRENT_COOKIE, \true))]);
}
}
/**
* Checks if the banner is active for the current page. This does not check any
* user relevant conditions because they need to be done in frontend (caching).
*
* @param string $context The context passed to `Assets#enqueue_script_and_styles`
* @see https://app.clickup.com/t/5yty88
*/
public function shouldLoadAssets($context)
{
// Are we on website frontend?
if (!\in_array($context, [UtilsConstants::ASSETS_TYPE_FRONTEND, UtilsConstants::ASSETS_TYPE_LOGIN], \true) || RealCookieBannerUtils::isPageBuilder()) {
return \false;
}
// ALways show in customize preview
if (\is_customize_preview()) {
return \true;
}
// Is the banner activated?
if (!General::getInstance()->isBannerActive()) {
return \false;
}
return RealCookieBannerUtils::isFrontend();
}
/**
* The `codeOnPageLoad` can be directly rendered to the Output Buffer cause
* it does not stand in conflict with any caching plugin (no conditional rendering).
*/
public function wp_head()
{
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- consent bootstrap snippets and blocking CSS from frontend bundle.
$additionalTags = CacheInvalidator::getInstance()->getExcludeHtmlAttributesString();
$frontend = Core::getInstance()->getCookieConsentManagement()->getFrontend();
$output = $frontend->generateCodeOnPageLoad(function ($html, $service) use($additionalTags) {
return AttributesHelper::skipHtmlTagsInContentBlocker($html, $additionalTags);
});
echo \join('', $output);
// Web vitals: Avoid large rerenderings when the content blocker gets overlapped the original item
// E.g. SVGs are loaded within the blocked element.
// Additionally, when a visual parent is used with the `childrenSelector` option, we need to hide all children of the blocked element
// when the specified children are not found.
// TODO: Move this to a frontend package?
echo \sprintf('<style>[%s]:not(.rcb-content-blocker):not([%s]):not([%s^="children:"]):not([%s]){opacity:0!important;}
.rcb-content-blocker+.rcb-content-blocker-children-fallback~*{display:none!important;}</style>', Constants::HTML_ATTRIBUTE_BLOCKER_ID, Constants::HTML_ATTRIBUTE_UNBLOCKED_TRANSACTION_COMPLETE, Constants::HTML_ATTRIBUTE_VISUAL_PARENT, Constants::HTML_ATTRIBUTE_CONFIRM);
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Print out the overlay at `wp_body_open`. See also `wp_footer` for backwards-compatibilty.
*/
public function wp_body_open()
{
$this->printOverlay();
}
/**
* Print out the overlay at `wp_footer` time but only when `wp_body_open` was not yet called (some
* themes do not support it).
*/
public function wp_footer()
{
// Some themes or caching mechanism lead to output the footer multiple times.
// Instead of finding the root cause itself, we could workaround this. In our case,
// the `div[id]` should be available only once.
\remove_action('wp_footer', [$this, 'wp_footer']);
if (!\did_action('wp_body_open')) {
$this->printOverlay();
}
$shouldLoadAssets = $this->shouldLoadAssets(UtilsConstants::ASSETS_TYPE_FRONTEND);
if ($shouldLoadAssets && !\is_customize_preview() && \get_option(FooterDesign::SETTING_POWERED_BY_LINK) && General::getInstance()->isBannerActive()) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- poweredLink returns a link.
echo $this->poweredLink();
}
}
/**
* Print out the overlay so it is server-side rendered (avoid flickering as early as possible).
*
* See also inlineStyle.tsx#overlay for more information!
*/
protected function printOverlay()
{
$customize = $this->getCustomize();
$shouldLoadAssets = $this->shouldLoadAssets(UtilsConstants::ASSETS_TYPE_FRONTEND);
if ($shouldLoadAssets && !\is_customize_preview()) {
$type = $customize->getSetting(BasicLayout::SETTING_TYPE);
$showOverlay = $customize->getSetting(BasicLayout::SETTING_OVERLAY);
$antiAdBlocker = \bool_from_yn($customize->getSetting(CustomCss::SETTING_ANTI_AD_BLOCKER));
$overlayBlur = $customize->getSetting(BasicLayout::SETTING_OVERLAY_BLUR);
// Calculate background color
$bgStyle = '';
if ($showOverlay) {
$overlayBg = $customize->getSetting(BasicLayout::SETTING_OVERLAY_BG);
$overlayBgAlpha = $customize->getSetting(BasicLayout::SETTING_OVERLAY_BG_ALPHA);
$bgStyle = \sprintf('background-color: %s;', Utils::calculateOverlay($overlayBg, $overlayBgAlpha));
}
$classPart = $antiAdBlocker ? '' : \sprintf('rcb-banner rcb-banner-%s %s', $type, empty($bgStyle) ? 'overlay-deactivated' : '');
$blurStylePart = $showOverlay && $this->isPro() ? \join('', \array_map(function ($prefix) use($overlayBlur) {
return \sprintf('%sbackdrop-filter:blur(%spx);', \esc_attr($prefix), \esc_attr((string) $overlayBlur));
}, ['-moz-', '-o-', '-webkit-', ''])) : '';
echo \sprintf('<div id="%s" %s="%s" class="%s" data-bg="%s" style="%s %s position:fixed;top:0;left:0;right:0;bottom:0;z-index:999999;pointer-events:%s;display:none;filter:none;max-width:100vw;max-height:100vh;transform:translateZ(0);" %s></div>', \esc_attr(Core::getInstance()->getPageRequestUuid4()), \esc_attr(Constants::HTML_ATTRIBUTE_CONSENT_SKIP_BLOCKER), \esc_attr(Constants::HTML_ATTRIBUTE_CONSENT_SKIP_BLOCKER_VALUE), \esc_attr(\trim($classPart)), \esc_attr($bgStyle), \esc_attr($bgStyle), \esc_attr($blurStylePart), \esc_attr(empty($bgStyle) ? 'none' : 'all'), \esc_attr(\trim(Core::getInstance()->getCompLanguage()->getSkipHTMLForTag())));
}
}
/**
* Get the "Powered by" link.
*/
protected function poweredLink()
{
$compLanguage = Core::getInstance()->getCompLanguage();
// TranslatePress / Weglot: We need to ensure that the powered by texts are not translated through `gettext`
// to avoid tags like `data-gettext`
$poweredByTexts = Texts::getPoweredByLinkTexts(!$compLanguage instanceof AbstractOutputBufferPlugin);
$currentPoweredByText = \get_option(Texts::SETTING_POWERED_BY_TEXT, 0);
$footerText = $compLanguage->translateArray([$poweredByTexts[$currentPoweredByText]])[0];
return \sprintf('<a href="%s" target="_blank" id="%s-powered-by" %s>%s</a>', \esc_url(\__('https://devowl.io/wordpress-real-cookie-banner/', 'real-cookie-banner')), \esc_attr(Core::getInstance()->getPageRequestUuid4()), \esc_attr(\trim($compLanguage->getSkipHTMLForTag())), \wp_kses_post($footerText));
}
/**
* Getter.
*
* @codeCoverageIgnore
*/
public function getCustomize()
{
return $this->customize;
}
/**
* New instance.
*
* @codeCoverageIgnore
*/
public static function instance()
{
return new \DevOwl\RealCookieBanner\view\Banner();
}
}