HEX
Server: Apache
System: Linux sg241.singhost.net 2.6.32-896.16.1.lve1.4.51.el6.x86_64 #1 SMP Wed Jan 17 13:19:23 EST 2018 x86_64
User: honghock (909)
PHP: 8.0.30
Disabled: passthru,system,shell_exec,show_source,exec,popen,proc_open
Upload Files
File: /home/honghock/public_html/wp-content/plugins/ninja-tables/app/Hooks/Handlers/NoticeHandler.php
<?php

namespace NinjaTables\App\Hooks\Handlers;

use NinjaTables\Framework\Support\Arr;

class NoticeHandler
{
    private $noticeKey = '_ninja_tables_admin_notices';
    private const TEMP_DISMISS_DAYS = 30;
    private const SECONDS_IN_A_DAY = 86400;
    private const VALID_NOTICE_TYPES = ['temp', 'permanent'];

    private static $customNotices = [];

    public function register()
    {
        $this->ensureInstalledAtTimestamp();
        add_action('admin_notices', [$this, 'appendNotices']);
        add_action('wp_ajax_ninja_tables_dismiss_admin_notices', [$this, 'handleDismissNotice']);
    }

    private function ensureInstalledAtTimestamp()
    {
        if (!get_option('ninja_tables_installed_at')) {
            add_option('ninja_tables_installed_at', current_time('mysql'));
        }
    }

    public static function addAdminNotice($key, $config)
    {
        if (empty($key) || !is_string($key)) {
            return false;
        }

        if (!isset($config['type']) || !isset($config['callback'])) {
            return false;
        }

        if (!in_array($config['type'], self::VALID_NOTICE_TYPES, true)) {
            return false;
        }

        if (!is_callable($config['callback'])) {
            return false;
        }

        self::$customNotices[$key] = wp_parse_args($config, ['condition' => true]);

        return true;
    }

    public function handleDismissNotice()
    {
        if (!check_ajax_referer('ninja_tables_admin_notice_nonce', '_wpnonce', false)) {
            $this->sendJsonError('Security check failed.', 403);
        }

        $key  = sanitize_text_field(Arr::get($_POST, 'notice_key', ''));
        $type = sanitize_text_field(Arr::get($_POST, 'notice_type', ''));

        if (empty($key) || empty($type)) {
            $this->sendJsonError('Invalid notice key or type.', 400);
        }

        if (!in_array($type, self::VALID_NOTICE_TYPES, true)) {
            $this->sendJsonError('Invalid notice type.', 400);
        }

        $notices       = get_option($this->noticeKey, []);
        $notices[$key] = ['type' => $type, 'dismissed_at' => current_time('mysql')];

        if (!update_option($this->noticeKey, $notices, false)) {
            $this->sendJsonError('Failed to save dismissal.', 500);
        }

        wp_send_json_success([
            'success' => true,
            'message' => 'Notice dismissed successfully.',
            'key'     => $key,
            'type'    => $type,
        ]);
    }

    private function sendJsonError($message, $code)
    {
        wp_send_json_error(['success' => false, 'message' => $message], $code);
    }

    public function notices()
    {
        $allNotices    = $this->getAllNoticeDefinitions();
        $dismissed     = get_option($this->noticeKey, []);
        $activeNotices = [];

        foreach ($allNotices as $key => $notice) {
            if ($this->shouldShowNotice($key, $notice, $dismissed)) {
                try {
                    $html = call_user_func($notice['callback'], $key);
                    if (!empty($html)) {
                        $activeNotices[$key] = $html;
                    }
                } catch (\Exception $e) {
                    $this->sendJsonError('Error rendering notice: ' . $e->getMessage(), 500);
                }
            }
        }

        return $activeNotices;
    }

    private function shouldShowNotice($key, $notice, $dismissed)
    {
        if (!$notice['condition']) {
            return false;
        }

        if (!isset($dismissed[$key])) {
            return true;
        }

        $dismissData = $dismissed[$key];

        if ($dismissData['type'] === 'permanent') {
            return false;
        }

        if ($notice['type'] === 'temp' && $dismissData['type'] === 'temp') {
            return $this->isTempDismissalExpired($dismissData['dismissed_at']);
        }

        return true;
    }

    private function isTempDismissalExpired($dismissedAt)
    {
        $dismissedTimestamp = strtotime($dismissedAt);
        if ($dismissedTimestamp === false) {
            return true;
        }

        $daysElapsed = (current_time('timestamp') - $dismissedTimestamp) / self::SECONDS_IN_A_DAY;

        return $daysElapsed >= self::TEMP_DISMISS_DAYS;
    }

    private function getAllNoticeDefinitions()
    {
        $notices = [];
        $segment = $this->detectUserReviewSegment();

        if ($segment !== '') {
            $reviewKey           = "review_notice_{$segment}";
            $notices[$reviewKey] = [
                'type'      => 'temp',
                'callback'  => function () use ($reviewKey, $segment) {
                    return $this->getReviewHtml($reviewKey, $segment);
                },
                'condition' => true,
            ];
        }

        $notices['upgrade_to_pro'] = [
            'type'      => 'temp',
            'callback'  => [$this, 'getUpgradeNoticeHtml'],
            'condition' => $this->shouldShowUpgradeNotice(),
        ];

        return apply_filters('ninja_tables_admin_notices', array_merge($notices, self::$customNotices));
    }

    private function detectUserReviewSegment()
    {
        global $wpdb;

        $tables = $wpdb->get_results(
            "SELECT ID, post_date FROM {$wpdb->posts} 
             WHERE post_type = 'ninja-table' AND post_status = 'publish' 
             ORDER BY post_date DESC LIMIT 10"
        );

        $installTime      = strtotime(get_option('ninja_tables_installed_at'));
        $daysSinceInstall = (current_time('timestamp') - $installTime) / self::SECONDS_IN_A_DAY;

        if (empty($tables)) {
            return $daysSinceInstall >= 7 ? 'no_tables' : '';
        }

        if ($daysSinceInstall < 7) {
            return '';
        }

        $usedDragDrop = $usedAdvanced = $createdWithin10Days = false;

        foreach ($tables as $table) {
            $provider = ninja_table_get_data_provider($table->ID);
            if ($provider === 'drag_and_drop') {
                $usedDragDrop = true;
            } else {
                $usedAdvanced = true;
            }

            if ((current_time('timestamp') - strtotime($table->post_date)) <= (self::SECONDS_IN_A_DAY * 10)) {
                $createdWithin10Days = true;
            }
        }

        if ($usedDragDrop && !$usedAdvanced) {
            return 'only_drag';
        }
        if ($usedAdvanced && !$usedDragDrop) {
            return 'only_advanced';
        }
        if ($usedDragDrop && $usedAdvanced && count($tables) >= 2 && $createdWithin10Days) {
            return 'both_modes_recent';
        }

        return '';
    }

    private function shouldShowUpgradeNotice()
    {
        return defined('NINJAPROPLUGIN_VERSION') &&
               version_compare(NINJAPROPLUGIN_VERSION, '5.0.0', '<');
    }

    private function renderNoticeTemplate($key, $message, $showRateButton = false)
    {
        $key        = esc_attr($key);
        $rateButton = '';

        if ($showRateButton) {
            $reviewUrl  = esc_url('https://wordpress.org/support/plugin/ninja-tables/reviews/?filter=5');
            $rateButton = "<a class='nt-btn nt-btn-primary' target='_blank' href='{$reviewUrl}' rel='noopener'>Rate Now</a><div class='nt-divider'></div>";
        }

        return <<<HTML
<div class="nt_review_notice" data-notice-key="{$key}">
    <div class="nt-notice-content">
        <div class="nt-notice-text">{$message}</div>
        <div class="nt-notice-actions">
            {$rateButton}
            <a class="nt-btn nt-btn-secondary remind-me-later" href="#" data-notice-type="temp">Remind Me Later</a>
        </div>
    </div>
</div>
HTML;
    }

    private function getUpgradeNoticeHtml($key)
    {
        $url     = esc_url(admin_url('plugins.php?s=ninja-tables-pro&plugin_status=all'));
        $message = "<h3>Update Ninja Tables Pro Plugin</h3><p>You are using an outdated version. Some features may not work properly. <a href='{$url}' target='_blank' class='nt-link' rel='noopener'>Please update to the latest version</a></p>";

        return $this->renderNoticeTemplate($key, $message);
    }

    private function getReviewHtml($key, $segment)
    {
        $messages = $this->getReviewMessages();
        $message  = isset($messages[$segment]) ? $messages[$segment] : '';

        if (empty($message)) {
            return '';
        }

        return $this->renderNoticeTemplate($key, $message, $segment === 'both_modes_recent');
    }

    private function getReviewMessages()
    {
        return [
            'no_tables' => "Having trouble creating your first table? Check out Ninja Tables <a class='nt-link' href='https://ninjatables.com/docs/' target='_blank'>documentation</a> or watch <a class='nt-link' href='https://youtube.com/playlist?list=PLXpD0vT4thWGhHDY0X7UpN9JoR0vu2O_C&si=XMx60a-0AGu7KxZB' target='_blank'>tutorial videos</a> to get you started.",

            'only_drag' => "Looks like you're having fun with Drag & Drop!<br>Did you know Ninja Tables has a lot more fun features in the Advanced Table mode? See the <a class='nt-link' href='https://ninjatables.com/docs-category/advanced-mode/' target='_blank'>documentation</a> and try it!",

            'only_advanced' => "Looks like you're having fun using Advanced mode for your tables.<br>Did you know Ninja Tables makes things even easier in Drag and Drop mode? See the <a class='nt-link' href='https://ninjatables.com/docs-category/simple-mode/' target='_blank'>documentation</a> and try it!",

            'both_modes_recent' => "You're doing amazing!<br><div class='flex items-center'>Loving Ninja Tables? Leave us a " . $this->getStarsSvg(
                ) . " review. It will encourage us to come up with more and more features.</div>"
        ];
    }

    private function getStarsSvg()
    {
        $svg = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 20 20" fill="none" style="vertical-align: middle; margin-right: 2px;"><path d="M9.9992 14.695L4.70945 17.656L5.8907 11.71L1.43945 7.594L7.4597 6.88L9.9992 1.375L12.5387 6.88L18.559 7.594L14.1077 11.71L15.289 17.656L9.9992 14.695Z" fill="#F6B51E"/></svg>';

        return str_repeat($svg, 5);
    }

    public function appendNotices()
    {
        $notices = $this->notices();

        if (empty($notices)) {
            return;
        }

        wp_localize_script('ninja-tables', 'ninjaTablesAdminNotices', [
            'notices'  => $notices,
            'ajax_url' => admin_url('admin-ajax.php'),
            'nonce'    => wp_create_nonce('ninja_tables_admin_notice_nonce'),
        ]);
    }
}