Plugin Directory

Changeset 3795776


Ignore:
Timestamp:
11/11/2026 07:09:11 AM (less than one hour ago)
Author:
calliko
Message:

Update to version 8.5.2 from GitHub

Location:
bonus-for-woo
Files:
20 added
48 edited
1 copied

Legend:

Unmodified
Added
Removed
  • bonus-for-woo/tags/8.5.2/_inc/bonus-computy-script.js

    r3784081 r3795776  
     1/*
     2 * Nonce больше не печатается в HTML страницы: под полностраничным кешем он
     3 * отдавался протухшим, и запросы падали с 403. Получаем его один раз при
     4 * загрузке и подставляем вместо лениво читаемого wpApiSettings.nonce.
     5 */
     6window.bfwRestHeaders = function (extra) {
     7    return Object.assign({}, extra || {}, { 'X-WP-Nonce': window.bfwRestNonce || '' });
     8};
     9
    110jQuery(document).ready(function ($) {
     11    if (window.wpApiSettings && window.wpApiSettings.isLoggedIn && window.wpApiSettings.nonceAjaxUrl) {
     12        fetch(window.wpApiSettings.nonceAjaxUrl, { credentials: 'same-origin' })
     13            .then(function (r) { return r.ok ? r.json() : null; })
     14            .then(function (res) { if (res && res.nonce) { window.bfwRestNonce = res.nonce; } })
     15            .catch(function () {});
     16    }
     17
    218    /*действие при удалении баллов   */
    319    $('body').on('click', '#bfw_remove_cart_point', async function () {
    … …  
    1026                    'Content-Type': 'application/json',
    1127                    // Если требуется nonce (для авторизованных действий):
    12                     'X-WP-Nonce': wpApiSettings.nonce
     28                    'X-WP-Nonce': window.bfwRestNonce || ''
    1329                },
    1430                body: JSON.stringify({
    … …  
    4965                    'Content-Type': 'application/json',
    5066                    // Если используете nonce:
    51                     'X-WP-Nonce': wpApiSettings.nonce
     67                    'X-WP-Nonce': window.bfwRestNonce || ''
    5268                },
    5369                body: JSON.stringify({
    … …  
    164180                    'Content-Type': 'application/json',
    165181                    // Добавляем nonce для безопасности REST API
    166                     'X-WP-Nonce': wpApiSettings.nonce // Убедитесь, что передаете nonce через wp_localize_script
     182                    'X-WP-Nonce': window.bfwRestNonce || ''
    167183                },
    168184                body: JSON.stringify({
    … …  
    213229                    'Content-Type': 'application/json',
    214230                    // Используем стандартный Nonce WordPress или ваш bfw_params.nonce
    215                     'X-WP-Nonce': typeof wpApiSettings !== 'undefined' ? wpApiSettings.nonce : bfw_params.nonce
     231                    'X-WP-Nonce': window.bfwRestNonce || ''
    216232                },
    217233                body: JSON.stringify(formData)
    … …  
    271287
    272288        const API_BASE = '/wp-json/bfw/v1';
    273         const NONCE = typeof wpApiSettings !== 'undefined' ? wpApiSettings.nonce : '';
     289        // Читаем в момент запроса: nonce приходит AJAX-ом после загрузки страницы
     290        const NONCE = () => window.bfwRestNonce || '';
    274291
    275292        const debounce = (func, delay) => {
    … …  
    287304                const response = await fetch(`${API_BASE}${endpoint}`, {
    288305                    method: 'POST',
    289                     headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': NONCE },
     306                    headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': NONCE() },
    290307                    body: JSON.stringify(params)
    291308                });
    … …  
    336353                const response = await fetch(`${API_BASE}/check-points-only-products`, {
    337354                    method: 'POST',
    338                     headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': NONCE },
     355                    headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': NONCE() },
    339356                    body: JSON.stringify({})
    340357                });
  • bonus-for-woo/tags/8.5.2/changelog.txt

    r3784081 r3795776  
     1
     2= 8.5.2 - 2026-11-04 =
     3* Fixed SQL injection in the coupons list ORDER BY.
     4* Added rate limiting for failed coupon code entries.
     5* Points export is streamed, never stored on disk.
     6* Redirect parameter validated on all entry points.
     7* CSV imports read the uploaded file instead of a client path.
     8* Unified capability for menu and AJAX handlers.
     9* Batched points export.
    110
    211= 7.6.12 - 2026-03-23 =
  • bonus-for-woo/tags/8.5.2/classes/BfwAdmin.php

    r3784081 r3795776  
    6060    public static function init(): void
    6161    {
    62         if (current_user_can('manage_options') || (defined('WP_CLI') && WP_CLI)) {
     62        if (current_user_can(BFW_CAP_MANAGE) || (defined('WP_CLI') && WP_CLI)) {
    6363            /* Инициализируем меню в админке*/
    6464            add_action('admin_menu', array('BfwAdmin', 'add_admin_menu'));
    … …  
    494494    public static function bfwoo_action_pre_get_users($query): void
    495495    {
    496         if (!current_user_can('manage_options')) {
     496        if (!current_user_can(BFW_CAP_MANAGE)) {
    497497            return;
    498498        }
    … …  
    609609        $menu_list_history_title = __('History of bonus points for all customers', 'bonus-for-woo');
    610610
     611        /*
     612         * Одна capability на всю бонусную систему: раньше пункты меню были
     613         * раскиданы между manage_options и manage_woocommerce, из-за чего
     614         * shop manager видел страницы, обработчики которых его отклоняли.
     615         * Настройки и логи дополнительно защищены capability по умолчанию.
     616         */
    611617        /*Страница с основными настройками*/
    612         add_menu_page($home_menu_title, $home_menu_title, 'manage_options', 'bonus_for_woo-plugin-options',
     618        add_menu_page($home_menu_title, $home_menu_title, BFW_CAP_MANAGE, 'bonus_for_woo-plugin-options',
    613619                array('BfwAdmin', 'bonus_plugin_options'), plugins_url('bonus-for-woo/img/coin.svg'), 30);
    614620
    615621        /* Добавляем в меню историю всех начислений клиентов*/
    616         add_submenu_page('bonus_for_woo-plugin-options', $menu_list_history_title, $menu_list_history, 'manage_options',
     622        add_submenu_page('bonus_for_woo-plugin-options', $menu_list_history_title, $menu_list_history, BFW_CAP_MANAGE,
    617623                'bonus-for-woo/pages/list_history.php', '', 1);
    618624
    619625        /*cтраница статистик*/
    620626        add_submenu_page('bonus_for_woo-plugin-options', __('Bonus points statistic - Bonus for Woo', 'bonus-for-woo'),
    621                 __('Bonus system statistic', 'bonus-for-woo'), 'manage_woocommerce',
     627                __('Bonus system statistic', 'bonus-for-woo'), BFW_CAP_MANAGE,
    622628                'bonus-for-woo/pages/statistic.php', '', 2);
    623629
    … …  
    626632            add_submenu_page('bonus_for_woo-plugin-options',
    627633                    __('Bonus points coupons - Bonus for Woo', 'bonus-for-woo'),
    628                     __('Bonus points coupons', 'bonus-for-woo'), 'manage_woocommerce',
     634                    __('Bonus points coupons', 'bonus-for-woo'), BFW_CAP_MANAGE,
    629635                    'bonus-for-woo/pages/coupons.php', '', 3);
    630636        }
    631637        add_submenu_page('bonus_for_woo-plugin-options', __('Tools', 'bonus-for-woo'), __('Tools', 'bonus-for-woo'),
    632                 'manage_woocommerce', 'bonus-for-woo/pages/tools.php', '', 4);
     638                BFW_CAP_MANAGE, 'bonus-for-woo/pages/tools.php', '', 4);
    633639
    634640
    635641        /*Генератор правил и условий*/
    636642        add_submenu_page('', __('Rules and Conditions Generator', 'bonus-for-woo'),
    637                 __('Rules and Conditions Generator', 'bonus-for-woo'), 'manage_woocommerce',
     643                __('Rules and Conditions Generator', 'bonus-for-woo'), BFW_CAP_MANAGE,
    638644                'bonus-for-woo/pages/generator.php', '', 4);
    639645
    … …  
    641647        /* Добавляем в меню историю всех начислений клиентов*/
    642648        add_submenu_page('bonus_for_woo-plugin-options', __('Logs', 'bonus-for-woo'), __('Logs', 'bonus-for-woo'),
    643                 'manage_options', 'bonus-for-woo/pages/logs.php', '', 5);
     649                BFW_CAP_MANAGE, 'bonus-for-woo/pages/logs.php', '', 5);
    644650
    645651    }
    … …  
    706712    {
    707713        if (isset($_GET['bfw_delete_all_post_history_points'])) {
     714            if (!current_user_can(BFW_CAP_MANAGE)) {
     715                wp_die(esc_html__('You do not have permission to do this.', 'bonus-for-woo'));
     716            }
     717
    708718            if (!isset($_GET['_wpnonce']) || !wp_verify_nonce($_GET['_wpnonce'], 'bfw_delete_all_history')) {
    709                 wp_die('Ошибка безопасности!');
     719                wp_die(esc_html__('Security check failed.', 'bonus-for-woo'));
    710720            }
    711721            $delete_history_points = (int) $_GET['bfw_delete_all_post_history_points'];
    … …  
    31813191    public static function bonus_plugin_options(): void
    31823192    {
    3183         if (!current_user_can('manage_options')) {
     3193        // Настройки — сборка бонусной системы, право то же, что у меню
     3194        if (!current_user_can(BFW_CAP_MANAGE)) {
    31843195            return;
    31853196        }
    … …  
    32283239                        </div>
    32293240
    3230 <?php
    3231 /**
    3232  * Парсит страницу https://computy.ru/blog/tag/sajty/
    3233  * Возвращает массив с заголовками и датами последних 5 постов.
    3234  */
    3235 function computy_parse_tag_page() {
    3236     $url = 'https://computy.ru/blog/tag/bfw/';
    3237     $cache_key = 'computy_tag_posts_parsed3';
    3238     $cached = get_transient($cache_key);
    3239 
    3240     // Если данные есть в кеше, возвращаем их
    3241     if ($cached !== false) {
    3242         return $cached;
    3243     }
    3244 
    3245     // Загружаем страницу
    3246     $response = wp_remote_get($url, array(
    3247             'timeout' => 15,
    3248             'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
    3249     ));
    3250 
    3251     if (is_wp_error($response)) {
    3252         return array('error' => 'Не удалось загрузить страницу: ' . $response->get_error_message());
    3253     }
    3254 
    3255     $html = wp_remote_retrieve_body($response);
    3256     if (empty($html)) {
    3257         return array('error' => 'Страница пуста');
    3258     }
    3259 
    3260     // Парсим HTML
    3261     $posts = array();
    3262 
    3263     // Используем DOMDocument для парсинга
    3264     $dom = new DOMDocument();
    3265     // Подавляем ошибки из-за невалидного HTML
    3266     libxml_use_internal_errors(true);
    3267     // Конвертируем в UTF-8, чтобы избежать проблем с кодировкой
    3268     $dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
    3269     libxml_clear_errors();
    3270 
    3271     $xpath = new DOMXPath($dom);
    3272 
    3273     // Находим все элементы article с классом 'loop'
    3274     $articles = $xpath->query("//article[contains(@class, 'loop')]");
    3275 
    3276     $count = 0;
    3277     foreach ($articles as $article) {
    3278         if ($count >= 5) break;
    3279 
    3280         // Ищем заголовок внутри .kakh3 > a
    3281         $title_node = $xpath->query(".//div[contains(@class, 'kakh3')]/a", $article);
    3282         $title = $title_node->length > 0 ? trim($title_node->item(0)->nodeValue) : '';
    3283 
    3284         // Ищем дату внутри .bottom-loop > ul > li с датой
    3285         // Дата находится в span[itemprop='datePublished']
    3286         $date_node = $xpath->query(".//span[@itemprop='datePublished']", $article);
    3287         $date = $date_node->length > 0 ? trim($date_node->item(0)->nodeValue) : '';
    3288 
    3289         // Если дата не нашлась, пробуем другой вариант
    3290         if (empty($date)) {
    3291             // Ищем паттерн даты вида "02.09.2026г."
    3292             preg_match('/\d{2}\.\d{2}\.\d{4}г\./', $article->textContent, $matches);
    3293             $date = !empty($matches) ? trim($matches[0]) : '';
    3294         }
    3295 
    3296         if (!empty($title) && !empty($date)) {
    3297             $posts[] = array(
    3298                     'title' => $title,
    3299                     'date' => $date,
    3300                     'link' => $title_node->length > 0 ? $title_node->item(0)->getAttribute('href') : ''
    3301             );
    3302             $count++;
    3303         }
    3304     }
    3305 
    3306     // Сохраняем в кеш на 1 час
    3307     set_transient($cache_key, $posts, HOUR_IN_SECONDS);
    3308 
    3309     return $posts;
    3310 }
    3311 /**
    3312  * Вывод виджета
    3313  */
    3314 function computy_dashboard_widget_render() {
    3315     $posts = computy_parse_tag_page();
    3316 
    3317     if (isset($posts['error'])) {
    3318         echo '<p style="color: #d63638;">⚠️ ' . esc_html($posts['error']) . '</p>';
    3319         return;
    3320     }
    3321 
    3322     if (empty($posts)) {
    3323         echo '<p>Статей не найдено.</p>';
    3324         return;
    3325     }
    3326 
    3327     echo '<ul style="list-style: auto; margin: 0; padding: 0;">';
    3328     foreach ($posts as $post) {
    3329         $title = esc_html($post['title']);
    3330         $date = esc_html($post['date']);
    3331         $link = esc_url($post['link']);
    3332 
    3333         echo '<li>';
    3334         if (!empty($link)) {
    3335             echo '<a href="' . $link . '" target="_blank" >';
    3336             echo $title;
    3337             echo '</a>';
    3338         } else {
    3339             echo '<strong>' . $title . '</strong>';
    3340         }
    3341         echo ' <span style="color: #fff; font-size: 12px;">📅 ' . $date . '</span>';
    3342         echo '</li>';
    3343     }
    3344     echo '</ul>';
    3345 }
    3346 
    3347 echo computy_dashboard_widget_render();
    3348 
    3349 ?>
     3241<?php echo BfwBlogWidget::render(); ?>
    33503242
    33513243                        <div class="bfw-sidebar-footer">
    … …  
    36383530                                </div>
    36393531                                <form action="https://computy.ru/buy_plugin?bonus-for-woo=buy" method="post">
    3640                                     <input type="hidden" name="successURL" value="<?php echo get_site_url() . $_SERVER['REQUEST_URI']; ?>">
    3641                                     <input type="hidden" name="label" value="<?php echo get_site_url(); ?>|bonus-for-woo|<?php echo BONUS_COMPUTY_VERSION; ?>|<?php echo get_option('admin_email'); ?>">
     3532                                    <input type="hidden" name="successURL" value="<?php echo esc_attr(admin_url('admin.php?page=bonus_for_woo-plugin-options')); ?>">
     3533                                    <input type="hidden" name="label" value="<?php echo esc_attr(wp_unslash(get_site_url(null, '') . '|bonus-for-woo|' . BONUS_COMPUTY_VERSION . '|' . get_option('admin_email'))); ?>">
    36423534                                    <button type="submit" class="bfw-btn bfw-btn-primary" style="padding: 16px 50px; font-size: 18px; background: #e67e22; border-radius: 12px;">
    36433535                                        <?php _e('Buy', 'bonus-for-woo'); ?>
  • bonus-for-woo/tags/8.5.2/classes/BfwAnalytics.php

    r3784081 r3795776  
    4747    {
    4848        check_ajax_referer('bfw_analytics_nonce', 'nonce');
    49         if (!current_user_can('manage_options')) {
     49        if (!current_user_can(BFW_CAP_MANAGE)) {
    5050            wp_send_json_error('Access denied');
    5151        }
    … …  
    9191    {
    9292        check_ajax_referer('bfw_analytics_nonce', 'nonce');
    93         if (!current_user_can('manage_options')) {
     93        if (!current_user_can(BFW_CAP_MANAGE)) {
    9494            wp_send_json_error('Access denied');
    9595        }
    … …  
    158158    {
    159159        check_ajax_referer('bfw_analytics_nonce', 'nonce');
    160         if (!current_user_can('manage_options')) {
     160        if (!current_user_can(BFW_CAP_MANAGE)) {
    161161            wp_send_json_error('Access denied');
    162162        }
    … …  
    199199    {
    200200        check_ajax_referer('bfw_analytics_nonce', 'nonce');
    201         if (!current_user_can('manage_options')) {
     201        if (!current_user_can(BFW_CAP_MANAGE)) {
    202202            wp_send_json_error('Access denied');
    203203        }
    … …  
    236236    {
    237237        check_ajax_referer('bfw_analytics_nonce', 'nonce');
    238         if (!current_user_can('manage_options')) {
     238        if (!current_user_can(BFW_CAP_MANAGE)) {
    239239            wp_send_json_error('Access denied');
    240240        }
    … …  
    298298    {
    299299        check_ajax_referer('bfw_analytics_nonce', 'nonce');
    300         if (!current_user_can('manage_options')) {
     300        if (!current_user_can(BFW_CAP_MANAGE)) {
    301301            wp_send_json_error('Access denied');
    302302        }
    … …  
    353353    {
    354354        check_ajax_referer('bfw_analytics_nonce', 'nonce');
    355         if (!current_user_can('manage_options')) {
     355        if (!current_user_can(BFW_CAP_MANAGE)) {
    356356            wp_send_json_error('Access denied');
    357357        }
    … …  
    367367    public static function exportToCSV(string $type): void
    368368    {
    369         if (!current_user_can('manage_options')) {
     369        if (!current_user_can(BFW_CAP_MANAGE)) {
    370370            wp_die('Access denied');
    371371        }
    … …  
    418418    {
    419419        check_ajax_referer('bfw_analytics_nonce', 'nonce');
    420         if (!current_user_can('manage_options')) {
     420        if (!current_user_can(BFW_CAP_MANAGE)) {
    421421            wp_send_json_error('Access denied');
    422422        }
  • bonus-for-woo/tags/8.5.2/classes/BfwCashback.php

    r3784081 r3795776  
    562562    public static function computyMassAddPoints(): void
    563563    {
    564         if (!current_user_can('manage_options')) {
     564        if (!current_user_can(BFW_CAP_MANAGE)) {
    565565            wp_send_json_error('Access denied');
    566566            return;
  • bonus-for-woo/tags/8.5.2/classes/BfwCoupons.php

    r3784081 r3795776  
    1212class BfwCoupons
    1313{
     14    /** Сколько неудачных попыток ввести код допустимо в окне */
     15    const MAX_FAILED_COUPON_ATTEMPTS = 5;
     16
     17    /** Длина окна ограничения перебора, секунд */
     18    const FAILED_COUPON_WINDOW = 900;
    1419
    1520    /**
    … …  
    348353    {
    349354        $code_coupon = trim($code_coupon);
     355
     356        /*
     357         * Защита от перебора кодов. Раньше счётчик daily_coupon увеличивался
     358         * только при успешной активации, а на неверный код сразу возвращался
     359         * 'not_coupon' — то есть подбирать купоны (а купон = деньги) можно было
     360         * без ограничений. Считаем неудачи в transient до поиска купона.
     361         */
     362        $fail_key = 'bfw_coupon_fails_' . $userid;
     363        $fails = (int) get_transient($fail_key);
     364
     365        if ($fails >= self::MAX_FAILED_COUPON_ATTEMPTS) {
     366            return 'limit';
     367        }
     368
    350369        $coupon = self::getCoupon($code_coupon);
    351370
    352371        if (!isset($coupon->code) || $coupon->status !== 'active') {
     372            set_transient($fail_key, $fails + 1, self::FAILED_COUPON_WINDOW);
    353373            return 'not_coupon';
    354374        }
     375
     376        delete_transient($fail_key);
    355377
    356378        // Лимит попыток в день
    … …  
    467489    public static function bfwExportCoupons(): void
    468490    {
    469         if (!current_user_can('manage_options')) {
     491        if (!current_user_can(BFW_CAP_MANAGE)) {
    470492            wp_send_json_error(__('Access denied.', 'bonus-for-woo'));
    471493            return;
    … …  
    477499        }
    478500
    479         $response = json_decode(stripslashes($_POST['response']), true);
    480         $url_export_file = $response['data']['url']; // ссылка на загруженный файл экспорта
    481 
    482         $upload_dir = wp_upload_dir();
    483         if (strpos($url_export_file, $upload_dir['baseurl']) !== 0 && strpos($url_export_file, $upload_dir['basedir']) !== 0) {
    484             wp_send_json_error(__('Invalid file path.', 'bonus-for-woo'));
     501        /*
     502         * Раньше путь приходил в JSON от клиента и проверялся только по префиксу
     503         * uploads: `.../uploads/../../wp-config.php` проходил проверку. Теперь
     504         * файл читается из загрузки запроса и на сайте не сохраняется.
     505         */
     506        $uploaded = $_FILES['bfw_import_file'] ?? null;
     507
     508        if (empty($uploaded['tmp_name']) || (int) ($uploaded['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK
     509                || !is_uploaded_file($uploaded['tmp_name'])) {
     510            wp_send_json_error(__('The file was not uploaded.', 'bonus-for-woo'));
    485511            return;
    486512        }
    487513
    488 
    489         $fileHandle = fopen($url_export_file, "rb");
     514        $fileHandle = fopen($uploaded['tmp_name'], 'rb');
    490515
    491516        if ($fileHandle === false) {
    492             die(__('Error opening', 'bonus-for-woo') . ' ' . htmlspecialchars($url_export_file));
     517            wp_send_json_error(__('Cannot open the uploaded file.', 'bonus-for-woo'));
     518            return;
    493519        }
    494520
    … …  
    517543
    518544        fclose($fileHandle);
    519         echo 'good';
    520         exit();
    521     }
    522 
    523     /**todo проверить на работоспособность. Возможно заменить верхним
    524      *
    525      * @return void
    526      */
    527     public static function NEWbfwExportCoupons(): void
    528     {
    529         // Проверяем nonce для безопасности
    530         check_ajax_referer('bfw_export_nonce', 'nonce');
    531 
    532         $response = json_decode(stripslashes($_POST['response']), true);
    533 
    534         // Проверяем наличие данных
    535         if (!isset($response['data']['url'])) {
    536             wp_die(__('Invalid data', 'bonus-for-woo'));
    537         }
    538 
    539         $url_export_file = $response['data']['url'];
    540 
    541         // Инициализируем WP_Filesystem
    542         global $wp_filesystem;
    543 
    544         if (!function_exists('WP_Filesystem')) {
    545             require_once ABSPATH . 'wp-admin/includes/file.php';
    546         }
    547 
    548         WP_Filesystem();
    549 
    550         // Проверяем, что файловая система доступна
    551         if (!$wp_filesystem || !$wp_filesystem->exists($url_export_file)) {
    552             wp_die(__('Error accessing file', 'bonus-for-woo') . ' ' . esc_html($url_export_file));
    553         }
    554 
    555         // Получаем содержимое файла
    556         $file_content = $wp_filesystem->get_contents($url_export_file);
    557 
    558         if ($file_content === false) {
    559             wp_die(__('Error reading file', 'bonus-for-woo'));
    560         }
    561 
    562         // Обрабатываем CSV данные
    563         $lines = explode("\n", $file_content);
    564         global $wpdb;
    565         $table = $wpdb->prefix . 'bfw_coupons_computy';
    566 
    567 
    568         $processed = 0;
    569         $total_lines = count($lines);
    570 
    571         for ($j = 0; $j < $total_lines; $j += BFW_CSV_IMPORT_LIMIT) {
    572             $end = min($j + BFW_CSV_IMPORT_LIMIT, $total_lines);
    573 
    574             for ($i = $j; $i < $end; $i++) {
    575                 // Пропускаем пустые строки
    576                 if (empty(trim($lines[$i]))) {
    577                     continue;
    578                 }
    579 
    580                 // Парсим CSV строку
    581                 $currRow = str_getcsv($lines[$i]);
    582 
    583                 // Проверяем корректность строки
    584                 if (count($currRow) < 5) {
    585                     continue;
    586                 }
    587 
    588                 $coupon = (string)$currRow[0];
    589                 $sum = (float)$currRow[1];
    590                 $comment_admin = $currRow[2];
    591                 $status = in_array($currRow[3], ['active', 'noactive', 'used']) ? $currRow[3] : 'noactive';
    592                 $reusable = isset($currRow[4]) ? (int)$currRow[4] : 0;
    593 
    594                 // Пропускаем заголовок, если он есть
    595                 if ($coupon === 'code' || $coupon === 'comment_admin') {
    596                     continue;
    597                 }
    598 
    599                 // Проверяем существование купона
    600                 $safe_table_name = str_replace('`', '``', $table);
    601                 $existing_coupon = $wpdb->get_var(
    602                         $wpdb->prepare(
    603                                 "SELECT code FROM `{$safe_table_name}` WHERE code = %s",
    604                                 $coupon
    605                         )
    606                 );
    607 
    608                 if (!$existing_coupon) {
    609                     self::addCoupon($coupon, $sum, $comment_admin, $status, $reusable);
    610                     $processed++;
    611                 }
    612             }
    613 
    614             // Делаем небольшую паузу между пакетами для оптимизации нагрузки
    615             if (($j + BFW_CSV_IMPORT_LIMIT) < $total_lines) {
    616                 sleep(1);
    617             }
    618         }
    619 
    620         // Удаляем временный файл, если он был загружен
    621         if (strpos($url_export_file, wp_upload_dir()['basedir']) !== false) {
    622             $wp_filesystem->delete($url_export_file);
    623         }
    624 
    625         wp_send_json_success(array(
    626                 'message' => __('Coupons imported successfully', 'bonus-for-woo'),
    627                 'processed' => $processed
    628         ));
    629     }
    630 
     545
     546        wp_send_json_success(array('message' => __('Coupons imported successfully', 'bonus-for-woo')));
     547    }
    631548
    632549    public static function getListCodeCoupons()
    … …  
    641558        // Проверяем, ошибка ли "купон не найден"
    642559
    643         if ($err_code == '105') {
     560        if ($err_code == '105' && $coupon instanceof WC_Coupon) {
    644561
    645562            $arrayCoupons = self::getListCodeCoupons();
  • bonus-for-woo/tags/8.5.2/classes/BfwFilters.php

    r3784081 r3795776  
    9292
    9393        add_filter('woocommerce_coupon_error', function ($err, $err_code, $coupon) {
     94            if (!($coupon instanceof WC_Coupon)) {
     95                return $err;
     96            }
    9497            $bonus_coupon_code = mb_strtolower(trim(BfwSetting::get('bonus-points-on-cart') ??  __('Bonus points', 'bonus-for-woo')));
     98
    9599            if (strtolower($coupon->get_code()) === $bonus_coupon_code) {
    96100                return false; // Подавляем ошибку для бонусного купона
  • bonus-for-woo/tags/8.5.2/classes/BfwFunctions.php

    r3784081 r3795776  
    1313{
    1414
     15    /**
     16     * Нужны ли ассеты плагина на текущей странице.
     17     *
     18     * Раньше CSS и JS подключались на каждой странице сайта, хотя работают
     19     * только там, где есть корзина, оформление, личный кабинет, карточка
     20     * товара или любой из шорткодов плагина.
     21     *
     22     * @return bool
     23     * @since 8.5.2
     24     */
     25    public static function isBonusContext(): bool
     26    {
     27        $context = false;
     28
     29        if (function_exists('is_cart') && (is_cart()
     30                || is_checkout()
     31                || is_account_page()
     32                || is_product()
     33                || (function_exists('is_wc_endpoint_url') && is_wc_endpoint_url()))) {
     34            $context = true;
     35        }
     36
     37        if (!$context && is_singular()) {
     38            $post_content = (string) get_post_field('post_content', get_queried_object_id());
     39
     40            foreach (array('bfw_', 'bfw-', 'link_on_rulles') as $shortcode_prefix) {
     41                if (has_shortcode($post_content, $shortcode_prefix)
     42                        || strpos($post_content, '[' . $shortcode_prefix) !== false) {
     43                    $context = true;
     44                    break;
     45                }
     46            }
     47        }
     48
     49        /**
     50         * Позволяет теме/другому плагину включить ассеты там, где нужно.
     51         *
     52         * @param bool $context
     53         */
     54        return (bool) apply_filters('bfw_enqueue_assets', $context);
     55    }
     56
     57
    1558    /**  Добавляем стили на фронте
    1659     *
    … …  
    1962    public static function bfwooComputyStyles(): void
    2063    {
     64        if (!self::isBonusContext()) {
     65            return;
     66        }
     67
    2168        wp_register_style(
    2269                'bonus-computy-style',
    … …  
    3582    public static function bfwooComputyScript(): void
    3683    {
     84        if (!self::isBonusContext()) {
     85            return;
     86        }
     87
    3788        wp_register_script(
    3889                'bonus-computy-script',
    … …  
    4394        );
    4495        wp_enqueue_script('bonus-computy-script');
     96
     97        /*
     98         * Nonce REST живёт 12 часов и, выведенный в HTML, умирает под
     99         * полностраничным кешем: страница отдаётся из кеша, а nonce в ней уже
     100         * просрочен, и запросы списания/начисления падают с 403. Раньше его
     101         * печатали на каждой странице сайта. Теперь отдаём странице только
     102         * флаг «залогинен», а nonce JS получает по AJAX (bfw/v1/nonce).
     103         */
    45104        wp_localize_script('bonus-computy-script', 'wpApiSettings', [
    46                 'nonce' => wp_create_nonce('wp_rest'),
    47                  'isLoggedIn' => is_user_logged_in(),
     105                'nonceAjaxUrl' => esc_url_raw(rest_url('bfw/v1/nonce')),
     106                'isLoggedIn'  => is_user_logged_in(),
    48107        ]);
    49108    }
    … …  
    51110
    52111    /**
    53      * Path to the points export CSV.
    54      *
    55      * The file used to live inside the plugin directory. A plugin installed from
    56      * the admin screen cannot write there (and on nginx the bundled .htaccess
    57      * does nothing), so the export silently failed. Uploads are writable and,
    58      * with the default flat structure, not web-readable.
    59      *
    60      * @return string
    61      * @since 8.5.1
    62      */
    63     public static function getExportFilePath(): string
     112     * Пути к legacy-файлам экспорта баллов, которые писались на диск.
     113     *
     114     * Версии <= 8.5.0 клали CSV в каталог плагина (туда нет прав записи при
     115     * установке из админки), 8.5.1 — в uploads под фиксированным именем, где
     116     * файл был доступен по прямому публичному URL. Начиная с 8.5.2 экспорт
     117     * отдаётся потоком и на диск не пишется; файлы старых версий надо удалить.
     118     *
     119     * @return string[]
     120     * @since 8.5.2
     121     */
     122    public static function getLegacyExportFilePaths(): array
    64123    {
    65124        $upload_dir = wp_upload_dir(null, false);
    66125
    67         return wp_normalize_path(trailingslashit($upload_dir['basedir']) . 'bfw_points_export.csv');
     126        return array(
     127            wp_normalize_path(BONUS_COMPUTY_PLUGIN_DIR . '/export_bfw.csv'),
     128            wp_normalize_path(trailingslashit($upload_dir['basedir']) . 'bfw_points_export.csv'),
     129        );
     130    }
     131
     132    /**
     133     * Удаляет файлы экспорта, оставшиеся от старых версий.
     134     *
     135     * @return void
     136     * @since 8.5.2
     137     */
     138    public static function deleteLegacyExportFile(): void
     139    {
     140        foreach (self::getLegacyExportFilePaths() as $path) {
     141            if (is_file($path)) {
     142                wp_delete_file($path);
     143            }
     144        }
    68145    }
    69146
  • bonus-for-woo/tags/8.5.2/classes/BfwHistory.php

    r3784081 r3795776  
    106106            <h3><?php echo $history_title; ?></h3>
    107107
    108             <?php if (current_user_can('manage_options')): ?>
     108            <?php if (current_user_can(BFW_CAP_MANAGE)): ?>
    109109                <a class="clear_history" href="javascript:void(0);" onclick="return confirmClearHistory();">
    110110                    <?php echo __('Clear the history', 'bonus-for-woo'); ?>
    … …  
    134134                <th><?php echo BfwPoints::pointsLabel(5); ?></th>
    135135                <th><?php echo __('Event', 'bonus-for-woo'); ?></th>
    136                 <?php if (current_user_can('manage_options')): ?>
     136                <?php if (current_user_can(BFW_CAP_MANAGE)): ?>
    137137                    <th><?php echo __('Action', 'bonus-for-woo'); ?></th>
    138138                <?php endif; ?>
    … …  
    148148                if ($item->orderz != '0') {
    149149                    $order_text = __('Order', 'bonus-for-woo') . ' №' . $item->orderz;
    150                     if (current_user_can('manage_options')) {
     150                    if (current_user_can(BFW_CAP_MANAGE)) {
    151151                        $order_link = sprintf(
    152152                                '<a href="%s">%s</a> ',
    … …  
    183183                    </td>
    184184                    <td><?php echo $order_link . wp_kses_post($item->comment_admin); ?></td>
    185                     <?php if (current_user_can('manage_options')): ?>
     185                    <?php if (current_user_can(BFW_CAP_MANAGE)): ?>
    186186                        <td>
    187187                            <form method="post" class="list_role_computy">
    … …  
    295295<td><span style="color:' . esc_attr($color) . ' ">' . esc_html($bfw->symbol . BfwPoints::formatNumber((float) $bfw->points)) . '</span></td>
    296296<td>' . $getorderz . wp_kses_post($bfw->comment_admin) . '</td>';
    297                     if (current_user_can('manage_options')) {
     297                    if (current_user_can(BFW_CAP_MANAGE)) {
    298298                        echo '<td><form method="post" action="" class="list_role_computy">
    299299                        ' . wp_nonce_field('bfw_action_history', 'bfw_nonce_history', true, false) . '
  • bonus-for-woo/tags/8.5.2/classes/BfwLicense.php

    r3784081 r3795776  
    77 * Проверка лицензии по криптографической подписи сервера.
    88 *
    9  * Почему так:
    10  *  - Раньше статус PRO хранился строкой в wp_options и проверялся сравнением
    11  *    get_option(...) === 'active'. Достаточно было одного SQL-запроса или
    12  *    любого плагина, чтобы включить платный режим. Обфускация base64_decode
    13  *    ничего не защищала — ключ декодировался одной командой.
    14  *  - Теперь решение принимается по ПОДПИСИ сервера лицензий: сервер
    15  *    подписывает payload своим приватным ключом, плагин проверяет вшитым
    16  *    публичным. Изменить payload (домен, план, срок) без приватного ключа
    17  *    невозможно — подпись перестанет сходиться.
    18  *
    19  * Модель доверия (важно понимать):
    20  *  - Это защищает от включения PRO без ключа через wp_options/SQL/ADMIN UI.
    21  *  - НЕ защищает от правки самого PHP-кода: код исполняется на сервере
    22  *    клиента, и тот, кто может править файлы, отключит проверку. От этого
    23  *    защиты не существует; барьер — только хеш-контроль файлов.
    24  *
    25  * Режим "не ломать честных клиентов":
    26  *  - посл��дний валидный токен кешируется; без сети плагин работает на нём;
    27  *  - отключаемся только когда токен старше GRACE_DAYS.
    289 *
    2910 * @version 8.5.0
    … …  
    3314{
    3415    /**
    35      * Публичный ключ сервера лицензий (боевой, р. computy.ru).
    36      *
    37      * Соответствует приватному ключу на сервере:
    38      *   D:/OSPanel/home/.secrets/bfw_license_private.pem (вне webroot)
    39      * Публичный ключ можно раздавать — он только проверяет подпись.
     16     * Публичный ключ сервера лицензий.
     17     *
     18     * Публичный ключ можно раздавать — он только проверяет подпись токена.
     19     * Приватный ключ подписи хранится на сервере лицензий и в репозиторий
     20     * (и вообще в плагин) не попадает.
    4021     */
    4122    const PUBLIC_KEY = <<<'PEM'
  • bonus-for-woo/tags/8.5.2/classes/BfwPoints.php

    r3784081 r3795776  
    805805        }
    806806
    807         $redirect = esc_url_raw($_POST['redirect'] ?? '');
    808         if (empty($redirect)) {
    809             wp_send_json_error('Invalid redirect URL');
    810             return;
    811         }
     807        $redirect = self::sanitizeRedirect(wp_unslash($_POST['redirect'] ?? ''));
    812808
    813809        echo self::bfw_write_off_points($redirect);
    … …  
    836832        }
    837833        $params = $request->get_json_params();
    838         $redirect = isset($params['redirect']) ? $params['redirect'] : '';
     834        $redirect = self::sanitizeRedirect($params['redirect'] ?? '');
    839835
    840836        ob_start();
    … …  
    884880     * @version 6.5.5
    885881     */
     882    /**
     883     * Приводит адрес возврата к безопасному внутреннему URL.
     884     *
     885     * Значение приходит из запроса/JSON и потом уходит в скрытое поле формы, в
     886     * JSON-ответ и в window.location.href на клиенте, поэтому проверять надо
     887     * не только экранирование, но и то, что адрес остаётся на своём сайте:
     888     * иначе получаем открытый редирект и javascript:-URL.
     889     *
     890     * @param mixed $redirect
     891     * @param string|null $fallback
     892     * @return string
     893     * @since 8.5.2
     894     */
     895    public static function sanitizeRedirect($redirect, ?string $fallback = null): string
     896    {
     897        $fallback = $fallback ?? wc_get_cart_url();
     898
     899        if (!is_string($redirect) || $redirect === '') {
     900            return $fallback;
     901        }
     902
     903        $candidate = esc_url_raw(wp_unslash($redirect));
     904
     905        if ($candidate === '') {
     906            return $fallback;
     907        }
     908
     909        return wp_validate_redirect($candidate, $fallback);
     910    }
     911
    886912    public static function bfw_write_off_points($redirect): string
    887913    {
     914        $redirect = self::sanitizeRedirect($redirect);
     915
    888916        global $pagenow;
    889917        if ($pagenow === 'post.php') {
    … …  
    10241052 
    10251053        <input type="hidden" name="action" value="computy_trata_points">
    1026         <input type="hidden" name="redirect" value="' . $redirect . '">
     1054        <input type="hidden" name="redirect" value="' . esc_attr($redirect) . '">
    10271055        <input type="hidden" name="_wpnonce" value="' . wp_create_nonce('bfw_trata_points') . '">
    10281056        <input type="text" name="computy_input_points" class="input-text" value="' . self::roundPoints(($user_fast_points > 0 ? $user_fast_points : $vozmojniy_ball)) . '">
    … …  
    10441072            $return .= '<div class="remove_points_form">
    10451073                <input type="hidden" name="action" value="clear_bonus" />
    1046                 <input type="hidden" name="redirect" value="' . esc_url($redirect) . '">
     1074                <input type="hidden" name="redirect" value="' . esc_attr($redirect) . '">
    10471075                <input type="hidden" name="security" value="' . wp_create_nonce('bfw_clear_fast_bonus') . '">
    10481076
    … …  
    10861114
    10871115        $requestedPoints = self::roundPoints((float) ($_POST['computy_input_points'] ?? 0));
    1088         $redirect = esc_url_raw($_POST['redirect'] ?? '');
     1116        $redirect = self::sanitizeRedirect(wp_unslash($_POST['redirect'] ?? ''));
    10891117
    10901118        if ($requestedPoints <= 0) {
    … …  
    11181146            self::updateFastPoints($user_id, $pointsToApply);
    11191147
    1120             wp_send_json_success($_POST['redirect']);
     1148            wp_send_json_success($redirect);
    11211149        } catch (Exception $e) {
    11221150            wp_send_json_error(__('An error occurred while processing your request.', 'bonus-for-woo'));
    … …  
    11331161        $params = $request->get_json_params();
    11341162        $requestedPoints = isset($params['points']) ? self::roundPoints((float) $params['points']) : 0;
    1135         $redirect = isset($params['redirect']) ? esc_url($params['redirect']) : wc_get_cart_url();
     1163        $redirect = self::sanitizeRedirect($params['redirect'] ?? '', wc_get_cart_url());
    11361164
    11371165        $user_id = get_current_user_id();
    … …  
    13571385        }
    13581386
    1359         $redirect = $request->get_param('redirect') ?: home_url();
     1387        $redirect = self::sanitizeRedirect($request->get_param('redirect'), home_url());
     1388
    13601389        return new WP_REST_Response(['success' => true, 'data' => $redirect], 200);
    13611390    }
    … …  
    13751404        $params = $request->get_json_params();
    13761405        $code_coupon = isset($params['code_coupon']) ? sanitize_text_field($params['code_coupon']) : '';
    1377         $redirect_url = isset($params['redirect']) ? esc_url($params['redirect']) : home_url();
     1406        $redirect_url = self::sanitizeRedirect($params['redirect'] ?? '', home_url());
    13781407
    13791408        if (empty($code_coupon)) {
    … …  
    19701999        check_ajax_referer('bfw_export_bonuses_nonce', 'nonce');
    19712000
    1972         if (!current_user_can('manage_options')) {
     2001        if (!current_user_can(BFW_CAP_MANAGE)) {
    19732002            wp_send_json_error('Access denied');
    19742003            return;
    … …  
    19812010            : 'by_id';
    19822011
    1983         $array = json_decode(stripslashes(wp_unslash($_POST['response'] ?? '')), true);
    1984         if (!$array || empty($array['data']['url'])) {
    1985             wp_send_json_error('Invalid response data');
    1986         }
    1987 
    19882012        /*
    1989          * Путь раньше склеивался как ABSPATH . str_replace(site_url(), '', $url)
    1990          * без канонизации: ".." не отсекались, что давало чтение произвольных
    1991          * файлов. Теперь допускается только файл внутри папки плагина или
    1992          * внутри uploads (там лежит экспорт после 8.5.1).
     2013         * Раньше сюда присылали JSON-ответ медиатеки с URL файла, загруженного
     2014         * на сайт: файл с балансами клиентов оставался в uploads навсегда, а
     2015         * путь вычислялся из клиентских данных. Теперь файл приходит в самом
     2016         * запросе (multipart), читается из временного каталога PHP и на сайте
     2017         * не сохраняется.
    19932018         */
    1994         $url = (string) $array['data']['url'];
    1995         $relative = str_replace(site_url(), '', $url);
    1996         $candidate = wp_normalize_path(ABSPATH . ltrim($relative, '/'));
    1997 
    1998         if (strpos($candidate, '..') !== false) {
    1999             wp_send_json_error('Invalid file path');
    2000         }
    2001 
    2002         $allowed_dirs = array(wp_normalize_path(BONUS_COMPUTY_PLUGIN_DIR));
    2003         $upload_dir = wp_upload_dir(null, false);
    2004         $allowed_dirs[] = wp_normalize_path($upload_dir['basedir']);
    2005 
    2006         $real = realpath($candidate);
    2007 
    2008         if ($real === false) {
    2009             wp_send_json_error('Файл не найден');
    2010         }
    2011 
    2012         $real = wp_normalize_path($real);
    2013         $allowed = false;
    2014         foreach ($allowed_dirs as $allowed_dir) {
    2015             if (strpos($real, trailingslashit($allowed_dir)) === 0) {
    2016                 $allowed = true;
    2017                 break;
    2018             }
    2019         }
    2020 
    2021         if (!$allowed) {
    2022             wp_send_json_error('Invalid file path');
    2023         }
    2024 
    2025         $file_path = $real;
     2019        $uploaded = $_FILES['bfw_import_file'] ?? null;
     2020
     2021        if (empty($uploaded) || !isset($uploaded['tmp_name']) || (int) ($uploaded['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
     2022            wp_send_json_error(__('The file was not uploaded.', 'bonus-for-woo'));
     2023        }
     2024
     2025        if (!is_uploaded_file($uploaded['tmp_name'])) {
     2026            wp_send_json_error(__('Invalid upload.', 'bonus-for-woo'));
     2027        }
     2028
     2029        $file_path = $uploaded['tmp_name'];
    20262030
    20272031        $fileHandle = fopen($file_path, 'rb');
    20282032        if ($fileHandle === false) {
    2029             wp_send_json_error('Ошибка при открытии файла');
     2033            wp_send_json_error(__('Cannot open the uploaded file.', 'bonus-for-woo'));
    20302034        }
    20312035
    … …  
    31973201        check_ajax_referer('bfw_recalc_nonce', 'nonce');
    31983202
    3199         if (!current_user_can('manage_options')) {
     3203        if (!current_user_can(BFW_CAP_MANAGE)) {
    32003204            wp_send_json_error(__('Access denied.', 'bonus-for-woo'));
    32013205            return;
  • bonus-for-woo/tags/8.5.2/classes/BfwRouter.php

    r3784081 r3795776  
    8181    private static function handleExportPoints()
    8282    {
    83         if (!isset($_GET['export_bfw_points']) || !current_user_can('manage_options')) {
    84             return;
    85         }
    86 
    87         if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'bfw_export_points')) {
    88             wp_die('Security check failed');
    89         }
    90 
    91         self::generateExportFile();
    92     }
    93 
    94     /**
    95      * Генерация CSV файла экспорта баллов
    96      */
    97     private static function generateExportFile()
    98     {
    99         $file_path = BfwFunctions::getExportFilePath();
    100         $buffer = fopen($file_path, 'w');
    101 
    102         if (!$buffer) {
    103             wp_die('Cannot create export file');
    104         }
    105 
    106         fwrite($buffer, "\xEF\xBB\xBF"); // BOM for UTF-8
    107 
    108         $title_export = ['User id', 'User name', 'Email', 'Phone', 'Points', 'Status', 'Comment'];
    109         fputcsv($buffer, $title_export, ',');
    110 
    111         $status = new BfwRoles();
    112         $data = self::prepareExportData($status);
    113 
    114         foreach ($data as $row) {
    115             fputcsv($buffer, $row, ',');
    116         }
    117 
    118         fclose($buffer);
    119     }
    120 
    121     /**
    122      * Подготовка данных для экспорта баллов
    123      */
    124     private static function prepareExportData($status): array
     83        if (!isset($_GET['export_bfw_points']) || !current_user_can(BFW_CAP_MANAGE)) {
     84            return;
     85        }
     86
     87        check_admin_referer('bfw_export_points');
     88
     89        self::streamExport();
     90    }
     91
     92    /**
     93     * Отдаёт CSV баллов потоком, не создавая файл на диске.
     94     *
     95     * Раньше файл писался в uploads с фиксированным именем и оставался там
     96     * после выгрузки — то есть данные всех клиентов (email, телефон, баланс)
     97     * лежали по постоянному публичному URL. Теперь данные уходят в php://output
     98     * и на диске не остаются.
     99     *
     100     * @return void
     101     */
     102    private static function streamExport(): void
     103    {
     104        $output = fopen('php://output', 'wb');
     105
     106        if ($output === false) {
     107            wp_die(esc_html__('Cannot create export file.', 'bonus-for-woo'));
     108        }
     109
     110        nocache_headers();
     111        header('Content-Type: text/csv; charset=utf-8');
     112        header('Content-Disposition: attachment; filename="bfw_points_' . gmdate('Y-m-d') . '.csv"');
     113
     114        fwrite($output, "\xEF\xBB\xBF"); // BOM для UTF-8
     115
     116        fputcsv($output, ['User id', 'User name', 'Email', 'Phone', 'Points', 'Status', 'Comment'], ',');
     117
     118        foreach (self::iterateExportRows() as $row) {
     119            fputcsv($output, $row, ',');
     120        }
     121
     122        fclose($output);
     123        exit;
     124    }
     125
     126    /** Размер пакета при экспорте: столько пользователей держим в памяти за раз */
     127    const EXPORT_BATCH_SIZE = 500;
     128
     129    /**
     130     * Построчный обход пользователей для экспорта.
     131     *
     132     * Раньше все пользователи и все их мета выбирались целиком в память, а
     133     * статус запрашивался отдельно на каждого (N+1). Теперь выборка идёт
     134     * пакетами, а статусы подтягиваются одним SQL-запросом на пакет.
     135     *
     136     * @return \Generator
     137     */
     138    private static function iterateExportRows(): \Generator
    125139    {
    126140        global $wpdb;
    127141
    128         // 1. Получаем только необходимые поля
    129         $users = $wpdb->get_results("SELECT ID, user_nicename, user_email FROM {$wpdb->users} ORDER BY ID");
    130 
    131         if (empty($users)) {
    132             return [];
    133         }
    134 
    135         $user_ids = wp_list_pluck($users, 'ID');
     142        $offset = 0;
     143
     144        do {
     145            $users = $wpdb->get_results($wpdb->prepare(
     146                "SELECT ID, user_nicename, user_email FROM {$wpdb->users} ORDER BY ID LIMIT %d OFFSET %d",
     147                self::EXPORT_BATCH_SIZE,
     148                $offset
     149            ));
     150
     151            if (empty($users)) {
     152                break;
     153            }
     154
     155            $user_ids = array_map('intval', wp_list_pluck($users, 'ID'));
     156
     157            // 1. Мета и статусы — по одному запросу на пакет
     158            $metas = self::getUsersMetaBatch($user_ids);
     159            $statuses = self::getUsersStatusNamesBatch($user_ids);
     160
     161            // 2. Отдаём строки
     162            foreach ($users as $user) {
     163                $user_id = (int) $user->ID;
     164
     165                yield [
     166                    'id'      => $user_id,
     167                    'name'    => sanitize_text_field($user->user_nicename),
     168                    'email'   => sanitize_email($user->user_email),
     169                    'phone'   => sanitize_text_field($metas[$user_id]['billing_phone'] ?? ''),
     170                    'points'  => (int) ($metas[$user_id]['computy_point'] ?? 0),
     171                    'status'  => sanitize_text_field($statuses[$user_id] ?? ''),
     172                    'comment' => '',
     173                ];
     174            }
     175
     176            $offset += self::EXPORT_BATCH_SIZE;
     177            unset($users, $metas, $statuses);
     178        } while (count($user_ids) === self::EXPORT_BATCH_SIZE);
     179    }
     180
     181    /**
     182     * Метрики баллов и телефона для пакета пользователей.
     183     *
     184     * @param int[] $user_ids
     185     * @return array<int, array<string, string>>
     186     */
     187    private static function getUsersMetaBatch(array $user_ids): array
     188    {
     189        global $wpdb;
     190
     191        $placeholders = implode(',', array_fill(0, count($user_ids), '%d'));
     192
     193        $rows = $wpdb->get_results($wpdb->prepare(
     194            "SELECT user_id, meta_key, meta_value
     195             FROM {$wpdb->usermeta}
     196             WHERE user_id IN ({$placeholders}) AND meta_key IN ('computy_point', 'billing_phone')",
     197            ...$user_ids
     198        ));
     199
    136200        $metas = [];
    137 
    138         // 2. Собираем метаполя ОДНИМ запросом для всех
     201        foreach ($rows as $row) {
     202            $metas[(int) $row->user_id][$row->meta_key] = $row->meta_value;
     203        }
     204
     205        return $metas;
     206    }
     207
     208    /**
     209     * Названия статусов для пакета пользователей — один запрос вместо getRole() в цикле.
     210     *
     211     * @param int[] $user_ids
     212     * @return array<int, string>
     213     */
     214    private static function getUsersStatusNamesBatch(array $user_ids): array
     215    {
     216        global $wpdb;
     217
    139218        $placeholders = implode(',', array_fill(0, count($user_ids), '%d'));
    140         $meta_query = $wpdb->prepare(
    141             "SELECT user_id, meta_key, meta_value
    142          FROM {$wpdb->usermeta}
    143          WHERE user_id IN ($placeholders) AND meta_key IN ('computy_point', 'billing_phone')",
    144             ...$user_ids // Важно: распаковываем массив для wpdb->prepare
    145         );
    146 
    147         foreach ($wpdb->get_results($meta_query) as $meta) {
    148             $metas[$meta->user_id][$meta->meta_key] = $meta->meta_value;
    149         }
    150 
    151         // 3. Формируем итоговый массив
    152         $data = [];
    153         foreach ($users as $user) {
    154             $points = (int)($metas[$user->ID]['computy_point'] ?? 0);
    155             $phone = $metas[$user->ID]['billing_phone'] ?? '';
    156 
    157             // Внимание: если getRole внутри делает SQL-запрос,
    158             // скрипт все еще может притормаживать на больших объемах.
    159             $arrayRole = $status->getRole($user->ID);
    160 
    161             $data[] = [
    162                 'id'      => (int)$user->ID,
    163                 'name'    => sanitize_text_field($user->user_nicename),
    164                 'email'   => sanitize_email($user->user_email),
    165                 'phone'   => sanitize_text_field($phone),
    166                 'points'  => $points,
    167                 'status'  => sanitize_text_field($arrayRole['name'] ?? ''),
    168                 'comment' => '',
    169             ];
    170         }
    171 
    172         return $data;
     219
     220        $rows = $wpdb->get_results($wpdb->prepare(
     221            "SELECT um.user_id, r.name
     222             FROM {$wpdb->usermeta} um
     223             INNER JOIN {$wpdb->prefix}bfw_computy r ON r.id = um.meta_value
     224             WHERE um.user_id IN ({$placeholders}) AND um.meta_key = 'bfw_status'",
     225            ...$user_ids
     226        ));
     227
     228        $statuses = [];
     229        foreach ($rows as $row) {
     230            $statuses[(int) $row->user_id] = (string) $row->name;
     231        }
     232
     233        return $statuses;
    173234    }
    174235
    … …  
    178239    private static function handleRemoveExport()
    179240    {
    180         if (!isset($_GET['remove_export_bfw_points']) || !current_user_can('manage_options')) {
     241        if (!isset($_GET['remove_export_bfw_points']) || !current_user_can(BFW_CAP_MANAGE)) {
    181242            return;
    182243        }
    … …  
    186247        }
    187248
    188         $file_path = BfwFunctions::getExportFilePath();
    189 
    190         if (!is_file($file_path)) {
    191             wp_die('Invalid file path');
    192         }
    193 
    194         if (file_exists($file_path)) {
    195             wp_delete_file($file_path);
    196         }
     249        /*
     250         * Экспорт больше не пишется на диск (см. streamExport()), но у сайтов,
     251         * обновлённых с версий <= 8.5.1, в uploads может остаться старый файл —
     252         * с персональными данными и по публичному URL. Чистим его.
     253         */
     254        BfwFunctions::deleteLegacyExportFile();
     255
     256        wp_safe_redirect(admin_url('admin.php?page=bonus-for-woo%2Fpages%2Ftools.php&bfw_export_removed=1'));
     257        exit;
    197258    }
    198259
    … …  
    202263    public static function handleAnalyticsExport()
    203264    {
    204         if (!isset($_GET['bfw_analytics_export']) || !current_user_can('manage_options')) {
     265        if (!isset($_GET['bfw_analytics_export']) || !current_user_can(BFW_CAP_MANAGE)) {
    205266            return;
    206267        }
  • bonus-for-woo/tags/8.5.2/classes/BfwStatistic.php

    r3784081 r3795776  
    2020    public static function ajax_get_stats_timestamp()
    2121    {
    22         if (!current_user_can('manage_options')) {
     22        if (!current_user_can(BFW_CAP_MANAGE)) {
    2323            wp_send_json_error(['message' => 'Access denied']);
    2424        }
    … …  
    8585        check_ajax_referer('bfw_stat_nonce', 'nonce');
    8686
    87         if (!current_user_can('manage_options')) {
     87        if (!current_user_can(BFW_CAP_MANAGE)) {
    8888            wp_send_json_error(['message' => 'Access denied']);
    8989        }
    … …  
    148148                $pending_total = 0;
    149149
    150                 $pending_order_ids = wc_get_orders([
    151                     'status' => ['processing', 'on-hold'],
    152                     'limit'  => -1,
    153                     'return' => 'ids',
    154                 ]);
    155 
    156                 foreach ($pending_order_ids as $order_id) {
    157                     $order = wc_get_order($order_id);
    158                     if (!$order) {
    159                         continue;
     150                /*
     151                 * Раньше здесь был wc_get_orders(limit => -1) с последующим
     152                 * wc_get_order() в цикле: на магазине с тысячами заказов в
     153                 * обработке один AJAX-шаг тянул их все в память.
     154                 * Теперь пагинация и «загруженные» объекты заказов — WC
     155                 * переиспользует их из своего кеша, поэтому повторный
     156                 * wc_get_order() в пределах страницы стоит одно чтение.
     157                 */
     158                $page      = 1;
     159                $per_page  = 200;
     160                $max_orders = (int) apply_filters('bfw_pending_statistics_max_orders', 2000);
     161
     162                do {
     163                    $pending_orders = wc_get_orders([
     164                        'status'       => ['processing', 'on-hold'],
     165                        'limit'        => $per_page,
     166                        'page'         => $page,
     167                        'return'       => 'objects',
     168                        'orderby'      => 'date',
     169                        'order'        => 'DESC',
     170                        'meta_query'   => [
     171                            [
     172                                'key'     => 'cashback_receipt',
     173                                'value'   => 'received',
     174                                'compare' => '!=',
     175                            ],
     176                        ],
     177                    ]);
     178
     179                    foreach ($pending_orders as $order) {
     180                        $pending_total += BfwPoints::howMatchCashbackInOrder($order->get_id());
    160181                    }
    161                     if ($order->get_meta('cashback_receipt') === 'received') {
    162                         continue;
    163                     }
    164                     $pending_total += BfwPoints::howMatchCashbackInOrder($order_id);
    165                 }
     182
     183                    $page++;
     184                } while (count($pending_orders) === $per_page && (($page - 1) * $per_page) < $max_orders);
    166185
    167186                $data['pending_points_total'] = (float) $pending_total;
    … …  
    427446        check_ajax_referer('bfw_stat_nonce', 'nonce');
    428447
    429         if (!current_user_can('manage_options')) {
     448        if (!current_user_can(BFW_CAP_MANAGE)) {
    430449            wp_send_json_error(['message' => 'Access denied']);
    431450        }
  • bonus-for-woo/tags/8.5.2/classes/Bfw_Coupons_List_Table.php

    r3784081 r3795776  
    3838        $this->_column_headers = [$columns, $hidden, $sortable];
    3939
     40        /*
     41         * orderby / order приходят из $_GET и вклеиваются в строку до prepare().
     42         * esc_sql() экранирует только кавычки, идентификаторы и ключевые слова
     43         * он не защищает, поэтому ORDER BY был инъектируемым. Только белый список.
     44         */
    4045        $allowed_orderby = ['id', 'created', 'sum', 'code', 'status'];
    41         $orderby = (!empty($_GET['orderby']) && in_array($_GET['orderby'], $allowed_orderby))
    42                 ? sanitize_text_field($_GET['orderby'])
     46        $orderby = (!empty($_GET['orderby']) && in_array($_GET['orderby'], $allowed_orderby, true))
     47                ? sanitize_key($_GET['orderby'])
    4348                : 'id';
    44         $order = (!empty($_GET['order'])) ? esc_sql($_GET['order']) : 'DESC';
     49        $order = (!empty($_GET['order']) && strtoupper(sanitize_key($_GET['order'])) === 'ASC') ? 'ASC' : 'DESC';
    4550
    4651        $where = '';
    4752        if (!empty($_REQUEST['s'])) {
    48             $search = esc_sql($_REQUEST['s']);
     53            $search = $wpdb->esc_like(sanitize_text_field(wp_unslash($_REQUEST['s'])));
    4954            $where = $wpdb->prepare(" WHERE code LIKE %s OR comment_admin LIKE %s", '%' . $search . '%', '%' . $search . '%');
    5055        }
  • bonus-for-woo/tags/8.5.2/classes/Bfw_Export.php

    r3784081 r3795776  
    88    {
    99        add_action('admin_init', [__CLASS__, 'handle_export_requests']);
    10         add_action('admin_post_bfw_download_export', [__CLASS__,'bfw_handle_download_export']);
    1110    }
    1211
    13     public static function bfw_handle_download_export() {
    14         // Проверяем права доступа
    15         if (!current_user_can('manage_options')) {
    16             wp_die(__('You do not have permission to download this file.', 'bonus-for-woo'));
    17         }
    18 
    19         check_admin_referer('bfw_download_export');
    20 
    21         $file_path = BfwFunctions::getExportFilePath();
    22 
    23         // Проверяем существование файла
    24         if (!is_file($file_path)) {
    25             wp_die(__('The export file was not found. Generate it first.', 'bonus-for-woo'));
    26         }
    27 
    28         // Отправляем заголовки для скачивания
    29         header('Content-Description: File Transfer');
    30         header('Content-Type: text/csv');
    31         header('Content-Disposition: attachment; filename="' . basename($file_path) . '"');
    32         header('Expires: 0');
    33         header('Cache-Control: must-revalidate');
    34         header('Pragma: public');
    35         header('Content-Length: ' . filesize($file_path));
    36 
    37         // Читаем файл и отправляем в браузер
    38         readfile($file_path);
    39         exit; // Обязательно завершаем выполнение
    40     }
    4112    public static function handle_export_requests()
    4213    {
    43         if (!is_admin() || !current_user_can('manage_options')) {
     14        if (!is_admin() || !current_user_can(BFW_CAP_MANAGE)) {
    4415            return;
    4516        }
  • bonus-for-woo/tags/8.5.2/index.php

    r3784081 r3795776  
    55 * @wordpress-plugin
    66 * Plugin Name:       Bonus for Woo
    7  * Version:           8.5.0
     7 * Version:           8.5.2
    88 * Plugin URI:        https://computy.ru/blog/bonus-for-woo-wordpress
    99 * Description:       A comprehensive cashback bonus system for WooCommerce with user status management.
    … …  
    2828
    2929// Define plugin constants.
    30 const BONUS_COMPUTY_VERSION = '8.5.0';
     30const BONUS_COMPUTY_VERSION = '8.5.2';
    3131const BONUS_COMPUTY_VERSION_DB = '8';
    3232define('BONUS_COMPUTY_PLUGIN_DIR', plugin_dir_path(__FILE__));
    … …  
    3535define('BFW_HISTORY_LIMIT', 500); //Лимит в истории начислений
    3636define('BFW_COUNT_STEPS_FOR_REQUESTS', 20);//Количество обработок за 1 запрос
     37
     38/*
     39 * Единое право на управление бонусной системой.
     40 *
     41 * Меню отдавалось manage_woocommerce (пункты статистики/купонов/инструментов),
     42 * а их AJAX-обработчики требовали manage_options — shop manager видел половину
     43 * интерфейса, которая молча не работала. Право собрано в одну константу,
     44 * чтобы интерфейс и обработчики нельзя было рассинхронизировать снова.
     45 */
     46define('BFW_CAP_MANAGE', 'manage_woocommerce');
    3747/**
    3848 * Автозагрузка классов
    … …  
    4050
    4151spl_autoload_register(static function ($class) {
    42     $file = str_replace('\\', '/', BONUS_COMPUTY_PLUGIN_DIR . '/classes/' . $class)
    43         . '.php';
    44     if (file_exists($file)) {
     52    /*
     53     * Только свои классы. Раньше автозагрузчик пробовал любой класс и
     54     * склеивал путь с двойным слэшем (DIR . '/classes/'), то есть лез в
     55     * файловую систему на каждый чужой class_exists().
     56     */
     57    if (strpos($class, 'Bfw') !== 0 || strpos($class, '/') !== false || strpos($class, '\\') !== false) {
     58        return;
     59    }
     60
     61    $file = BONUS_COMPUTY_PLUGIN_DIR . 'classes/' . $class . '.php';
     62
     63    if (is_file($file)) {
    4564        require_once $file;
    4665    }
    … …  
    109128
    110129/*-------Действия после обновления-------*/
    111 if (get_transient('bfw_pro_updated')) {
    112     /*Проверка бд после обновления */
    113     BfwAdmin::bfw_search_pro();
     130/*
     131 * Раньше это выполнялось прямо при подключении файла плагина — до
     132 * plugins_loaded и до проверки наличия WooCommerce, то есть миграция БД и
     133 * поиск PRO могли стартовать в неготовом окружении.
     134 */
     135add_action('plugins_loaded', function () {
     136    if (!get_transient('bfw_pro_updated')) {
     137        return;
     138    }
     139
     140    if (bfw_dependencies_met()) {
     141        BfwAdmin::bfw_search_pro();
     142    }
     143
    114144    BfwDB::checkDb();
     145
    115146    delete_transient('bfw_pro_updated');
    116 }
     147}, 20);
    117148/*-------Действия после обновления-------*/
    118149
    … …  
    140171/*-------Регистрация REST API маршрутов (отдельно от init для корректной работы)-------*/
    141172add_action('rest_api_init', function () {
     173    /*
     174     * Без WooCommerce все обработчики ниже обращаются к WC()/WC_Cart и
     175     * падают фаталом. Маршруты регистрируем только при живых зависимостях.
     176     */
     177    if (!bfw_dependencies_met()) {
     178        return;
     179    }
     180
    142181    register_rest_route('bfw/v1', '/clear-fast-bonus', [
    143182        'methods' => 'POST',
    … …  
    180219    ]);
    181220
     221    /*
     222     * Свежий REST nonce. Нужен потому, что nonce, напечатанный в HTML,
     223     * протухает под полностраничным кешем; страница запрашивает его при
     224     * загрузке и получает актуальный.
     225     */
     226    register_rest_route('bfw/v1', '/nonce', [
     227        'methods' => 'GET',
     228        'callback' => function () {
     229            return new WP_REST_Response(['nonce' => wp_create_nonce('wp_rest')], 200);
     230        },
     231        'permission_callback' => function () {
     232            return is_user_logged_in();
     233        },
     234    ]);
     235
    182236    register_rest_route('bfw/v1', '/check-points-only-products', [
    183237        'methods' => 'POST',
  • bonus-for-woo/tags/8.5.2/license.txt

    r3442739 r3795776  
    1                     GNU GENERAL PUBLIC LICENSE
    2                        Version 2, June 1991
    3 
    4  Copyright (C) 1989, 1991 Free Software Foundation, Inc.
    5               51 Franklin St, Fifth Floor, Boston, MA 02110, USA
    6 
     1                    GNU GENERAL PUBLIC LICENSE
     2                       Version 3, 29 June 2007
     3
     4 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
    75 Everyone is permitted to copy and distribute verbatim copies
    86 of this license document, but changing it is not allowed.
    97
    10                             Preamble
    11 
    12   The licenses for most software are designed to take away your
    13 freedom to share and change it.  By contrast, the GNU General Public
    14 License is intended to guarantee your freedom to share and change free
    15 software--to make sure the software is free for all its users.  This
    16 General Public License applies to most of the Free Software
    17 Foundation's software and to any other program whose authors commit to
    18 using it.  (Some other Free Software Foundation software is covered by
    19 the GNU Library General Public License instead.)  You can apply it to
     8                            Preamble
     9
     10  The GNU General Public License is a free, copyleft license for
     11software and other kinds of works.
     12
     13  The licenses for most software and other practical works are designed
     14to take away your freedom to share and change the works.  By contrast,
     15the GNU General Public License is intended to guarantee your freedom to
     16share and change all versions of a program--to make sure it remains free
     17software for all its users.  We, the Free Software Foundation, use the
     18GNU General Public License for most of our software; it applies also to
     19any other work released this way by its authors.  You can apply it to
    2020your programs, too.
    2121
    2222  When we speak of free software, we are referring to freedom, not
    23 price.  Our Public Licenses are designed to make sure that you
     23price.  Our General Public Licenses are designed to make sure that you
    2424have the freedom to distribute copies of free software (and charge for
    25 this service if you wish), that you receive source code or can get it
    26 if you want it, that you can change the software or use pieces of it
    27 in new free programs; and that you know you can do these things.
    28 
    29   To protect your rights, we need to make restrictions that forbid
    30 anyone to deny you these rights or to ask you to surrender the rights.
    31 These restrictions translate to certain responsibilities for you if you
    32 distribute copies of the software, or if you modify it.
     25them if you wish), that you receive source code or can get it if you
     26want it, that you can change the software or use pieces of it in new
     27free programs, and that you know you can do these things.
     28
     29  To protect your rights, we need to prevent others from denying you
     30these rights or asking you to surrender the rights.  Therefore, you have
     31certain responsibilities if you distribute copies of the software, or if
     32you modify it: responsibilities to respect the freedom of others.
    3333
    3434  For example, if you distribute copies of such a program, whether
    35 gratis or for a fee, you must give the recipients all the rights that
    36 you have.  You must make sure that they, too, receive or can get the
    37 source code.  And you must show them these terms, so they know their
    38 rights.
    39 
    40   We protect your rights with two steps: (1) copyright the software, and
    41 (2) offer you this license which gives you legal permission to copy,
    42 distribute and/or modify the software.
    43 
    44   Also, for each author's protection and ours, we want to make certain
    45 that everyone understands that there is no warranty for this free
    46 software.  If the software is modified by someone else and passed on, we
    47 want its recipients to know that what they have is not the original, so
    48 that any problems introduced by others will not reflect on the original
    49 authors' reputations.
    50 
    51   Finally, any free program is threatened constantly by software
    52 patents.  We wish to avoid the danger that redistributors of a free
    53 program will individually obtain patent licenses, in effect making the
    54 program proprietary.  To prevent this, we have made it clear that any
    55 patent must be licensed for everyone's free use or not licensed at all.
     35gratis or for a fee, you must pass on to the recipients the same
     36freedoms that you received.  You must make sure that they, too, receive
     37or can get the source code.  And you must show them these terms so they
     38know their rights.
     39
     40  Developers that use the GNU GPL protect your rights with two steps:
     41(1) assert copyright on the software, and (2) offer you this License
     42giving you legal permission to copy, distribute and/or modify it.
     43
     44  For the developers' and authors' protection, the GPL clearly explains
     45that there is no warranty for this free software.  For both users' and
     46authors' sake, the GPL requires that modified versions be marked as
     47changed, so that their problems will not be attributed erroneously to
     48authors of previous versions.
     49
     50  Some devices are designed to deny users access to install or run
     51modified versions of the software inside them, although the manufacturer
     52can do so.  This is fundamentally incompatible with the aim of
     53protecting users' freedom to change the software.  The systematic
     54pattern of such abuse occurs in the area of products for individuals to
     55use, which is precisely where it is most unacceptable.  Therefore, we
     56have designed this version of the GPL to prohibit the practice for those
     57products.  If such problems arise substantially in other domains, we
     58stand ready to extend this provision to those domains in future versions
     59of the GPL, as needed to protect the freedom of users.
     60
     61  Finally, every program is threatened constantly by software patents.
     62States should not allow patents to restrict development and use of
     63software on general-purpose computers, but in those that do, we wish to
     64avoid the special danger that patents applied to a free program could
     65make it effectively proprietary.  To prevent this, the GPL assures that
     66patents cannot be used to render the program non-free.
    5667
    5768  The precise terms and conditions for copying, distribution and
    5869modification follow.
    5970
    60                     GNU GENERAL PUBLIC LICENSE
    61    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
    62 
    63   0. This License applies to any program or other work which contains
    64 a notice placed by the copyright holder saying it may be distributed
    65 under the terms of this General Public License.  The "Program", below,
    66 refers to any such program or work, and a "work based on the Program"
    67 means either the Program or any derivative work under copyright law:
    68 that is to say, a work containing the Program or a portion of it,
    69 either verbatim or with modifications and/or translated into another
    70 language.  (Hereinafter, translation is included without limitation in
    71 the term "modification".)  Each licensee is addressed as "you".
    72 
    73 Activities other than copying, distribution and modification are not
    74 covered by this License; they are outside its scope.  The act of
    75 running the Program is not restricted, and the output from the Program
    76 is covered only if its contents constitute a work based on the
    77 Program (independent of having been made by running the Program).
    78 Whether that is true depends on what the Program does.
    79 
    80   1. You may copy and distribute verbatim copies of the Program's
    81 source code as you receive it, in any medium, provided that you
    82 conspicuously and appropriately publish on each copy an appropriate
    83 copyright notice and disclaimer of warranty; keep intact all the
    84 notices that refer to this License and to the absence of any warranty;
    85 and give any other recipients of the Program a copy of this License
    86 along with the Program.
    87 
    88 You may charge a fee for the physical act of transferring a copy, and
    89 you may at your option offer warranty protection in exchange for a fee.
    90 
    91   2. You may modify your copy or copies of the Program or any portion
    92 of it, thus forming a work based on the Program, and copy and
    93 distribute such modifications or work under the terms of Section 1
    94 above, provided that you also meet all of these conditions:
    95 
    96     a) You must cause the modified files to carry prominent notices
    97     stating that you changed the files and the date of any change.
    98 
    99     b) You must cause any work that you distribute or publish, that in
    100     whole or in part contains or is derived from the Program or any
    101     part thereof, to be licensed as a whole at no charge to all third
    102     parties under the terms of this License.
    103 
    104     c) If the modified program normally reads commands interactively
    105     when run, you must cause it, when started running for such
    106     interactive use in the most ordinary way, to print or display an
    107     announcement including an appropriate copyright notice and a
    108     notice that there is no warranty (or else, saying that you provide
    109     a warranty) and that users may redistribute the program under
    110     these conditions, and telling the user how to view a copy of this
    111     License.  (Exception: if the Program itself is interactive but
    112     does not normally print such an announcement, your work based on
    113     the Program is not required to print an announcement.)
    114 
    115 These requirements apply to the modified work as a whole.  If
    116 identifiable sections of that work are not derived from the Program,
    117 and can be reasonably considered independent and separate works in
    118 themselves, then this License, and its terms, do not apply to those
    119 sections when you distribute them as separate works.  But when you
    120 distribute the same sections as part of an entire which is a work based
    121 on the Program, the distribution of the entire must be on the terms of
    122 this License, whose permissions for other licensees extend to the
    123 entire, and thus to each and every part regardless of who wrote it.
    124 Thus, it is not the intent of this section to claim rights or contest
    125 your rights to work written entirely by you; rather, the intent is to
    126 exercise the right to control the distribution of derivative or
    127 collective works based on the Program.
    128 
    129 In addition, mere aggregation of another work not based on the Program
    130 with the Program (or with a work based on the Program) on a volume of
    131 a storage or distribution medium does not bring the other work under
    132 the scope of this License.
    133 
    134   3. You may copy and distribute the Program (or a work based on it,
    135 under Section 2) in object code or executable form under the terms of
    136 Sections 1 and 2 above provided that you also do one of the following:
    137 
    138     a) Accompany it with the complete corresponding machine-readable
    139     source code, which must be distributed under the terms of Sections
    140     1 and 2 above on a medium customarily used for software interchange; or,
    141 
    142     b) Accompany it with a written offer, valid for at least three
    143     years, to give any third party, for a charge no more than your
    144     cost of physically performing source distribution, a complete
    145     machine-readable copy of the corresponding source code, to be
    146     distributed under the terms of Sections 1 and 2 above on a medium
    147     customarily used for software interchange; or,
    148 
    149     c) Accompany it with the information you received as to the offer
    150     to distribute corresponding source code.  (This alternative is
    151     allowed only for noncommercial distribution and only if you
    152     received the program in object code or executable form with such
    153     an offer, in accord with Subsection b above.)
    154 
    155 The source code for a work means the preferred form of the work for
    156 making modifications to it.  For an executable work, complete source
    157 code means all the source code for all modules it contains, plus any
    158 associated interface definition files, plus the scripts used to
    159 control compilation and installation of the executable.  However, as a
    160 special exception, the source code distributed need not include
    161 anything that is normally distributed (in either source or binary
    162 form) with the major components (compiler, kernel, and so on) of the
    163 operating system on which the executable runs, unless that component
    164 itself accompanies the executable.
    165 
    166 If distribution of executable or object code is made by offering
    167 access to copy from a designated place, then offering equivalent
    168 access to copy the source code from the same place counts as
    169 distribution of the source code, even though third parties are not
    170 compelled to copy the source along with the object code.
    171 
    172   4. You may not copy, modify, sublicense, or distribute the Program
    173 except as expressly provided under this License.  Any attempt
    174 otherwise to copy, modify, sublicense or distribute the Program is
    175 void, and will automatically terminate your rights under this License.
    176 However, parties who have received copies, or rights, from you under
    177 this License will not have their licenses terminated so long as such
    178 parties remain in full compliance.
    179 
    180   5. You are not required to accept this License, since you have not
    181 signed it.  However, nothing else grants you permission to modify or
    182 distribute the Program or its derivative works.  These actions are
    183 prohibited by law if you do not accept this License.  Therefore, by
    184 modifying or distributing the Program (or any work based on the
    185 Program), you indicate your acceptance of this License to do so, and
    186 all its terms and conditions for copying, distributing or modifying
    187 the Program or works based on it.
    188 
    189   6. Each time you redistribute the Program (or any work based on the
    190 Program), the recipient automatically receives a license from the
    191 original licensor to copy, distribute or modify the Program subject to
    192 these terms and conditions.  You may not impose any further
    193 restrictions on the recipients' exercise of the rights granted herein.
    194 You are not responsible for enforcing compliance by third parties to
     71                       TERMS AND CONDITIONS
     72
     73  0. Definitions.
     74
     75  "This License" refers to version 3 of the GNU General Public License.
     76
     77  "Copyright" also means copyright-like laws that apply to other kinds of
     78works, such as semiconductor masks.
     79
     80  "The Program" refers to any copyrightable work licensed under this
     81License.  Each licensee is addressed as "you".  "Licensees" and
     82"recipients" may be individuals or organizations.
     83
     84  To "modify" a work means to copy from or adapt all or part of the work
     85in a fashion requiring copyright permission, other than the making of an
     86exact copy.  The resulting work is called a "modified version" of the
     87earlier work or a work "based on" the earlier work.
     88
     89  A "covered work" means either the unmodified Program or a work based
     90on the Program.
     91
     92  To "propagate" a work means to do anything with it that, without
     93permission, would make you directly or secondarily liable for
     94infringement under applicable copyright law, except executing it on a
     95computer or modifying a private copy.  Propagation includes copying,
     96distribution (with or without modification), making available to the
     97public, and in some countries other activities as well.
     98
     99  To "convey" a work means any kind of propagation that enables other
     100parties to make or receive copies.  Mere interaction with a user through
     101a computer network, with no transfer of a copy, is not conveying.
     102
     103  An interactive user interface displays "Appropriate Legal Notices"
     104to the extent that it includes a convenient and prominently visible
     105feature that (1) displays an appropriate copyright notice, and (2)
     106tells the user that there is no warranty for the work (except to the
     107extent that warranties are provided), that licensees may convey the
     108work under this License, and how to view a copy of this License.  If
     109the interface presents a list of user commands or options, such as a
     110menu, a prominent item in the list meets this criterion.
     111
     112  1. Source Code.
     113
     114  The "source code" for a work means the preferred form of the work
     115for making modifications to it.  "Object code" means any non-source
     116form of a work.
     117
     118  A "Standard Interface" means an interface that either is an official
     119standard defined by a recognized standards body, or, in the case of
     120interfaces specified for a particular programming language, one that
     121is widely used among developers working in that language.
     122
     123  The "System Libraries" of an executable work include anything, other
     124than the work as a whole, that (a) is included in the normal form of
     125packaging a Major Component, but which is not part of that Major
     126Component, and (b) serves only to enable use of the work with that
     127Major Component, or to implement a Standard Interface for which an
     128implementation is available to the public in source code form.  A
     129"Major Component", in this context, means a major essential component
     130(kernel, window system, and so on) of the specific operating system
     131(if any) on which the executable work runs, or a compiler used to
     132produce the work, or an object code interpreter used to run it.
     133
     134  The "Corresponding Source" for a work in object code form means all
     135the source code needed to generate, install, and (for an executable
     136work) run the object code and to modify the work, including scripts to
     137control those activities.  However, it does not include the work's
     138System Libraries, or general-purpose tools or generally available free
     139programs which are used unmodified in performing those activities but
     140which are not part of the work.  For example, Corresponding Source
     141includes interface definition files associated with source files for
     142the work, and the source code for shared libraries and dynamically
     143linked subprograms that the work is specifically designed to require,
     144such as by intimate data communication or control flow between those
     145subprograms and other parts of the work.
     146
     147  The Corresponding Source need not include anything that users
     148can regenerate automatically from other parts of the Corresponding
     149Source.
     150
     151  The Corresponding Source for a work in source code form is that
     152same work.
     153
     154  2. Basic Permissions.
     155
     156  All rights granted under this License are granted for the term of
     157copyright on the Program, and are irrevocable provided the stated
     158conditions are met.  This License explicitly affirms your unlimited
     159permission to run the unmodified Program.  The output from running a
     160covered work is covered by this License only if the output, given its
     161content, constitutes a covered work.  This License acknowledges your
     162rights of fair use or other equivalent, as provided by copyright law.
     163
     164  You may make, run and propagate covered works that you do not
     165convey, without conditions so long as your license otherwise remains
     166in force.  You may convey covered works to others for the sole purpose
     167of having them make modifications exclusively for you, or provide you
     168with facilities for running those works, provided that you comply with
     169the terms of this License in conveying all material for which you do
     170not control copyright.  Those thus making or running the covered works
     171for you must do so exclusively on your behalf, under your direction
     172and control, on terms that prohibit them from making any copies of
     173your copyrighted material outside their relationship with you.
     174
     175  Conveying under any other circumstances is permitted solely under
     176the conditions stated below.  Sublicensing is not allowed; section 10
     177makes it unnecessary.
     178
     179  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
     180
     181  No covered work shall be deemed part of an effective technological
     182measure under any applicable law fulfilling obligations under article
     18311 of the WIPO copyright treaty adopted on 20 December 1996, or
     184similar laws prohibiting or restricting circumvention of such
     185measures.
     186
     187  When you convey a covered work, you waive any legal power to forbid
     188circumvention of technological measures to the extent such circumvention
     189is effected by exercising rights under this License with respect to
     190the covered work, and you disclaim any intention to limit operation or
     191modification of the work as a means of enforcing, against the work's
     192users, your or third parties' legal rights to forbid circumvention of
     193technological measures.
     194
     195  4. Conveying Verbatim Copies.
     196
     197  You may convey verbatim copies of the Program's source code as you
     198receive it, in any medium, provided that you conspicuously and
     199appropriately publish on each copy an appropriate copyright notice;
     200keep intact all notices stating that this License and any
     201non-permissive terms added in accord with section 7 apply to the code;
     202keep intact all notices of the absence of any warranty; and give all
     203recipients a copy of this License along with the Program.
     204
     205  You may charge any price or no price for each copy that you convey,
     206and you may offer support or warranty protection for a fee.
     207
     208  5. Conveying Modified Source Versions.
     209
     210  You may convey a work based on the Program, or the modifications to
     211produce it from the Program, in the form of source code under the
     212terms of section 4, provided that you also meet all of these conditions:
     213
     214    a) The work must carry prominent notices stating that you modified
     215    it, and giving a relevant date.
     216
     217    b) The work must carry prominent notices stating that it is
     218    released under this License and any conditions added under section
     219    7.  This requirement modifies the requirement in section 4 to
     220    "keep intact all notices".
     221
     222    c) You must license the entire work, as a whole, under this
     223    License to anyone who comes into possession of a copy.  This
     224    License will therefore apply, along with any applicable section 7
     225    additional terms, to the whole of the work, and all its parts,
     226    regardless of how they are packaged.  This License gives no
     227    permission to license the work in any other way, but it does not
     228    invalidate such permission if you have separately received it.
     229
     230    d) If the work has interactive user interfaces, each must display
     231    Appropriate Legal Notices; however, if the Program has interactive
     232    interfaces that do not display Appropriate Legal Notices, your
     233    work need not make them do so.
     234
     235  A compilation of a covered work with other separate and independent
     236works, which are not by their nature extensions of the covered work,
     237and which are not combined with it such as to form a larger program,
     238in or on a volume of a storage or distribution medium, is called an
     239"aggregate" if the compilation and its resulting copyright are not
     240used to limit the access or legal rights of the compilation's users
     241beyond what the individual works permit.  Inclusion of a covered work
     242in an aggregate does not cause this License to apply to the other
     243parts of the aggregate.
     244
     245  6. Conveying Non-Source Forms.
     246
     247  You may convey a covered work in object code form under the terms
     248of sections 4 and 5, provided that you also convey the
     249machine-readable Corresponding Source under the terms of this License,
     250in one of these ways:
     251
     252    a) Convey the object code in, or embodied in, a physical product
     253    (including a physical distribution medium), accompanied by the
     254    Corresponding Source fixed on a durable physical medium
     255    customarily used for software interchange.
     256
     257    b) Convey the object code in, or embodied in, a physical product
     258    (including a physical distribution medium), accompanied by a
     259    written offer, valid for at least three years and valid for as
     260    long as you offer spare parts or customer support for that product
     261    model, to give anyone who possesses the object code either (1) a
     262    copy of the Corresponding Source for all the software in the
     263    product that is covered by this License, on a durable physical
     264    medium customarily used for software interchange, for a price no
     265    more than your reasonable cost of physically performing this
     266    conveying of source, or (2) access to copy the
     267    Corresponding Source from a network server at no charge.
     268
     269    c) Convey individual copies of the object code with a copy of the
     270    written offer to provide the Corresponding Source.  This
     271    alternative is allowed only occasionally and noncommercially, and
     272    only if you received the object code with such an offer, in accord
     273    with subsection 6b.
     274
     275    d) Convey the object code by offering access from a designated
     276    place (gratis or for a charge), and offer equivalent access to the
     277    Corresponding Source in the same way through the same place at no
     278    further charge.  You need not require recipients to copy the
     279    Corresponding Source along with the object code.  If the place to
     280    copy the object code is a network server, the Corresponding Source
     281    may be on a different server (operated by you or a third party)
     282    that supports equivalent copying facilities, provided you maintain
     283    clear directions next to the object code saying where to find the
     284    Corresponding Source.  Regardless of what server hosts the
     285    Corresponding Source, you remain obligated to ensure that it is
     286    available for as long as needed to satisfy these requirements.
     287
     288    e) Convey the object code using peer-to-peer transmission, provided
     289    you inform other peers where the object code and Corresponding
     290    Source of the work are being offered to the general public at no
     291    charge under subsection 6d.
     292
     293  A separable portion of the object code, whose source code is excluded
     294from the Corresponding Source as a System Library, need not be
     295included in conveying the object code work.
     296
     297  A "User Product" is either (1) a "consumer product", which means any
     298tangible personal property which is normally used for personal, family,
     299or household purposes, or (2) anything designed or sold for incorporation
     300into a dwelling.  In determining whether a product is a consumer product,
     301doubtful cases shall be resolved in favor of coverage.  For a particular
     302product received by a particular user, "normally used" refers to a
     303typical or common use of that class of product, regardless of the status
     304of the particular user or of the way in which the particular user
     305actually uses, or expects or is expected to use, the product.  A product
     306is a consumer product regardless of whether the product has substantial
     307commercial, industrial or non-consumer uses, unless such uses represent
     308the only significant mode of use of the product.
     309
     310  "Installation Information" for a User Product means any methods,
     311procedures, authorization keys, or other information required to install
     312and execute modified versions of a covered work in that User Product from
     313a modified version of its Corresponding Source.  The information must
     314suffice to ensure that the continued functioning of the modified object
     315code is in no case prevented or interfered with solely because
     316modification has been made.
     317
     318  If you convey an object code work under this section in, or with, or
     319specifically for use in, a User Product, and the conveying occurs as
     320part of a transaction in which the right of possession and use of the
     321User Product is transferred to the recipient in perpetuity or for a
     322fixed term (regardless of how the transaction is characterized), the
     323Corresponding Source conveyed under this section must be accompanied
     324by the Installation Information.  But this requirement does not apply
     325if neither you nor any third party retains the ability to install
     326modified object code on the User Product (for example, the work has
     327been installed in ROM).
     328
     329  The requirement to provide Installation Information does not include a
     330requirement to continue to provide support service, warranty, or updates
     331for a work that has been modified or installed by the recipient, or for
     332the User Product in which it has been modified or installed.  Access to a
     333network may be denied when the modification itself materially and
     334adversely affects the operation of the network or violates the rules and
     335protocols for communication across the network.
     336
     337  Corresponding Source conveyed, and Installation Information provided,
     338in accord with this section must be in a format that is publicly
     339documented (and with an implementation available to the public in
     340source code form), and must require no special password or key for
     341unpacking, reading or copying.
     342
     343  7. Additional Terms.
     344
     345  "Additional permissions" are terms that supplement the terms of this
     346License by making exceptions from one or more of its conditions.
     347Additional permissions that are applicable to the entire Program shall
     348be treated as though they were included in this License, to the extent
     349that they are valid under applicable law.  If additional permissions
     350apply only to part of the Program, that part may be used separately
     351under those permissions, but the entire Program remains governed by
     352this License without regard to the additional permissions.
     353
     354  When you convey a copy of a covered work, you may at your option
     355remove any additional permissions from that copy, or from any part of
     356it.  (Additional permissions may be written to require their own
     357removal in certain cases when you modify the work.)  You may place
     358additional permissions on material, added by you to a covered work,
     359for which you have or can give appropriate copyright permission.
     360
     361  Notwithstanding any other provision of this License, for material you
     362add to a covered work, you may (if authorized by the copyright holders of
     363that material) supplement the terms of this License with terms:
     364
     365    a) Disclaiming warranty or limiting liability differently from the
     366    terms of sections 15 and 16 of this License; or
     367
     368    b) Requiring preservation of specified reasonable legal notices or
     369    author attributions in that material or in the Appropriate Legal
     370    Notices displayed by works containing it; or
     371
     372    c) Prohibiting misrepresentation of the origin of that material, or
     373    requiring that modified versions of such material be marked in
     374    reasonable ways as different from the original version; or
     375
     376    d) Limiting the use for publicity purposes of names of licensors or
     377    authors of the material; or
     378
     379    e) Declining to grant rights under trademark law for use of some
     380    trade names, trademarks, or service marks; or
     381
     382    f) Requiring indemnification of licensors and authors of that
     383    material by anyone who conveys the material (or modified versions of
     384    it) with contractual assumptions of liability to the recipient, for
     385    any liability that these contractual assumptions directly impose on
     386    those licensors and authors.
     387
     388  All other non-permissive additional terms are considered "further
     389restrictions" within the meaning of section 10.  If the Program as you
     390received it, or any part of it, contains a notice stating that it is
     391governed by this License along with a term that is a further
     392restriction, you may remove that term.  If a license document contains
     393a further restriction but permits relicensing or conveying under this
     394License, you may add to a covered work material governed by the terms
     395of that license document, provided that the further restriction does
     396not survive such relicensing or conveying.
     397
     398  If you add terms to a covered work in accord with this section, you
     399must place, in the relevant source files, a statement of the
     400additional terms that apply to those files, or a notice indicating
     401where to find the applicable terms.
     402
     403  Additional terms, permissive or non-permissive, may be stated in the
     404form of a separately written license, or stated as exceptions;
     405the above requirements apply either way.
     406
     407  8. Termination.
     408
     409  You may not propagate or modify a covered work except as expressly
     410provided under this License.  Any attempt otherwise to propagate or
     411modify it is void, and will automatically terminate your rights under
     412this License (including any patent licenses granted under the third
     413paragraph of section 11).
     414
     415  However, if you cease all violation of this License, then your
     416license from a particular copyright holder is reinstated (a)
     417provisionally, unless and until the copyright holder explicitly and
     418finally terminates your license, and (b) permanently, if the copyright
     419holder fails to notify you of the violation by some reasonable means
     420prior to 60 days after the cessation.
     421
     422  Moreover, your license from a particular copyright holder is
     423reinstated permanently if the copyright holder notifies you of the
     424violation by some reasonable means, this is the first time you have
     425received notice of violation of this License (for any work) from that
     426copyright holder, and you cure the violation prior to 30 days after
     427your receipt of the notice.
     428
     429  Termination of your rights under this section does not terminate the
     430licenses of parties who have received copies or rights from you under
     431this License.  If your rights have been terminated and not permanently
     432reinstated, you do not qualify to receive new licenses for the same
     433material under section 10.
     434
     435  9. Acceptance Not Required for Having Copies.
     436
     437  You are not required to accept this License in order to receive or
     438run a copy of the Program.  Ancillary propagation of a covered work
     439occurring solely as a consequence of using peer-to-peer transmission
     440to receive a copy likewise does not require acceptance.  However,
     441nothing other than this License grants you permission to propagate or
     442modify any covered work.  These actions infringe copyright if you do
     443not accept this License.  Therefore, by modifying or propagating a
     444covered work, you indicate your acceptance of this License to do so.
     445
     446  10. Automatic Licensing of Downstream Recipients.
     447
     448  Each time you convey a covered work, the recipient automatically
     449receives a license from the original licensors, to run, modify and
     450propagate that work, subject to this License.  You are not responsible
     451for enforcing compliance by third parties with this License.
     452
     453  An "entity transaction" is a transaction transferring control of an
     454organization, or substantially all assets of one, or subdividing an
     455organization, or merging organizations.  If propagation of a covered
     456work results from an entity transaction, each party to that
     457transaction who receives a copy of the work also receives whatever
     458licenses to the work the party's predecessor in interest had or could
     459give under the previous paragraph, plus a right to possession of the
     460Corresponding Source of the work from the predecessor in interest, if
     461the predecessor has it or can get it with reasonable efforts.
     462
     463  You may not impose any further restrictions on the exercise of the
     464rights granted or affirmed under this License.  For example, you may
     465not impose a license fee, royalty, or other charge for exercise of
     466rights granted under this License, and you may not initiate litigation
     467(including a cross-claim or counterclaim in a lawsuit) alleging that
     468any patent claim is infringed by making, using, selling, offering for
     469sale, or importing the Program or any portion of it.
     470
     471  11. Patents.
     472
     473  A "contributor" is a copyright holder who authorizes use under this
     474License of the Program or a work on which the Program is based.  The
     475work thus licensed is called the contributor's "contributor version".
     476
     477  A contributor's "essential patent claims" are all patent claims
     478owned or controlled by the contributor, whether already acquired or
     479hereafter acquired, that would be infringed by some manner, permitted
     480by this License, of making, using, or selling its contributor version,
     481but do not include claims that would be infringed only as a
     482consequence of further modification of the contributor version.  For
     483purposes of this definition, "control" includes the right to grant
     484patent sublicenses in a manner consistent with the requirements of
    195485this License.
    196486
    197   7. If, as a consequence of a court judgment or allegation of patent
    198 infringement or for any other reason (not limited to patent issues),
    199 conditions are imposed on you (whether by court order, agreement or
     487  Each contributor grants you a non-exclusive, worldwide, royalty-free
     488patent license under the contributor's essential patent claims, to
     489make, use, sell, offer for sale, import and otherwise run, modify and
     490propagate the contents of its contributor version.
     491
     492  In the following three paragraphs, a "patent license" is any express
     493agreement or commitment, however denominated, not to enforce a patent
     494(such as an express permission to practice a patent or covenant not to
     495sue for patent infringement).  To "grant" such a patent license to a
     496party means to make such an agreement or commitment not to enforce a
     497patent against the party.
     498
     499  If you convey a covered work, knowingly relying on a patent license,
     500and the Corresponding Source of the work is not available for anyone
     501to copy, free of charge and under the terms of this License, through a
     502publicly available network server or other readily accessible means,
     503then you must either (1) cause the Corresponding Source to be so
     504available, or (2) arrange to deprive yourself of the benefit of the
     505patent license for this particular work, or (3) arrange, in a manner
     506consistent with the requirements of this License, to extend the patent
     507license to downstream recipients.  "Knowingly relying" means you have
     508actual knowledge that, but for the patent license, your conveying the
     509covered work in a country, or your recipient's use of the covered work
     510in a country, would infringe one or more identifiable patents in that
     511country that you have reason to believe are valid.
     512
     513  If, pursuant to or in connection with a single transaction or
     514arrangement, you convey, or propagate by procuring conveyance of, a
     515covered work, and grant a patent license to some of the parties
     516receiving the covered work authorizing them to use, propagate, modify
     517or convey a specific copy of the covered work, then the patent license
     518you grant is automatically extended to all recipients of the covered
     519work and works based on it.
     520
     521  A patent license is "discriminatory" if it does not include within
     522the scope of its coverage, prohibits the exercise of, or is
     523conditioned on the non-exercise of one or more of the rights that are
     524specifically granted under this License.  You may not convey a covered
     525work if you are a party to an arrangement with a third party that is
     526in the business of distributing software, under which you make payment
     527to the third party based on the extent of your activity of conveying
     528the work, and under which the third party grants, to any of the
     529parties who would receive the covered work from you, a discriminatory
     530patent license (a) in connection with copies of the covered work
     531conveyed by you (or copies made from those copies), or (b) primarily
     532for and in connection with specific products or compilations that
     533contain the covered work, unless you entered into that arrangement,
     534or that patent license was granted, prior to 28 March 2007.
     535
     536  Nothing in this License shall be construed as excluding or limiting
     537any implied license or other defenses to infringement that may
     538otherwise be available to you under applicable patent law.
     539
     540  12. No Surrender of Others' Freedom.
     541
     542  If conditions are imposed on you (whether by court order, agreement or
    200543otherwise) that contradict the conditions of this License, they do not
    201 excuse you from the conditions of this License.  If you cannot
    202 distribute to satisfy simultaneously your obligations under this
    203 License and any other pertinent obligations, then as a consequence you
    204 may not distribute the Program at all.  For example, if a patent
    205 license would not permit royalty-free redistribution of the Program by
    206 all those who receive copies directly or indirectly through you, then
    207 the only way you could satisfy both it and this License would be to
    208 refrain entirely from distribution of the Program.
    209 
    210 If any portion of this section is held invalid or unenforceable under
    211 any particular circumstance, the balance of the section is intended to
    212 apply and the section as a whole is intended to apply in other
    213 circumstances.
    214 
    215 It is not the purpose of this section to induce you to infringe any
    216 patents or other property right claims or to contest validity of any
    217 such claims; this section has the sole purpose of protecting the
    218 integrity of the free software distribution system, which is
    219 implemented by public license practices.  Many people have made
    220 generous contributions to the wide range of software distributed
    221 through that system in reliance on consistent application of that
    222 system; it is up to the author/donor to decide if he or she is willing
    223 to distribute software through any other system and a licensee cannot
    224 impose that choice.
    225 
    226 This section is intended to make thoroughly clear what is believed to
    227 be a consequence of the rest of this License.
    228 
    229   8. If the distribution and/or use of the Program is restricted in
    230 certain countries either by patents or by copyrighted interfaces, the
    231 original copyright holder who places the Program under this License
    232 may add an explicit geographical distribution limitation excluding
    233 those countries, so that distribution is permitted only in or among
    234 countries not thus excluded.  In such case, this License incorporates
    235 the limitation as if written in the body of this License.
    236 
    237   9. The Free Software Foundation may publish revised and/or new versions
    238 of the General Public License from time to time.  Such new versions will
     544excuse you from the conditions of this License.  If you cannot convey a
     545covered work so as to satisfy simultaneously your obligations under this
     546License and any other pertinent obligations, then as a consequence you may
     547not convey it at all.  For example, if you agree to terms that obligate you
     548to collect a royalty for further conveying from those to whom you convey
     549the Program, the only way you could satisfy both those terms and this
     550License would be to refrain entirely from conveying the Program.
     551
     552  13. Use with the GNU Affero General Public License.
     553
     554  Notwithstanding any other provision of this License, you have
     555permission to link or combine any covered work with a work licensed
     556under version 3 of the GNU Affero General Public License into a single
     557combined work, and to convey the resulting work.  The terms of this
     558License will continue to apply to the part which is the covered work,
     559but the special requirements of the GNU Affero General Public License,
     560section 13, concerning interaction through a network will apply to the
     561combination as such.
     562
     563  14. Revised Versions of this License.
     564
     565  The Free Software Foundation may publish revised and/or new versions of
     566the GNU General Public License from time to time.  Such new versions will
    239567be similar in spirit to the present version, but may differ in detail to
    240568address new problems or concerns.
    241569
    242 Each version is given a distinguishing version number.  If the Program
    243 specifies a version number of this License which applies to it and "any
    244 later version", you have the option of following the terms and conditions
    245 either of that version or of any later version published by the Free
    246 Software Foundation.  If the Program does not specify a version number of
    247 this License, you may choose any version ever published by the Free Software
    248 Foundation.
    249 
    250   10. If you wish to incorporate parts of the Program into other free
    251 programs whose distribution conditions are different, write to the author
    252 to ask for permission.  For software which is copyrighted by the Free
    253 Software Foundation, write to the Free Software Foundation; we sometimes
    254 make exceptions for this.  Our decision will be guided by the two goals
    255 of preserving the free status of all derivatives of our free software and
    256 of promoting the sharing and reuse of software generally.
    257 
    258                             NO WARRANTY
    259 
    260   11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
    261 FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
    262 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
    263 PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
    264 OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
    265 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
    266 TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
    267 PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
    268 REPAIR OR CORRECTION.
    269 
    270   12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
    271 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
    272 REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
    273 INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
    274 OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
    275 TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
    276 YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
    277 PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
    278 POSSIBILITY OF SUCH DAMAGES.
    279 
    280                      END OF TERMS AND CONDITIONS
     570  Each version is given a distinguishing version number.  If the
     571Program specifies that a certain numbered version of the GNU General
     572Public License "or any later version" applies to it, you have the
     573option of following the terms and conditions either of that numbered
     574version or of any later version published by the Free Software
     575Foundation.  If the Program does not specify a version number of the
     576GNU General Public License, you may choose any version ever published
     577by the Free Software Foundation.
     578
     579  If the Program specifies that a proxy can decide which future
     580versions of the GNU General Public License can be used, that proxy's
     581public statement of acceptance of a version permanently authorizes you
     582to choose that version for the Program.
     583
     584  Later license versions may give you additional or different
     585permissions.  However, no additional obligations are imposed on any
     586author or copyright holder as a result of your choosing to follow a
     587later version.
     588
     589  15. Disclaimer of Warranty.
     590
     591  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
     592APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
     593HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
     594OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
     595THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     596PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
     597IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
     598ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
     599
     600  16. Limitation of Liability.
     601
     602  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
     603WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
     604THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
     605GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
     606USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
     607DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
     608PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
     609EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
     610SUCH DAMAGES.
     611
     612  17. Interpretation of Sections 15 and 16.
     613
     614  If the disclaimer of warranty and limitation of liability provided
     615above cannot be given local legal effect according to their terms,
     616reviewing courts shall apply local law that most closely approximates
     617an absolute waiver of all civil liability in connection with the
     618Program, unless a warranty or assumption of liability accompanies a
     619copy of the Program in return for a fee.
     620
     621                     END OF TERMS AND CONDITIONS
     622
     623            How to Apply These Terms to Your New Programs
     624
     625  If you develop a new program, and you want it to be of the greatest
     626possible use to the public, the best way to achieve this is to make it
     627free software which everyone can redistribute and change under these terms.
     628
     629  To do so, attach the following notices to the program.  It is safest
     630to attach them to the start of each source file to most effectively
     631state the exclusion of warranty; and each file should have at least
     632the "copyright" line and a pointer to where the full notice is found.
     633
     634    <one line to give the program's name and a brief idea of what it does.>
     635    Copyright (C) <year>  <name of author>
     636
     637    This program is free software: you can redistribute it and/or modify
     638    it under the terms of the GNU General Public License as published by
     639    the Free Software Foundation, either version 3 of the License, or
     640    (at your option) any later version.
     641
     642    This program is distributed in the hope that it will be useful,
     643    but WITHOUT ANY WARRANTY; without even the implied warranty of
     644    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     645    GNU General Public License for more details.
     646
     647    You should have received a copy of the GNU General Public License
     648    along with this program.  If not, see <https://www.gnu.org/licenses/>.
     649
     650Also add information on how to contact you by electronic and paper mail.
     651
     652  If the program does terminal interaction, make it output a short
     653notice like this when it starts in an interactive mode:
     654
     655    <program>  Copyright (C) <year>  <name of author>
     656    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
     657    This is free software, and you are welcome to redistribute it
     658    under certain conditions; type `show c' for details.
     659
     660The hypothetical commands `show w' and `show c' should show the appropriate
     661parts of the General Public License.  Of course, your program's commands
     662might be different; for a GUI interface, you would use an "about box".
     663
     664  You should also get your employer (if you work as a programmer) or school,
     665if any, to sign a "copyright disclaimer" for the program, if necessary.
     666For more information on this, and how to apply and follow the GNU GPL, see
     667<https://www.gnu.org/licenses/>.
     668
     669  The GNU General Public License does not permit incorporating your program
     670into proprietary programs.  If your program is a subroutine library, you
     671may consider it more useful to permit linking proprietary applications with
     672the library.  If this is what you want to do, use the GNU Lesser General
     673Public License instead of this License.  But first, please read
     674<https://www.gnu.org/licenses/why-not-lgpl.html>.
  • bonus-for-woo/tags/8.5.2/pages/coupons.php

    r3543185 r3795776  
    88 */
    99
    10 /* Обработка форм купонов (логика сохранена без изменений) */
     10/*
     11 * Обработка форм купонов.
     12 *
     13 * Купон — это деньги на балансе клиента, поэтому действия с ним требуют того
     14 * же права, что и пункт меню: раньше проверялся только nonce, и любой, кто
     15 * мог открыть страницу (а меню отдаётся manage_woocommerce), создавал и
     16 * удалял купоны на произвольные суммы.
     17 */
     18if (!current_user_can('manage_woocommerce') && !current_user_can('manage_options')) {
     19    return;
     20}
     21
    1122if (isset($_POST['bfw_computy_add_coupon_ajax'])) {
    1223    if ($_POST['bfw_computy_add_coupon_ajax'] === 'bfw_computy_add_coupon_ajax') {
    13         if (!isset($_POST['bfw_nonce_coupon']) || !wp_verify_nonce($_POST['bfw_nonce_coupon'], 'bfw_action_coupon')) {
    14             wp_die('Ошибка безопасности!');
    15         }
     24        check_admin_referer('bfw_action_coupon', 'bfw_nonce_coupon');
     25
    1626        global $wpdb;
    17         $code = sanitize_text_field($_POST['code']);
    18         $sum = sanitize_text_field($_POST['sum']);
    19         $comment_admin = sanitize_text_field($_POST['comment_admin']);
    20         $status = sanitize_text_field($_POST['status']);
    21         $reusable = $_POST['reusable'] ?? 0;
    22 
    23         if ($wpdb->get_results($wpdb->prepare("SELECT id,code,sum,created,status,date_use,user,reusable,comment_admin FROM " . $wpdb->prefix . "bfw_coupons_computy WHERE `code` = %s", $code))) {
    24             echo '<div class="bfw-notice bfw-notice-warning"><span class="dashicons dashicons-warning"></span> ' . sprintf(__('Coupon <b>%s</b> is already being used.', 'bonus-for-woo'), $code) . '</div>';
     27        $code = sanitize_text_field(wp_unslash($_POST['code'] ?? ''));
     28        $sum = (float) sanitize_text_field(wp_unslash($_POST['sum'] ?? 0));
     29        $comment_admin = sanitize_text_field(wp_unslash($_POST['comment_admin'] ?? ''));
     30        $status = sanitize_text_field(wp_unslash($_POST['status'] ?? 'noactive'));
     31        $reusable = (int) ($_POST['reusable'] ?? 0);
     32
     33        $exists = $wpdb->get_var($wpdb->prepare(
     34                "SELECT id FROM %i WHERE `code` = %s",
     35                $wpdb->prefix . 'bfw_coupons_computy',
     36                $code
     37        ));
     38
     39        if ($exists) {
     40            echo '<div class="bfw-notice bfw-notice-warning"><span class="dashicons dashicons-warning"></span> '
     41                . sprintf(
     42                    /* translators: %s: coupon code */
     43                    wp_kses(__('Coupon <b>%s</b> is already being used.', 'bonus-for-woo'), ['b' => []]),
     44                    esc_html($code)
     45                ) . '</div>';
    2546        } else {
    2647            BfwCoupons::addCoupon($code, $sum, $comment_admin, $status, $reusable);
    27             echo '<div class="bfw-notice bfw-notice-success"><span class="dashicons dashicons-yes-alt"></span> ' . __('Coupon', 'bonus-for-woo') . ' <b>' . $code . '</b> ' . __('added', 'bonus-for-woo') . '.</div>';
     48            echo '<div class="bfw-notice bfw-notice-success"><span class="dashicons dashicons-yes-alt"></span> '
     49                . esc_html__('Coupon', 'bonus-for-woo') . ' <b>' . esc_html($code) . '</b> '
     50                . esc_html__('added', 'bonus-for-woo') . '.</div>';
    2851        }
    2952    }
    … …  
    3154
    3255if (isset($_POST['bfw_delete_coupon'])) {
    33     if (!isset($_POST['bfw_nonce_coupon']) || !wp_verify_nonce($_POST['bfw_nonce_coupon'], 'bfw_action_coupon')) {
    34         wp_die('Ошибка безопасности!');
    35     }
    36     BfwCoupons::deleteCoupon($_POST['bfw_delete_coupon']);
    37     echo '<div class="bfw-notice bfw-notice-warning"><span class="dashicons dashicons-trash"></span> ' . __('deleted', 'bonus-for-woo') . '.</div>';
     56    check_admin_referer('bfw_action_coupon', 'bfw_nonce_coupon');
     57
     58    BfwCoupons::deleteCoupon((int) $_POST['bfw_delete_coupon']);
     59    echo '<div class="bfw-notice bfw-notice-warning"><span class="dashicons dashicons-trash"></span> '
     60        . esc_html__('deleted', 'bonus-for-woo') . '.</div>';
    3861}
    3962
    4063if (isset($_POST['bfw_edit_status_coupon'])) {
    41     if (!isset($_POST['bfw_nonce_coupon']) || !wp_verify_nonce($_POST['bfw_nonce_coupon'], 'bfw_action_coupon')) {
    42         wp_die('Ошибка безопасности!');
    43     }
    44     $status = (sanitize_text_field($_POST['status_coupon']) == 'active') ? 'noactive' : 'active';
    45     BfwCoupons::editStatusCoupon($_POST['bfw_edit_status_coupon'], $status);
    46     echo '<div class="bfw-notice bfw-notice-warning"><span class="dashicons dashicons-update"></span> ' . __('Coupon status changed', 'bonus-for-woo') . '.</div>';
     64    check_admin_referer('bfw_action_coupon', 'bfw_nonce_coupon');
     65
     66    $status = (sanitize_text_field(wp_unslash($_POST['status_coupon'] ?? '')) === 'active') ? 'noactive' : 'active';
     67    BfwCoupons::editStatusCoupon((int) $_POST['bfw_edit_status_coupon'], $status);
     68    echo '<div class="bfw-notice bfw-notice-warning"><span class="dashicons dashicons-update"></span> '
     69        . esc_html__('Coupon status changed', 'bonus-for-woo') . '.</div>';
    4770}
    4871?>
    … …  
    185208            <?php echo sprintf(__('Coupons for %s ', 'bonus-for-woo'), BfwPoints::pointsLabel(5)); ?>
    186209        </h1>
    187         <p><?php _e('Create, import and manage loyalty bonus coupons for your customers.', 'bonus-for-woo'); ?></p>
     210        <p><?php esc_html_e('Create, import and manage loyalty bonus coupons for your customers.', 'bonus-for-woo'); ?></p>
    188211    </header>
    189212
    … …  
    191214        <!-- Card: Add Coupon -->
    192215        <div class="bfw-action-card">
    193             <h3><span class="dashicons dashicons-plus-alt"></span> <?php _e('Add a new coupon', 'bonus-for-woo'); ?></h3>
     216            <h3><span class="dashicons dashicons-plus-alt"></span> <?php esc_html_e('Add a new coupon', 'bonus-for-woo'); ?></h3>
    194217            <form method="post" id="add_coupon_form">
    195218                <?php wp_nonce_field('bfw_action_coupon', 'bfw_nonce_coupon'); ?>
    … …  
    197220               
    198221                <div class="bfw-form-group">
    199                     <label><?php _e('Сode coupon', 'bonus-for-woo'); ?></label>
     222                    <label><?php esc_html_e('Сode coupon', 'bonus-for-woo'); ?></label>
    200223                    <input type="text" name="code" class="bfw-input" placeholder="SUMMER2024" required>
    201224                </div>
    … …  
    203226                <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
    204227                    <div class="bfw-form-group">
    205                         <label><?php _e('Number of points', 'bonus-for-woo'); ?></label>
     228                        <label><?php esc_html_e('Number of points', 'bonus-for-woo'); ?></label>
    206229                        <input type="number" name="sum" class="bfw-input" min="1" placeholder="500" required>
    207230                    </div>
    208231                    <div class="bfw-form-group">
    209                         <label><?php _e('Status', 'bonus-for-woo'); ?></label>
     232                        <label><?php esc_html_e('Status', 'bonus-for-woo'); ?></label>
    210233                        <select name="status" class="bfw-input">
    211                             <option value="active"><?php _e('Active', 'bonus-for-woo'); ?></option>
    212                             <option value="noactive"><?php _e('Not active', 'bonus-for-woo'); ?></option>
     234                            <option value="active"><?php esc_html_e('Active', 'bonus-for-woo'); ?></option>
     235                            <option value="noactive"><?php esc_html_e('Not active', 'bonus-for-woo'); ?></option>
    213236                        </select>
    214237                    </div>
    … …  
    216239
    217240                <div class="bfw-form-group">
    218                     <label><?php _e('Comment admin', 'bonus-for-woo'); ?></label>
     241                    <label><?php esc_html_e('Comment admin', 'bonus-for-woo'); ?></label>
    219242                    <textarea name="comment_admin" class="bfw-input" style="height: 60px;"></textarea>
    220243                </div>
    … …  
    222245                <div class="bfw-form-group">
    223246                    <label style="display:flex; align-items:center; gap:8px;">
    224                         <input type="checkbox" name="reusable" value="1"> <?php _e('Reusable', 'bonus-for-woo'); ?>
     247                        <input type="checkbox" name="reusable" value="1"> <?php esc_html_e('Reusable', 'bonus-for-woo'); ?>
    225248                    </label>
    226249                </div>
    227250
    228251                <button type="submit" class="bfw-btn bfw-btn-primary" style="width: 100%;">
    229                     <span class="dashicons dashicons-saved"></span> <?php _e('Add coupon', 'bonus-for-woo'); ?>
     252                    <span class="dashicons dashicons-saved"></span> <?php esc_html_e('Add coupon', 'bonus-for-woo'); ?>
    230253                </button>
    231254            </form>
    … …  
    234257        <!-- Card: Import -->
    235258        <div class="bfw-action-card">
    236             <h3><span class="dashicons dashicons-upload"></span> <?php _e('Import coupons', 'bonus-for-woo'); ?></h3>
     259            <h3><span class="dashicons dashicons-upload"></span> <?php esc_html_e('Import coupons', 'bonus-for-woo'); ?></h3>
    237260            <p style="font-size: 13px; color: var(--bfw-text-muted); margin-bottom: 15px;">
    238                 <?php _e('To create multiple coupons, create a csv file and import it using the form below.', 'bonus-for-woo'); ?>
     261                <?php esc_html_e('To create multiple coupons, create a csv file and import it using the form below.', 'bonus-for-woo'); ?>
    239262            </p>
    240263           
    … …  
    246269                    </div>
    247270                    <button type="button" class="bfw-btn bfw-btn-primary" onclick="upload();">
    248                         <span class="dashicons dashicons-database-import"></span> <?php _e('Import', 'bonus-for-woo'); ?>
     271                        <span class="dashicons dashicons-database-import"></span> <?php esc_html_e('Import', 'bonus-for-woo'); ?>
    249272                    </button>
    250273                </form>
    … …  
    254277            <div style="margin-top: 20px;">
    255278                <a href="<?php echo BONUS_COMPUTY_PLUGIN_URL; ?>templates/coupons_bfw.csv" class="bfw-btn bfw-btn-outline" style="border: 1px solid #ddd; justify-content: center;">
    256                     <span class="dashicons dashicons-download"></span> <?php _e('Example of CSV file', 'bonus-for-woo'); ?>
     279                    <span class="dashicons dashicons-download"></span> <?php esc_html_e('Example of CSV file', 'bonus-for-woo'); ?>
    257280                </a>
    258281            </div>
    … …  
    261284        <!-- Card: Info -->
    262285        <div class="bfw-action-card" style="background: #f8fafc;">
    263             <h3><span class="dashicons dashicons-info"></span> <?php _e('Description', 'bonus-for-woo'); ?></h3>
     286            <h3><span class="dashicons dashicons-info"></span> <?php esc_html_e('Description', 'bonus-for-woo'); ?></h3>
    264287            <ul style="font-size: 13px; line-height: 1.6; color: #475569; padding-left: 20px; margin: 0;">
    265                 <li><?php _e('Create a coupon in the form and it will appear in the list of coupons.', 'bonus-for-woo'); ?></li>
    266                 <li><?php _e('The customer can only use active coupons.', 'bonus-for-woo'); ?></li>
    267                 <li><?php _e('Coupon can only be used once.', 'bonus-for-woo'); ?></li>
    268                 <li><?php _e('Once a coupon has been used, it cannot be activated. Only delete.', 'bonus-for-woo'); ?></li>
    269                 <li><?php _e('If the "Reusable" box is checked, then all users can use it.', 'bonus-for-woo'); ?></li>
     288                <li><?php esc_html_e('Create a coupon in the form and it will appear in the list of coupons.', 'bonus-for-woo'); ?></li>
     289                <li><?php esc_html_e('The customer can only use active coupons.', 'bonus-for-woo'); ?></li>
     290                <li><?php esc_html_e('Coupon can only be used once.', 'bonus-for-woo'); ?></li>
     291                <li><?php esc_html_e('Once a coupon has been used, it cannot be activated. Only delete.', 'bonus-for-woo'); ?></li>
     292                <li><?php esc_html_e('If the "Reusable" box is checked, then all users can use it.', 'bonus-for-woo'); ?></li>
    270293            </ul>
    271294
    … …  
    275298
    276299    <div class="bfw-table-container">
    277         <h2 style="margin: 0 0 20px 0;"><?php _e('Coupons list', 'bonus-for-woo'); ?></h2>
     300        <h2 style="margin: 0 0 20px 0;"><?php esc_html_e('Coupons list', 'bonus-for-woo'); ?></h2>
    278301        <?php
    279302        $coupons_table = new Bfw_Coupons_List_Table();
    … …  
    294317        let fileExtension = ['csv'];
    295318        if (jQuery.inArray(jQuery('#bfw-file-export').val().split('.').pop().toLowerCase(), fileExtension) === -1) {
    296             jQuery('#bfw-file-export-result').html('<span style="color:red"><?php _e('Only CSV format allowed!', 'bonus-for-woo'); ?></span>');
     319            jQuery('#bfw-file-export-result').html('<span style="color:red"><?php esc_html_e('Only CSV format allowed!', 'bonus-for-woo'); ?></span>');
    297320        } else {
    298321            jQuery('.bfw_export_bonuses').css('opacity', '0.5');
    299322            let formData = new FormData();
    300             formData.append("action", "upload-attachment");
    301323            let fileInputElement = document.getElementById("bfw-file-export");
    302324
    303             formData.append("async-upload", fileInputElement.files[0]);
    304             formData.append("name", fileInputElement.files[0].name);
    305             formData.append("_wpnonce", "<?php echo wp_create_nonce('media-form'); ?>");
    306            
    307             let xhr = new XMLHttpRequest();
    308             xhr.onreadystatechange = function () {
    309                 if (xhr.readyState === 4 && xhr.status === 200) {
    310                     jQuery.ajax({
    311                         type: 'POST',
    312                         url: ajaxurl,
    313                         data: {
    314                             action: 'bfw_export_coupons',
    315                             nonce: '<?php echo wp_create_nonce('bfw_export_coupons'); ?>',
    316                             response: xhr.responseText,
    317                         },
    318                         success: function (data) {
    319                             if (data === 'good') {
    320                                 jQuery('#bfw-file-export-result').html('<span style="color:green;font-weight:bold;"><?php _e('Import successful! Reloading...', 'bonus-for-woo'); ?></span>');
    321                                 setTimeout(function () { location.reload(); }, 1500);
    322                             } else {
    323                                 jQuery('#bfw-file-export-result').html('<span style="color:red"><?php _e('Import failed.', 'bonus-for-woo'); ?></span>');
    324                                 jQuery('.bfw_export_bonuses').css('opacity', '1');
    325                             }
    326                         }
    327                     });
    328                 }
    329             }
    330             xhr.open("POST", "/wp-admin/async-upload.php", true);
    331             xhr.send(formData);
     325            /*
     326             * Файл уходит прямо в наш обработчик, без загрузки в медиатеку:
     327             * раньше CSV оставался на сайте по публичному URL.
     328             */
     329            formData.append("action", "bfw_export_coupons");
     330            formData.append("nonce", "<?php echo esc_js(wp_create_nonce('bfw_export_coupons')); ?>");
     331            formData.append("bfw_import_file", fileInputElement.files[0]);
     332
     333            fetch(ajaxurl, { method: "POST", body: formData, credentials: 'same-origin' })
     334                .then(r => r.json())
     335                .then(res => {
     336                    if (res.success) {
     337                        jQuery('#bfw-file-export-result').html('<span style="color:green;font-weight:bold;"><?php esc_html_e('Import successful! Reloading...', 'bonus-for-woo'); ?></span>');
     338                        setTimeout(function () { location.reload(); }, 1500);
     339                    } else {
     340                        jQuery('#bfw-file-export-result').html('<span style="color:red"><?php esc_html_e('Import failed.', 'bonus-for-woo'); ?></span>');
     341                        jQuery('.bfw_export_bonuses').css('opacity', '1');
     342                    }
     343                })
     344                .catch(() => {
     345                    jQuery('#bfw-file-export-result').html('<span style="color:red"><?php esc_html_e('Import failed.', 'bonus-for-woo'); ?></span>');
     346                    jQuery('.bfw_export_bonuses').css('opacity', '1');
     347                });
    332348        }
    333349    }
  • bonus-for-woo/tags/8.5.2/pages/generator.php

    r3784081 r3795776  
    120120<div class="bfw-generator-dashboard">
    121121    <a class="bfw-back-link" href="?page=bonus-for-woo/pages/tools.php">
    122         <span class="dashicons dashicons-arrow-left-alt"></span> <?php _e('Back to Tools', 'bonus-for-woo'); ?>
     122        <span class="dashicons dashicons-arrow-left-alt"></span> <?php esc_html_e('Back to Tools', 'bonus-for-woo'); ?>
    123123    </a>
    124124
    125125    <div class="bfw-gen-header">
    126126        <div class="bfw-gen-title">
    127             <h1><?php _e('Rules and Conditions Generator', 'bonus-for-woo'); ?></h1>
    128             <p><?php _e('Automatically generated text based on your current plugin settings.', 'bonus-for-woo'); ?></p>
     127            <h1><?php esc_html_e('Rules and Conditions Generator', 'bonus-for-woo'); ?></h1>
     128            <p><?php esc_html_e('Automatically generated text based on your current plugin settings.', 'bonus-for-woo'); ?></p>
    129129        </div>
    130130        <div class="bfw-action-bar">
    131131            <button class="bfw-btn-copy" id="bfw-copy-doc">
    132                 <span class="dashicons dashicons-admin-page"></span> <?php _e('Copy Text to Clipboard', 'bonus-for-woo'); ?>
    133                 <div class="copy-success-tip" id="copy-tip"><?php _e('Copied!', 'bonus-for-woo'); ?></div>
     132                <span class="dashicons dashicons-admin-page"></span> <?php esc_html_e('Copy Text to Clipboard', 'bonus-for-woo'); ?>
     133                <div class="copy-success-tip" id="copy-tip"><?php esc_html_e('Copied!', 'bonus-for-woo'); ?></div>
    134134            </button>
    135135        </div>
    … …  
    145145            ?>
    146146
    147             <h1>Программа лояльности "<?php echo !empty($val['bonus-points-on-cart']) ? $val['bonus-points-on-cart'] : 'бонусной системы'; ?>"</h1>
     147            <h1><?php echo esc_html(sprintf(__('Loyalty program "%s"', 'bonus-for-woo'), !empty($val['bonus-points-on-cart']) ? $val['bonus-points-on-cart'] : __('bonus system', 'bonus-for-woo'))); ?></h1>
    148148           
    149149            <p>Наша система бонусов создана с целью предоставить покупателям выгодные условия, которые помогут им экономить свои деньги.</p>
    150150
    151             <p>Программа лояльности включает в себя возврат части стоимости покупок в виде бонусных <?php echo $label_points; ?>, где каждый <?php echo $label_point; ?> эквивалентен одному рублю. Это означает, что за каждую покупку покупатели получают бонусы, которые могут использовать для будущих расходов, тем самым уменьшая общую стоимость их следующих покупок.</p>
     151            <p>Программа лояльности включает в себя возврат части стоимости покупок в виде бонусных <?php echo esc_html($label_points); ?>, где каждый <?php echo esc_html($label_point); ?> эквивалентен одному рублю. Это означает, что за каждую покупку покупатели получают бонусы, которые могут использовать для будущих расходов, тем самым уменьшая общую стоимость их следующих покупок.</p>
    152152           
    153153            <p>Размер возврата определяется суммой заказа и статусом клиента. Статус клиента зависит от общей суммы его заказов. В зависимости от общей суммы заказов клиенту присваивается соответствующий статус:</p>
    … …  
    164164            </ul>
    165165
    166             <h2>Отображение <?php echo $label_points; ?> и кешбэка</h2>
    167             <p>Узнать сколько <?php echo $label_points; ?> на счету можно в личном кабинете во вкладке "<?php echo !empty($val['title-on-account']) ? $val['title-on-account'] : 'Страница бонусов'; ?>" </p>
     166            <h2>Отображение <?php echo esc_html($label_points); ?> и кешбэка</h2>
     167            <p>Узнать сколько <?php echo esc_html($label_points); ?> на счету можно в личном кабинете во вкладке "<?php echo esc_html(!empty($val['title-on-account']) ? $val['title-on-account'] : __('Bonuses page', 'bonus-for-woo')); ?>" </p>
    168168           
    169169            <?php if (empty($val['hystory-hide'])) : ?>
    170                 <p>Также в этой вкладке можно посмотреть историю списаний и начислений <?php echo $label_points; ?>.</p>
     170                <p>Также в этой вкладке можно посмотреть историю списаний и начислений <?php echo esc_html($label_points); ?>.</p>
    171171            <?php endif; ?>
    172172
    173173            <?php if (!empty($val['bonus-in-price'])) : ?>
    174                 <p>На странице товара указано, сколько вам вернется <?php echo $label_points; ?> за покупку данного товара.</p>
     174                <p>На странице товара указано, сколько вам вернется <?php echo esc_html($label_points); ?> за покупку данного товара.</p>
    175175            <?php endif; ?>
    176176
    177177            <?php if (!empty($val['cashback-in-cart'])) : ?>
    178                 <p>При оформлении заказа и в корзине будет показано, сколько бонусных <?php echo $label_points; ?> вы получите за ваш заказ.</p>
     178                <p>При оформлении заказа и в корзине будет показано, сколько бонусных <?php echo esc_html($label_points); ?> вы получите за ваш заказ.</p>
    179179            <?php endif; ?>
    180180
    181181
    182             <h2>Начисление <?php echo $label_points; ?></h2>
    183             <p>Кроме начисления <?php echo $label_points; ?> за покупки товаров, предусмотрены другие вознаграждения:</p>
     182            <h2>Начисление <?php echo esc_html($label_points); ?></h2>
     183            <p>Кроме начисления <?php echo esc_html($label_points); ?> за покупки товаров, предусмотрены другие вознаграждения:</p>
    184184            <ul>
    185185                <?php if (!empty($val['bonus-for-otziv'])) : ?>
    186                     <li><?php echo $val['bonus-for-otziv'] . ' ' . BfwPoints::pointsLabel((int)$val['bonus-for-otziv']); ?> за отзыв о купленном товаре.</li>
     186                    <li><?php echo esc_html(absint($val['bonus-for-otziv'])) . ' ' . esc_html(BfwPoints::pointsLabel((int) $val['bonus-for-otziv'])); ?> за отзыв о купленном товаре.</li>
    187187                <?php endif; ?>
    188188
    … …  
    190190                    <?php if (!empty($val['points-for-registration'])) : ?>
    191191                        <?php $reg_suffix = !empty($val['register-points-only-referal']) ? ' (только при регистрации по реферальной ссылке)' : ''; ?>
    192                         <li><?php echo $val['points-for-registration'] . ' ' . BfwPoints::pointsLabel((int)$val['points-for-registration']); ?> начислят за регистрацию в нашем магазине<?php echo $reg_suffix; ?>.</li>
     192                        <li><?php echo esc_html(absint($val['points-for-registration'])) . ' ' . esc_html(BfwPoints::pointsLabel((int) $val['points-for-registration'])); ?> начислят за регистрацию в нашем магазине<?php echo esc_html($reg_suffix); ?>.</li>
    193193                    <?php endif; ?>
    194194
    … …  
    202202                        }
    203203                        ?>
    204                         <li><?php echo $val['birthday'] . ' ' . BfwPoints::pointsLabel((int)$val['birthday']); ?> начислят <?php echo $bday_when; ?> (его можно указать в настройках профиля).</li>
     204                        <li><?php echo esc_html(absint($val['birthday'])) . ' ' . esc_html(BfwPoints::pointsLabel((int) $val['birthday'])); ?> начислят <?php echo $bday_when; ?> (его можно указать в настройках профиля).</li>
    205205                    <?php endif; ?>
    206206
    207207                    <?php if (!empty($val['every_days'])) : ?>
    208                         <li><?php echo $val['every_days'] . ' ' . BfwPoints::pointsLabel((int)$val['every_days']); ?> начислят за ежедневный вход в личный кабинет.</li>
     208                        <li><?php echo esc_html(absint($val['every_days'])) . ' ' . esc_html(BfwPoints::pointsLabel((int) $val['every_days'])); ?> начислят за ежедневный вход в личный кабинет.</li>
    209209                    <?php endif; ?>
    210210
    … …  
    215215            </ul>
    216216
    217             <h2>Статус заказа для начисления <?php echo $label_points; ?></h2>
     217            <h2>Статус заказа для начисления <?php echo esc_html($label_points); ?></h2>
    218218            <?php
    219219            $order_statuses = wc_get_order_statuses();
    … …  
    223223            ?>
    224224
    225             <h2>Ограничения начисления <?php echo $label_points; ?></h2>
     225            <h2>Ограничения начисления <?php echo esc_html($label_points); ?></h2>
    226226            <?php
    227227                if (!empty($val['cashback-for-shipping'])) echo '<p>Кешбэк за доставку не будет начислен.</p>';
    … …  
    259259            <p>В корзине<?php echo !empty($val['spisanie-in-checkout']) ? ' и при оформлении заказа ' : ' '; ?>вы можете использовать баллы для оплаты товаров.</p>
    260260
    261             <h2>Ограничения использования <?php echo $label_points; ?></h2>
     261            <h2>Ограничения использования <?php echo esc_html($label_points); ?></h2>
    262262            <?php
    263263                if (!empty($val['spisanie-onsale'])) echo '<p>Вы не можете использовать баллы на покупку товаров со скидкой.</p>';
    … …  
    267267                    $max_percent = $val['max-percent-bonuses'] ?? 100;
    268268                    if ($max_percent < 100) {
    269                         echo '<p>Вы не можете потратить более ' . $max_percent . '% ' . $label_points . ' от суммы заказа.</p>';
     269                        echo '<p>' . esc_html(sprintf(__('You cannot spend more than %1$s%% %2$s of the order total.', 'bonus-for-woo'), $max_percent, $label_points)) . '</p>';
    270270                    }
    271271
    … …  
    284284                    if (!empty($val['minimal-amount'])) {
    285285                        $extra = !empty($val['minimal-amount-cashback']) ? ' и получения кешбэка' : '';
    286                         echo '<p>Для списания ' . $label_points . $extra . ' сумма в заказе должна быть не менее ' . $val['minimal-amount'] . $currency . '.</p>';
     286                        echo '<p>' . esc_html(sprintf(__('To redeem %1$s%2$s the order total must be at least %3$s%4$s.', 'bonus-for-woo'), $label_points, $extra, $val['minimal-amount'], $currency)) . '</p>';
    287287                    }
    288288
    289289                    if (!empty($val['day-inactive'])) {
    290                         echo '<h2>Сгорание ' . $label_points . '</h2>';
     290                        echo '<h2>' . esc_html(sprintf(__('Expiration of %s', 'bonus-for-woo'), $label_points)) . '</h2>';
    291291                        $notice_days = !empty($val['day-inactive-notice']) ? (int)$val['day-inactive-notice'] : 0;
    292292                        if ($notice_days > 0) {
    … …  
    304304                        $min_sum_ref = !empty($val['sum-orders-for-referral']) ? (float)$val['sum-orders-for-referral'] : 0;
    305305                        if ($min_sum_ref > 0) {
    306                             echo '<p>Кешбэк за приглашённого друга начисляется только при сумме его заказа от ' . $min_sum_ref . $currency . '.</p>';
     306                            echo '<p>' . esc_html(sprintf(__('Cashback for an invited friend is credited only if their order total is at least %1$s%2$s.', 'bonus-for-woo'), $min_sum_ref, $currency)) . '</p>';
    307307                        }
    308308
  • bonus-for-woo/tags/8.5.2/pages/list_history.php

    r3509064 r3795776  
    161161       
    162162        <div class="bfw-filter-group">
    163             <label><?php _e('From', 'bonus-for-woo'); ?></label>
     163            <label><?php esc_html_e('From', 'bonus-for-woo'); ?></label>
    164164            <input type="date" id="date_start" name="date_start" class="bfw-input-date"
    165165                   value="<?php echo esc_html($date_start); ?>"
    … …  
    169169
    170170        <div class="bfw-filter-group">
    171             <label><?php _e('to', 'bonus-for-woo'); ?></label>
     171            <label><?php esc_html_e('to', 'bonus-for-woo'); ?></label>
    172172            <input type="date" id="date_finish" name="date_finish" class="bfw-input-date"
    173173                   value="<?php echo esc_html($date_finish); ?>"
    … …  
    179179            <button type="submit" class="bfw-btn bfw-btn-primary">
    180180                <span class="dashicons dashicons-search"></span>
    181                 <?php _e('Search', 'bonus-for-woo'); ?>
     181                <?php esc_html_e('Search', 'bonus-for-woo'); ?>
    182182            </button>
    183183            <a href="/wp-admin/admin.php?page=bonus-for-woo%2Fpages%2Flist_history.php" class="bfw-btn bfw-btn-outline">
    184                 <?php _e('Reset', 'bonus-for-woo'); ?>
     184                <?php esc_html_e('Reset', 'bonus-for-woo'); ?>
    185185            </a>
    186186        </div>
  • bonus-for-woo/tags/8.5.2/pages/logs.php

    r3784081 r3795776  
    140140        <h1>
    141141            <span class="dashicons dashicons-media-text" style="font-size: 32px; width:32px; height:32px;"></span>
    142             <?php echo __('Logs', 'bonus-for-woo'); ?>
     142            <?php echo esc_html__('Logs', 'bonus-for-woo'); ?>
    143143        </h1>
    144         <p><?php _e('Review system activities, errors, and automated processes tracking.', 'bonus-for-woo'); ?></p>
     144        <p><?php esc_html_e('Review system activities, errors, and automated processes tracking.', 'bonus-for-woo'); ?></p>
    145145    </header>
    146146
    … …  
    150150           
    151151            <div class="bfw-filter-group">
    152                 <label><?php echo __('From', 'bonus-for-woo'); ?></label>
     152                <label><?php echo esc_html__('From', 'bonus-for-woo'); ?></label>
    153153                <input type="date" id="date_start" name="date_start" class="bfw-date-input"
    154154                       value="<?php echo esc_attr($date_start); ?>"
    … …  
    158158
    159159            <div class="bfw-filter-group">
    160                 <label><?php echo __('to', 'bonus-for-woo'); ?></label>
     160                <label><?php echo esc_html__('to', 'bonus-for-woo'); ?></label>
    161161                <input type="date" id="date_finish" name="date_finish" class="bfw-date-input"
    162162                       value="<?php echo esc_attr($date_finish); ?>"
    … …  
    166166
    167167            <button type="submit" class="bfw-btn bfw-btn-primary">
    168                 <span class="dashicons dashicons-search"></span> <?php echo __('Search', 'bonus-for-woo'); ?>
     168                <span class="dashicons dashicons-search"></span> <?php echo esc_html__('Search', 'bonus-for-woo'); ?>
    169169            </button>
    170170           
    171171            <a class="bfw-btn bfw-btn-outline" href="admin.php?page=bonus-for-woo/pages/logs.php">
    172                 <span class="dashicons dashicons-no-alt"></span> <?php echo __('Clear', 'bonus-for-woo'); ?>
     172                <span class="dashicons dashicons-no-alt"></span> <?php echo esc_html__('Clear', 'bonus-for-woo'); ?>
    173173            </a>
    174174        </form>
    … …  
    177177            <div class="bfw-log-notice">
    178178                <span class="dashicons dashicons-info"></span>
    179                 <?php echo __('The last 500 entries are displayed.', 'bonus-for-woo'); ?>
     179                <?php echo esc_html__('The last 500 entries are displayed.', 'bonus-for-woo'); ?>
    180180            </div>
    181181        <?php endif; ?>
  • bonus-for-woo/tags/8.5.2/pages/statistic.php

    r3784081 r3795776  
    154154
    155155<div class="wrap bfw-stat-wrap">
    156     <h1 style="font-weight: 700; margin-bottom: 25px;"><?php _e('Advanced Business Analytics', 'bonus-for-woo'); ?></h1>
     156    <h1 style="font-weight: 700; margin-bottom: 25px;"><?php esc_html_e('Advanced Business Analytics', 'bonus-for-woo'); ?></h1>
    157157
    158158    <?php
    … …  
    163163            <div class="bfw-badge-time">
    164164                <span class="dashicons dashicons-calendar-alt"></span>
    165                 <?php _e('Range:', 'bonus-for-woo'); ?>
    166                 <strong><?php echo !empty($stats['date_start']) ? esc_html($stats['date_start']) :  _e('All time', 'bonus-for-woo'); ?> - <?php echo !empty($stats['date_end']) ? esc_html($stats['date_end']) : 'All time'; ?></strong>
    167                 <span style="font-size: 11px; margin-left:10px;">(<?php _e('Updated', 'bonus-for-woo'); ?>: <?php echo date_i18n('H:i', $stats['timestamp']); ?>)</span>
     165                <?php esc_html_e('Range:', 'bonus-for-woo'); ?>
     166                <strong><?php echo !empty($stats['date_start']) ? esc_html($stats['date_start']) :  esc_html_e('All time', 'bonus-for-woo'); ?> - <?php echo !empty($stats['date_end']) ? esc_html($stats['date_end']) : 'All time'; ?></strong>
     167                <span style="font-size: 11px; margin-left:10px;">(<?php esc_html_e('Updated', 'bonus-for-woo'); ?>: <?php echo esc_html(date_i18n('H:i', (int) $stats['timestamp'])); ?>)</span>
    168168            </div>
    169169            <div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
    … …  
    171171                <span>&mdash;</span>
    172172                <input type="date" id="bfw-date-end" class="regular-text" value="<?php echo esc_attr($stats['date_end'] ?? date('Y-m-d')); ?>" style="height:35px; width: 140px;">
    173                 <button id="bfw-clear-stats" class="button"><?php _e('Reset', 'bonus-for-woo'); ?></button>
    174                 <button id="bfw-recalc-start" class="button button-primary" style="height: auto; padding: 6px 20px;"><?php _e('Refresh Analytics', 'bonus-for-woo'); ?></button>
     173                <button id="bfw-clear-stats" class="button"><?php esc_html_e('Reset', 'bonus-for-woo'); ?></button>
     174                <button id="bfw-recalc-start" class="button button-primary" style="height: auto; padding: 6px 20px;"><?php esc_html_e('Refresh Analytics', 'bonus-for-woo'); ?></button>
    175175            </div>
    176176        </div>
    177177        <div id="bfw-recalc-progress" style="display:none;">
    178             <h3 style="margin-top: 0;"><?php _e('Deep Data Analysis', 'bonus-for-woo'); ?>...</h3>
     178            <h3 style="margin-top: 0;"><?php esc_html_e('Deep Data Analysis', 'bonus-for-woo'); ?>...</h3>
    179179            <div style="background: #f0f0f1; height: 10px; border-radius: 5px; overflow: hidden; margin: 15px 0;">
    180180                <div id="bfw-progress-inner" style="width: 0%; background: var(--bfw-primary); height: 100%; transition: width 0.3s;"></div>
    181181            </div>
    182             <p id="bfw-progress-text" style="font-weight: 600;"><?php _e('Processing...', 'bonus-for-woo'); ?></p>
     182            <p id="bfw-progress-text" style="font-weight: 600;"><?php esc_html_e('Processing...', 'bonus-for-woo'); ?></p>
    183183        </div>
    184184        <!-- Ряд 1: Главные деньги -->
    … …  
    186186            <div class="bfw-card">
    187187                <div class="bfw-card-header">
    188                     <span class="bfw-card-title"><?php _e('Lifetime Value (LTV)', 'bonus-for-woo'); ?></span>
     188                    <span class="bfw-card-title"><?php esc_html_e('Lifetime Value (LTV)', 'bonus-for-woo'); ?></span>
    189189                    <div class="bfw-card-icon"><span class="dashicons dashicons-id"></span></div>
    190190                </div>
    191                 <div class="bfw-card-value"><?php echo wc_price($stats['ltv_bonus'] ?? 0); ?></div>
     191                <div class="bfw-card-value"><?php echo wp_kses_post(wc_price($stats['ltv_bonus'] ?? 0)); ?></div>
    192192                <div class="bfw-card-footer">
    193                     <?php echo __('Average total revenue from each participant of the bonus system.', 'bonus-for-woo'); ?>
     193                    <?php echo esc_html__('Average total revenue from each participant of the bonus system.', 'bonus-for-woo'); ?>
    194194                </div>
    195195            </div>
    … …  
    197197            <div class="bfw-card">
    198198                <div class="bfw-card-header">
    199                     <span class="bfw-card-title"><?php _e('Referral Revenue', 'bonus-for-woo'); ?></span>
     199                    <span class="bfw-card-title"><?php esc_html_e('Referral Revenue', 'bonus-for-woo'); ?></span>
    200200                    <div class="bfw-card-icon"><span class="dashicons dashicons-groups"></span></div>
    201201                </div>
    202                 <div class="bfw-card-value"><?php echo wc_price($stats['referral_revenue'] ?? 0); ?></div>
     202                <div class="bfw-card-value"><?php echo wp_kses_post(wc_price($stats['referral_revenue'] ?? 0)); ?></div>
    203203                <div class="bfw-card-footer">
    204                     <?php _e('Total money brought by invited friends.', 'bonus-for-woo'); ?>
     204                    <?php esc_html_e('Total money brought by invited friends.', 'bonus-for-woo'); ?>
    205205                </div>
    206206            </div>
    … …  
    208208            <div class="bfw-card">
    209209                <div class="bfw-card-header">
    210                     <span class="bfw-card-title"><?php _e('Customer Retention', 'bonus-for-woo'); ?></span>
     210                    <span class="bfw-card-title"><?php esc_html_e('Customer Retention', 'bonus-for-woo'); ?></span>
    211211                    <div class="bfw-card-icon"><span class="dashicons dashicons-update"></span></div>
    212212                </div>
    213                 <div class="bfw-card-value"><?php echo round($stats['retention_rate'] ?? 0, 1); ?>%</div>
     213                <div class="bfw-card-value"><?php echo esc_html(round((float) ($stats['retention_rate'] ?? 0), 1)); ?>%</div>
    214214                <div class="bfw-card-footer">
    215                     <?php _e('Percentage of loyal customers who make repeat purchases.', 'bonus-for-woo'); ?>
     215                    <?php esc_html_e('Percentage of loyal customers who make repeat purchases.', 'bonus-for-woo'); ?>
    216216                </div>
    217217            </div>
    … …  
    219219            <div class="bfw-card">
    220220                <div class="bfw-card-header">
    221                     <span class="bfw-card-title"><?php _e('Point Liability', 'bonus-for-woo'); ?></span>
     221                    <span class="bfw-card-title"><?php esc_html_e('Point Liability', 'bonus-for-woo'); ?></span>
    222222                    <div class="bfw-card-icon"><span class="dashicons dashicons-money-alt"></span></div>
    223223                </div>
    224                 <div class="bfw-card-value"><?php echo wc_price($stats['points_total'] ?? 0); ?></div>
     224                <div class="bfw-card-value"><?php echo wp_kses_post(wc_price($stats['points_total'] ?? 0)); ?></div>
    225225                <div class="bfw-card-footer">
    226                     <?php _e('Current financial "debt" in points sitting on user balances.', 'bonus-for-woo'); ?>
     226                    <?php esc_html_e('Current financial "debt" in points sitting on user balances.', 'bonus-for-woo'); ?>
    227227                </div>
    228228            </div>
    … …  
    230230            <div class="bfw-card">
    231231                <div class="bfw-card-header">
    232                     <span class="bfw-card-title"><?php _e('Pending Points', 'bonus-for-woo'); ?></span>
     232                    <span class="bfw-card-title"><?php esc_html_e('Pending Points', 'bonus-for-woo'); ?></span>
    233233                    <div class="bfw-card-icon"><span class="dashicons dashicons-clock"></span></div>
    234234                </div>
    235235                <div class="bfw-card-value" style="color: var(--bfw-warning);"><?php echo esc_html(BfwPoints::formatNumber((float) ($stats['pending_points_total'] ?? 0))); ?></div>
    236236                <div class="bfw-card-footer">
    237                     <?php _e('Points awaiting accrual — orders currently in Processing status.', 'bonus-for-woo'); ?>
     237                    <?php esc_html_e('Points awaiting accrual — orders currently in Processing status.', 'bonus-for-woo'); ?>
    238238                </div>
    239239            </div>
    … …  
    241241            <div class="bfw-card">
    242242                <div class="bfw-card-header">
    243                     <span class="bfw-card-title"><?php _e('Burn Rate', 'bonus-for-woo'); ?></span>
     243                    <span class="bfw-card-title"><?php esc_html_e('Burn Rate', 'bonus-for-woo'); ?></span>
    244244                    <div class="bfw-card-icon"><span class="dashicons dashicons-chart-line"></span></div>
    245245                </div>
    246246                <?php $burn = round($stats['prr'] ?? 0, 1); ?>
    247                 <div class="bfw-card-value" style="color: <?php echo $burn >= 20 && $burn <= 50 ? 'var(--bfw-success)' : 'var(--bfw-warning)'; ?>"><?php echo $burn; ?>%</div>
     247                <div class="bfw-card-value" style="color: <?php echo $burn >= 20 && $burn <= 50 ? 'var(--bfw-success)' : 'var(--bfw-warning)'; ?>"><?php echo esc_html(round((float) $burn, 1)); ?>%</div>
    248248                <div class="bfw-card-footer">
    249                     <?php _e('20-40% is a healthy figure for most stores.', 'bonus-for-woo'); ?>
     249                    <?php esc_html_e('20-40% is a healthy figure for most stores.', 'bonus-for-woo'); ?>
    250250                </div>
    251251            </div>
    … …  
    253253            <div class="bfw-card">
    254254                <div class="bfw-card-header">
    255                     <span class="bfw-card-title"><?php _e('Program Penetration', 'bonus-for-woo'); ?></span>
     255                    <span class="bfw-card-title"><?php esc_html_e('Program Penetration', 'bonus-for-woo'); ?></span>
    256256                    <div class="bfw-card-icon"><span class="dashicons dashicons-admin-users"></span></div>
    257257                </div>
    258                 <div class="bfw-card-value" style="color: var(--bfw-primary);"><?php echo round($stats['penetration'] ?? 0, 1); ?>%</div>
     258                <div class="bfw-card-value" style="color: var(--bfw-primary);"><?php echo esc_html(round((float) ($stats['penetration'] ?? 0), 1)); ?>%</div>
    259259                <div class="bfw-card-footer">
    260                     <?php _e('Share of all active customers using the bonus system.', 'bonus-for-woo'); ?>
     260                    <?php esc_html_e('Share of all active customers using the bonus system.', 'bonus-for-woo'); ?>
    261261                </div>
    262262            </div>
    … …  
    266266            <!-- Здоровье системы -->
    267267            <div class="bfw-card">
    268                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Ecosystem Health', 'bonus-for-woo'); ?></h3>
     268                <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Ecosystem Health', 'bonus-for-woo'); ?></h3>
    269269                <div style="margin-bottom: 25px;">
    270270                    <div style="display: flex; justify-content: space-between; align-items: baseline;">
    271                         <span style="font-weight: 600;"><?php _e('Redemption Rate', 'bonus-for-woo'); ?></span>
    272                         <span style="font-size: 24px; font-weight: 800; color: var(--bfw-primary);"><?php echo round($stats['prr'] ?? 0, 1); ?>%</span>
     271                        <span style="font-weight: 600;"><?php esc_html_e('Redemption Rate', 'bonus-for-woo'); ?></span>
     272                        <span style="font-size: 24px; font-weight: 800; color: var(--bfw-primary);"><?php echo esc_html(round((float) ($stats['prr'] ?? 0), 1)); ?>%</span>
    273273                    </div>
    274                     <div class="bfw-health-bar"><div class="bfw-health-fill" style="width: <?php echo min(100, $stats['prr'] ?? 0); ?>%"></div></div>
     274                    <div class="bfw-health-bar"><div class="bfw-health-fill" style="width: <?php echo esc_attr(min(100, (float) ($stats['prr'] ?? 0))); ?>%"></div></div>
    275275                    <p style="font-size: 13px; color: var(--bfw-text-muted);">
    276                         <?php _e('This shows how actively users spend their points. 20-40% is healthy for most stores.', 'bonus-for-woo'); ?>
     276                        <?php esc_html_e('This shows how actively users spend their points. 20-40% is healthy for most stores.', 'bonus-for-woo'); ?>
    277277                    </p>
    278278                </div>
    … …  
    280280                <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; padding-top: 15px; border-top: 1px solid var(--bfw-border);">
    281281                    <div>
    282                         <small style="color: var(--bfw-text-muted); display: block; margin-bottom: 4px;"><?php _e('Total Issued', 'bonus-for-woo'); ?></small>
     282                        <small style="color: var(--bfw-text-muted); display: block; margin-bottom: 4px;"><?php esc_html_e('Total Issued', 'bonus-for-woo'); ?></small>
    283283                        <strong style="font-size: 16px;"><?php echo esc_html(BfwPoints::formatNumber((float) ($stats['issued_total_life'] ?? 0))); ?></strong>
    284284                    </div>
    285285                    <div>
    286                         <small style="color: var(--bfw-text-muted); display: block; margin-bottom: 4px;"><?php _e('Total Spent', 'bonus-for-woo'); ?></small>
     286                        <small style="color: var(--bfw-text-muted); display: block; margin-bottom: 4px;"><?php esc_html_e('Total Spent', 'bonus-for-woo'); ?></small>
    287287                        <strong style="font-size: 16px;"><?php echo esc_html(BfwPoints::formatNumber((float) ($stats['spent_total_life'] ?? 0))); ?></strong>
    288288                    </div>
    … …  
    292292            <!-- Распределение уровней -->
    293293            <div class="bfw-card">
    294                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Customer Segmentation', 'bonus-for-woo'); ?></h3>
     294                <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Customer Segmentation', 'bonus-for-woo'); ?></h3>
    295295                <?php
    296296                $labels = []; $counts = [];
    … …  
    312312             <!-- Распределение баллов -->
    313313             <div class="bfw-card">
    314                  <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Points Distribution', 'bonus-for-woo'); ?></h3>
     314                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Points Distribution', 'bonus-for-woo'); ?></h3>
    315315                 <div id="bfw-distribution-loading" style="text-align: center; padding: 20px;">
    316316                     <span class="dashicons dashicons-spinner is-active"></span>
    317                      <?php _e('Loading...', 'bonus-for-woo'); ?>
     317                     <?php esc_html_e('Loading...', 'bonus-for-woo'); ?>
    318318                 </div>
    319319                 <div id="bfw-distribution-content" style="display: none;">
    … …  
    323323            <!-- Топ клиентов -->
    324324            <div class="bfw-card">
    325                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Top 5 VIP Customers', 'bonus-for-woo'); ?></h3>
     325                <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Top 5 VIP Customers', 'bonus-for-woo'); ?></h3>
    326326                <table style="width: 100%; border-collapse: collapse; text-align: left;">
    327327                    <thead>
    328328                    <tr style="border-bottom: 2px solid var(--bfw-border); color: var(--bfw-text-muted);">
    329                         <th style="padding: 10px 5px;"><?php _e('User', 'bonus-for-woo'); ?></th>
    330                         <th style="padding: 10px 5px;"><?php _e('Revenue', 'bonus-for-woo'); ?></th>
    331                         <th style="padding: 10px 5px;"><?php _e('Orders', 'bonus-for-woo'); ?></th>
     329                        <th style="padding: 10px 5px;"><?php esc_html_e('User', 'bonus-for-woo'); ?></th>
     330                        <th style="padding: 10px 5px;"><?php esc_html_e('Revenue', 'bonus-for-woo'); ?></th>
     331                        <th style="padding: 10px 5px;"><?php esc_html_e('Orders', 'bonus-for-woo'); ?></th>
    332332                    </tr>
    333333                    </thead>
    … …  
    339339                        <tr style="border-bottom: 1px solid #eee;">
    340340                            <td style="padding: 12px 5px; font-weight: 600;"><?php echo esc_html($name); ?></td>
    341                             <td style="padding: 12px 5px; color: var(--bfw-success);"><?php echo wc_price($vip['revenue']); ?></td>
    342                             <td style="padding: 12px 5px;"><?php echo $vip['orders']; ?></td>
     341                            <td style="padding: 12px 5px; color: var(--bfw-success);"><?php echo wp_kses_post(wc_price($vip['revenue'])); ?></td>
     342                            <td style="padding: 12px 5px;"><?php echo esc_html((int) $vip['orders']); ?></td>
    343343                        </tr>
    344344                    <?php endforeach; else: ?>
    345                         <tr><td colspan="3" style="padding: 15px; text-align: center;"><?php _e('Not enough data.', 'bonus-for-woo'); ?></td></tr>
     345                        <tr><td colspan="3" style="padding: 15px; text-align: center;"><?php esc_html_e('Not enough data.', 'bonus-for-woo'); ?></td></tr>
    346346                    <?php endif; ?>
    347347                    </tbody>
    … …  
    355355             <div class="bfw-card">
    356356                 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
    357                      <h3 style="margin: 0;"><?php _e('Points Trend Analytics', 'bonus-for-woo'); ?></h3>
     357                     <h3 style="margin: 0;"><?php esc_html_e('Points Trend Analytics', 'bonus-for-woo'); ?></h3>
    358358                     <select id="bfw-trend-period" style="padding: 6px 12px; border-radius: 4px; border: 1px solid var(--bfw-border);">
    359                          <option value="7"><?php _e('Last 7 days', 'bonus-for-woo'); ?></option>
    360                          <option value="30" selected><?php _e('Last 30 days', 'bonus-for-woo'); ?></option>
    361                          <option value="90"><?php _e('Last 90 days', 'bonus-for-woo'); ?></option>
    362                          <option value="365"><?php _e('Last year', 'bonus-for-woo'); ?></option>
     359                         <option value="7"><?php esc_html_e('Last 7 days', 'bonus-for-woo'); ?></option>
     360                         <option value="30" selected><?php esc_html_e('Last 30 days', 'bonus-for-woo'); ?></option>
     361                         <option value="90"><?php esc_html_e('Last 90 days', 'bonus-for-woo'); ?></option>
     362                         <option value="365"><?php esc_html_e('Last year', 'bonus-for-woo'); ?></option>
    363363                     </select>
    364364                 </div>
    365365                 <div id="bfw-trend-loading" style="text-align: center; padding: 20px;">
    366366                     <span class="dashicons dashicons-spinner is-active"></span>
    367                      <?php _e('Loading...', 'bonus-for-woo'); ?>
     367                     <?php esc_html_e('Loading...', 'bonus-for-woo'); ?>
    368368                 </div>
    369369                 <div id="bfw-trend-content" style="display: none;">
    … …  
    376376             <!-- Overall Stats -->
    377377             <div class="bfw-card">
    378                  <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Overall System Statistics', 'bonus-for-woo'); ?></h3>
     378                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Overall System Statistics', 'bonus-for-woo'); ?></h3>
    379379                 <div id="bfw-overall-loading" style="text-align: center; padding: 20px;">
    380380                     <span class="dashicons dashicons-spinner is-active"></span>
    381                      <?php _e('Loading...', 'bonus-for-woo'); ?>
     381                     <?php esc_html_e('Loading...', 'bonus-for-woo'); ?>
    382382                 </div>
    383383                 <div id="bfw-overall-content" style="display: none;">
    384384                     <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px;">
    385385                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    386                              <small style="color: var(--bfw-text-muted);"><?php _e('Total Points', 'bonus-for-woo'); ?></small>
     386                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Total Points', 'bonus-for-woo'); ?></small>
    387387                             <div style="font-size: 24px; font-weight: 700; color: var(--bfw-primary);" id="bfw-total-points">0</div>
    388388                         </div>
    389389                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    390                              <small style="color: var(--bfw-text-muted);"><?php _e('Users with Points', 'bonus-for-woo'); ?></small>
     390                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Users with Points', 'bonus-for-woo'); ?></small>
    391391                             <div style="font-size: 24px; font-weight: 700; color: var(--bfw-primary);" id="bfw-users-with-points">0</div>
    392392                         </div>
    393393                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    394                              <small style="color: var(--bfw-text-muted);"><?php _e('Total Earned', 'bonus-for-woo'); ?></small>
     394                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Total Earned', 'bonus-for-woo'); ?></small>
    395395                             <div style="font-size: 24px; font-weight: 700; color: var(--bfw-success);" id="bfw-total-earned">0</div>
    396396                         </div>
    397397                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    398                              <small style="color: var(--bfw-text-muted);"><?php _e('Total Spent', 'bonus-for-woo'); ?></small>
     398                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Total Spent', 'bonus-for-woo'); ?></small>
    399399                             <div style="font-size: 24px; font-weight: 700; color: var(--bfw-warning);" id="bfw-total-spent">0</div>
    400400                         </div>
    401401                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px; grid-column: span 2;">
    402                              <small style="color: var(--bfw-text-muted);"><?php _e('Active Users (30 days)', 'bonus-for-woo'); ?></small>
     402                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Active Users (30 days)', 'bonus-for-woo'); ?></small>
    403403                             <div style="font-size: 24px; font-weight: 700; color: var(--bfw-primary);" id="bfw-active-users">0</div>
    404404                         </div>
    … …  
    409409             <!-- Top Users -->
    410410             <div class="bfw-card">
    411                  <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Top Users by Points', 'bonus-for-woo'); ?></h3>
     411                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Top Users by Points', 'bonus-for-woo'); ?></h3>
    412412                 <div id="bfw-top-users-loading" style="text-align: center; padding: 20px;">
    413413                     <span class="dashicons dashicons-spinner is-active"></span>
    414                      <?php _e('Loading...', 'bonus-for-woo'); ?>
     414                     <?php esc_html_e('Loading...', 'bonus-for-woo'); ?>
    415415                 </div>
    416416                 <div id="bfw-top-users-content" style="display: none;">
    … …  
    418418                         <thead>
    419419                             <tr style="border-bottom: 2px solid var(--bfw-border); color: var(--bfw-text-muted);">
    420                                  <th style="padding: 10px; text-align: left;"><?php _e('User', 'bonus-for-woo'); ?></th>
    421                                  <th style="padding: 10px; text-align: right;"><?php _e('Points', 'bonus-for-woo'); ?></th>
     420                                 <th style="padding: 10px; text-align: left;"><?php esc_html_e('User', 'bonus-for-woo'); ?></th>
     421                                 <th style="padding: 10px; text-align: right;"><?php esc_html_e('Points', 'bonus-for-woo'); ?></th>
    422422                             </tr>
    423423                         </thead>
    … …  
    431431             <!-- Coupon Stats -->
    432432             <div class="bfw-card">
    433                  <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Coupon Statistics', 'bonus-for-woo'); ?></h3>
     433                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Coupon Statistics', 'bonus-for-woo'); ?></h3>
    434434                 <div id="bfw-coupon-loading" style="text-align: center; padding: 20px;">
    435435                     <span class="dashicons dashicons-spinner is-active"></span>
    436                      <?php _e('Loading...', 'bonus-for-woo'); ?>
     436                     <?php esc_html_e('Loading...', 'bonus-for-woo'); ?>
    437437                 </div>
    438438                 <div id="bfw-coupon-content" style="display: none;">
    439439                     <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px;">
    440440                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    441                              <small style="color: var(--bfw-text-muted);"><?php _e('Total Coupons', 'bonus-for-woo'); ?></small>
     441                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Total Coupons', 'bonus-for-woo'); ?></small>
    442442                             <div style="font-size: 20px; font-weight: 700; color: var(--bfw-primary);" id="bfw-total-coupons">0</div>
    443443                         </div>
    444444                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    445                              <small style="color: var(--bfw-text-muted);"><?php _e('Active Coupons', 'bonus-for-woo'); ?></small>
     445                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Active Coupons', 'bonus-for-woo'); ?></small>
    446446                             <div style="font-size: 20px; font-weight: 700; color: var(--bfw-success);" id="bfw-active-coupons">0</div>
    447447                         </div>
    448448                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    449                              <small style="color: var(--bfw-text-muted);"><?php _e('Used Coupons', 'bonus-for-woo'); ?></small>
     449                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Used Coupons', 'bonus-for-woo'); ?></small>
    450450                             <div style="font-size: 20px; font-weight: 700; color: var(--bfw-warning);" id="bfw-used-coupons">0</div>
    451451                         </div>
    452452                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    453                              <small style="color: var(--bfw-text-muted);"><?php _e('Total Coupon Sum', 'bonus-for-woo'); ?></small>
     453                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Total Coupon Sum', 'bonus-for-woo'); ?></small>
    454454                             <div style="font-size: 20px; font-weight: 700; color: var(--bfw-primary);" id="bfw-total-coupon-sum">0</div>
    455455                         </div>
    … …  
    464464             <!-- Сводка оборота -->
    465465             <div class="bfw-card">
    466                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Revenue Breakdown', 'bonus-for-woo'); ?></h3>
     466                <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Revenue Breakdown', 'bonus-for-woo'); ?></h3>
    467467                <div style="margin-bottom: 15px;">
    468468                    <div style="display: flex; justify-content: space-between; margin-bottom: 6px;">
    469                         <span><?php _e('Total Store Revenue', 'bonus-for-woo'); ?></span>
    470                         <strong><?php echo wc_price($stats['total_revenue'] ?? 0); ?></strong>
     469                        <span><?php esc_html_e('Total Store Revenue', 'bonus-for-woo'); ?></span>
     470                        <strong><?php echo wp_kses_post(wc_price($stats['total_revenue'] ?? 0)); ?></strong>
    471471                    </div>
    472472                    <div style="display: flex; justify-content: space-between; font-size: 13px; color: var(--bfw-text-muted);">
    473                         <span><?php _e('From orders with bonus discount', 'bonus-for-woo'); ?></span>
    474                         <span><?php echo wc_price($stats['total_revenue'] - ($stats['rev_no_bonus'] ?? ($stats['total_revenue'] / 2))); ?></span>
     473                        <span><?php esc_html_e('From orders with bonus discount', 'bonus-for-woo'); ?></span>
     474                        <span><?php echo wp_kses_post(wc_price($stats['total_revenue'] - ($stats['rev_no_bonus'] ?? ($stats['total_revenue'] / 2)))); ?></span>
    475475                    </div>
    476476                </div>
    … …  
    478478                <div style="padding-top: 15px; border-top: 1px solid var(--bfw-border);">
    479479                     <div style="display: flex; justify-content: space-between; align-items: center;">
    480                         <span style="font-weight: 600;"><?php _e('AOV Increase', 'bonus-for-woo'); ?></span>
     480                        <span style="font-weight: 600;"><?php esc_html_e('AOV Increase', 'bonus-for-woo'); ?></span>
    481481                        <?php
    482482                        $aov_no = $stats['aov_no_bonus'] ?? 0;
    … …  
    486486                        $trend_sign = $increase >= 0 ? '+' : '';
    487487                        ?>
    488                         <span class="bfw-trend <?php echo $trend_class; ?>" style="font-size: 14px;"><?php echo $trend_sign . round($increase, 1); ?>%</span>
     488                        <span class="bfw-trend <?php echo esc_attr($trend_class); ?>" style="font-size: 14px;"><?php echo esc_html($trend_sign . round((float) $increase, 1)); ?>%</span>
    489489                     </div>
    490490                     <p style="font-size: 12px; color: var(--bfw-text-muted); margin-top: 5px;">
    … …  
    496496             <!-- Реферальная активность -->
    497497             <div class="bfw-card">
    498                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Viral Growth (Referrals)', 'bonus-for-woo'); ?></h3>
     498                <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Viral Growth (Referrals)', 'bonus-for-woo'); ?></h3>
    499499                <?php if (BfwRoles::isPro() && BfwSetting::get('referal-system')) : ?>
    500500                    <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; margin-bottom: 20px;">
    501501                        <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    502                             <small style="color: var(--bfw-text-muted);"><?php _e('Ambassadors', 'bonus-for-woo'); ?></small>
    503                             <div style="font-size: 20px; font-weight: 700; color: var(--bfw-primary);"><?php echo $stats['referrals_total'] ?? 0; ?></div>
     502                            <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Ambassadors', 'bonus-for-woo'); ?></small>
     503                            <div style="font-size: 20px; font-weight: 700; color: var(--bfw-primary);"><?php echo esc_html((int) ($stats['referrals_total'] ?? 0)); ?></div>
    504504                        </div>
    505505                        <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    506                             <small style="color: var(--bfw-text-muted);"><?php _e('Friends Invited', 'bonus-for-woo'); ?></small>
    507                             <div style="font-size: 20px; font-weight: 700; color: var(--bfw-success);"><?php echo $stats['referrals_invited'] ?? 0; ?></div>
     506                            <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Friends Invited', 'bonus-for-woo'); ?></small>
     507                            <div style="font-size: 20px; font-weight: 700; color: var(--bfw-success);"><?php echo esc_html((int) ($stats['referrals_invited'] ?? 0)); ?></div>
    508508                        </div>
    509509                        <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    510                             <small style="color: var(--bfw-text-muted);"><?php _e('Users with Referral Link', 'bonus-for-woo'); ?></small>
     510                            <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Users with Referral Link', 'bonus-for-woo'); ?></small>
    511511                            <div style="font-size: 20px; font-weight: 700; color: var(--bfw-primary);" id="bfw-referral-users">0</div>
    512512                        </div>
    513513                        <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    514                             <small style="color: var(--bfw-text-muted);"><?php _e('Referral Revenue', 'bonus-for-woo'); ?></small>
    515                             <div style="font-size: 20px; font-weight: 700; color: var(--bfw-success);"><?php echo wc_price($stats['referral_revenue'] ?? 0); ?></div>
     514                            <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Referral Revenue', 'bonus-for-woo'); ?></small>
     515                            <div style="font-size: 20px; font-weight: 700; color: var(--bfw-success);"><?php echo wp_kses_post(wc_price($stats['referral_revenue'] ?? 0)); ?></div>
    516516                        </div>
    517517                    </div>
    518                     <h4 style="margin-bottom: 10px;"><?php _e('Top Referrers', 'bonus-for-woo'); ?></h4>
     518                    <h4 style="margin-bottom: 10px;"><?php esc_html_e('Top Referrers', 'bonus-for-woo'); ?></h4>
    519519                    <table style="width: 100%; border-collapse: collapse;">
    520520                        <thead>
    521521                            <tr style="border-bottom: 2px solid var(--bfw-border); color: var(--bfw-text-muted);">
    522                                 <th style="padding: 10px; text-align: left;"><?php _e('User', 'bonus-for-woo'); ?></th>
    523                                 <th style="padding: 10px; text-align: right;"><?php _e('Invitations', 'bonus-for-woo'); ?></th>
     522                                <th style="padding: 10px; text-align: left;"><?php esc_html_e('User', 'bonus-for-woo'); ?></th>
     523                                <th style="padding: 10px; text-align: right;"><?php esc_html_e('Invitations', 'bonus-for-woo'); ?></th>
    524524                            </tr>
    525525                        </thead>
    … …  
    528528                    <div id="bfw-referral-loading" style="text-align: center; padding: 20px; display: none;">
    529529                        <span class="dashicons dashicons-spinner is-active"></span>
    530                         <?php _e('Loading...', 'bonus-for-woo'); ?>
     530                        <?php esc_html_e('Loading...', 'bonus-for-woo'); ?>
    531531                    </div>
    532532                <?php else: ?>
    533533                    <p style="color: var(--bfw-text-muted); text-align: center; padding: 20px 0;">
    534                         <?php _e('Referral system is currently disabled.', 'bonus-for-woo'); ?>
     534                        <?php esc_html_e('Referral system is currently disabled.', 'bonus-for-woo'); ?>
    535535                    </p>
    536536                <?php endif; ?>
    … …  
    542542            <!-- Export Buttons -->
    543543            <div class="bfw-card">
    544                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Export Analytics', 'bonus-for-woo'); ?></h3>
     544                <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Export Analytics', 'bonus-for-woo'); ?></h3>
    545545                <div style="display: flex; gap: 10px; flex-wrap: wrap;">
    546                     <button class="button" id="bfw-export-distribution"><?php _e('Export Distribution', 'bonus-for-woo'); ?></button>
    547                     <button class="button" id="bfw-export-trend"><?php _e('Export Trend', 'bonus-for-woo'); ?></button>
    548                     <button class="button" id="bfw-export-overall"><?php _e('Export Overall Stats', 'bonus-for-woo'); ?></button>
     546                    <button class="button" id="bfw-export-distribution"><?php esc_html_e('Export Distribution', 'bonus-for-woo'); ?></button>
     547                    <button class="button" id="bfw-export-trend"><?php esc_html_e('Export Trend', 'bonus-for-woo'); ?></button>
     548                    <button class="button" id="bfw-export-overall"><?php esc_html_e('Export Overall Stats', 'bonus-for-woo'); ?></button>
    549549                </div>
    550550            </div>
    … …  
    612612                                    labels: labels,
    613613                                    datasets: [{
    614                                         label: '<?php _e('Number of Users', 'bonus-for-woo'); ?>',
     614                                        label: '<?php esc_html_e('Number of Users', 'bonus-for-woo'); ?>',
    615615                                        data: data,
    616616                                        backgroundColor: '#2271b1',
    … …  
    627627                            });
    628628                        } else {
    629                             $('#bfw-distribution-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     629                            $('#bfw-distribution-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    630630                        }
    631631                    }).fail(function(xhr, status, error) {
    632632                        console.error('Distribution AJAX failed:', status, error);
    633                         $('#bfw-distribution-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     633                        $('#bfw-distribution-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    634634                    });
    635635                }
    … …  
    668668                                        datasets: [
    669669                                            {
    670                                                 label: '<?php _e('Issued Points (+)', 'bonus-for-woo'); ?>',
     670                                                label: '<?php esc_html_e('Issued Points (+)', 'bonus-for-woo'); ?>',
    671671                                                data: issuedData,
    672672                                                borderColor: '#00a65a',
    … …  
    677677                                            },
    678678                                            {
    679                                                 label: '<?php _e('Spent Points (-)', 'bonus-for-woo'); ?>',
     679                                                label: '<?php esc_html_e('Spent Points (-)', 'bonus-for-woo'); ?>',
    680680                                                data: spentData,
    681681                                                borderColor: '#f0b849',
    … …  
    696696                                });
    697697                            } else {
    698                                 $('#bfw-trend-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     698                                $('#bfw-trend-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    699699                            }
    700700                        }).fail(function() {
    701                             $('#bfw-trend-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     701                            $('#bfw-trend-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    702702                        });
    703703                    }
    … …  
    724724                        $('#bfw-active-users').text(response.data.active_users.toLocaleString());
    725725                    } else {
    726                         $('#bfw-overall-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     726                        $('#bfw-overall-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    727727                    }
    728728                }).fail(function() {
    729                     $('#bfw-overall-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     729                    $('#bfw-overall-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    730730                });
    731731
    … …  
    742742                        });
    743743                    } else {
    744                         $('#bfw-top-users-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     744                        $('#bfw-top-users-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    745745                    }
    746746                }).fail(function() {
    747                     $('#bfw-top-users-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     747                    $('#bfw-top-users-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    748748                });
    749749
    … …  
    763763                            });
    764764                        } else {
    765                             tbody.append('<tr><td colspan="2" style="padding: 15px; text-align: center;"><?php _e('No data', 'bonus-for-woo'); ?></td></tr>');
     765                            tbody.append('<tr><td colspan="2" style="padding: 15px; text-align: center;"><?php esc_html_e('No data', 'bonus-for-woo'); ?></td></tr>');
    766766                        }
    767767                    }
    768768                }).fail(function() {
    769                     $('#bfw-top-referrers-table').append('<tr><td colspan="2" style="padding: 15px; text-align: center;"><?php _e('Failed to load data', 'bonus-for-woo'); ?></td></tr>');
     769                    $('#bfw-top-referrers-table').append('<tr><td colspan="2" style="padding: 15px; text-align: center;"><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></td></tr>');
    770770                });
    771771
    … …  
    780780                        $('#bfw-total-coupon-sum').text(response.data.total_coupon_sum.toLocaleString());
    781781                    } else {
    782                         $('#bfw-coupon-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     782                        $('#bfw-coupon-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    783783                    }
    784784                }).fail(function() {
    785                     $('#bfw-coupon-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     785                    $('#bfw-coupon-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    786786                });
    787787
    … …  
    808808        <div style="text-align: center; padding: 80px 20px; background: #fff; border-radius: 12px; border: 1px solid var(--bfw-border);">
    809809            <span class="dashicons dashicons-analytics" style="font-size: 80px; width: 80px; height: 80px; color: #ccd0d4; margin-bottom: 20px;"></span>
    810             <h2><?php _e('Analytics is ready to work', 'bonus-for-woo'); ?></h2>
    811             <p><?php _e('Calculate your first report to unlock insights about LTV, Retention and Referral ROI.', 'bonus-for-woo'); ?></p>
    812             <button id="bfw-recalc-start" class="button button-primary button-large" style="margin-top: 20px; height: auto; padding: 10px 40px;"><?php _e('Start Initial Calculation', 'bonus-for-woo'); ?></button>
     810            <h2><?php esc_html_e('Analytics is ready to work', 'bonus-for-woo'); ?></h2>
     811            <p><?php esc_html_e('Calculate your first report to unlock insights about LTV, Retention and Referral ROI.', 'bonus-for-woo'); ?></p>
     812            <button id="bfw-recalc-start" class="button button-primary button-large" style="margin-top: 20px; height: auto; padding: 10px 40px;"><?php esc_html_e('Start Initial Calculation', 'bonus-for-woo'); ?></button>
    813813        </div>
    814814    <?php endif; ?>
    … …  
    831831        $('#bfw-progress-inner').css('width', percent + '%');
    832832        const msgs = [
    833             '<?php _e('Analyzing user statuses...', 'bonus-for-woo'); ?>',
    834             '<?php _e('Calculating Point Redemption Rate...', 'bonus-for-woo'); ?>',
    835             '<?php _e('Auditing Referral network and Revenue...', 'bonus-for-woo'); ?>',
    836             '<?php _e('Correlating Retention and LTV metrics...', 'bonus-for-woo'); ?>',
    837             '<?php _e('Rendering dynamic timeline...', 'bonus-for-woo'); ?>'
     833            '<?php esc_html_e('Analyzing user statuses...', 'bonus-for-woo'); ?>',
     834            '<?php esc_html_e('Calculating Point Redemption Rate...', 'bonus-for-woo'); ?>',
     835            '<?php esc_html_e('Auditing Referral network and Revenue...', 'bonus-for-woo'); ?>',
     836            '<?php esc_html_e('Correlating Retention and LTV metrics...', 'bonus-for-woo'); ?>',
     837            '<?php esc_html_e('Rendering dynamic timeline...', 'bonus-for-woo'); ?>'
    838838        ];
    839         $('#bfw-progress-text').text(msgs[step] || '<?php _e('Finalizing...', 'bonus-for-woo'); ?>');
     839        $('#bfw-progress-text').text(msgs[step] || '<?php esc_html_e('Finalizing...', 'bonus-for-woo'); ?>');
    840840    }
    841841
    … …  
    861861                    setTimeout(runStep, 100);
    862862                } else {
    863                     $('#bfw-progress-text').text('✅ <?php _e('Analysis Complete! Reloading...', 'bonus-for-woo'); ?>');
     863                    $('#bfw-progress-text').text('✅ <?php esc_html_e('Analysis Complete! Reloading...', 'bonus-for-woo'); ?>');
    864864                    setTimeout(() => window.location.reload(), 1000);
    865865                }
    … …  
    872872
    873873    $('#bfw-clear-stats').on('click', function () {
    874         if (!confirm('<?php _e('Clear stats cache?', 'bonus-for-woo'); ?>')) return;
     874        if (!confirm('<?php esc_html_e('Clear stats cache?', 'bonus-for-woo'); ?>')) return;
    875875        $.post(ajaxurl, {
    876876            action: 'bfw_clear_stats',
  • bonus-for-woo/tags/8.5.2/pages/tools.php

    r3784081 r3795776  
    228228        <h1>
    229229            <span class="dashicons dashicons-admin-tools" style="font-size: 32px; width: 32px; height: 32px;"></span>
    230             <?php echo __('Bonus for Woo', 'bonus-for-woo') . ' - ' . __('Tools', 'bonus-for-woo'); ?>
     230            <?php echo esc_html__('Bonus for Woo', 'bonus-for-woo') . ' - ' . __('Tools', 'bonus-for-woo'); ?>
    231231        </h1>
    232232    </div>
    … …  
    238238            <div class="bfw-tool-header">
    239239                <div class="bfw-tool-icon"><span class="dashicons dashicons-backup"></span></div>
    240                 <h2 class="bfw-tool-title"><?php _e('Accrual of points for previous orders.', 'bonus-for-woo'); ?></h2>
     240                <h2 class="bfw-tool-title"><?php esc_html_e('Accrual of points for previous orders.', 'bonus-for-woo'); ?></h2>
    241241            </div>
    242242            <p class="bfw-tool-desc">
    243                 <?php _e('We will award points to users for previous orders, before installing the bonus system plugin.', 'bonus-for-woo'); ?>
    244                 <br><a href="https://computy.ru/blog/docs/bonus-for-woo/instrumenty/nachislenie-ballov-za-predydushhie-zakazy/" target="_blank" style="text-decoration:none; color: var(--bfw-primary); font-size: 13px;"><?php _e('More details', 'bonus-for-woo'); ?> ↗</a>
     243                <?php esc_html_e('We will award points to users for previous orders, before installing the bonus system plugin.', 'bonus-for-woo'); ?>
     244                <br><a href="https://computy.ru/blog/docs/bonus-for-woo/instrumenty/nachislenie-ballov-za-predydushhie-zakazy/" target="_blank" style="text-decoration:none; color: var(--bfw-primary); font-size: 13px;"><?php esc_html_e('More details', 'bonus-for-woo'); ?> ↗</a>
    245245            </p>
    246246
    247247            <div class="bfw-alert bfw-alert-warning">
    248248                <span class="dashicons dashicons-warning"></span>
    249                 <span> <?php _e('Attention! The action is irreversible!', 'bonus-for-woo'); ?></span>
     249                <span> <?php esc_html_e('Attention! The action is irreversible!', 'bonus-for-woo'); ?></span>
    250250            </div>
    251251
    … …  
    255255                        <button id="startRecalculation" class="bfw-btn bfw-btn-danger">
    256256                            <span class="dashicons dashicons-controls-play"></span>
    257                             <?php _e('Recalculation', 'bonus-for-woo'); ?>
     257                            <?php esc_html_e('Recalculation', 'bonus-for-woo'); ?>
    258258                        </button>
    259259                        <div id="progress" style="font-weight: 600; color: var(--bfw-success); font-size: 13px;"></div>
    … …  
    263263                    </div>
    264264                <?php else : ?>
    265                     <span class="bfw-badge-pro"><?php _e('Available in PRO version.', 'bonus-for-woo'); ?></span>
     265                    <span class="bfw-badge-pro"><?php esc_html_e('Available in PRO version.', 'bonus-for-woo'); ?></span>
    266266                <?php endif; ?>
    267267            </div>
    … …  
    272272            <div class="bfw-tool-header">
    273273                <div class="bfw-tool-icon"><span class="dashicons dashicons-plus-alt"></span></div>
    274                 <h2 class="bfw-tool-title"><?php _e('Bulk adding points', 'bonus-for-woo'); ?></h2>
    275             </div>
    276             <p class="bfw-tool-desc"><?php _e('Works without creating a notification. It won\'t be sent to your email.', 'bonus-for-woo'); ?></p>
     274                <h2 class="bfw-tool-title"><?php esc_html_e('Bulk adding points', 'bonus-for-woo'); ?></h2>
     275            </div>
     276            <p class="bfw-tool-desc"><?php esc_html_e('Works without creating a notification. It won\'t be sent to your email.', 'bonus-for-woo'); ?></p>
    277277
    278278            <?php if (BfwRoles::isPro()) : ?>
    279279                <div class="bfw-tool-form">
    280280                    <div class="bfw-form-group">
    281                         <label><?php _e('How many points should I add?', 'bonus-for-woo'); ?></label>
     281                        <label><?php esc_html_e('How many points should I add?', 'bonus-for-woo'); ?></label>
    282282                        <input type="number" id="mass_points" class="bfw-input" value="100" min="1">
    283283                    </div>
    284284                    <div class="bfw-form-group">
    285                         <label><?php _e('Text in history', 'bonus-for-woo'); ?></label>
    286                         <input type="text" id="mass_text" class="bfw-input" placeholder="<?php _e('e.g. Holiday Gift', 'bonus-for-woo'); ?>">
     285                        <label><?php esc_html_e('Text in history', 'bonus-for-woo'); ?></label>
     286                        <input type="text" id="mass_text" class="bfw-input" placeholder="<?php esc_html_e('e.g. Holiday Gift', 'bonus-for-woo'); ?>">
    287287                    </div>
    288288                </div>
    … …  
    291291                        <button id="mass-start" class="bfw-btn bfw-btn-primary">
    292292                            <span class="dashicons dashicons-share-alt"></span>
    293                             <?php _e('Start adding points', 'bonus-for-woo'); ?>
     293                            <?php esc_html_e('Start adding points', 'bonus-for-woo'); ?>
    294294                        </button>
    295295                        <div id="mass-progress" style="font-size: 13px;"></div>
    … …  
    298298            <?php else : ?>
    299299                <div class="bfw-tool-footer">
    300                     <span class="bfw-badge-pro"><?php _e('Available in PRO version.', 'bonus-for-woo'); ?></span>
     300                    <span class="bfw-badge-pro"><?php esc_html_e('Available in PRO version.', 'bonus-for-woo'); ?></span>
    301301                </div>
    302302            <?php endif; ?>
    … …  
    307307            <div class="bfw-tool-header">
    308308                <div class="bfw-tool-icon"><span class="dashicons dashicons-media-spreadsheet"></span></div>
    309                 <h2 class="bfw-tool-title"><?php _e('Export/Import', 'bonus-for-woo'); ?></h2>
    310             </div>
    311             <p class="bfw-tool-desc"><?php _e('When you click the "Create CSV export file" button, a link to download the file will appear next to it. You can download the file and edit it and then import it in the form below. After that, the bonus points data will be updated.', 'bonus-for-woo'); ?></p>
     309                <h2 class="bfw-tool-title"><?php esc_html_e('Export/Import', 'bonus-for-woo'); ?></h2>
     310            </div>
     311            <p class="bfw-tool-desc"><?php esc_html_e('Download the CSV export, edit it in a spreadsheet, then upload it in the form below. After the import, the bonus points data will be updated.', 'bonus-for-woo'); ?></p>
    312312
    313313            <div class="bfw-tool-form">
    314314                <div style="display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 20px;">
    315                     <?php $filename = BfwFunctions::getExportFilePath(); ?>
    316                     <?php if (is_file($filename)) : ?>
    317                         <a class="bfw-btn bfw-btn-outline" href="<?php echo wp_nonce_url('?page=bonus-for-woo%2Fpages%2Ftools.php&export_bfw_points=true', 'bfw_export_points'); ?>">
    318                             <span class="dashicons dashicons-update"></span> <?php _e('Refresh CSV', 'bonus-for-woo'); ?>
    319                         </a>
    320                         <a class="bfw-btn bfw-btn-outline" href="<?php echo esc_url(wp_nonce_url(admin_url('admin-post.php?action=bfw_download_export'), 'bfw_download_export')); ?>" download>
    321                             <span class="dashicons dashicons-download"></span> <?php _e('Download CSV', 'bonus-for-woo'); ?>
    322                         </a>
    323                         <a title="<?php _e('Delete export file', 'bonus-for-woo'); ?>" class="bfw-btn bfw-btn-danger" style="padding: 10px;" href="<?php echo wp_nonce_url('?page=bonus-for-woo%2Fpages%2Ftools.php&remove_export_bfw_points=true', 'bfw_remove_export'); ?>">
    324                             <span class="dashicons dashicons-trash"></span>
    325                         </a>
    326                     <?php else : ?>
    327                         <a class="bfw-btn bfw-btn-outline" href="<?php echo wp_nonce_url('?page=bonus-for-woo%2Fpages%2Ftools.php&export_bfw_points=true', 'bfw_export_points'); ?>">
    328                              <span class="dashicons dashicons-media-spreadsheet"></span> <?php _e('Сreate CSV export file', 'bonus-for-woo'); ?>
    329                         </a>
    330                     <?php endif; ?>
     315                    <a class="bfw-btn bfw-btn-outline" href="<?php echo esc_url(wp_nonce_url('?page=bonus-for-woo%2Fpages%2Ftools.php&export_bfw_points=true', 'bfw_export_points')); ?>">
     316                        <span class="dashicons dashicons-download"></span> <?php esc_html_e('Download CSV export', 'bonus-for-woo'); ?>
     317                    </a>
    331318                </div>
    332319
    333320                <div style="padding: 15px; background: #fcfcfc; border: 1px dashed var(--bfw-border); border-radius: 8px;">
    334                     <label style="display:block; font-weight:600; margin-bottom:10px; font-size:13px;"><?php _e('Upload CSV file', 'bonus-for-woo'); ?></label>
     321                    <label style="display:block; font-weight:600; margin-bottom:10px; font-size:13px;"><?php esc_html_e('Upload CSV file', 'bonus-for-woo'); ?></label>
    335322                    <input name="file" type="file" id="bfw-file-export" style="font-size:12px; width:100%; margin-bottom:15px;">
    336323                   
    337324                    <div style="display:flex; gap:10px;">
    338325                        <select id="search_by" class="bfw-input" style="width: auto; height: 38px;">
    339                             <option value="by_id"><?php _e('by ID', 'bonus-for-woo'); ?></option>
    340                             <option value="by_email"><?php _e('by email', 'bonus-for-woo'); ?></option>
    341                             <option value="by_phone"><?php _e('by phone', 'bonus-for-woo'); ?></option>
     326                            <option value="by_id"><?php esc_html_e('by ID', 'bonus-for-woo'); ?></option>
     327                            <option value="by_email"><?php esc_html_e('by email', 'bonus-for-woo'); ?></option>
     328                            <option value="by_phone"><?php esc_html_e('by phone', 'bonus-for-woo'); ?></option>
    342329                        </select>
    343330                        <button class="bfw-btn bfw-btn-primary" onclick="upload(); return false;">
    344                             <span class="dashicons dashicons-upload"></span> <?php _e('import', 'bonus-for-woo'); ?>
     331                            <span class="dashicons dashicons-upload"></span> <?php esc_html_e('import', 'bonus-for-woo'); ?>
    345332                        </button>
    346333                    </div>
    … …  
    354341            <div class="bfw-tool-header">
    355342                <div class="bfw-tool-icon"><span class="dashicons dashicons-admin-users"></span></div>
    356                 <h2 class="bfw-tool-title"><?php _e('Update user statuses', 'bonus-for-woo'); ?></h2>
     343                <h2 class="bfw-tool-title"><?php esc_html_e('Update user statuses', 'bonus-for-woo'); ?></h2>
    357344            </div>
    358345            <div class="bfw-tool-desc">
    359346                 <div style="margin-bottom: 20px;">
    360                     <p style="font-size:13px; margin-bottom:12px;"><?php _e('Force update all loyalty statuses based on current user balance.', 'bonus-for-woo'); ?></p>
     347                    <p style="font-size:13px; margin-bottom:12px;"><?php esc_html_e('Force update all loyalty statuses based on current user balance.', 'bonus-for-woo'); ?></p>
    361348                     <div class="bfw-alert bfw-alert-warning">
    362349                         <span class="dashicons dashicons-warning"></span>
    363                          <span> <?php _e('Update all users statuses. Requires a lot of resources!', 'bonus-for-woo'); ?></span>
     350                         <span> <?php esc_html_e('Update all users statuses. Requires a lot of resources!', 'bonus-for-woo'); ?></span>
    364351                     </div>
    365352                    <form method="post" action="">
    … …  
    368355                        <button type="submit" class="bfw-btn bfw-btn-outline">
    369356                            <span class="dashicons dashicons-update-alt"></span>
    370                             <?php _e('Update user statuses', 'bonus-for-woo'); ?>
     357                            <?php esc_html_e('Update user statuses', 'bonus-for-woo'); ?>
    371358                        </button>
    372359                    </form>
    … …  
    391378            <div class="bfw-tool-header">
    392379                <div class="bfw-tool-icon"><span class="dashicons dashicons-list-view"></span></div>
    393                 <h2 class="bfw-tool-title"><?php _e('Transaction ledger', 'bonus-for-woo'); ?></h2>
     380                <h2 class="bfw-tool-title"><?php esc_html_e('Transaction ledger', 'bonus-for-woo'); ?></h2>
    394381            </div>
    395382            <div class="bfw-tool-desc">
    396383                <div style="margin-bottom: 20px;">
    397384                    <p style="font-size:13px; margin-bottom:12px;">
    398                         <?php _e('The ledger records every operation on a balance and makes repeated accrual impossible. Run the migration once after upgrading, then use the audit to find balances that disagree with the ledger.', 'bonus-for-woo'); ?>
     385                        <?php esc_html_e('The ledger records every operation on a balance and makes repeated accrual impossible. Run the migration once after upgrading, then use the audit to find balances that disagree with the ledger.', 'bonus-for-woo'); ?>
    399386                    </p>
    400387
    … …  
    404391                        <button type="submit" class="bfw-btn bfw-btn-outline">
    405392                            <span class="dashicons dashicons-database-import"></span>
    406                             <?php _e('Import history into ledger', 'bonus-for-woo'); ?>
     393                            <?php esc_html_e('Import history into ledger', 'bonus-for-woo'); ?>
    407394                        </button>
    408395                    </form>
    … …  
    413400                        <button type="submit" class="bfw-btn bfw-btn-outline">
    414401                            <span class="dashicons dashicons-search"></span>
    415                             <?php _e('Audit balances against ledger', 'bonus-for-woo'); ?>
     402                            <?php esc_html_e('Audit balances against ledger', 'bonus-for-woo'); ?>
    416403                        </button>
    417404                    </form>
    … …  
    491478            <div class="bfw-tool-header">
    492479                <div class="bfw-tool-icon"><span class="dashicons dashicons-editor-paragraph"></span></div>
    493                 <h2 class="bfw-tool-title"><?php _e('Rules and Conditions Generator', 'bonus-for-woo'); ?></h2>
     480                <h2 class="bfw-tool-title"><?php esc_html_e('Rules and Conditions Generator', 'bonus-for-woo'); ?></h2>
    494481            </div>
    495482            <div class="bfw-tool-desc">
    … …  
    498485                <div>
    499486
    500                     <p style="font-size:13px; margin-bottom:12px;"><?php _e('This generator will create text for you based on the plugin settings.', 'bonus-for-woo'); ?></p>
     487                    <p style="font-size:13px; margin-bottom:12px;"><?php esc_html_e('This generator will create text for you based on the plugin settings.', 'bonus-for-woo'); ?></p>
    501488                    <a class="bfw-btn bfw-btn-outline" href="?page=bonus-for-woo/pages/generator.php">
    502489                        <span class="dashicons dashicons-editor-paragraph"></span>
    503                         <?php _e('Rules and Conditions Generator', 'bonus-for-woo'); ?>
     490                        <?php esc_html_e('Rules and Conditions Generator', 'bonus-for-woo'); ?>
    504491                    </a>
    505492                </div>
    … …  
    528515            success: function(response) {
    529516                if (response.data && response.data.done) {
    530                     $('#mass-progress').html('<span style="color:var(--bfw-success); font-weight:700;">✅ ' + '<?php _e('Completed Successfully!', 'bonus-for-woo'); ?>' + '</span>');
     517                    $('#mass-progress').html('<span style="color:var(--bfw-success); font-weight:700;">✅ ' + '<?php esc_html_e('Completed Successfully!', 'bonus-for-woo'); ?>' + '</span>');
    531518                    $('#mass-start').prop('disabled', false);
    532519                    return;
    533520                }
    534521                if (response.data && response.data.next_page) {
    535                     $('#mass-progress').html('<?php _e('Processing page:', 'bonus-for-woo'); ?> ' + massPage);
     522                    $('#mass-progress').html('<?php esc_html_e('Processing page:', 'bonus-for-woo'); ?> ' + massPage);
    536523                    massPage = response.data.next_page;
    537524                    setTimeout(processMassBatch, 300);
    … …  
    542529
    543530    $('#mass-start').on('click', function(){
    544         if(!confirm('<?php _e('Add points to ALL users?', 'bonus-for-woo'); ?>')) return;
     531        if(!confirm('<?php esc_html_e('Add points to ALL users?', 'bonus-for-woo'); ?>')) return;
    545532        massPage = 1;
    546533        $(this).prop('disabled', true);
    547         $('#mass-progress').html('🚀 <?php _e('Initiating...', 'bonus-for-woo'); ?>');
     534        $('#mass-progress').html('🚀 <?php esc_html_e('Initiating...', 'bonus-for-woo'); ?>');
    548535        processMassBatch();
    549536    });
    … …  
    554541        let search_by = jQuery('#search_by').val();
    555542
    556         if (!fileInput.files.length) return alert("<?php _e('Select CSV file', 'bonus-for-woo'); ?>");
    557 
     543        if (!fileInput.files.length) return alert("<?php esc_html_e('Select CSV file', 'bonus-for-woo'); ?>");
     544
     545        /*
     546         * Файл уходит в наш обработчик как есть, без промежуточной загрузки в
     547         * медиатеку: иначе CSV с балансами клиентов оставался на сайте по
     548         * постоянному публичному URL.
     549         */
    558550        let formData = new FormData();
    559         formData.append("action", "upload-attachment");
    560         formData.append("async-upload", fileInput.files[0]);
    561         formData.append("name", fileInput.files[0].name);
    562         formData.append("_ajax_nonce", "<?php echo wp_create_nonce('media-form'); ?>");
     551        formData.append("action", "bfw_export_bonuses");
     552        formData.append("search_by", search_by);
     553        formData.append("offset", 0);
     554        formData.append("nonce", "<?php echo esc_js(wp_create_nonce('bfw_export_bonuses_nonce')); ?>");
     555        formData.append("bfw_import_file", fileInput.files[0]);
    563556
    564557        let resultBlock = jQuery('#bfw-file-export-result');
    565         resultBlock.html('<?php _e('Uploading...', 'bonus-for-woo'); ?>');
    566 
    567         fetch("/wp-admin/async-upload.php", { method: "POST", body: formData })
    568         .then(r => r.text())
    569         .then(responseText => {
    570             jQuery.ajax({
    571                 type: 'POST',
    572                 url: ajaxurl,
    573                 data: {
    574                     action: 'bfw_export_bonuses',
    575                     response: responseText,
    576                     search_by: search_by,
    577                     offset: 0,
    578                     nonce: '<?php echo wp_create_nonce("bfw_export_bonuses_nonce"); ?>'
    579                 },
    580                 success: function (init) {
    581                     if (!init.success) {
    582                         resultBlock.html('<span style="color:var(--bfw-danger);"><?php _e('Initialization error', 'bonus-for-woo'); ?></span>');
    583                         return;
    584                     }
    585                     processImportBatch(responseText, search_by, init.data.next_offset, init.data.total || 5000);
     558        resultBlock.html('<?php esc_html_e('Importing...', 'bonus-for-woo'); ?>');
     559
     560        fetch(ajaxurl, { method: "POST", body: formData, credentials: 'same-origin' })
     561            .then(r => r.json())
     562            .then(res => {
     563                if (!res.success) {
     564                    resultBlock.html('<span style="color:var(--bfw-danger);">' + (res.data && res.data.message ? res.data.message : '<?php esc_html_e('Import error', 'bonus-for-woo'); ?>') + '</span>');
     565                    return;
    586566                }
    587             });
    588         });
     567                resultBlock.html('<span style="color:var(--bfw-success); font-weight:700;">✅ <?php esc_html_e('Import Complete!', 'bonus-for-woo'); ?></span>');
     568                jQuery('#bfw-file-export').val('');
     569            })
     570            .catch(() => resultBlock.html('<span style="color:var(--bfw-danger);"><?php esc_html_e('Import error', 'bonus-for-woo'); ?></span>'));
    589571    };
    590 
    591     function processImportBatch(response, search_by, offset, total) {
    592         let resultBlock = jQuery('#bfw-file-export-result');
    593         jQuery.ajax({
    594             type: 'POST',
    595             url: ajaxurl,
    596             data: {
    597                 action: 'bfw_export_bonuses',
    598                 response: response,
    599                 search_by: search_by,
    600                 offset: offset,
    601                 nonce: '<?php echo wp_create_nonce("bfw_export_bonuses_nonce"); ?>'
    602             },
    603             success: function (res) {
    604                 if (!res.success) { resultBlock.html('<span style="color:var(--bfw-danger);"><?php _e('Import error', 'bonus-for-woo'); ?></span>'); return; }
    605                 if (!res.data.done) {
    606                     let nextOffset = res.data.next_offset;
    607                     let percent = Math.round((nextOffset / total) * 100);
    608                     resultBlock.html('<?php _e('Importing:', 'bonus-for-woo'); ?> ' + (percent > 100 ? 100 : percent) + '%');
    609                     processImportBatch(response, search_by, nextOffset, total);
    610                 } else {
    611                     resultBlock.html('<span style="color:var(--bfw-success); font-weight:700;">✅ <?php _e('Import Complete!', 'bonus-for-woo'); ?></span>');
    612                     jQuery('#bfw-file-export').val('');
    613                 }
    614             }
    615         });
    616     }
    617572});
    618573</script>
  • bonus-for-woo/tags/8.5.2/readme.txt

    r3784081 r3795776  
    55Requires at least:  6.2
    66Tested up to:  7.1
    7 WC requires at least: 6.0
    8 WC tested up to: 11.0.1
    9 Stable tag: 8.5.0
     7WC requires at least: 6.2
     8WC tested up to: 11.2.1
     9Stable tag: 8.5.2
    1010Requires PHP: 7.4
    1111License: GPLv3 or later
    … …  
    8383== Installation ==
    8484
    85 1. Upload dir `bonus-for-woo-computy` to the `/wp-content/plugins/` directory
     851. Upload dir `bonus-for-woo` to the `/wp-content/plugins/` directory
    86862. Activate the plugin through the 'Plugins' menu in WordPress
    8787
     88
     89== External services ==
     90
     91This plugin connects to the plugin author's server in the following cases.
     92
     93= License activation (PRO) =
     94The PRO key is verified against `https://computy.ru/API/api.php`. The request sends the license key, the site domain, the plugin version and the product identifier. The server returns a signed token that is stored in the site options.
     95
     96* Service: computy.ru
     97* Data sent: license key, site domain, plugin version, product ID
     98* When: on activation and no more than once every 7 days
     99* Terms: https://computy.ru/privacy
     100
     101= Buy button =
     102The "Buy" button on the PRO settings screen posts the site URL, the plugin version and the site administrator email to `https://computy.ru/buy_plugin`.
     103
     104* Service: computy.ru
     105* Data sent: site URL, plugin version, admin email
     106* When: only when the administrator clicks the button
     107* Terms: https://computy.ru/privacy
     108
     109= Blog articles widget =
     110The plugin settings screen loads the list of the author's recent articles from `https://computy.ru/blog/tag/bfw/` and caches the result in a transient.
     111
     112* Service: computy.ru
     113* Data sent: no visitor data, an ordinary HTTP request
     114* When: when the plugin settings screen is opened while the cache is empty
     115* Terms: https://computy.ru/privacy
    88116
    89117== Screenshots ==
    … …  
    124152== Changelog ==
    125153
    126 = 8.5.0 - 2026-11-04 =
     154= 8.5.2 - 2026-11-04 =
     155* Security: the coupons list no longer interpolates the `order`/`orderby` query arguments; only an allowlist is accepted.
     156* Security: failed coupon code entries are rate-limited, so codes can no longer be brute-forced.
     157* Security: the points export is streamed and never written to disk; the download link is nonce-protected.
     158* Security: the redirect parameter is validated against the site host on every write-off entry point.
     159* Security: CSV import reads the uploaded file instead of a client-supplied path.
     160* Security: coupon actions require the plugin capability, not just a nonce.
     161* One capability (BFW_CAP_MANAGE) now drives both the menu and the AJAX handlers.
     162* Points export is batched (500 users per query) instead of loading every customer at once.
     163* Removed an unused coupon import handler and the leftover QR / DataTables demo files.
     164* A critical error that occurred when entering an empty coupon has been fixed.
     165
     166= 8.5.1 - 2026-11-04 =
    127167* Cashback percent, points and sums now follow the WooCommerce "Number of decimals" setting: 0 decimals shows 3%, 250; 2 decimals shows 3.50%, 250.75. Rounding of points uses the same setting.
    128168* Points export file moved from the plugin directory to uploads — a plugin installed from the admin screen could not write there. Download link is now nonce-protected.
    … …  
    224264== Upgrade Notice ==
    225265
    226 = 9.0.0 =
    227 * Super update.
     266= 8.5.2 =
     267* Points export is now streamed and is never written to the disk.
     268* Coupons: the list respects a strict ORDER BY whitelist, and repeated failed code entries are rate-limited.
     269* Cashback percent and points follow the WooCommerce number of decimals setting.
  • bonus-for-woo/trunk/_inc/bonus-computy-script.js

    r3784081 r3795776  
     1/*
     2 * Nonce больше не печатается в HTML страницы: под полностраничным кешем он
     3 * отдавался протухшим, и запросы падали с 403. Получаем его один раз при
     4 * загрузке и подставляем вместо лениво читаемого wpApiSettings.nonce.
     5 */
     6window.bfwRestHeaders = function (extra) {
     7    return Object.assign({}, extra || {}, { 'X-WP-Nonce': window.bfwRestNonce || '' });
     8};
     9
    110jQuery(document).ready(function ($) {
     11    if (window.wpApiSettings && window.wpApiSettings.isLoggedIn && window.wpApiSettings.nonceAjaxUrl) {
     12        fetch(window.wpApiSettings.nonceAjaxUrl, { credentials: 'same-origin' })
     13            .then(function (r) { return r.ok ? r.json() : null; })
     14            .then(function (res) { if (res && res.nonce) { window.bfwRestNonce = res.nonce; } })
     15            .catch(function () {});
     16    }
     17
    218    /*действие при удалении баллов   */
    319    $('body').on('click', '#bfw_remove_cart_point', async function () {
    … …  
    1026                    'Content-Type': 'application/json',
    1127                    // Если требуется nonce (для авторизованных действий):
    12                     'X-WP-Nonce': wpApiSettings.nonce
     28                    'X-WP-Nonce': window.bfwRestNonce || ''
    1329                },
    1430                body: JSON.stringify({
    … …  
    4965                    'Content-Type': 'application/json',
    5066                    // Если используете nonce:
    51                     'X-WP-Nonce': wpApiSettings.nonce
     67                    'X-WP-Nonce': window.bfwRestNonce || ''
    5268                },
    5369                body: JSON.stringify({
    … …  
    164180                    'Content-Type': 'application/json',
    165181                    // Добавляем nonce для безопасности REST API
    166                     'X-WP-Nonce': wpApiSettings.nonce // Убедитесь, что передаете nonce через wp_localize_script
     182                    'X-WP-Nonce': window.bfwRestNonce || ''
    167183                },
    168184                body: JSON.stringify({
    … …  
    213229                    'Content-Type': 'application/json',
    214230                    // Используем стандартный Nonce WordPress или ваш bfw_params.nonce
    215                     'X-WP-Nonce': typeof wpApiSettings !== 'undefined' ? wpApiSettings.nonce : bfw_params.nonce
     231                    'X-WP-Nonce': window.bfwRestNonce || ''
    216232                },
    217233                body: JSON.stringify(formData)
    … …  
    271287
    272288        const API_BASE = '/wp-json/bfw/v1';
    273         const NONCE = typeof wpApiSettings !== 'undefined' ? wpApiSettings.nonce : '';
     289        // Читаем в момент запроса: nonce приходит AJAX-ом после загрузки страницы
     290        const NONCE = () => window.bfwRestNonce || '';
    274291
    275292        const debounce = (func, delay) => {
    … …  
    287304                const response = await fetch(`${API_BASE}${endpoint}`, {
    288305                    method: 'POST',
    289                     headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': NONCE },
     306                    headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': NONCE() },
    290307                    body: JSON.stringify(params)
    291308                });
    … …  
    336353                const response = await fetch(`${API_BASE}/check-points-only-products`, {
    337354                    method: 'POST',
    338                     headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': NONCE },
     355                    headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': NONCE() },
    339356                    body: JSON.stringify({})
    340357                });
  • bonus-for-woo/trunk/changelog.txt

    r3784081 r3795776  
     1
     2= 8.5.2 - 2026-11-04 =
     3* Fixed SQL injection in the coupons list ORDER BY.
     4* Added rate limiting for failed coupon code entries.
     5* Points export is streamed, never stored on disk.
     6* Redirect parameter validated on all entry points.
     7* CSV imports read the uploaded file instead of a client path.
     8* Unified capability for menu and AJAX handlers.
     9* Batched points export.
    110
    211= 7.6.12 - 2026-03-23 =
  • bonus-for-woo/trunk/classes/BfwAdmin.php

    r3784081 r3795776  
    6060    public static function init(): void
    6161    {
    62         if (current_user_can('manage_options') || (defined('WP_CLI') && WP_CLI)) {
     62        if (current_user_can(BFW_CAP_MANAGE) || (defined('WP_CLI') && WP_CLI)) {
    6363            /* Инициализируем меню в админке*/
    6464            add_action('admin_menu', array('BfwAdmin', 'add_admin_menu'));
    … …  
    494494    public static function bfwoo_action_pre_get_users($query): void
    495495    {
    496         if (!current_user_can('manage_options')) {
     496        if (!current_user_can(BFW_CAP_MANAGE)) {
    497497            return;
    498498        }
    … …  
    609609        $menu_list_history_title = __('History of bonus points for all customers', 'bonus-for-woo');
    610610
     611        /*
     612         * Одна capability на всю бонусную систему: раньше пункты меню были
     613         * раскиданы между manage_options и manage_woocommerce, из-за чего
     614         * shop manager видел страницы, обработчики которых его отклоняли.
     615         * Настройки и логи дополнительно защищены capability по умолчанию.
     616         */
    611617        /*Страница с основными настройками*/
    612         add_menu_page($home_menu_title, $home_menu_title, 'manage_options', 'bonus_for_woo-plugin-options',
     618        add_menu_page($home_menu_title, $home_menu_title, BFW_CAP_MANAGE, 'bonus_for_woo-plugin-options',
    613619                array('BfwAdmin', 'bonus_plugin_options'), plugins_url('bonus-for-woo/img/coin.svg'), 30);
    614620
    615621        /* Добавляем в меню историю всех начислений клиентов*/
    616         add_submenu_page('bonus_for_woo-plugin-options', $menu_list_history_title, $menu_list_history, 'manage_options',
     622        add_submenu_page('bonus_for_woo-plugin-options', $menu_list_history_title, $menu_list_history, BFW_CAP_MANAGE,
    617623                'bonus-for-woo/pages/list_history.php', '', 1);
    618624
    619625        /*cтраница статистик*/
    620626        add_submenu_page('bonus_for_woo-plugin-options', __('Bonus points statistic - Bonus for Woo', 'bonus-for-woo'),
    621                 __('Bonus system statistic', 'bonus-for-woo'), 'manage_woocommerce',
     627                __('Bonus system statistic', 'bonus-for-woo'), BFW_CAP_MANAGE,
    622628                'bonus-for-woo/pages/statistic.php', '', 2);
    623629
    … …  
    626632            add_submenu_page('bonus_for_woo-plugin-options',
    627633                    __('Bonus points coupons - Bonus for Woo', 'bonus-for-woo'),
    628                     __('Bonus points coupons', 'bonus-for-woo'), 'manage_woocommerce',
     634                    __('Bonus points coupons', 'bonus-for-woo'), BFW_CAP_MANAGE,
    629635                    'bonus-for-woo/pages/coupons.php', '', 3);
    630636        }
    631637        add_submenu_page('bonus_for_woo-plugin-options', __('Tools', 'bonus-for-woo'), __('Tools', 'bonus-for-woo'),
    632                 'manage_woocommerce', 'bonus-for-woo/pages/tools.php', '', 4);
     638                BFW_CAP_MANAGE, 'bonus-for-woo/pages/tools.php', '', 4);
    633639
    634640
    635641        /*Генератор правил и условий*/
    636642        add_submenu_page('', __('Rules and Conditions Generator', 'bonus-for-woo'),
    637                 __('Rules and Conditions Generator', 'bonus-for-woo'), 'manage_woocommerce',
     643                __('Rules and Conditions Generator', 'bonus-for-woo'), BFW_CAP_MANAGE,
    638644                'bonus-for-woo/pages/generator.php', '', 4);
    639645
    … …  
    641647        /* Добавляем в меню историю всех начислений клиентов*/
    642648        add_submenu_page('bonus_for_woo-plugin-options', __('Logs', 'bonus-for-woo'), __('Logs', 'bonus-for-woo'),
    643                 'manage_options', 'bonus-for-woo/pages/logs.php', '', 5);
     649                BFW_CAP_MANAGE, 'bonus-for-woo/pages/logs.php', '', 5);
    644650
    645651    }
    … …  
    706712    {
    707713        if (isset($_GET['bfw_delete_all_post_history_points'])) {
     714            if (!current_user_can(BFW_CAP_MANAGE)) {
     715                wp_die(esc_html__('You do not have permission to do this.', 'bonus-for-woo'));
     716            }
     717
    708718            if (!isset($_GET['_wpnonce']) || !wp_verify_nonce($_GET['_wpnonce'], 'bfw_delete_all_history')) {
    709                 wp_die('Ошибка безопасности!');
     719                wp_die(esc_html__('Security check failed.', 'bonus-for-woo'));
    710720            }
    711721            $delete_history_points = (int) $_GET['bfw_delete_all_post_history_points'];
    … …  
    31813191    public static function bonus_plugin_options(): void
    31823192    {
    3183         if (!current_user_can('manage_options')) {
     3193        // Настройки — сборка бонусной системы, право то же, что у меню
     3194        if (!current_user_can(BFW_CAP_MANAGE)) {
    31843195            return;
    31853196        }
    … …  
    32283239                        </div>
    32293240
    3230 <?php
    3231 /**
    3232  * Парсит страницу https://computy.ru/blog/tag/sajty/
    3233  * Возвращает массив с заголовками и датами последних 5 постов.
    3234  */
    3235 function computy_parse_tag_page() {
    3236     $url = 'https://computy.ru/blog/tag/bfw/';
    3237     $cache_key = 'computy_tag_posts_parsed3';
    3238     $cached = get_transient($cache_key);
    3239 
    3240     // Если данные есть в кеше, возвращаем их
    3241     if ($cached !== false) {
    3242         return $cached;
    3243     }
    3244 
    3245     // Загружаем страницу
    3246     $response = wp_remote_get($url, array(
    3247             'timeout' => 15,
    3248             'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
    3249     ));
    3250 
    3251     if (is_wp_error($response)) {
    3252         return array('error' => 'Не удалось загрузить страницу: ' . $response->get_error_message());
    3253     }
    3254 
    3255     $html = wp_remote_retrieve_body($response);
    3256     if (empty($html)) {
    3257         return array('error' => 'Страница пуста');
    3258     }
    3259 
    3260     // Парсим HTML
    3261     $posts = array();
    3262 
    3263     // Используем DOMDocument для парсинга
    3264     $dom = new DOMDocument();
    3265     // Подавляем ошибки из-за невалидного HTML
    3266     libxml_use_internal_errors(true);
    3267     // Конвертируем в UTF-8, чтобы избежать проблем с кодировкой
    3268     $dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
    3269     libxml_clear_errors();
    3270 
    3271     $xpath = new DOMXPath($dom);
    3272 
    3273     // Находим все элементы article с классом 'loop'
    3274     $articles = $xpath->query("//article[contains(@class, 'loop')]");
    3275 
    3276     $count = 0;
    3277     foreach ($articles as $article) {
    3278         if ($count >= 5) break;
    3279 
    3280         // Ищем заголовок внутри .kakh3 > a
    3281         $title_node = $xpath->query(".//div[contains(@class, 'kakh3')]/a", $article);
    3282         $title = $title_node->length > 0 ? trim($title_node->item(0)->nodeValue) : '';
    3283 
    3284         // Ищем дату внутри .bottom-loop > ul > li с датой
    3285         // Дата находится в span[itemprop='datePublished']
    3286         $date_node = $xpath->query(".//span[@itemprop='datePublished']", $article);
    3287         $date = $date_node->length > 0 ? trim($date_node->item(0)->nodeValue) : '';
    3288 
    3289         // Если дата не нашлась, пробуем другой вариант
    3290         if (empty($date)) {
    3291             // Ищем паттерн даты вида "02.09.2026г."
    3292             preg_match('/\d{2}\.\d{2}\.\d{4}г\./', $article->textContent, $matches);
    3293             $date = !empty($matches) ? trim($matches[0]) : '';
    3294         }
    3295 
    3296         if (!empty($title) && !empty($date)) {
    3297             $posts[] = array(
    3298                     'title' => $title,
    3299                     'date' => $date,
    3300                     'link' => $title_node->length > 0 ? $title_node->item(0)->getAttribute('href') : ''
    3301             );
    3302             $count++;
    3303         }
    3304     }
    3305 
    3306     // Сохраняем в кеш на 1 час
    3307     set_transient($cache_key, $posts, HOUR_IN_SECONDS);
    3308 
    3309     return $posts;
    3310 }
    3311 /**
    3312  * Вывод виджета
    3313  */
    3314 function computy_dashboard_widget_render() {
    3315     $posts = computy_parse_tag_page();
    3316 
    3317     if (isset($posts['error'])) {
    3318         echo '<p style="color: #d63638;">⚠️ ' . esc_html($posts['error']) . '</p>';
    3319         return;
    3320     }
    3321 
    3322     if (empty($posts)) {
    3323         echo '<p>Статей не найдено.</p>';
    3324         return;
    3325     }
    3326 
    3327     echo '<ul style="list-style: auto; margin: 0; padding: 0;">';
    3328     foreach ($posts as $post) {
    3329         $title = esc_html($post['title']);
    3330         $date = esc_html($post['date']);
    3331         $link = esc_url($post['link']);
    3332 
    3333         echo '<li>';
    3334         if (!empty($link)) {
    3335             echo '<a href="' . $link . '" target="_blank" >';
    3336             echo $title;
    3337             echo '</a>';
    3338         } else {
    3339             echo '<strong>' . $title . '</strong>';
    3340         }
    3341         echo ' <span style="color: #fff; font-size: 12px;">📅 ' . $date . '</span>';
    3342         echo '</li>';
    3343     }
    3344     echo '</ul>';
    3345 }
    3346 
    3347 echo computy_dashboard_widget_render();
    3348 
    3349 ?>
     3241<?php echo BfwBlogWidget::render(); ?>
    33503242
    33513243                        <div class="bfw-sidebar-footer">
    … …  
    36383530                                </div>
    36393531                                <form action="https://computy.ru/buy_plugin?bonus-for-woo=buy" method="post">
    3640                                     <input type="hidden" name="successURL" value="<?php echo get_site_url() . $_SERVER['REQUEST_URI']; ?>">
    3641                                     <input type="hidden" name="label" value="<?php echo get_site_url(); ?>|bonus-for-woo|<?php echo BONUS_COMPUTY_VERSION; ?>|<?php echo get_option('admin_email'); ?>">
     3532                                    <input type="hidden" name="successURL" value="<?php echo esc_attr(admin_url('admin.php?page=bonus_for_woo-plugin-options')); ?>">
     3533                                    <input type="hidden" name="label" value="<?php echo esc_attr(wp_unslash(get_site_url(null, '') . '|bonus-for-woo|' . BONUS_COMPUTY_VERSION . '|' . get_option('admin_email'))); ?>">
    36423534                                    <button type="submit" class="bfw-btn bfw-btn-primary" style="padding: 16px 50px; font-size: 18px; background: #e67e22; border-radius: 12px;">
    36433535                                        <?php _e('Buy', 'bonus-for-woo'); ?>
  • bonus-for-woo/trunk/classes/BfwAnalytics.php

    r3784081 r3795776  
    4747    {
    4848        check_ajax_referer('bfw_analytics_nonce', 'nonce');
    49         if (!current_user_can('manage_options')) {
     49        if (!current_user_can(BFW_CAP_MANAGE)) {
    5050            wp_send_json_error('Access denied');
    5151        }
    … …  
    9191    {
    9292        check_ajax_referer('bfw_analytics_nonce', 'nonce');
    93         if (!current_user_can('manage_options')) {
     93        if (!current_user_can(BFW_CAP_MANAGE)) {
    9494            wp_send_json_error('Access denied');
    9595        }
    … …  
    158158    {
    159159        check_ajax_referer('bfw_analytics_nonce', 'nonce');
    160         if (!current_user_can('manage_options')) {
     160        if (!current_user_can(BFW_CAP_MANAGE)) {
    161161            wp_send_json_error('Access denied');
    162162        }
    … …  
    199199    {
    200200        check_ajax_referer('bfw_analytics_nonce', 'nonce');
    201         if (!current_user_can('manage_options')) {
     201        if (!current_user_can(BFW_CAP_MANAGE)) {
    202202            wp_send_json_error('Access denied');
    203203        }
    … …  
    236236    {
    237237        check_ajax_referer('bfw_analytics_nonce', 'nonce');
    238         if (!current_user_can('manage_options')) {
     238        if (!current_user_can(BFW_CAP_MANAGE)) {
    239239            wp_send_json_error('Access denied');
    240240        }
    … …  
    298298    {
    299299        check_ajax_referer('bfw_analytics_nonce', 'nonce');
    300         if (!current_user_can('manage_options')) {
     300        if (!current_user_can(BFW_CAP_MANAGE)) {
    301301            wp_send_json_error('Access denied');
    302302        }
    … …  
    353353    {
    354354        check_ajax_referer('bfw_analytics_nonce', 'nonce');
    355         if (!current_user_can('manage_options')) {
     355        if (!current_user_can(BFW_CAP_MANAGE)) {
    356356            wp_send_json_error('Access denied');
    357357        }
    … …  
    367367    public static function exportToCSV(string $type): void
    368368    {
    369         if (!current_user_can('manage_options')) {
     369        if (!current_user_can(BFW_CAP_MANAGE)) {
    370370            wp_die('Access denied');
    371371        }
    … …  
    418418    {
    419419        check_ajax_referer('bfw_analytics_nonce', 'nonce');
    420         if (!current_user_can('manage_options')) {
     420        if (!current_user_can(BFW_CAP_MANAGE)) {
    421421            wp_send_json_error('Access denied');
    422422        }
  • bonus-for-woo/trunk/classes/BfwCashback.php

    r3784081 r3795776  
    562562    public static function computyMassAddPoints(): void
    563563    {
    564         if (!current_user_can('manage_options')) {
     564        if (!current_user_can(BFW_CAP_MANAGE)) {
    565565            wp_send_json_error('Access denied');
    566566            return;
  • bonus-for-woo/trunk/classes/BfwCoupons.php

    r3784081 r3795776  
    1212class BfwCoupons
    1313{
     14    /** Сколько неудачных попыток ввести код допустимо в окне */
     15    const MAX_FAILED_COUPON_ATTEMPTS = 5;
     16
     17    /** Длина окна ограничения перебора, секунд */
     18    const FAILED_COUPON_WINDOW = 900;
    1419
    1520    /**
    … …  
    348353    {
    349354        $code_coupon = trim($code_coupon);
     355
     356        /*
     357         * Защита от перебора кодов. Раньше счётчик daily_coupon увеличивался
     358         * только при успешной активации, а на неверный код сразу возвращался
     359         * 'not_coupon' — то есть подбирать купоны (а купон = деньги) можно было
     360         * без ограничений. Считаем неудачи в transient до поиска купона.
     361         */
     362        $fail_key = 'bfw_coupon_fails_' . $userid;
     363        $fails = (int) get_transient($fail_key);
     364
     365        if ($fails >= self::MAX_FAILED_COUPON_ATTEMPTS) {
     366            return 'limit';
     367        }
     368
    350369        $coupon = self::getCoupon($code_coupon);
    351370
    352371        if (!isset($coupon->code) || $coupon->status !== 'active') {
     372            set_transient($fail_key, $fails + 1, self::FAILED_COUPON_WINDOW);
    353373            return 'not_coupon';
    354374        }
     375
     376        delete_transient($fail_key);
    355377
    356378        // Лимит попыток в день
    … …  
    467489    public static function bfwExportCoupons(): void
    468490    {
    469         if (!current_user_can('manage_options')) {
     491        if (!current_user_can(BFW_CAP_MANAGE)) {
    470492            wp_send_json_error(__('Access denied.', 'bonus-for-woo'));
    471493            return;
    … …  
    477499        }
    478500
    479         $response = json_decode(stripslashes($_POST['response']), true);
    480         $url_export_file = $response['data']['url']; // ссылка на загруженный файл экспорта
    481 
    482         $upload_dir = wp_upload_dir();
    483         if (strpos($url_export_file, $upload_dir['baseurl']) !== 0 && strpos($url_export_file, $upload_dir['basedir']) !== 0) {
    484             wp_send_json_error(__('Invalid file path.', 'bonus-for-woo'));
     501        /*
     502         * Раньше путь приходил в JSON от клиента и проверялся только по префиксу
     503         * uploads: `.../uploads/../../wp-config.php` проходил проверку. Теперь
     504         * файл читается из загрузки запроса и на сайте не сохраняется.
     505         */
     506        $uploaded = $_FILES['bfw_import_file'] ?? null;
     507
     508        if (empty($uploaded['tmp_name']) || (int) ($uploaded['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK
     509                || !is_uploaded_file($uploaded['tmp_name'])) {
     510            wp_send_json_error(__('The file was not uploaded.', 'bonus-for-woo'));
    485511            return;
    486512        }
    487513
    488 
    489         $fileHandle = fopen($url_export_file, "rb");
     514        $fileHandle = fopen($uploaded['tmp_name'], 'rb');
    490515
    491516        if ($fileHandle === false) {
    492             die(__('Error opening', 'bonus-for-woo') . ' ' . htmlspecialchars($url_export_file));
     517            wp_send_json_error(__('Cannot open the uploaded file.', 'bonus-for-woo'));
     518            return;
    493519        }
    494520
    … …  
    517543
    518544        fclose($fileHandle);
    519         echo 'good';
    520         exit();
    521     }
    522 
    523     /**todo проверить на работоспособность. Возможно заменить верхним
    524      *
    525      * @return void
    526      */
    527     public static function NEWbfwExportCoupons(): void
    528     {
    529         // Проверяем nonce для безопасности
    530         check_ajax_referer('bfw_export_nonce', 'nonce');
    531 
    532         $response = json_decode(stripslashes($_POST['response']), true);
    533 
    534         // Проверяем наличие данных
    535         if (!isset($response['data']['url'])) {
    536             wp_die(__('Invalid data', 'bonus-for-woo'));
    537         }
    538 
    539         $url_export_file = $response['data']['url'];
    540 
    541         // Инициализируем WP_Filesystem
    542         global $wp_filesystem;
    543 
    544         if (!function_exists('WP_Filesystem')) {
    545             require_once ABSPATH . 'wp-admin/includes/file.php';
    546         }
    547 
    548         WP_Filesystem();
    549 
    550         // Проверяем, что файловая система доступна
    551         if (!$wp_filesystem || !$wp_filesystem->exists($url_export_file)) {
    552             wp_die(__('Error accessing file', 'bonus-for-woo') . ' ' . esc_html($url_export_file));
    553         }
    554 
    555         // Получаем содержимое файла
    556         $file_content = $wp_filesystem->get_contents($url_export_file);
    557 
    558         if ($file_content === false) {
    559             wp_die(__('Error reading file', 'bonus-for-woo'));
    560         }
    561 
    562         // Обрабатываем CSV данные
    563         $lines = explode("\n", $file_content);
    564         global $wpdb;
    565         $table = $wpdb->prefix . 'bfw_coupons_computy';
    566 
    567 
    568         $processed = 0;
    569         $total_lines = count($lines);
    570 
    571         for ($j = 0; $j < $total_lines; $j += BFW_CSV_IMPORT_LIMIT) {
    572             $end = min($j + BFW_CSV_IMPORT_LIMIT, $total_lines);
    573 
    574             for ($i = $j; $i < $end; $i++) {
    575                 // Пропускаем пустые строки
    576                 if (empty(trim($lines[$i]))) {
    577                     continue;
    578                 }
    579 
    580                 // Парсим CSV строку
    581                 $currRow = str_getcsv($lines[$i]);
    582 
    583                 // Проверяем корректность строки
    584                 if (count($currRow) < 5) {
    585                     continue;
    586                 }
    587 
    588                 $coupon = (string)$currRow[0];
    589                 $sum = (float)$currRow[1];
    590                 $comment_admin = $currRow[2];
    591                 $status = in_array($currRow[3], ['active', 'noactive', 'used']) ? $currRow[3] : 'noactive';
    592                 $reusable = isset($currRow[4]) ? (int)$currRow[4] : 0;
    593 
    594                 // Пропускаем заголовок, если он есть
    595                 if ($coupon === 'code' || $coupon === 'comment_admin') {
    596                     continue;
    597                 }
    598 
    599                 // Проверяем существование купона
    600                 $safe_table_name = str_replace('`', '``', $table);
    601                 $existing_coupon = $wpdb->get_var(
    602                         $wpdb->prepare(
    603                                 "SELECT code FROM `{$safe_table_name}` WHERE code = %s",
    604                                 $coupon
    605                         )
    606                 );
    607 
    608                 if (!$existing_coupon) {
    609                     self::addCoupon($coupon, $sum, $comment_admin, $status, $reusable);
    610                     $processed++;
    611                 }
    612             }
    613 
    614             // Делаем небольшую паузу между пакетами для оптимизации нагрузки
    615             if (($j + BFW_CSV_IMPORT_LIMIT) < $total_lines) {
    616                 sleep(1);
    617             }
    618         }
    619 
    620         // Удаляем временный файл, если он был загружен
    621         if (strpos($url_export_file, wp_upload_dir()['basedir']) !== false) {
    622             $wp_filesystem->delete($url_export_file);
    623         }
    624 
    625         wp_send_json_success(array(
    626                 'message' => __('Coupons imported successfully', 'bonus-for-woo'),
    627                 'processed' => $processed
    628         ));
    629     }
    630 
     545
     546        wp_send_json_success(array('message' => __('Coupons imported successfully', 'bonus-for-woo')));
     547    }
    631548
    632549    public static function getListCodeCoupons()
    … …  
    641558        // Проверяем, ошибка ли "купон не найден"
    642559
    643         if ($err_code == '105') {
     560        if ($err_code == '105' && $coupon instanceof WC_Coupon) {
    644561
    645562            $arrayCoupons = self::getListCodeCoupons();
  • bonus-for-woo/trunk/classes/BfwFilters.php

    r3784081 r3795776  
    9292
    9393        add_filter('woocommerce_coupon_error', function ($err, $err_code, $coupon) {
     94            if (!($coupon instanceof WC_Coupon)) {
     95                return $err;
     96            }
    9497            $bonus_coupon_code = mb_strtolower(trim(BfwSetting::get('bonus-points-on-cart') ??  __('Bonus points', 'bonus-for-woo')));
     98
    9599            if (strtolower($coupon->get_code()) === $bonus_coupon_code) {
    96100                return false; // Подавляем ошибку для бонусного купона
  • bonus-for-woo/trunk/classes/BfwFunctions.php

    r3784081 r3795776  
    1313{
    1414
     15    /**
     16     * Нужны ли ассеты плагина на текущей странице.
     17     *
     18     * Раньше CSS и JS подключались на каждой странице сайта, хотя работают
     19     * только там, где есть корзина, оформление, личный кабинет, карточка
     20     * товара или любой из шорткодов плагина.
     21     *
     22     * @return bool
     23     * @since 8.5.2
     24     */
     25    public static function isBonusContext(): bool
     26    {
     27        $context = false;
     28
     29        if (function_exists('is_cart') && (is_cart()
     30                || is_checkout()
     31                || is_account_page()
     32                || is_product()
     33                || (function_exists('is_wc_endpoint_url') && is_wc_endpoint_url()))) {
     34            $context = true;
     35        }
     36
     37        if (!$context && is_singular()) {
     38            $post_content = (string) get_post_field('post_content', get_queried_object_id());
     39
     40            foreach (array('bfw_', 'bfw-', 'link_on_rulles') as $shortcode_prefix) {
     41                if (has_shortcode($post_content, $shortcode_prefix)
     42                        || strpos($post_content, '[' . $shortcode_prefix) !== false) {
     43                    $context = true;
     44                    break;
     45                }
     46            }
     47        }
     48
     49        /**
     50         * Позволяет теме/другому плагину включить ассеты там, где нужно.
     51         *
     52         * @param bool $context
     53         */
     54        return (bool) apply_filters('bfw_enqueue_assets', $context);
     55    }
     56
     57
    1558    /**  Добавляем стили на фронте
    1659     *
    … …  
    1962    public static function bfwooComputyStyles(): void
    2063    {
     64        if (!self::isBonusContext()) {
     65            return;
     66        }
     67
    2168        wp_register_style(
    2269                'bonus-computy-style',
    … …  
    3582    public static function bfwooComputyScript(): void
    3683    {
     84        if (!self::isBonusContext()) {
     85            return;
     86        }
     87
    3788        wp_register_script(
    3889                'bonus-computy-script',
    … …  
    4394        );
    4495        wp_enqueue_script('bonus-computy-script');
     96
     97        /*
     98         * Nonce REST живёт 12 часов и, выведенный в HTML, умирает под
     99         * полностраничным кешем: страница отдаётся из кеша, а nonce в ней уже
     100         * просрочен, и запросы списания/начисления падают с 403. Раньше его
     101         * печатали на каждой странице сайта. Теперь отдаём странице только
     102         * флаг «залогинен», а nonce JS получает по AJAX (bfw/v1/nonce).
     103         */
    45104        wp_localize_script('bonus-computy-script', 'wpApiSettings', [
    46                 'nonce' => wp_create_nonce('wp_rest'),
    47                  'isLoggedIn' => is_user_logged_in(),
     105                'nonceAjaxUrl' => esc_url_raw(rest_url('bfw/v1/nonce')),
     106                'isLoggedIn'  => is_user_logged_in(),
    48107        ]);
    49108    }
    … …  
    51110
    52111    /**
    53      * Path to the points export CSV.
    54      *
    55      * The file used to live inside the plugin directory. A plugin installed from
    56      * the admin screen cannot write there (and on nginx the bundled .htaccess
    57      * does nothing), so the export silently failed. Uploads are writable and,
    58      * with the default flat structure, not web-readable.
    59      *
    60      * @return string
    61      * @since 8.5.1
    62      */
    63     public static function getExportFilePath(): string
     112     * Пути к legacy-файлам экспорта баллов, которые писались на диск.
     113     *
     114     * Версии <= 8.5.0 клали CSV в каталог плагина (туда нет прав записи при
     115     * установке из админки), 8.5.1 — в uploads под фиксированным именем, где
     116     * файл был доступен по прямому публичному URL. Начиная с 8.5.2 экспорт
     117     * отдаётся потоком и на диск не пишется; файлы старых версий надо удалить.
     118     *
     119     * @return string[]
     120     * @since 8.5.2
     121     */
     122    public static function getLegacyExportFilePaths(): array
    64123    {
    65124        $upload_dir = wp_upload_dir(null, false);
    66125
    67         return wp_normalize_path(trailingslashit($upload_dir['basedir']) . 'bfw_points_export.csv');
     126        return array(
     127            wp_normalize_path(BONUS_COMPUTY_PLUGIN_DIR . '/export_bfw.csv'),
     128            wp_normalize_path(trailingslashit($upload_dir['basedir']) . 'bfw_points_export.csv'),
     129        );
     130    }
     131
     132    /**
     133     * Удаляет файлы экспорта, оставшиеся от старых версий.
     134     *
     135     * @return void
     136     * @since 8.5.2
     137     */
     138    public static function deleteLegacyExportFile(): void
     139    {
     140        foreach (self::getLegacyExportFilePaths() as $path) {
     141            if (is_file($path)) {
     142                wp_delete_file($path);
     143            }
     144        }
    68145    }
    69146
  • bonus-for-woo/trunk/classes/BfwHistory.php

    r3784081 r3795776  
    106106            <h3><?php echo $history_title; ?></h3>
    107107
    108             <?php if (current_user_can('manage_options')): ?>
     108            <?php if (current_user_can(BFW_CAP_MANAGE)): ?>
    109109                <a class="clear_history" href="javascript:void(0);" onclick="return confirmClearHistory();">
    110110                    <?php echo __('Clear the history', 'bonus-for-woo'); ?>
    … …  
    134134                <th><?php echo BfwPoints::pointsLabel(5); ?></th>
    135135                <th><?php echo __('Event', 'bonus-for-woo'); ?></th>
    136                 <?php if (current_user_can('manage_options')): ?>
     136                <?php if (current_user_can(BFW_CAP_MANAGE)): ?>
    137137                    <th><?php echo __('Action', 'bonus-for-woo'); ?></th>
    138138                <?php endif; ?>
    … …  
    148148                if ($item->orderz != '0') {
    149149                    $order_text = __('Order', 'bonus-for-woo') . ' №' . $item->orderz;
    150                     if (current_user_can('manage_options')) {
     150                    if (current_user_can(BFW_CAP_MANAGE)) {
    151151                        $order_link = sprintf(
    152152                                '<a href="%s">%s</a> ',
    … …  
    183183                    </td>
    184184                    <td><?php echo $order_link . wp_kses_post($item->comment_admin); ?></td>
    185                     <?php if (current_user_can('manage_options')): ?>
     185                    <?php if (current_user_can(BFW_CAP_MANAGE)): ?>
    186186                        <td>
    187187                            <form method="post" class="list_role_computy">
    … …  
    295295<td><span style="color:' . esc_attr($color) . ' ">' . esc_html($bfw->symbol . BfwPoints::formatNumber((float) $bfw->points)) . '</span></td>
    296296<td>' . $getorderz . wp_kses_post($bfw->comment_admin) . '</td>';
    297                     if (current_user_can('manage_options')) {
     297                    if (current_user_can(BFW_CAP_MANAGE)) {
    298298                        echo '<td><form method="post" action="" class="list_role_computy">
    299299                        ' . wp_nonce_field('bfw_action_history', 'bfw_nonce_history', true, false) . '
  • bonus-for-woo/trunk/classes/BfwLicense.php

    r3784081 r3795776  
    77 * Проверка лицензии по криптографической подписи сервера.
    88 *
    9  * Почему так:
    10  *  - Раньше статус PRO хранился строкой в wp_options и проверялся сравнением
    11  *    get_option(...) === 'active'. Достаточно было одного SQL-запроса или
    12  *    любого плагина, чтобы включить платный режим. Обфускация base64_decode
    13  *    ничего не защищала — ключ декодировался одной командой.
    14  *  - Теперь решение принимается по ПОДПИСИ сервера лицензий: сервер
    15  *    подписывает payload своим приватным ключом, плагин проверяет вшитым
    16  *    публичным. Изменить payload (домен, план, срок) без приватного ключа
    17  *    невозможно — подпись перестанет сходиться.
    18  *
    19  * Модель доверия (важно понимать):
    20  *  - Это защищает от включения PRO без ключа через wp_options/SQL/ADMIN UI.
    21  *  - НЕ защищает от правки самого PHP-кода: код исполняется на сервере
    22  *    клиента, и тот, кто может править файлы, отключит проверку. От этого
    23  *    защиты не существует; барьер — только хеш-контроль файлов.
    24  *
    25  * Режим "не ломать честных клиентов":
    26  *  - посл��дний валидный токен кешируется; без сети плагин работает на нём;
    27  *  - отключаемся только когда токен старше GRACE_DAYS.
    289 *
    2910 * @version 8.5.0
    … …  
    3314{
    3415    /**
    35      * Публичный ключ сервера лицензий (боевой, р. computy.ru).
    36      *
    37      * Соответствует приватному ключу на сервере:
    38      *   D:/OSPanel/home/.secrets/bfw_license_private.pem (вне webroot)
    39      * Публичный ключ можно раздавать — он только проверяет подпись.
     16     * Публичный ключ сервера лицензий.
     17     *
     18     * Публичный ключ можно раздавать — он только проверяет подпись токена.
     19     * Приватный ключ подписи хранится на сервере лицензий и в репозиторий
     20     * (и вообще в плагин) не попадает.
    4021     */
    4122    const PUBLIC_KEY = <<<'PEM'
  • bonus-for-woo/trunk/classes/BfwPoints.php

    r3784081 r3795776  
    805805        }
    806806
    807         $redirect = esc_url_raw($_POST['redirect'] ?? '');
    808         if (empty($redirect)) {
    809             wp_send_json_error('Invalid redirect URL');
    810             return;
    811         }
     807        $redirect = self::sanitizeRedirect(wp_unslash($_POST['redirect'] ?? ''));
    812808
    813809        echo self::bfw_write_off_points($redirect);
    … …  
    836832        }
    837833        $params = $request->get_json_params();
    838         $redirect = isset($params['redirect']) ? $params['redirect'] : '';
     834        $redirect = self::sanitizeRedirect($params['redirect'] ?? '');
    839835
    840836        ob_start();
    … …  
    884880     * @version 6.5.5
    885881     */
     882    /**
     883     * Приводит адрес возврата к безопасному внутреннему URL.
     884     *
     885     * Значение приходит из запроса/JSON и потом уходит в скрытое поле формы, в
     886     * JSON-ответ и в window.location.href на клиенте, поэтому проверять надо
     887     * не только экранирование, но и то, что адрес остаётся на своём сайте:
     888     * иначе получаем открытый редирект и javascript:-URL.
     889     *
     890     * @param mixed $redirect
     891     * @param string|null $fallback
     892     * @return string
     893     * @since 8.5.2
     894     */
     895    public static function sanitizeRedirect($redirect, ?string $fallback = null): string
     896    {
     897        $fallback = $fallback ?? wc_get_cart_url();
     898
     899        if (!is_string($redirect) || $redirect === '') {
     900            return $fallback;
     901        }
     902
     903        $candidate = esc_url_raw(wp_unslash($redirect));
     904
     905        if ($candidate === '') {
     906            return $fallback;
     907        }
     908
     909        return wp_validate_redirect($candidate, $fallback);
     910    }
     911
    886912    public static function bfw_write_off_points($redirect): string
    887913    {
     914        $redirect = self::sanitizeRedirect($redirect);
     915
    888916        global $pagenow;
    889917        if ($pagenow === 'post.php') {
    … …  
    10241052 
    10251053        <input type="hidden" name="action" value="computy_trata_points">
    1026         <input type="hidden" name="redirect" value="' . $redirect . '">
     1054        <input type="hidden" name="redirect" value="' . esc_attr($redirect) . '">
    10271055        <input type="hidden" name="_wpnonce" value="' . wp_create_nonce('bfw_trata_points') . '">
    10281056        <input type="text" name="computy_input_points" class="input-text" value="' . self::roundPoints(($user_fast_points > 0 ? $user_fast_points : $vozmojniy_ball)) . '">
    … …  
    10441072            $return .= '<div class="remove_points_form">
    10451073                <input type="hidden" name="action" value="clear_bonus" />
    1046                 <input type="hidden" name="redirect" value="' . esc_url($redirect) . '">
     1074                <input type="hidden" name="redirect" value="' . esc_attr($redirect) . '">
    10471075                <input type="hidden" name="security" value="' . wp_create_nonce('bfw_clear_fast_bonus') . '">
    10481076
    … …  
    10861114
    10871115        $requestedPoints = self::roundPoints((float) ($_POST['computy_input_points'] ?? 0));
    1088         $redirect = esc_url_raw($_POST['redirect'] ?? '');
     1116        $redirect = self::sanitizeRedirect(wp_unslash($_POST['redirect'] ?? ''));
    10891117
    10901118        if ($requestedPoints <= 0) {
    … …  
    11181146            self::updateFastPoints($user_id, $pointsToApply);
    11191147
    1120             wp_send_json_success($_POST['redirect']);
     1148            wp_send_json_success($redirect);
    11211149        } catch (Exception $e) {
    11221150            wp_send_json_error(__('An error occurred while processing your request.', 'bonus-for-woo'));
    … …  
    11331161        $params = $request->get_json_params();
    11341162        $requestedPoints = isset($params['points']) ? self::roundPoints((float) $params['points']) : 0;
    1135         $redirect = isset($params['redirect']) ? esc_url($params['redirect']) : wc_get_cart_url();
     1163        $redirect = self::sanitizeRedirect($params['redirect'] ?? '', wc_get_cart_url());
    11361164
    11371165        $user_id = get_current_user_id();
    … …  
    13571385        }
    13581386
    1359         $redirect = $request->get_param('redirect') ?: home_url();
     1387        $redirect = self::sanitizeRedirect($request->get_param('redirect'), home_url());
     1388
    13601389        return new WP_REST_Response(['success' => true, 'data' => $redirect], 200);
    13611390    }
    … …  
    13751404        $params = $request->get_json_params();
    13761405        $code_coupon = isset($params['code_coupon']) ? sanitize_text_field($params['code_coupon']) : '';
    1377         $redirect_url = isset($params['redirect']) ? esc_url($params['redirect']) : home_url();
     1406        $redirect_url = self::sanitizeRedirect($params['redirect'] ?? '', home_url());
    13781407
    13791408        if (empty($code_coupon)) {
    … …  
    19701999        check_ajax_referer('bfw_export_bonuses_nonce', 'nonce');
    19712000
    1972         if (!current_user_can('manage_options')) {
     2001        if (!current_user_can(BFW_CAP_MANAGE)) {
    19732002            wp_send_json_error('Access denied');
    19742003            return;
    … …  
    19812010            : 'by_id';
    19822011
    1983         $array = json_decode(stripslashes(wp_unslash($_POST['response'] ?? '')), true);
    1984         if (!$array || empty($array['data']['url'])) {
    1985             wp_send_json_error('Invalid response data');
    1986         }
    1987 
    19882012        /*
    1989          * Путь раньше склеивался как ABSPATH . str_replace(site_url(), '', $url)
    1990          * без канонизации: ".." не отсекались, что давало чтение произвольных
    1991          * файлов. Теперь допускается только файл внутри папки плагина или
    1992          * внутри uploads (там лежит экспорт после 8.5.1).
     2013         * Раньше сюда присылали JSON-ответ медиатеки с URL файла, загруженного
     2014         * на сайт: файл с балансами клиентов оставался в uploads навсегда, а
     2015         * путь вычислялся из клиентских данных. Теперь файл приходит в самом
     2016         * запросе (multipart), читается из временного каталога PHP и на сайте
     2017         * не сохраняется.
    19932018         */
    1994         $url = (string) $array['data']['url'];
    1995         $relative = str_replace(site_url(), '', $url);
    1996         $candidate = wp_normalize_path(ABSPATH . ltrim($relative, '/'));
    1997 
    1998         if (strpos($candidate, '..') !== false) {
    1999             wp_send_json_error('Invalid file path');
    2000         }
    2001 
    2002         $allowed_dirs = array(wp_normalize_path(BONUS_COMPUTY_PLUGIN_DIR));
    2003         $upload_dir = wp_upload_dir(null, false);
    2004         $allowed_dirs[] = wp_normalize_path($upload_dir['basedir']);
    2005 
    2006         $real = realpath($candidate);
    2007 
    2008         if ($real === false) {
    2009             wp_send_json_error('Файл не найден');
    2010         }
    2011 
    2012         $real = wp_normalize_path($real);
    2013         $allowed = false;
    2014         foreach ($allowed_dirs as $allowed_dir) {
    2015             if (strpos($real, trailingslashit($allowed_dir)) === 0) {
    2016                 $allowed = true;
    2017                 break;
    2018             }
    2019         }
    2020 
    2021         if (!$allowed) {
    2022             wp_send_json_error('Invalid file path');
    2023         }
    2024 
    2025         $file_path = $real;
     2019        $uploaded = $_FILES['bfw_import_file'] ?? null;
     2020
     2021        if (empty($uploaded) || !isset($uploaded['tmp_name']) || (int) ($uploaded['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
     2022            wp_send_json_error(__('The file was not uploaded.', 'bonus-for-woo'));
     2023        }
     2024
     2025        if (!is_uploaded_file($uploaded['tmp_name'])) {
     2026            wp_send_json_error(__('Invalid upload.', 'bonus-for-woo'));
     2027        }
     2028
     2029        $file_path = $uploaded['tmp_name'];
    20262030
    20272031        $fileHandle = fopen($file_path, 'rb');
    20282032        if ($fileHandle === false) {
    2029             wp_send_json_error('Ошибка при открытии файла');
     2033            wp_send_json_error(__('Cannot open the uploaded file.', 'bonus-for-woo'));
    20302034        }
    20312035
    … …  
    31973201        check_ajax_referer('bfw_recalc_nonce', 'nonce');
    31983202
    3199         if (!current_user_can('manage_options')) {
     3203        if (!current_user_can(BFW_CAP_MANAGE)) {
    32003204            wp_send_json_error(__('Access denied.', 'bonus-for-woo'));
    32013205            return;
  • bonus-for-woo/trunk/classes/BfwRouter.php

    r3784081 r3795776  
    8181    private static function handleExportPoints()
    8282    {
    83         if (!isset($_GET['export_bfw_points']) || !current_user_can('manage_options')) {
    84             return;
    85         }
    86 
    87         if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'bfw_export_points')) {
    88             wp_die('Security check failed');
    89         }
    90 
    91         self::generateExportFile();
    92     }
    93 
    94     /**
    95      * Генерация CSV файла экспорта баллов
    96      */
    97     private static function generateExportFile()
    98     {
    99         $file_path = BfwFunctions::getExportFilePath();
    100         $buffer = fopen($file_path, 'w');
    101 
    102         if (!$buffer) {
    103             wp_die('Cannot create export file');
    104         }
    105 
    106         fwrite($buffer, "\xEF\xBB\xBF"); // BOM for UTF-8
    107 
    108         $title_export = ['User id', 'User name', 'Email', 'Phone', 'Points', 'Status', 'Comment'];
    109         fputcsv($buffer, $title_export, ',');
    110 
    111         $status = new BfwRoles();
    112         $data = self::prepareExportData($status);
    113 
    114         foreach ($data as $row) {
    115             fputcsv($buffer, $row, ',');
    116         }
    117 
    118         fclose($buffer);
    119     }
    120 
    121     /**
    122      * Подготовка данных для экспорта баллов
    123      */
    124     private static function prepareExportData($status): array
     83        if (!isset($_GET['export_bfw_points']) || !current_user_can(BFW_CAP_MANAGE)) {
     84            return;
     85        }
     86
     87        check_admin_referer('bfw_export_points');
     88
     89        self::streamExport();
     90    }
     91
     92    /**
     93     * Отдаёт CSV баллов потоком, не создавая файл на диске.
     94     *
     95     * Раньше файл писался в uploads с фиксированным именем и оставался там
     96     * после выгрузки — то есть данные всех клиентов (email, телефон, баланс)
     97     * лежали по постоянному публичному URL. Теперь данные уходят в php://output
     98     * и на диске не остаются.
     99     *
     100     * @return void
     101     */
     102    private static function streamExport(): void
     103    {
     104        $output = fopen('php://output', 'wb');
     105
     106        if ($output === false) {
     107            wp_die(esc_html__('Cannot create export file.', 'bonus-for-woo'));
     108        }
     109
     110        nocache_headers();
     111        header('Content-Type: text/csv; charset=utf-8');
     112        header('Content-Disposition: attachment; filename="bfw_points_' . gmdate('Y-m-d') . '.csv"');
     113
     114        fwrite($output, "\xEF\xBB\xBF"); // BOM для UTF-8
     115
     116        fputcsv($output, ['User id', 'User name', 'Email', 'Phone', 'Points', 'Status', 'Comment'], ',');
     117
     118        foreach (self::iterateExportRows() as $row) {
     119            fputcsv($output, $row, ',');
     120        }
     121
     122        fclose($output);
     123        exit;
     124    }
     125
     126    /** Размер пакета при экспорте: столько пользователей держим в памяти за раз */
     127    const EXPORT_BATCH_SIZE = 500;
     128
     129    /**
     130     * Построчный обход пользователей для экспорта.
     131     *
     132     * Раньше все пользователи и все их мета выбирались целиком в память, а
     133     * статус запрашивался отдельно на каждого (N+1). Теперь выборка идёт
     134     * пакетами, а статусы подтягиваются одним SQL-запросом на пакет.
     135     *
     136     * @return \Generator
     137     */
     138    private static function iterateExportRows(): \Generator
    125139    {
    126140        global $wpdb;
    127141
    128         // 1. Получаем только необходимые поля
    129         $users = $wpdb->get_results("SELECT ID, user_nicename, user_email FROM {$wpdb->users} ORDER BY ID");
    130 
    131         if (empty($users)) {
    132             return [];
    133         }
    134 
    135         $user_ids = wp_list_pluck($users, 'ID');
     142        $offset = 0;
     143
     144        do {
     145            $users = $wpdb->get_results($wpdb->prepare(
     146                "SELECT ID, user_nicename, user_email FROM {$wpdb->users} ORDER BY ID LIMIT %d OFFSET %d",
     147                self::EXPORT_BATCH_SIZE,
     148                $offset
     149            ));
     150
     151            if (empty($users)) {
     152                break;
     153            }
     154
     155            $user_ids = array_map('intval', wp_list_pluck($users, 'ID'));
     156
     157            // 1. Мета и статусы — по одному запросу на пакет
     158            $metas = self::getUsersMetaBatch($user_ids);
     159            $statuses = self::getUsersStatusNamesBatch($user_ids);
     160
     161            // 2. Отдаём строки
     162            foreach ($users as $user) {
     163                $user_id = (int) $user->ID;
     164
     165                yield [
     166                    'id'      => $user_id,
     167                    'name'    => sanitize_text_field($user->user_nicename),
     168                    'email'   => sanitize_email($user->user_email),
     169                    'phone'   => sanitize_text_field($metas[$user_id]['billing_phone'] ?? ''),
     170                    'points'  => (int) ($metas[$user_id]['computy_point'] ?? 0),
     171                    'status'  => sanitize_text_field($statuses[$user_id] ?? ''),
     172                    'comment' => '',
     173                ];
     174            }
     175
     176            $offset += self::EXPORT_BATCH_SIZE;
     177            unset($users, $metas, $statuses);
     178        } while (count($user_ids) === self::EXPORT_BATCH_SIZE);
     179    }
     180
     181    /**
     182     * Метрики баллов и телефона для пакета пользователей.
     183     *
     184     * @param int[] $user_ids
     185     * @return array<int, array<string, string>>
     186     */
     187    private static function getUsersMetaBatch(array $user_ids): array
     188    {
     189        global $wpdb;
     190
     191        $placeholders = implode(',', array_fill(0, count($user_ids), '%d'));
     192
     193        $rows = $wpdb->get_results($wpdb->prepare(
     194            "SELECT user_id, meta_key, meta_value
     195             FROM {$wpdb->usermeta}
     196             WHERE user_id IN ({$placeholders}) AND meta_key IN ('computy_point', 'billing_phone')",
     197            ...$user_ids
     198        ));
     199
    136200        $metas = [];
    137 
    138         // 2. Собираем метаполя ОДНИМ запросом для всех
     201        foreach ($rows as $row) {
     202            $metas[(int) $row->user_id][$row->meta_key] = $row->meta_value;
     203        }
     204
     205        return $metas;
     206    }
     207
     208    /**
     209     * Названия статусов для пакета пользователей — один запрос вместо getRole() в цикле.
     210     *
     211     * @param int[] $user_ids
     212     * @return array<int, string>
     213     */
     214    private static function getUsersStatusNamesBatch(array $user_ids): array
     215    {
     216        global $wpdb;
     217
    139218        $placeholders = implode(',', array_fill(0, count($user_ids), '%d'));
    140         $meta_query = $wpdb->prepare(
    141             "SELECT user_id, meta_key, meta_value
    142          FROM {$wpdb->usermeta}
    143          WHERE user_id IN ($placeholders) AND meta_key IN ('computy_point', 'billing_phone')",
    144             ...$user_ids // Важно: распаковываем массив для wpdb->prepare
    145         );
    146 
    147         foreach ($wpdb->get_results($meta_query) as $meta) {
    148             $metas[$meta->user_id][$meta->meta_key] = $meta->meta_value;
    149         }
    150 
    151         // 3. Формируем итоговый массив
    152         $data = [];
    153         foreach ($users as $user) {
    154             $points = (int)($metas[$user->ID]['computy_point'] ?? 0);
    155             $phone = $metas[$user->ID]['billing_phone'] ?? '';
    156 
    157             // Внимание: если getRole внутри делает SQL-запрос,
    158             // скрипт все еще может притормаживать на больших объемах.
    159             $arrayRole = $status->getRole($user->ID);
    160 
    161             $data[] = [
    162                 'id'      => (int)$user->ID,
    163                 'name'    => sanitize_text_field($user->user_nicename),
    164                 'email'   => sanitize_email($user->user_email),
    165                 'phone'   => sanitize_text_field($phone),
    166                 'points'  => $points,
    167                 'status'  => sanitize_text_field($arrayRole['name'] ?? ''),
    168                 'comment' => '',
    169             ];
    170         }
    171 
    172         return $data;
     219
     220        $rows = $wpdb->get_results($wpdb->prepare(
     221            "SELECT um.user_id, r.name
     222             FROM {$wpdb->usermeta} um
     223             INNER JOIN {$wpdb->prefix}bfw_computy r ON r.id = um.meta_value
     224             WHERE um.user_id IN ({$placeholders}) AND um.meta_key = 'bfw_status'",
     225            ...$user_ids
     226        ));
     227
     228        $statuses = [];
     229        foreach ($rows as $row) {
     230            $statuses[(int) $row->user_id] = (string) $row->name;
     231        }
     232
     233        return $statuses;
    173234    }
    174235
    … …  
    178239    private static function handleRemoveExport()
    179240    {
    180         if (!isset($_GET['remove_export_bfw_points']) || !current_user_can('manage_options')) {
     241        if (!isset($_GET['remove_export_bfw_points']) || !current_user_can(BFW_CAP_MANAGE)) {
    181242            return;
    182243        }
    … …  
    186247        }
    187248
    188         $file_path = BfwFunctions::getExportFilePath();
    189 
    190         if (!is_file($file_path)) {
    191             wp_die('Invalid file path');
    192         }
    193 
    194         if (file_exists($file_path)) {
    195             wp_delete_file($file_path);
    196         }
     249        /*
     250         * Экспорт больше не пишется на диск (см. streamExport()), но у сайтов,
     251         * обновлённых с версий <= 8.5.1, в uploads может остаться старый файл —
     252         * с персональными данными и по публичному URL. Чистим его.
     253         */
     254        BfwFunctions::deleteLegacyExportFile();
     255
     256        wp_safe_redirect(admin_url('admin.php?page=bonus-for-woo%2Fpages%2Ftools.php&bfw_export_removed=1'));
     257        exit;
    197258    }
    198259
    … …  
    202263    public static function handleAnalyticsExport()
    203264    {
    204         if (!isset($_GET['bfw_analytics_export']) || !current_user_can('manage_options')) {
     265        if (!isset($_GET['bfw_analytics_export']) || !current_user_can(BFW_CAP_MANAGE)) {
    205266            return;
    206267        }
  • bonus-for-woo/trunk/classes/BfwStatistic.php

    r3784081 r3795776  
    2020    public static function ajax_get_stats_timestamp()
    2121    {
    22         if (!current_user_can('manage_options')) {
     22        if (!current_user_can(BFW_CAP_MANAGE)) {
    2323            wp_send_json_error(['message' => 'Access denied']);
    2424        }
    … …  
    8585        check_ajax_referer('bfw_stat_nonce', 'nonce');
    8686
    87         if (!current_user_can('manage_options')) {
     87        if (!current_user_can(BFW_CAP_MANAGE)) {
    8888            wp_send_json_error(['message' => 'Access denied']);
    8989        }
    … …  
    148148                $pending_total = 0;
    149149
    150                 $pending_order_ids = wc_get_orders([
    151                     'status' => ['processing', 'on-hold'],
    152                     'limit'  => -1,
    153                     'return' => 'ids',
    154                 ]);
    155 
    156                 foreach ($pending_order_ids as $order_id) {
    157                     $order = wc_get_order($order_id);
    158                     if (!$order) {
    159                         continue;
     150                /*
     151                 * Раньше здесь был wc_get_orders(limit => -1) с последующим
     152                 * wc_get_order() в цикле: на магазине с тысячами заказов в
     153                 * обработке один AJAX-шаг тянул их все в память.
     154                 * Теперь пагинация и «загруженные» объекты заказов — WC
     155                 * переиспользует их из своего кеша, поэтому повторный
     156                 * wc_get_order() в пределах страницы стоит одно чтение.
     157                 */
     158                $page      = 1;
     159                $per_page  = 200;
     160                $max_orders = (int) apply_filters('bfw_pending_statistics_max_orders', 2000);
     161
     162                do {
     163                    $pending_orders = wc_get_orders([
     164                        'status'       => ['processing', 'on-hold'],
     165                        'limit'        => $per_page,
     166                        'page'         => $page,
     167                        'return'       => 'objects',
     168                        'orderby'      => 'date',
     169                        'order'        => 'DESC',
     170                        'meta_query'   => [
     171                            [
     172                                'key'     => 'cashback_receipt',
     173                                'value'   => 'received',
     174                                'compare' => '!=',
     175                            ],
     176                        ],
     177                    ]);
     178
     179                    foreach ($pending_orders as $order) {
     180                        $pending_total += BfwPoints::howMatchCashbackInOrder($order->get_id());
    160181                    }
    161                     if ($order->get_meta('cashback_receipt') === 'received') {
    162                         continue;
    163                     }
    164                     $pending_total += BfwPoints::howMatchCashbackInOrder($order_id);
    165                 }
     182
     183                    $page++;
     184                } while (count($pending_orders) === $per_page && (($page - 1) * $per_page) < $max_orders);
    166185
    167186                $data['pending_points_total'] = (float) $pending_total;
    … …  
    427446        check_ajax_referer('bfw_stat_nonce', 'nonce');
    428447
    429         if (!current_user_can('manage_options')) {
     448        if (!current_user_can(BFW_CAP_MANAGE)) {
    430449            wp_send_json_error(['message' => 'Access denied']);
    431450        }
  • bonus-for-woo/trunk/classes/Bfw_Coupons_List_Table.php

    r3784081 r3795776  
    3838        $this->_column_headers = [$columns, $hidden, $sortable];
    3939
     40        /*
     41         * orderby / order приходят из $_GET и вклеиваются в строку до prepare().
     42         * esc_sql() экранирует только кавычки, идентификаторы и ключевые слова
     43         * он не защищает, поэтому ORDER BY был инъектируемым. Только белый список.
     44         */
    4045        $allowed_orderby = ['id', 'created', 'sum', 'code', 'status'];
    41         $orderby = (!empty($_GET['orderby']) && in_array($_GET['orderby'], $allowed_orderby))
    42                 ? sanitize_text_field($_GET['orderby'])
     46        $orderby = (!empty($_GET['orderby']) && in_array($_GET['orderby'], $allowed_orderby, true))
     47                ? sanitize_key($_GET['orderby'])
    4348                : 'id';
    44         $order = (!empty($_GET['order'])) ? esc_sql($_GET['order']) : 'DESC';
     49        $order = (!empty($_GET['order']) && strtoupper(sanitize_key($_GET['order'])) === 'ASC') ? 'ASC' : 'DESC';
    4550
    4651        $where = '';
    4752        if (!empty($_REQUEST['s'])) {
    48             $search = esc_sql($_REQUEST['s']);
     53            $search = $wpdb->esc_like(sanitize_text_field(wp_unslash($_REQUEST['s'])));
    4954            $where = $wpdb->prepare(" WHERE code LIKE %s OR comment_admin LIKE %s", '%' . $search . '%', '%' . $search . '%');
    5055        }
  • bonus-for-woo/trunk/classes/Bfw_Export.php

    r3784081 r3795776  
    88    {
    99        add_action('admin_init', [__CLASS__, 'handle_export_requests']);
    10         add_action('admin_post_bfw_download_export', [__CLASS__,'bfw_handle_download_export']);
    1110    }
    1211
    13     public static function bfw_handle_download_export() {
    14         // Проверяем права доступа
    15         if (!current_user_can('manage_options')) {
    16             wp_die(__('You do not have permission to download this file.', 'bonus-for-woo'));
    17         }
    18 
    19         check_admin_referer('bfw_download_export');
    20 
    21         $file_path = BfwFunctions::getExportFilePath();
    22 
    23         // Проверяем существование файла
    24         if (!is_file($file_path)) {
    25             wp_die(__('The export file was not found. Generate it first.', 'bonus-for-woo'));
    26         }
    27 
    28         // Отправляем заголовки для скачивания
    29         header('Content-Description: File Transfer');
    30         header('Content-Type: text/csv');
    31         header('Content-Disposition: attachment; filename="' . basename($file_path) . '"');
    32         header('Expires: 0');
    33         header('Cache-Control: must-revalidate');
    34         header('Pragma: public');
    35         header('Content-Length: ' . filesize($file_path));
    36 
    37         // Читаем файл и отправляем в браузер
    38         readfile($file_path);
    39         exit; // Обязательно завершаем выполнение
    40     }
    4112    public static function handle_export_requests()
    4213    {
    43         if (!is_admin() || !current_user_can('manage_options')) {
     14        if (!is_admin() || !current_user_can(BFW_CAP_MANAGE)) {
    4415            return;
    4516        }
  • bonus-for-woo/trunk/index.php

    r3784081 r3795776  
    55 * @wordpress-plugin
    66 * Plugin Name:       Bonus for Woo
    7  * Version:           8.5.0
     7 * Version:           8.5.2
    88 * Plugin URI:        https://computy.ru/blog/bonus-for-woo-wordpress
    99 * Description:       A comprehensive cashback bonus system for WooCommerce with user status management.
    … …  
    2828
    2929// Define plugin constants.
    30 const BONUS_COMPUTY_VERSION = '8.5.0';
     30const BONUS_COMPUTY_VERSION = '8.5.2';
    3131const BONUS_COMPUTY_VERSION_DB = '8';
    3232define('BONUS_COMPUTY_PLUGIN_DIR', plugin_dir_path(__FILE__));
    … …  
    3535define('BFW_HISTORY_LIMIT', 500); //Лимит в истории начислений
    3636define('BFW_COUNT_STEPS_FOR_REQUESTS', 20);//Количество обработок за 1 запрос
     37
     38/*
     39 * Единое право на управление бонусной системой.
     40 *
     41 * Меню отдавалось manage_woocommerce (пункты статистики/купонов/инструментов),
     42 * а их AJAX-обработчики требовали manage_options — shop manager видел половину
     43 * интерфейса, которая молча не работала. Право собрано в одну константу,
     44 * чтобы интерфейс и обработчики нельзя было рассинхронизировать снова.
     45 */
     46define('BFW_CAP_MANAGE', 'manage_woocommerce');
    3747/**
    3848 * Автозагрузка классов
    … …  
    4050
    4151spl_autoload_register(static function ($class) {
    42     $file = str_replace('\\', '/', BONUS_COMPUTY_PLUGIN_DIR . '/classes/' . $class)
    43         . '.php';
    44     if (file_exists($file)) {
     52    /*
     53     * Только свои классы. Раньше автозагрузчик пробовал любой класс и
     54     * склеивал путь с двойным слэшем (DIR . '/classes/'), то есть лез в
     55     * файловую систему на каждый чужой class_exists().
     56     */
     57    if (strpos($class, 'Bfw') !== 0 || strpos($class, '/') !== false || strpos($class, '\\') !== false) {
     58        return;
     59    }
     60
     61    $file = BONUS_COMPUTY_PLUGIN_DIR . 'classes/' . $class . '.php';
     62
     63    if (is_file($file)) {
    4564        require_once $file;
    4665    }
    … …  
    109128
    110129/*-------Действия после обновления-------*/
    111 if (get_transient('bfw_pro_updated')) {
    112     /*Проверка бд после обновления */
    113     BfwAdmin::bfw_search_pro();
     130/*
     131 * Раньше это выполнялось прямо при подключении файла плагина — до
     132 * plugins_loaded и до проверки наличия WooCommerce, то есть миграция БД и
     133 * поиск PRO могли стартовать в неготовом окружении.
     134 */
     135add_action('plugins_loaded', function () {
     136    if (!get_transient('bfw_pro_updated')) {
     137        return;
     138    }
     139
     140    if (bfw_dependencies_met()) {
     141        BfwAdmin::bfw_search_pro();
     142    }
     143
    114144    BfwDB::checkDb();
     145
    115146    delete_transient('bfw_pro_updated');
    116 }
     147}, 20);
    117148/*-------Действия после обновления-------*/
    118149
    … …  
    140171/*-------Регистрация REST API маршрутов (отдельно от init для корректной работы)-------*/
    141172add_action('rest_api_init', function () {
     173    /*
     174     * Без WooCommerce все обработчики ниже обращаются к WC()/WC_Cart и
     175     * падают фаталом. Маршруты регистрируем только при живых зависимостях.
     176     */
     177    if (!bfw_dependencies_met()) {
     178        return;
     179    }
     180
    142181    register_rest_route('bfw/v1', '/clear-fast-bonus', [
    143182        'methods' => 'POST',
    … …  
    180219    ]);
    181220
     221    /*
     222     * Свежий REST nonce. Нужен потому, что nonce, напечатанный в HTML,
     223     * протухает под полностраничным кешем; страница запрашивает его при
     224     * загрузке и получает актуальный.
     225     */
     226    register_rest_route('bfw/v1', '/nonce', [
     227        'methods' => 'GET',
     228        'callback' => function () {
     229            return new WP_REST_Response(['nonce' => wp_create_nonce('wp_rest')], 200);
     230        },
     231        'permission_callback' => function () {
     232            return is_user_logged_in();
     233        },
     234    ]);
     235
    182236    register_rest_route('bfw/v1', '/check-points-only-products', [
    183237        'methods' => 'POST',
  • bonus-for-woo/trunk/license.txt

    r3442739 r3795776  
    1                     GNU GENERAL PUBLIC LICENSE
    2                        Version 2, June 1991
    3 
    4  Copyright (C) 1989, 1991 Free Software Foundation, Inc.
    5               51 Franklin St, Fifth Floor, Boston, MA 02110, USA
    6 
     1                    GNU GENERAL PUBLIC LICENSE
     2                       Version 3, 29 June 2007
     3
     4 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
    75 Everyone is permitted to copy and distribute verbatim copies
    86 of this license document, but changing it is not allowed.
    97
    10                             Preamble
    11 
    12   The licenses for most software are designed to take away your
    13 freedom to share and change it.  By contrast, the GNU General Public
    14 License is intended to guarantee your freedom to share and change free
    15 software--to make sure the software is free for all its users.  This
    16 General Public License applies to most of the Free Software
    17 Foundation's software and to any other program whose authors commit to
    18 using it.  (Some other Free Software Foundation software is covered by
    19 the GNU Library General Public License instead.)  You can apply it to
     8                            Preamble
     9
     10  The GNU General Public License is a free, copyleft license for
     11software and other kinds of works.
     12
     13  The licenses for most software and other practical works are designed
     14to take away your freedom to share and change the works.  By contrast,
     15the GNU General Public License is intended to guarantee your freedom to
     16share and change all versions of a program--to make sure it remains free
     17software for all its users.  We, the Free Software Foundation, use the
     18GNU General Public License for most of our software; it applies also to
     19any other work released this way by its authors.  You can apply it to
    2020your programs, too.
    2121
    2222  When we speak of free software, we are referring to freedom, not
    23 price.  Our Public Licenses are designed to make sure that you
     23price.  Our General Public Licenses are designed to make sure that you
    2424have the freedom to distribute copies of free software (and charge for
    25 this service if you wish), that you receive source code or can get it
    26 if you want it, that you can change the software or use pieces of it
    27 in new free programs; and that you know you can do these things.
    28 
    29   To protect your rights, we need to make restrictions that forbid
    30 anyone to deny you these rights or to ask you to surrender the rights.
    31 These restrictions translate to certain responsibilities for you if you
    32 distribute copies of the software, or if you modify it.
     25them if you wish), that you receive source code or can get it if you
     26want it, that you can change the software or use pieces of it in new
     27free programs, and that you know you can do these things.
     28
     29  To protect your rights, we need to prevent others from denying you
     30these rights or asking you to surrender the rights.  Therefore, you have
     31certain responsibilities if you distribute copies of the software, or if
     32you modify it: responsibilities to respect the freedom of others.
    3333
    3434  For example, if you distribute copies of such a program, whether
    35 gratis or for a fee, you must give the recipients all the rights that
    36 you have.  You must make sure that they, too, receive or can get the
    37 source code.  And you must show them these terms, so they know their
    38 rights.
    39 
    40   We protect your rights with two steps: (1) copyright the software, and
    41 (2) offer you this license which gives you legal permission to copy,
    42 distribute and/or modify the software.
    43 
    44   Also, for each author's protection and ours, we want to make certain
    45 that everyone understands that there is no warranty for this free
    46 software.  If the software is modified by someone else and passed on, we
    47 want its recipients to know that what they have is not the original, so
    48 that any problems introduced by others will not reflect on the original
    49 authors' reputations.
    50 
    51   Finally, any free program is threatened constantly by software
    52 patents.  We wish to avoid the danger that redistributors of a free
    53 program will individually obtain patent licenses, in effect making the
    54 program proprietary.  To prevent this, we have made it clear that any
    55 patent must be licensed for everyone's free use or not licensed at all.
     35gratis or for a fee, you must pass on to the recipients the same
     36freedoms that you received.  You must make sure that they, too, receive
     37or can get the source code.  And you must show them these terms so they
     38know their rights.
     39
     40  Developers that use the GNU GPL protect your rights with two steps:
     41(1) assert copyright on the software, and (2) offer you this License
     42giving you legal permission to copy, distribute and/or modify it.
     43
     44  For the developers' and authors' protection, the GPL clearly explains
     45that there is no warranty for this free software.  For both users' and
     46authors' sake, the GPL requires that modified versions be marked as
     47changed, so that their problems will not be attributed erroneously to
     48authors of previous versions.
     49
     50  Some devices are designed to deny users access to install or run
     51modified versions of the software inside them, although the manufacturer
     52can do so.  This is fundamentally incompatible with the aim of
     53protecting users' freedom to change the software.  The systematic
     54pattern of such abuse occurs in the area of products for individuals to
     55use, which is precisely where it is most unacceptable.  Therefore, we
     56have designed this version of the GPL to prohibit the practice for those
     57products.  If such problems arise substantially in other domains, we
     58stand ready to extend this provision to those domains in future versions
     59of the GPL, as needed to protect the freedom of users.
     60
     61  Finally, every program is threatened constantly by software patents.
     62States should not allow patents to restrict development and use of
     63software on general-purpose computers, but in those that do, we wish to
     64avoid the special danger that patents applied to a free program could
     65make it effectively proprietary.  To prevent this, the GPL assures that
     66patents cannot be used to render the program non-free.
    5667
    5768  The precise terms and conditions for copying, distribution and
    5869modification follow.
    5970
    60                     GNU GENERAL PUBLIC LICENSE
    61    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
    62 
    63   0. This License applies to any program or other work which contains
    64 a notice placed by the copyright holder saying it may be distributed
    65 under the terms of this General Public License.  The "Program", below,
    66 refers to any such program or work, and a "work based on the Program"
    67 means either the Program or any derivative work under copyright law:
    68 that is to say, a work containing the Program or a portion of it,
    69 either verbatim or with modifications and/or translated into another
    70 language.  (Hereinafter, translation is included without limitation in
    71 the term "modification".)  Each licensee is addressed as "you".
    72 
    73 Activities other than copying, distribution and modification are not
    74 covered by this License; they are outside its scope.  The act of
    75 running the Program is not restricted, and the output from the Program
    76 is covered only if its contents constitute a work based on the
    77 Program (independent of having been made by running the Program).
    78 Whether that is true depends on what the Program does.
    79 
    80   1. You may copy and distribute verbatim copies of the Program's
    81 source code as you receive it, in any medium, provided that you
    82 conspicuously and appropriately publish on each copy an appropriate
    83 copyright notice and disclaimer of warranty; keep intact all the
    84 notices that refer to this License and to the absence of any warranty;
    85 and give any other recipients of the Program a copy of this License
    86 along with the Program.
    87 
    88 You may charge a fee for the physical act of transferring a copy, and
    89 you may at your option offer warranty protection in exchange for a fee.
    90 
    91   2. You may modify your copy or copies of the Program or any portion
    92 of it, thus forming a work based on the Program, and copy and
    93 distribute such modifications or work under the terms of Section 1
    94 above, provided that you also meet all of these conditions:
    95 
    96     a) You must cause the modified files to carry prominent notices
    97     stating that you changed the files and the date of any change.
    98 
    99     b) You must cause any work that you distribute or publish, that in
    100     whole or in part contains or is derived from the Program or any
    101     part thereof, to be licensed as a whole at no charge to all third
    102     parties under the terms of this License.
    103 
    104     c) If the modified program normally reads commands interactively
    105     when run, you must cause it, when started running for such
    106     interactive use in the most ordinary way, to print or display an
    107     announcement including an appropriate copyright notice and a
    108     notice that there is no warranty (or else, saying that you provide
    109     a warranty) and that users may redistribute the program under
    110     these conditions, and telling the user how to view a copy of this
    111     License.  (Exception: if the Program itself is interactive but
    112     does not normally print such an announcement, your work based on
    113     the Program is not required to print an announcement.)
    114 
    115 These requirements apply to the modified work as a whole.  If
    116 identifiable sections of that work are not derived from the Program,
    117 and can be reasonably considered independent and separate works in
    118 themselves, then this License, and its terms, do not apply to those
    119 sections when you distribute them as separate works.  But when you
    120 distribute the same sections as part of an entire which is a work based
    121 on the Program, the distribution of the entire must be on the terms of
    122 this License, whose permissions for other licensees extend to the
    123 entire, and thus to each and every part regardless of who wrote it.
    124 Thus, it is not the intent of this section to claim rights or contest
    125 your rights to work written entirely by you; rather, the intent is to
    126 exercise the right to control the distribution of derivative or
    127 collective works based on the Program.
    128 
    129 In addition, mere aggregation of another work not based on the Program
    130 with the Program (or with a work based on the Program) on a volume of
    131 a storage or distribution medium does not bring the other work under
    132 the scope of this License.
    133 
    134   3. You may copy and distribute the Program (or a work based on it,
    135 under Section 2) in object code or executable form under the terms of
    136 Sections 1 and 2 above provided that you also do one of the following:
    137 
    138     a) Accompany it with the complete corresponding machine-readable
    139     source code, which must be distributed under the terms of Sections
    140     1 and 2 above on a medium customarily used for software interchange; or,
    141 
    142     b) Accompany it with a written offer, valid for at least three
    143     years, to give any third party, for a charge no more than your
    144     cost of physically performing source distribution, a complete
    145     machine-readable copy of the corresponding source code, to be
    146     distributed under the terms of Sections 1 and 2 above on a medium
    147     customarily used for software interchange; or,
    148 
    149     c) Accompany it with the information you received as to the offer
    150     to distribute corresponding source code.  (This alternative is
    151     allowed only for noncommercial distribution and only if you
    152     received the program in object code or executable form with such
    153     an offer, in accord with Subsection b above.)
    154 
    155 The source code for a work means the preferred form of the work for
    156 making modifications to it.  For an executable work, complete source
    157 code means all the source code for all modules it contains, plus any
    158 associated interface definition files, plus the scripts used to
    159 control compilation and installation of the executable.  However, as a
    160 special exception, the source code distributed need not include
    161 anything that is normally distributed (in either source or binary
    162 form) with the major components (compiler, kernel, and so on) of the
    163 operating system on which the executable runs, unless that component
    164 itself accompanies the executable.
    165 
    166 If distribution of executable or object code is made by offering
    167 access to copy from a designated place, then offering equivalent
    168 access to copy the source code from the same place counts as
    169 distribution of the source code, even though third parties are not
    170 compelled to copy the source along with the object code.
    171 
    172   4. You may not copy, modify, sublicense, or distribute the Program
    173 except as expressly provided under this License.  Any attempt
    174 otherwise to copy, modify, sublicense or distribute the Program is
    175 void, and will automatically terminate your rights under this License.
    176 However, parties who have received copies, or rights, from you under
    177 this License will not have their licenses terminated so long as such
    178 parties remain in full compliance.
    179 
    180   5. You are not required to accept this License, since you have not
    181 signed it.  However, nothing else grants you permission to modify or
    182 distribute the Program or its derivative works.  These actions are
    183 prohibited by law if you do not accept this License.  Therefore, by
    184 modifying or distributing the Program (or any work based on the
    185 Program), you indicate your acceptance of this License to do so, and
    186 all its terms and conditions for copying, distributing or modifying
    187 the Program or works based on it.
    188 
    189   6. Each time you redistribute the Program (or any work based on the
    190 Program), the recipient automatically receives a license from the
    191 original licensor to copy, distribute or modify the Program subject to
    192 these terms and conditions.  You may not impose any further
    193 restrictions on the recipients' exercise of the rights granted herein.
    194 You are not responsible for enforcing compliance by third parties to
     71                       TERMS AND CONDITIONS
     72
     73  0. Definitions.
     74
     75  "This License" refers to version 3 of the GNU General Public License.
     76
     77  "Copyright" also means copyright-like laws that apply to other kinds of
     78works, such as semiconductor masks.
     79
     80  "The Program" refers to any copyrightable work licensed under this
     81License.  Each licensee is addressed as "you".  "Licensees" and
     82"recipients" may be individuals or organizations.
     83
     84  To "modify" a work means to copy from or adapt all or part of the work
     85in a fashion requiring copyright permission, other than the making of an
     86exact copy.  The resulting work is called a "modified version" of the
     87earlier work or a work "based on" the earlier work.
     88
     89  A "covered work" means either the unmodified Program or a work based
     90on the Program.
     91
     92  To "propagate" a work means to do anything with it that, without
     93permission, would make you directly or secondarily liable for
     94infringement under applicable copyright law, except executing it on a
     95computer or modifying a private copy.  Propagation includes copying,
     96distribution (with or without modification), making available to the
     97public, and in some countries other activities as well.
     98
     99  To "convey" a work means any kind of propagation that enables other
     100parties to make or receive copies.  Mere interaction with a user through
     101a computer network, with no transfer of a copy, is not conveying.
     102
     103  An interactive user interface displays "Appropriate Legal Notices"
     104to the extent that it includes a convenient and prominently visible
     105feature that (1) displays an appropriate copyright notice, and (2)
     106tells the user that there is no warranty for the work (except to the
     107extent that warranties are provided), that licensees may convey the
     108work under this License, and how to view a copy of this License.  If
     109the interface presents a list of user commands or options, such as a
     110menu, a prominent item in the list meets this criterion.
     111
     112  1. Source Code.
     113
     114  The "source code" for a work means the preferred form of the work
     115for making modifications to it.  "Object code" means any non-source
     116form of a work.
     117
     118  A "Standard Interface" means an interface that either is an official
     119standard defined by a recognized standards body, or, in the case of
     120interfaces specified for a particular programming language, one that
     121is widely used among developers working in that language.
     122
     123  The "System Libraries" of an executable work include anything, other
     124than the work as a whole, that (a) is included in the normal form of
     125packaging a Major Component, but which is not part of that Major
     126Component, and (b) serves only to enable use of the work with that
     127Major Component, or to implement a Standard Interface for which an
     128implementation is available to the public in source code form.  A
     129"Major Component", in this context, means a major essential component
     130(kernel, window system, and so on) of the specific operating system
     131(if any) on which the executable work runs, or a compiler used to
     132produce the work, or an object code interpreter used to run it.
     133
     134  The "Corresponding Source" for a work in object code form means all
     135the source code needed to generate, install, and (for an executable
     136work) run the object code and to modify the work, including scripts to
     137control those activities.  However, it does not include the work's
     138System Libraries, or general-purpose tools or generally available free
     139programs which are used unmodified in performing those activities but
     140which are not part of the work.  For example, Corresponding Source
     141includes interface definition files associated with source files for
     142the work, and the source code for shared libraries and dynamically
     143linked subprograms that the work is specifically designed to require,
     144such as by intimate data communication or control flow between those
     145subprograms and other parts of the work.
     146
     147  The Corresponding Source need not include anything that users
     148can regenerate automatically from other parts of the Corresponding
     149Source.
     150
     151  The Corresponding Source for a work in source code form is that
     152same work.
     153
     154  2. Basic Permissions.
     155
     156  All rights granted under this License are granted for the term of
     157copyright on the Program, and are irrevocable provided the stated
     158conditions are met.  This License explicitly affirms your unlimited
     159permission to run the unmodified Program.  The output from running a
     160covered work is covered by this License only if the output, given its
     161content, constitutes a covered work.  This License acknowledges your
     162rights of fair use or other equivalent, as provided by copyright law.
     163
     164  You may make, run and propagate covered works that you do not
     165convey, without conditions so long as your license otherwise remains
     166in force.  You may convey covered works to others for the sole purpose
     167of having them make modifications exclusively for you, or provide you
     168with facilities for running those works, provided that you comply with
     169the terms of this License in conveying all material for which you do
     170not control copyright.  Those thus making or running the covered works
     171for you must do so exclusively on your behalf, under your direction
     172and control, on terms that prohibit them from making any copies of
     173your copyrighted material outside their relationship with you.
     174
     175  Conveying under any other circumstances is permitted solely under
     176the conditions stated below.  Sublicensing is not allowed; section 10
     177makes it unnecessary.
     178
     179  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
     180
     181  No covered work shall be deemed part of an effective technological
     182measure under any applicable law fulfilling obligations under article
     18311 of the WIPO copyright treaty adopted on 20 December 1996, or
     184similar laws prohibiting or restricting circumvention of such
     185measures.
     186
     187  When you convey a covered work, you waive any legal power to forbid
     188circumvention of technological measures to the extent such circumvention
     189is effected by exercising rights under this License with respect to
     190the covered work, and you disclaim any intention to limit operation or
     191modification of the work as a means of enforcing, against the work's
     192users, your or third parties' legal rights to forbid circumvention of
     193technological measures.
     194
     195  4. Conveying Verbatim Copies.
     196
     197  You may convey verbatim copies of the Program's source code as you
     198receive it, in any medium, provided that you conspicuously and
     199appropriately publish on each copy an appropriate copyright notice;
     200keep intact all notices stating that this License and any
     201non-permissive terms added in accord with section 7 apply to the code;
     202keep intact all notices of the absence of any warranty; and give all
     203recipients a copy of this License along with the Program.
     204
     205  You may charge any price or no price for each copy that you convey,
     206and you may offer support or warranty protection for a fee.
     207
     208  5. Conveying Modified Source Versions.
     209
     210  You may convey a work based on the Program, or the modifications to
     211produce it from the Program, in the form of source code under the
     212terms of section 4, provided that you also meet all of these conditions:
     213
     214    a) The work must carry prominent notices stating that you modified
     215    it, and giving a relevant date.
     216
     217    b) The work must carry prominent notices stating that it is
     218    released under this License and any conditions added under section
     219    7.  This requirement modifies the requirement in section 4 to
     220    "keep intact all notices".
     221
     222    c) You must license the entire work, as a whole, under this
     223    License to anyone who comes into possession of a copy.  This
     224    License will therefore apply, along with any applicable section 7
     225    additional terms, to the whole of the work, and all its parts,
     226    regardless of how they are packaged.  This License gives no
     227    permission to license the work in any other way, but it does not
     228    invalidate such permission if you have separately received it.
     229
     230    d) If the work has interactive user interfaces, each must display
     231    Appropriate Legal Notices; however, if the Program has interactive
     232    interfaces that do not display Appropriate Legal Notices, your
     233    work need not make them do so.
     234
     235  A compilation of a covered work with other separate and independent
     236works, which are not by their nature extensions of the covered work,
     237and which are not combined with it such as to form a larger program,
     238in or on a volume of a storage or distribution medium, is called an
     239"aggregate" if the compilation and its resulting copyright are not
     240used to limit the access or legal rights of the compilation's users
     241beyond what the individual works permit.  Inclusion of a covered work
     242in an aggregate does not cause this License to apply to the other
     243parts of the aggregate.
     244
     245  6. Conveying Non-Source Forms.
     246
     247  You may convey a covered work in object code form under the terms
     248of sections 4 and 5, provided that you also convey the
     249machine-readable Corresponding Source under the terms of this License,
     250in one of these ways:
     251
     252    a) Convey the object code in, or embodied in, a physical product
     253    (including a physical distribution medium), accompanied by the
     254    Corresponding Source fixed on a durable physical medium
     255    customarily used for software interchange.
     256
     257    b) Convey the object code in, or embodied in, a physical product
     258    (including a physical distribution medium), accompanied by a
     259    written offer, valid for at least three years and valid for as
     260    long as you offer spare parts or customer support for that product
     261    model, to give anyone who possesses the object code either (1) a
     262    copy of the Corresponding Source for all the software in the
     263    product that is covered by this License, on a durable physical
     264    medium customarily used for software interchange, for a price no
     265    more than your reasonable cost of physically performing this
     266    conveying of source, or (2) access to copy the
     267    Corresponding Source from a network server at no charge.
     268
     269    c) Convey individual copies of the object code with a copy of the
     270    written offer to provide the Corresponding Source.  This
     271    alternative is allowed only occasionally and noncommercially, and
     272    only if you received the object code with such an offer, in accord
     273    with subsection 6b.
     274
     275    d) Convey the object code by offering access from a designated
     276    place (gratis or for a charge), and offer equivalent access to the
     277    Corresponding Source in the same way through the same place at no
     278    further charge.  You need not require recipients to copy the
     279    Corresponding Source along with the object code.  If the place to
     280    copy the object code is a network server, the Corresponding Source
     281    may be on a different server (operated by you or a third party)
     282    that supports equivalent copying facilities, provided you maintain
     283    clear directions next to the object code saying where to find the
     284    Corresponding Source.  Regardless of what server hosts the
     285    Corresponding Source, you remain obligated to ensure that it is
     286    available for as long as needed to satisfy these requirements.
     287
     288    e) Convey the object code using peer-to-peer transmission, provided
     289    you inform other peers where the object code and Corresponding
     290    Source of the work are being offered to the general public at no
     291    charge under subsection 6d.
     292
     293  A separable portion of the object code, whose source code is excluded
     294from the Corresponding Source as a System Library, need not be
     295included in conveying the object code work.
     296
     297  A "User Product" is either (1) a "consumer product", which means any
     298tangible personal property which is normally used for personal, family,
     299or household purposes, or (2) anything designed or sold for incorporation
     300into a dwelling.  In determining whether a product is a consumer product,
     301doubtful cases shall be resolved in favor of coverage.  For a particular
     302product received by a particular user, "normally used" refers to a
     303typical or common use of that class of product, regardless of the status
     304of the particular user or of the way in which the particular user
     305actually uses, or expects or is expected to use, the product.  A product
     306is a consumer product regardless of whether the product has substantial
     307commercial, industrial or non-consumer uses, unless such uses represent
     308the only significant mode of use of the product.
     309
     310  "Installation Information" for a User Product means any methods,
     311procedures, authorization keys, or other information required to install
     312and execute modified versions of a covered work in that User Product from
     313a modified version of its Corresponding Source.  The information must
     314suffice to ensure that the continued functioning of the modified object
     315code is in no case prevented or interfered with solely because
     316modification has been made.
     317
     318  If you convey an object code work under this section in, or with, or
     319specifically for use in, a User Product, and the conveying occurs as
     320part of a transaction in which the right of possession and use of the
     321User Product is transferred to the recipient in perpetuity or for a
     322fixed term (regardless of how the transaction is characterized), the
     323Corresponding Source conveyed under this section must be accompanied
     324by the Installation Information.  But this requirement does not apply
     325if neither you nor any third party retains the ability to install
     326modified object code on the User Product (for example, the work has
     327been installed in ROM).
     328
     329  The requirement to provide Installation Information does not include a
     330requirement to continue to provide support service, warranty, or updates
     331for a work that has been modified or installed by the recipient, or for
     332the User Product in which it has been modified or installed.  Access to a
     333network may be denied when the modification itself materially and
     334adversely affects the operation of the network or violates the rules and
     335protocols for communication across the network.
     336
     337  Corresponding Source conveyed, and Installation Information provided,
     338in accord with this section must be in a format that is publicly
     339documented (and with an implementation available to the public in
     340source code form), and must require no special password or key for
     341unpacking, reading or copying.
     342
     343  7. Additional Terms.
     344
     345  "Additional permissions" are terms that supplement the terms of this
     346License by making exceptions from one or more of its conditions.
     347Additional permissions that are applicable to the entire Program shall
     348be treated as though they were included in this License, to the extent
     349that they are valid under applicable law.  If additional permissions
     350apply only to part of the Program, that part may be used separately
     351under those permissions, but the entire Program remains governed by
     352this License without regard to the additional permissions.
     353
     354  When you convey a copy of a covered work, you may at your option
     355remove any additional permissions from that copy, or from any part of
     356it.  (Additional permissions may be written to require their own
     357removal in certain cases when you modify the work.)  You may place
     358additional permissions on material, added by you to a covered work,
     359for which you have or can give appropriate copyright permission.
     360
     361  Notwithstanding any other provision of this License, for material you
     362add to a covered work, you may (if authorized by the copyright holders of
     363that material) supplement the terms of this License with terms:
     364
     365    a) Disclaiming warranty or limiting liability differently from the
     366    terms of sections 15 and 16 of this License; or
     367
     368    b) Requiring preservation of specified reasonable legal notices or
     369    author attributions in that material or in the Appropriate Legal
     370    Notices displayed by works containing it; or
     371
     372    c) Prohibiting misrepresentation of the origin of that material, or
     373    requiring that modified versions of such material be marked in
     374    reasonable ways as different from the original version; or
     375
     376    d) Limiting the use for publicity purposes of names of licensors or
     377    authors of the material; or
     378
     379    e) Declining to grant rights under trademark law for use of some
     380    trade names, trademarks, or service marks; or
     381
     382    f) Requiring indemnification of licensors and authors of that
     383    material by anyone who conveys the material (or modified versions of
     384    it) with contractual assumptions of liability to the recipient, for
     385    any liability that these contractual assumptions directly impose on
     386    those licensors and authors.
     387
     388  All other non-permissive additional terms are considered "further
     389restrictions" within the meaning of section 10.  If the Program as you
     390received it, or any part of it, contains a notice stating that it is
     391governed by this License along with a term that is a further
     392restriction, you may remove that term.  If a license document contains
     393a further restriction but permits relicensing or conveying under this
     394License, you may add to a covered work material governed by the terms
     395of that license document, provided that the further restriction does
     396not survive such relicensing or conveying.
     397
     398  If you add terms to a covered work in accord with this section, you
     399must place, in the relevant source files, a statement of the
     400additional terms that apply to those files, or a notice indicating
     401where to find the applicable terms.
     402
     403  Additional terms, permissive or non-permissive, may be stated in the
     404form of a separately written license, or stated as exceptions;
     405the above requirements apply either way.
     406
     407  8. Termination.
     408
     409  You may not propagate or modify a covered work except as expressly
     410provided under this License.  Any attempt otherwise to propagate or
     411modify it is void, and will automatically terminate your rights under
     412this License (including any patent licenses granted under the third
     413paragraph of section 11).
     414
     415  However, if you cease all violation of this License, then your
     416license from a particular copyright holder is reinstated (a)
     417provisionally, unless and until the copyright holder explicitly and
     418finally terminates your license, and (b) permanently, if the copyright
     419holder fails to notify you of the violation by some reasonable means
     420prior to 60 days after the cessation.
     421
     422  Moreover, your license from a particular copyright holder is
     423reinstated permanently if the copyright holder notifies you of the
     424violation by some reasonable means, this is the first time you have
     425received notice of violation of this License (for any work) from that
     426copyright holder, and you cure the violation prior to 30 days after
     427your receipt of the notice.
     428
     429  Termination of your rights under this section does not terminate the
     430licenses of parties who have received copies or rights from you under
     431this License.  If your rights have been terminated and not permanently
     432reinstated, you do not qualify to receive new licenses for the same
     433material under section 10.
     434
     435  9. Acceptance Not Required for Having Copies.
     436
     437  You are not required to accept this License in order to receive or
     438run a copy of the Program.  Ancillary propagation of a covered work
     439occurring solely as a consequence of using peer-to-peer transmission
     440to receive a copy likewise does not require acceptance.  However,
     441nothing other than this License grants you permission to propagate or
     442modify any covered work.  These actions infringe copyright if you do
     443not accept this License.  Therefore, by modifying or propagating a
     444covered work, you indicate your acceptance of this License to do so.
     445
     446  10. Automatic Licensing of Downstream Recipients.
     447
     448  Each time you convey a covered work, the recipient automatically
     449receives a license from the original licensors, to run, modify and
     450propagate that work, subject to this License.  You are not responsible
     451for enforcing compliance by third parties with this License.
     452
     453  An "entity transaction" is a transaction transferring control of an
     454organization, or substantially all assets of one, or subdividing an
     455organization, or merging organizations.  If propagation of a covered
     456work results from an entity transaction, each party to that
     457transaction who receives a copy of the work also receives whatever
     458licenses to the work the party's predecessor in interest had or could
     459give under the previous paragraph, plus a right to possession of the
     460Corresponding Source of the work from the predecessor in interest, if
     461the predecessor has it or can get it with reasonable efforts.
     462
     463  You may not impose any further restrictions on the exercise of the
     464rights granted or affirmed under this License.  For example, you may
     465not impose a license fee, royalty, or other charge for exercise of
     466rights granted under this License, and you may not initiate litigation
     467(including a cross-claim or counterclaim in a lawsuit) alleging that
     468any patent claim is infringed by making, using, selling, offering for
     469sale, or importing the Program or any portion of it.
     470
     471  11. Patents.
     472
     473  A "contributor" is a copyright holder who authorizes use under this
     474License of the Program or a work on which the Program is based.  The
     475work thus licensed is called the contributor's "contributor version".
     476
     477  A contributor's "essential patent claims" are all patent claims
     478owned or controlled by the contributor, whether already acquired or
     479hereafter acquired, that would be infringed by some manner, permitted
     480by this License, of making, using, or selling its contributor version,
     481but do not include claims that would be infringed only as a
     482consequence of further modification of the contributor version.  For
     483purposes of this definition, "control" includes the right to grant
     484patent sublicenses in a manner consistent with the requirements of
    195485this License.
    196486
    197   7. If, as a consequence of a court judgment or allegation of patent
    198 infringement or for any other reason (not limited to patent issues),
    199 conditions are imposed on you (whether by court order, agreement or
     487  Each contributor grants you a non-exclusive, worldwide, royalty-free
     488patent license under the contributor's essential patent claims, to
     489make, use, sell, offer for sale, import and otherwise run, modify and
     490propagate the contents of its contributor version.
     491
     492  In the following three paragraphs, a "patent license" is any express
     493agreement or commitment, however denominated, not to enforce a patent
     494(such as an express permission to practice a patent or covenant not to
     495sue for patent infringement).  To "grant" such a patent license to a
     496party means to make such an agreement or commitment not to enforce a
     497patent against the party.
     498
     499  If you convey a covered work, knowingly relying on a patent license,
     500and the Corresponding Source of the work is not available for anyone
     501to copy, free of charge and under the terms of this License, through a
     502publicly available network server or other readily accessible means,
     503then you must either (1) cause the Corresponding Source to be so
     504available, or (2) arrange to deprive yourself of the benefit of the
     505patent license for this particular work, or (3) arrange, in a manner
     506consistent with the requirements of this License, to extend the patent
     507license to downstream recipients.  "Knowingly relying" means you have
     508actual knowledge that, but for the patent license, your conveying the
     509covered work in a country, or your recipient's use of the covered work
     510in a country, would infringe one or more identifiable patents in that
     511country that you have reason to believe are valid.
     512
     513  If, pursuant to or in connection with a single transaction or
     514arrangement, you convey, or propagate by procuring conveyance of, a
     515covered work, and grant a patent license to some of the parties
     516receiving the covered work authorizing them to use, propagate, modify
     517or convey a specific copy of the covered work, then the patent license
     518you grant is automatically extended to all recipients of the covered
     519work and works based on it.
     520
     521  A patent license is "discriminatory" if it does not include within
     522the scope of its coverage, prohibits the exercise of, or is
     523conditioned on the non-exercise of one or more of the rights that are
     524specifically granted under this License.  You may not convey a covered
     525work if you are a party to an arrangement with a third party that is
     526in the business of distributing software, under which you make payment
     527to the third party based on the extent of your activity of conveying
     528the work, and under which the third party grants, to any of the
     529parties who would receive the covered work from you, a discriminatory
     530patent license (a) in connection with copies of the covered work
     531conveyed by you (or copies made from those copies), or (b) primarily
     532for and in connection with specific products or compilations that
     533contain the covered work, unless you entered into that arrangement,
     534or that patent license was granted, prior to 28 March 2007.
     535
     536  Nothing in this License shall be construed as excluding or limiting
     537any implied license or other defenses to infringement that may
     538otherwise be available to you under applicable patent law.
     539
     540  12. No Surrender of Others' Freedom.
     541
     542  If conditions are imposed on you (whether by court order, agreement or
    200543otherwise) that contradict the conditions of this License, they do not
    201 excuse you from the conditions of this License.  If you cannot
    202 distribute to satisfy simultaneously your obligations under this
    203 License and any other pertinent obligations, then as a consequence you
    204 may not distribute the Program at all.  For example, if a patent
    205 license would not permit royalty-free redistribution of the Program by
    206 all those who receive copies directly or indirectly through you, then
    207 the only way you could satisfy both it and this License would be to
    208 refrain entirely from distribution of the Program.
    209 
    210 If any portion of this section is held invalid or unenforceable under
    211 any particular circumstance, the balance of the section is intended to
    212 apply and the section as a whole is intended to apply in other
    213 circumstances.
    214 
    215 It is not the purpose of this section to induce you to infringe any
    216 patents or other property right claims or to contest validity of any
    217 such claims; this section has the sole purpose of protecting the
    218 integrity of the free software distribution system, which is
    219 implemented by public license practices.  Many people have made
    220 generous contributions to the wide range of software distributed
    221 through that system in reliance on consistent application of that
    222 system; it is up to the author/donor to decide if he or she is willing
    223 to distribute software through any other system and a licensee cannot
    224 impose that choice.
    225 
    226 This section is intended to make thoroughly clear what is believed to
    227 be a consequence of the rest of this License.
    228 
    229   8. If the distribution and/or use of the Program is restricted in
    230 certain countries either by patents or by copyrighted interfaces, the
    231 original copyright holder who places the Program under this License
    232 may add an explicit geographical distribution limitation excluding
    233 those countries, so that distribution is permitted only in or among
    234 countries not thus excluded.  In such case, this License incorporates
    235 the limitation as if written in the body of this License.
    236 
    237   9. The Free Software Foundation may publish revised and/or new versions
    238 of the General Public License from time to time.  Such new versions will
     544excuse you from the conditions of this License.  If you cannot convey a
     545covered work so as to satisfy simultaneously your obligations under this
     546License and any other pertinent obligations, then as a consequence you may
     547not convey it at all.  For example, if you agree to terms that obligate you
     548to collect a royalty for further conveying from those to whom you convey
     549the Program, the only way you could satisfy both those terms and this
     550License would be to refrain entirely from conveying the Program.
     551
     552  13. Use with the GNU Affero General Public License.
     553
     554  Notwithstanding any other provision of this License, you have
     555permission to link or combine any covered work with a work licensed
     556under version 3 of the GNU Affero General Public License into a single
     557combined work, and to convey the resulting work.  The terms of this
     558License will continue to apply to the part which is the covered work,
     559but the special requirements of the GNU Affero General Public License,
     560section 13, concerning interaction through a network will apply to the
     561combination as such.
     562
     563  14. Revised Versions of this License.
     564
     565  The Free Software Foundation may publish revised and/or new versions of
     566the GNU General Public License from time to time.  Such new versions will
    239567be similar in spirit to the present version, but may differ in detail to
    240568address new problems or concerns.
    241569
    242 Each version is given a distinguishing version number.  If the Program
    243 specifies a version number of this License which applies to it and "any
    244 later version", you have the option of following the terms and conditions
    245 either of that version or of any later version published by the Free
    246 Software Foundation.  If the Program does not specify a version number of
    247 this License, you may choose any version ever published by the Free Software
    248 Foundation.
    249 
    250   10. If you wish to incorporate parts of the Program into other free
    251 programs whose distribution conditions are different, write to the author
    252 to ask for permission.  For software which is copyrighted by the Free
    253 Software Foundation, write to the Free Software Foundation; we sometimes
    254 make exceptions for this.  Our decision will be guided by the two goals
    255 of preserving the free status of all derivatives of our free software and
    256 of promoting the sharing and reuse of software generally.
    257 
    258                             NO WARRANTY
    259 
    260   11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
    261 FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
    262 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
    263 PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
    264 OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
    265 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
    266 TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
    267 PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
    268 REPAIR OR CORRECTION.
    269 
    270   12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
    271 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
    272 REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
    273 INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
    274 OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
    275 TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
    276 YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
    277 PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
    278 POSSIBILITY OF SUCH DAMAGES.
    279 
    280                      END OF TERMS AND CONDITIONS
     570  Each version is given a distinguishing version number.  If the
     571Program specifies that a certain numbered version of the GNU General
     572Public License "or any later version" applies to it, you have the
     573option of following the terms and conditions either of that numbered
     574version or of any later version published by the Free Software
     575Foundation.  If the Program does not specify a version number of the
     576GNU General Public License, you may choose any version ever published
     577by the Free Software Foundation.
     578
     579  If the Program specifies that a proxy can decide which future
     580versions of the GNU General Public License can be used, that proxy's
     581public statement of acceptance of a version permanently authorizes you
     582to choose that version for the Program.
     583
     584  Later license versions may give you additional or different
     585permissions.  However, no additional obligations are imposed on any
     586author or copyright holder as a result of your choosing to follow a
     587later version.
     588
     589  15. Disclaimer of Warranty.
     590
     591  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
     592APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
     593HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
     594OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
     595THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     596PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
     597IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
     598ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
     599
     600  16. Limitation of Liability.
     601
     602  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
     603WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
     604THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
     605GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
     606USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
     607DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
     608PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
     609EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
     610SUCH DAMAGES.
     611
     612  17. Interpretation of Sections 15 and 16.
     613
     614  If the disclaimer of warranty and limitation of liability provided
     615above cannot be given local legal effect according to their terms,
     616reviewing courts shall apply local law that most closely approximates
     617an absolute waiver of all civil liability in connection with the
     618Program, unless a warranty or assumption of liability accompanies a
     619copy of the Program in return for a fee.
     620
     621                     END OF TERMS AND CONDITIONS
     622
     623            How to Apply These Terms to Your New Programs
     624
     625  If you develop a new program, and you want it to be of the greatest
     626possible use to the public, the best way to achieve this is to make it
     627free software which everyone can redistribute and change under these terms.
     628
     629  To do so, attach the following notices to the program.  It is safest
     630to attach them to the start of each source file to most effectively
     631state the exclusion of warranty; and each file should have at least
     632the "copyright" line and a pointer to where the full notice is found.
     633
     634    <one line to give the program's name and a brief idea of what it does.>
     635    Copyright (C) <year>  <name of author>
     636
     637    This program is free software: you can redistribute it and/or modify
     638    it under the terms of the GNU General Public License as published by
     639    the Free Software Foundation, either version 3 of the License, or
     640    (at your option) any later version.
     641
     642    This program is distributed in the hope that it will be useful,
     643    but WITHOUT ANY WARRANTY; without even the implied warranty of
     644    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     645    GNU General Public License for more details.
     646
     647    You should have received a copy of the GNU General Public License
     648    along with this program.  If not, see <https://www.gnu.org/licenses/>.
     649
     650Also add information on how to contact you by electronic and paper mail.
     651
     652  If the program does terminal interaction, make it output a short
     653notice like this when it starts in an interactive mode:
     654
     655    <program>  Copyright (C) <year>  <name of author>
     656    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
     657    This is free software, and you are welcome to redistribute it
     658    under certain conditions; type `show c' for details.
     659
     660The hypothetical commands `show w' and `show c' should show the appropriate
     661parts of the General Public License.  Of course, your program's commands
     662might be different; for a GUI interface, you would use an "about box".
     663
     664  You should also get your employer (if you work as a programmer) or school,
     665if any, to sign a "copyright disclaimer" for the program, if necessary.
     666For more information on this, and how to apply and follow the GNU GPL, see
     667<https://www.gnu.org/licenses/>.
     668
     669  The GNU General Public License does not permit incorporating your program
     670into proprietary programs.  If your program is a subroutine library, you
     671may consider it more useful to permit linking proprietary applications with
     672the library.  If this is what you want to do, use the GNU Lesser General
     673Public License instead of this License.  But first, please read
     674<https://www.gnu.org/licenses/why-not-lgpl.html>.
  • bonus-for-woo/trunk/pages/coupons.php

    r3543185 r3795776  
    88 */
    99
    10 /* Обработка форм купонов (логика сохранена без изменений) */
     10/*
     11 * Обработка форм купонов.
     12 *
     13 * Купон — это деньги на балансе клиента, поэтому действия с ним требуют того
     14 * же права, что и пункт меню: раньше проверялся только nonce, и любой, кто
     15 * мог открыть страницу (а меню отдаётся manage_woocommerce), создавал и
     16 * удалял купоны на произвольные суммы.
     17 */
     18if (!current_user_can('manage_woocommerce') && !current_user_can('manage_options')) {
     19    return;
     20}
     21
    1122if (isset($_POST['bfw_computy_add_coupon_ajax'])) {
    1223    if ($_POST['bfw_computy_add_coupon_ajax'] === 'bfw_computy_add_coupon_ajax') {
    13         if (!isset($_POST['bfw_nonce_coupon']) || !wp_verify_nonce($_POST['bfw_nonce_coupon'], 'bfw_action_coupon')) {
    14             wp_die('Ошибка безопасности!');
    15         }
     24        check_admin_referer('bfw_action_coupon', 'bfw_nonce_coupon');
     25
    1626        global $wpdb;
    17         $code = sanitize_text_field($_POST['code']);
    18         $sum = sanitize_text_field($_POST['sum']);
    19         $comment_admin = sanitize_text_field($_POST['comment_admin']);
    20         $status = sanitize_text_field($_POST['status']);
    21         $reusable = $_POST['reusable'] ?? 0;
    22 
    23         if ($wpdb->get_results($wpdb->prepare("SELECT id,code,sum,created,status,date_use,user,reusable,comment_admin FROM " . $wpdb->prefix . "bfw_coupons_computy WHERE `code` = %s", $code))) {
    24             echo '<div class="bfw-notice bfw-notice-warning"><span class="dashicons dashicons-warning"></span> ' . sprintf(__('Coupon <b>%s</b> is already being used.', 'bonus-for-woo'), $code) . '</div>';
     27        $code = sanitize_text_field(wp_unslash($_POST['code'] ?? ''));
     28        $sum = (float) sanitize_text_field(wp_unslash($_POST['sum'] ?? 0));
     29        $comment_admin = sanitize_text_field(wp_unslash($_POST['comment_admin'] ?? ''));
     30        $status = sanitize_text_field(wp_unslash($_POST['status'] ?? 'noactive'));
     31        $reusable = (int) ($_POST['reusable'] ?? 0);
     32
     33        $exists = $wpdb->get_var($wpdb->prepare(
     34                "SELECT id FROM %i WHERE `code` = %s",
     35                $wpdb->prefix . 'bfw_coupons_computy',
     36                $code
     37        ));
     38
     39        if ($exists) {
     40            echo '<div class="bfw-notice bfw-notice-warning"><span class="dashicons dashicons-warning"></span> '
     41                . sprintf(
     42                    /* translators: %s: coupon code */
     43                    wp_kses(__('Coupon <b>%s</b> is already being used.', 'bonus-for-woo'), ['b' => []]),
     44                    esc_html($code)
     45                ) . '</div>';
    2546        } else {
    2647            BfwCoupons::addCoupon($code, $sum, $comment_admin, $status, $reusable);
    27             echo '<div class="bfw-notice bfw-notice-success"><span class="dashicons dashicons-yes-alt"></span> ' . __('Coupon', 'bonus-for-woo') . ' <b>' . $code . '</b> ' . __('added', 'bonus-for-woo') . '.</div>';
     48            echo '<div class="bfw-notice bfw-notice-success"><span class="dashicons dashicons-yes-alt"></span> '
     49                . esc_html__('Coupon', 'bonus-for-woo') . ' <b>' . esc_html($code) . '</b> '
     50                . esc_html__('added', 'bonus-for-woo') . '.</div>';
    2851        }
    2952    }
    … …  
    3154
    3255if (isset($_POST['bfw_delete_coupon'])) {
    33     if (!isset($_POST['bfw_nonce_coupon']) || !wp_verify_nonce($_POST['bfw_nonce_coupon'], 'bfw_action_coupon')) {
    34         wp_die('Ошибка безопасности!');
    35     }
    36     BfwCoupons::deleteCoupon($_POST['bfw_delete_coupon']);
    37     echo '<div class="bfw-notice bfw-notice-warning"><span class="dashicons dashicons-trash"></span> ' . __('deleted', 'bonus-for-woo') . '.</div>';
     56    check_admin_referer('bfw_action_coupon', 'bfw_nonce_coupon');
     57
     58    BfwCoupons::deleteCoupon((int) $_POST['bfw_delete_coupon']);
     59    echo '<div class="bfw-notice bfw-notice-warning"><span class="dashicons dashicons-trash"></span> '
     60        . esc_html__('deleted', 'bonus-for-woo') . '.</div>';
    3861}
    3962
    4063if (isset($_POST['bfw_edit_status_coupon'])) {
    41     if (!isset($_POST['bfw_nonce_coupon']) || !wp_verify_nonce($_POST['bfw_nonce_coupon'], 'bfw_action_coupon')) {
    42         wp_die('Ошибка безопасности!');
    43     }
    44     $status = (sanitize_text_field($_POST['status_coupon']) == 'active') ? 'noactive' : 'active';
    45     BfwCoupons::editStatusCoupon($_POST['bfw_edit_status_coupon'], $status);
    46     echo '<div class="bfw-notice bfw-notice-warning"><span class="dashicons dashicons-update"></span> ' . __('Coupon status changed', 'bonus-for-woo') . '.</div>';
     64    check_admin_referer('bfw_action_coupon', 'bfw_nonce_coupon');
     65
     66    $status = (sanitize_text_field(wp_unslash($_POST['status_coupon'] ?? '')) === 'active') ? 'noactive' : 'active';
     67    BfwCoupons::editStatusCoupon((int) $_POST['bfw_edit_status_coupon'], $status);
     68    echo '<div class="bfw-notice bfw-notice-warning"><span class="dashicons dashicons-update"></span> '
     69        . esc_html__('Coupon status changed', 'bonus-for-woo') . '.</div>';
    4770}
    4871?>
    … …  
    185208            <?php echo sprintf(__('Coupons for %s ', 'bonus-for-woo'), BfwPoints::pointsLabel(5)); ?>
    186209        </h1>
    187         <p><?php _e('Create, import and manage loyalty bonus coupons for your customers.', 'bonus-for-woo'); ?></p>
     210        <p><?php esc_html_e('Create, import and manage loyalty bonus coupons for your customers.', 'bonus-for-woo'); ?></p>
    188211    </header>
    189212
    … …  
    191214        <!-- Card: Add Coupon -->
    192215        <div class="bfw-action-card">
    193             <h3><span class="dashicons dashicons-plus-alt"></span> <?php _e('Add a new coupon', 'bonus-for-woo'); ?></h3>
     216            <h3><span class="dashicons dashicons-plus-alt"></span> <?php esc_html_e('Add a new coupon', 'bonus-for-woo'); ?></h3>
    194217            <form method="post" id="add_coupon_form">
    195218                <?php wp_nonce_field('bfw_action_coupon', 'bfw_nonce_coupon'); ?>
    … …  
    197220               
    198221                <div class="bfw-form-group">
    199                     <label><?php _e('Сode coupon', 'bonus-for-woo'); ?></label>
     222                    <label><?php esc_html_e('Сode coupon', 'bonus-for-woo'); ?></label>
    200223                    <input type="text" name="code" class="bfw-input" placeholder="SUMMER2024" required>
    201224                </div>
    … …  
    203226                <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
    204227                    <div class="bfw-form-group">
    205                         <label><?php _e('Number of points', 'bonus-for-woo'); ?></label>
     228                        <label><?php esc_html_e('Number of points', 'bonus-for-woo'); ?></label>
    206229                        <input type="number" name="sum" class="bfw-input" min="1" placeholder="500" required>
    207230                    </div>
    208231                    <div class="bfw-form-group">
    209                         <label><?php _e('Status', 'bonus-for-woo'); ?></label>
     232                        <label><?php esc_html_e('Status', 'bonus-for-woo'); ?></label>
    210233                        <select name="status" class="bfw-input">
    211                             <option value="active"><?php _e('Active', 'bonus-for-woo'); ?></option>
    212                             <option value="noactive"><?php _e('Not active', 'bonus-for-woo'); ?></option>
     234                            <option value="active"><?php esc_html_e('Active', 'bonus-for-woo'); ?></option>
     235                            <option value="noactive"><?php esc_html_e('Not active', 'bonus-for-woo'); ?></option>
    213236                        </select>
    214237                    </div>
    … …  
    216239
    217240                <div class="bfw-form-group">
    218                     <label><?php _e('Comment admin', 'bonus-for-woo'); ?></label>
     241                    <label><?php esc_html_e('Comment admin', 'bonus-for-woo'); ?></label>
    219242                    <textarea name="comment_admin" class="bfw-input" style="height: 60px;"></textarea>
    220243                </div>
    … …  
    222245                <div class="bfw-form-group">
    223246                    <label style="display:flex; align-items:center; gap:8px;">
    224                         <input type="checkbox" name="reusable" value="1"> <?php _e('Reusable', 'bonus-for-woo'); ?>
     247                        <input type="checkbox" name="reusable" value="1"> <?php esc_html_e('Reusable', 'bonus-for-woo'); ?>
    225248                    </label>
    226249                </div>
    227250
    228251                <button type="submit" class="bfw-btn bfw-btn-primary" style="width: 100%;">
    229                     <span class="dashicons dashicons-saved"></span> <?php _e('Add coupon', 'bonus-for-woo'); ?>
     252                    <span class="dashicons dashicons-saved"></span> <?php esc_html_e('Add coupon', 'bonus-for-woo'); ?>
    230253                </button>
    231254            </form>
    … …  
    234257        <!-- Card: Import -->
    235258        <div class="bfw-action-card">
    236             <h3><span class="dashicons dashicons-upload"></span> <?php _e('Import coupons', 'bonus-for-woo'); ?></h3>
     259            <h3><span class="dashicons dashicons-upload"></span> <?php esc_html_e('Import coupons', 'bonus-for-woo'); ?></h3>
    237260            <p style="font-size: 13px; color: var(--bfw-text-muted); margin-bottom: 15px;">
    238                 <?php _e('To create multiple coupons, create a csv file and import it using the form below.', 'bonus-for-woo'); ?>
     261                <?php esc_html_e('To create multiple coupons, create a csv file and import it using the form below.', 'bonus-for-woo'); ?>
    239262            </p>
    240263           
    … …  
    246269                    </div>
    247270                    <button type="button" class="bfw-btn bfw-btn-primary" onclick="upload();">
    248                         <span class="dashicons dashicons-database-import"></span> <?php _e('Import', 'bonus-for-woo'); ?>
     271                        <span class="dashicons dashicons-database-import"></span> <?php esc_html_e('Import', 'bonus-for-woo'); ?>
    249272                    </button>
    250273                </form>
    … …  
    254277            <div style="margin-top: 20px;">
    255278                <a href="<?php echo BONUS_COMPUTY_PLUGIN_URL; ?>templates/coupons_bfw.csv" class="bfw-btn bfw-btn-outline" style="border: 1px solid #ddd; justify-content: center;">
    256                     <span class="dashicons dashicons-download"></span> <?php _e('Example of CSV file', 'bonus-for-woo'); ?>
     279                    <span class="dashicons dashicons-download"></span> <?php esc_html_e('Example of CSV file', 'bonus-for-woo'); ?>
    257280                </a>
    258281            </div>
    … …  
    261284        <!-- Card: Info -->
    262285        <div class="bfw-action-card" style="background: #f8fafc;">
    263             <h3><span class="dashicons dashicons-info"></span> <?php _e('Description', 'bonus-for-woo'); ?></h3>
     286            <h3><span class="dashicons dashicons-info"></span> <?php esc_html_e('Description', 'bonus-for-woo'); ?></h3>
    264287            <ul style="font-size: 13px; line-height: 1.6; color: #475569; padding-left: 20px; margin: 0;">
    265                 <li><?php _e('Create a coupon in the form and it will appear in the list of coupons.', 'bonus-for-woo'); ?></li>
    266                 <li><?php _e('The customer can only use active coupons.', 'bonus-for-woo'); ?></li>
    267                 <li><?php _e('Coupon can only be used once.', 'bonus-for-woo'); ?></li>
    268                 <li><?php _e('Once a coupon has been used, it cannot be activated. Only delete.', 'bonus-for-woo'); ?></li>
    269                 <li><?php _e('If the "Reusable" box is checked, then all users can use it.', 'bonus-for-woo'); ?></li>
     288                <li><?php esc_html_e('Create a coupon in the form and it will appear in the list of coupons.', 'bonus-for-woo'); ?></li>
     289                <li><?php esc_html_e('The customer can only use active coupons.', 'bonus-for-woo'); ?></li>
     290                <li><?php esc_html_e('Coupon can only be used once.', 'bonus-for-woo'); ?></li>
     291                <li><?php esc_html_e('Once a coupon has been used, it cannot be activated. Only delete.', 'bonus-for-woo'); ?></li>
     292                <li><?php esc_html_e('If the "Reusable" box is checked, then all users can use it.', 'bonus-for-woo'); ?></li>
    270293            </ul>
    271294
    … …  
    275298
    276299    <div class="bfw-table-container">
    277         <h2 style="margin: 0 0 20px 0;"><?php _e('Coupons list', 'bonus-for-woo'); ?></h2>
     300        <h2 style="margin: 0 0 20px 0;"><?php esc_html_e('Coupons list', 'bonus-for-woo'); ?></h2>
    278301        <?php
    279302        $coupons_table = new Bfw_Coupons_List_Table();
    … …  
    294317        let fileExtension = ['csv'];
    295318        if (jQuery.inArray(jQuery('#bfw-file-export').val().split('.').pop().toLowerCase(), fileExtension) === -1) {
    296             jQuery('#bfw-file-export-result').html('<span style="color:red"><?php _e('Only CSV format allowed!', 'bonus-for-woo'); ?></span>');
     319            jQuery('#bfw-file-export-result').html('<span style="color:red"><?php esc_html_e('Only CSV format allowed!', 'bonus-for-woo'); ?></span>');
    297320        } else {
    298321            jQuery('.bfw_export_bonuses').css('opacity', '0.5');
    299322            let formData = new FormData();
    300             formData.append("action", "upload-attachment");
    301323            let fileInputElement = document.getElementById("bfw-file-export");
    302324
    303             formData.append("async-upload", fileInputElement.files[0]);
    304             formData.append("name", fileInputElement.files[0].name);
    305             formData.append("_wpnonce", "<?php echo wp_create_nonce('media-form'); ?>");
    306            
    307             let xhr = new XMLHttpRequest();
    308             xhr.onreadystatechange = function () {
    309                 if (xhr.readyState === 4 && xhr.status === 200) {
    310                     jQuery.ajax({
    311                         type: 'POST',
    312                         url: ajaxurl,
    313                         data: {
    314                             action: 'bfw_export_coupons',
    315                             nonce: '<?php echo wp_create_nonce('bfw_export_coupons'); ?>',
    316                             response: xhr.responseText,
    317                         },
    318                         success: function (data) {
    319                             if (data === 'good') {
    320                                 jQuery('#bfw-file-export-result').html('<span style="color:green;font-weight:bold;"><?php _e('Import successful! Reloading...', 'bonus-for-woo'); ?></span>');
    321                                 setTimeout(function () { location.reload(); }, 1500);
    322                             } else {
    323                                 jQuery('#bfw-file-export-result').html('<span style="color:red"><?php _e('Import failed.', 'bonus-for-woo'); ?></span>');
    324                                 jQuery('.bfw_export_bonuses').css('opacity', '1');
    325                             }
    326                         }
    327                     });
    328                 }
    329             }
    330             xhr.open("POST", "/wp-admin/async-upload.php", true);
    331             xhr.send(formData);
     325            /*
     326             * Файл уходит прямо в наш обработчик, без загрузки в медиатеку:
     327             * раньше CSV оставался на сайте по публичному URL.
     328             */
     329            formData.append("action", "bfw_export_coupons");
     330            formData.append("nonce", "<?php echo esc_js(wp_create_nonce('bfw_export_coupons')); ?>");
     331            formData.append("bfw_import_file", fileInputElement.files[0]);
     332
     333            fetch(ajaxurl, { method: "POST", body: formData, credentials: 'same-origin' })
     334                .then(r => r.json())
     335                .then(res => {
     336                    if (res.success) {
     337                        jQuery('#bfw-file-export-result').html('<span style="color:green;font-weight:bold;"><?php esc_html_e('Import successful! Reloading...', 'bonus-for-woo'); ?></span>');
     338                        setTimeout(function () { location.reload(); }, 1500);
     339                    } else {
     340                        jQuery('#bfw-file-export-result').html('<span style="color:red"><?php esc_html_e('Import failed.', 'bonus-for-woo'); ?></span>');
     341                        jQuery('.bfw_export_bonuses').css('opacity', '1');
     342                    }
     343                })
     344                .catch(() => {
     345                    jQuery('#bfw-file-export-result').html('<span style="color:red"><?php esc_html_e('Import failed.', 'bonus-for-woo'); ?></span>');
     346                    jQuery('.bfw_export_bonuses').css('opacity', '1');
     347                });
    332348        }
    333349    }
  • bonus-for-woo/trunk/pages/generator.php

    r3784081 r3795776  
    120120<div class="bfw-generator-dashboard">
    121121    <a class="bfw-back-link" href="?page=bonus-for-woo/pages/tools.php">
    122         <span class="dashicons dashicons-arrow-left-alt"></span> <?php _e('Back to Tools', 'bonus-for-woo'); ?>
     122        <span class="dashicons dashicons-arrow-left-alt"></span> <?php esc_html_e('Back to Tools', 'bonus-for-woo'); ?>
    123123    </a>
    124124
    125125    <div class="bfw-gen-header">
    126126        <div class="bfw-gen-title">
    127             <h1><?php _e('Rules and Conditions Generator', 'bonus-for-woo'); ?></h1>
    128             <p><?php _e('Automatically generated text based on your current plugin settings.', 'bonus-for-woo'); ?></p>
     127            <h1><?php esc_html_e('Rules and Conditions Generator', 'bonus-for-woo'); ?></h1>
     128            <p><?php esc_html_e('Automatically generated text based on your current plugin settings.', 'bonus-for-woo'); ?></p>
    129129        </div>
    130130        <div class="bfw-action-bar">
    131131            <button class="bfw-btn-copy" id="bfw-copy-doc">
    132                 <span class="dashicons dashicons-admin-page"></span> <?php _e('Copy Text to Clipboard', 'bonus-for-woo'); ?>
    133                 <div class="copy-success-tip" id="copy-tip"><?php _e('Copied!', 'bonus-for-woo'); ?></div>
     132                <span class="dashicons dashicons-admin-page"></span> <?php esc_html_e('Copy Text to Clipboard', 'bonus-for-woo'); ?>
     133                <div class="copy-success-tip" id="copy-tip"><?php esc_html_e('Copied!', 'bonus-for-woo'); ?></div>
    134134            </button>
    135135        </div>
    … …  
    145145            ?>
    146146
    147             <h1>Программа лояльности "<?php echo !empty($val['bonus-points-on-cart']) ? $val['bonus-points-on-cart'] : 'бонусной системы'; ?>"</h1>
     147            <h1><?php echo esc_html(sprintf(__('Loyalty program "%s"', 'bonus-for-woo'), !empty($val['bonus-points-on-cart']) ? $val['bonus-points-on-cart'] : __('bonus system', 'bonus-for-woo'))); ?></h1>
    148148           
    149149            <p>Наша система бонусов создана с целью предоставить покупателям выгодные условия, которые помогут им экономить свои деньги.</p>
    150150
    151             <p>Программа лояльности включает в себя возврат части стоимости покупок в виде бонусных <?php echo $label_points; ?>, где каждый <?php echo $label_point; ?> эквивалентен одному рублю. Это означает, что за каждую покупку покупатели получают бонусы, которые могут использовать для будущих расходов, тем самым уменьшая общую стоимость их следующих покупок.</p>
     151            <p>Программа лояльности включает в себя возврат части стоимости покупок в виде бонусных <?php echo esc_html($label_points); ?>, где каждый <?php echo esc_html($label_point); ?> эквивалентен одному рублю. Это означает, что за каждую покупку покупатели получают бонусы, которые могут использовать для будущих расходов, тем самым уменьшая общую стоимость их следующих покупок.</p>
    152152           
    153153            <p>Размер возврата определяется суммой заказа и статусом клиента. Статус клиента зависит от общей суммы его заказов. В зависимости от общей суммы заказов клиенту присваивается соответствующий статус:</p>
    … …  
    164164            </ul>
    165165
    166             <h2>Отображение <?php echo $label_points; ?> и кешбэка</h2>
    167             <p>Узнать сколько <?php echo $label_points; ?> на счету можно в личном кабинете во вкладке "<?php echo !empty($val['title-on-account']) ? $val['title-on-account'] : 'Страница бонусов'; ?>" </p>
     166            <h2>Отображение <?php echo esc_html($label_points); ?> и кешбэка</h2>
     167            <p>Узнать сколько <?php echo esc_html($label_points); ?> на счету можно в личном кабинете во вкладке "<?php echo esc_html(!empty($val['title-on-account']) ? $val['title-on-account'] : __('Bonuses page', 'bonus-for-woo')); ?>" </p>
    168168           
    169169            <?php if (empty($val['hystory-hide'])) : ?>
    170                 <p>Также в этой вкладке можно посмотреть историю списаний и начислений <?php echo $label_points; ?>.</p>
     170                <p>Также в этой вкладке можно посмотреть историю списаний и начислений <?php echo esc_html($label_points); ?>.</p>
    171171            <?php endif; ?>
    172172
    173173            <?php if (!empty($val['bonus-in-price'])) : ?>
    174                 <p>На странице товара указано, сколько вам вернется <?php echo $label_points; ?> за покупку данного товара.</p>
     174                <p>На странице товара указано, сколько вам вернется <?php echo esc_html($label_points); ?> за покупку данного товара.</p>
    175175            <?php endif; ?>
    176176
    177177            <?php if (!empty($val['cashback-in-cart'])) : ?>
    178                 <p>При оформлении заказа и в корзине будет показано, сколько бонусных <?php echo $label_points; ?> вы получите за ваш заказ.</p>
     178                <p>При оформлении заказа и в корзине будет показано, сколько бонусных <?php echo esc_html($label_points); ?> вы получите за ваш заказ.</p>
    179179            <?php endif; ?>
    180180
    181181
    182             <h2>Начисление <?php echo $label_points; ?></h2>
    183             <p>Кроме начисления <?php echo $label_points; ?> за покупки товаров, предусмотрены другие вознаграждения:</p>
     182            <h2>Начисление <?php echo esc_html($label_points); ?></h2>
     183            <p>Кроме начисления <?php echo esc_html($label_points); ?> за покупки товаров, предусмотрены другие вознаграждения:</p>
    184184            <ul>
    185185                <?php if (!empty($val['bonus-for-otziv'])) : ?>
    186                     <li><?php echo $val['bonus-for-otziv'] . ' ' . BfwPoints::pointsLabel((int)$val['bonus-for-otziv']); ?> за отзыв о купленном товаре.</li>
     186                    <li><?php echo esc_html(absint($val['bonus-for-otziv'])) . ' ' . esc_html(BfwPoints::pointsLabel((int) $val['bonus-for-otziv'])); ?> за отзыв о купленном товаре.</li>
    187187                <?php endif; ?>
    188188
    … …  
    190190                    <?php if (!empty($val['points-for-registration'])) : ?>
    191191                        <?php $reg_suffix = !empty($val['register-points-only-referal']) ? ' (только при регистрации по реферальной ссылке)' : ''; ?>
    192                         <li><?php echo $val['points-for-registration'] . ' ' . BfwPoints::pointsLabel((int)$val['points-for-registration']); ?> начислят за регистрацию в нашем магазине<?php echo $reg_suffix; ?>.</li>
     192                        <li><?php echo esc_html(absint($val['points-for-registration'])) . ' ' . esc_html(BfwPoints::pointsLabel((int) $val['points-for-registration'])); ?> начислят за регистрацию в нашем магазине<?php echo esc_html($reg_suffix); ?>.</li>
    193193                    <?php endif; ?>
    194194
    … …  
    202202                        }
    203203                        ?>
    204                         <li><?php echo $val['birthday'] . ' ' . BfwPoints::pointsLabel((int)$val['birthday']); ?> начислят <?php echo $bday_when; ?> (его можно указать в настройках профиля).</li>
     204                        <li><?php echo esc_html(absint($val['birthday'])) . ' ' . esc_html(BfwPoints::pointsLabel((int) $val['birthday'])); ?> начислят <?php echo $bday_when; ?> (его можно указать в настройках профиля).</li>
    205205                    <?php endif; ?>
    206206
    207207                    <?php if (!empty($val['every_days'])) : ?>
    208                         <li><?php echo $val['every_days'] . ' ' . BfwPoints::pointsLabel((int)$val['every_days']); ?> начислят за ежедневный вход в личный кабинет.</li>
     208                        <li><?php echo esc_html(absint($val['every_days'])) . ' ' . esc_html(BfwPoints::pointsLabel((int) $val['every_days'])); ?> начислят за ежедневный вход в личный кабинет.</li>
    209209                    <?php endif; ?>
    210210
    … …  
    215215            </ul>
    216216
    217             <h2>Статус заказа для начисления <?php echo $label_points; ?></h2>
     217            <h2>Статус заказа для начисления <?php echo esc_html($label_points); ?></h2>
    218218            <?php
    219219            $order_statuses = wc_get_order_statuses();
    … …  
    223223            ?>
    224224
    225             <h2>Ограничения начисления <?php echo $label_points; ?></h2>
     225            <h2>Ограничения начисления <?php echo esc_html($label_points); ?></h2>
    226226            <?php
    227227                if (!empty($val['cashback-for-shipping'])) echo '<p>Кешбэк за доставку не будет начислен.</p>';
    … …  
    259259            <p>В корзине<?php echo !empty($val['spisanie-in-checkout']) ? ' и при оформлении заказа ' : ' '; ?>вы можете использовать баллы для оплаты товаров.</p>
    260260
    261             <h2>Ограничения использования <?php echo $label_points; ?></h2>
     261            <h2>Ограничения использования <?php echo esc_html($label_points); ?></h2>
    262262            <?php
    263263                if (!empty($val['spisanie-onsale'])) echo '<p>Вы не можете использовать баллы на покупку товаров со скидкой.</p>';
    … …  
    267267                    $max_percent = $val['max-percent-bonuses'] ?? 100;
    268268                    if ($max_percent < 100) {
    269                         echo '<p>Вы не можете потратить более ' . $max_percent . '% ' . $label_points . ' от суммы заказа.</p>';
     269                        echo '<p>' . esc_html(sprintf(__('You cannot spend more than %1$s%% %2$s of the order total.', 'bonus-for-woo'), $max_percent, $label_points)) . '</p>';
    270270                    }
    271271
    … …  
    284284                    if (!empty($val['minimal-amount'])) {
    285285                        $extra = !empty($val['minimal-amount-cashback']) ? ' и получения кешбэка' : '';
    286                         echo '<p>Для списания ' . $label_points . $extra . ' сумма в заказе должна быть не менее ' . $val['minimal-amount'] . $currency . '.</p>';
     286                        echo '<p>' . esc_html(sprintf(__('To redeem %1$s%2$s the order total must be at least %3$s%4$s.', 'bonus-for-woo'), $label_points, $extra, $val['minimal-amount'], $currency)) . '</p>';
    287287                    }
    288288
    289289                    if (!empty($val['day-inactive'])) {
    290                         echo '<h2>Сгорание ' . $label_points . '</h2>';
     290                        echo '<h2>' . esc_html(sprintf(__('Expiration of %s', 'bonus-for-woo'), $label_points)) . '</h2>';
    291291                        $notice_days = !empty($val['day-inactive-notice']) ? (int)$val['day-inactive-notice'] : 0;
    292292                        if ($notice_days > 0) {
    … …  
    304304                        $min_sum_ref = !empty($val['sum-orders-for-referral']) ? (float)$val['sum-orders-for-referral'] : 0;
    305305                        if ($min_sum_ref > 0) {
    306                             echo '<p>Кешбэк за приглашённого друга начисляется только при сумме его заказа от ' . $min_sum_ref . $currency . '.</p>';
     306                            echo '<p>' . esc_html(sprintf(__('Cashback for an invited friend is credited only if their order total is at least %1$s%2$s.', 'bonus-for-woo'), $min_sum_ref, $currency)) . '</p>';
    307307                        }
    308308
  • bonus-for-woo/trunk/pages/list_history.php

    r3509064 r3795776  
    161161       
    162162        <div class="bfw-filter-group">
    163             <label><?php _e('From', 'bonus-for-woo'); ?></label>
     163            <label><?php esc_html_e('From', 'bonus-for-woo'); ?></label>
    164164            <input type="date" id="date_start" name="date_start" class="bfw-input-date"
    165165                   value="<?php echo esc_html($date_start); ?>"
    … …  
    169169
    170170        <div class="bfw-filter-group">
    171             <label><?php _e('to', 'bonus-for-woo'); ?></label>
     171            <label><?php esc_html_e('to', 'bonus-for-woo'); ?></label>
    172172            <input type="date" id="date_finish" name="date_finish" class="bfw-input-date"
    173173                   value="<?php echo esc_html($date_finish); ?>"
    … …  
    179179            <button type="submit" class="bfw-btn bfw-btn-primary">
    180180                <span class="dashicons dashicons-search"></span>
    181                 <?php _e('Search', 'bonus-for-woo'); ?>
     181                <?php esc_html_e('Search', 'bonus-for-woo'); ?>
    182182            </button>
    183183            <a href="/wp-admin/admin.php?page=bonus-for-woo%2Fpages%2Flist_history.php" class="bfw-btn bfw-btn-outline">
    184                 <?php _e('Reset', 'bonus-for-woo'); ?>
     184                <?php esc_html_e('Reset', 'bonus-for-woo'); ?>
    185185            </a>
    186186        </div>
  • bonus-for-woo/trunk/pages/logs.php

    r3784081 r3795776  
    140140        <h1>
    141141            <span class="dashicons dashicons-media-text" style="font-size: 32px; width:32px; height:32px;"></span>
    142             <?php echo __('Logs', 'bonus-for-woo'); ?>
     142            <?php echo esc_html__('Logs', 'bonus-for-woo'); ?>
    143143        </h1>
    144         <p><?php _e('Review system activities, errors, and automated processes tracking.', 'bonus-for-woo'); ?></p>
     144        <p><?php esc_html_e('Review system activities, errors, and automated processes tracking.', 'bonus-for-woo'); ?></p>
    145145    </header>
    146146
    … …  
    150150           
    151151            <div class="bfw-filter-group">
    152                 <label><?php echo __('From', 'bonus-for-woo'); ?></label>
     152                <label><?php echo esc_html__('From', 'bonus-for-woo'); ?></label>
    153153                <input type="date" id="date_start" name="date_start" class="bfw-date-input"
    154154                       value="<?php echo esc_attr($date_start); ?>"
    … …  
    158158
    159159            <div class="bfw-filter-group">
    160                 <label><?php echo __('to', 'bonus-for-woo'); ?></label>
     160                <label><?php echo esc_html__('to', 'bonus-for-woo'); ?></label>
    161161                <input type="date" id="date_finish" name="date_finish" class="bfw-date-input"
    162162                       value="<?php echo esc_attr($date_finish); ?>"
    … …  
    166166
    167167            <button type="submit" class="bfw-btn bfw-btn-primary">
    168                 <span class="dashicons dashicons-search"></span> <?php echo __('Search', 'bonus-for-woo'); ?>
     168                <span class="dashicons dashicons-search"></span> <?php echo esc_html__('Search', 'bonus-for-woo'); ?>
    169169            </button>
    170170           
    171171            <a class="bfw-btn bfw-btn-outline" href="admin.php?page=bonus-for-woo/pages/logs.php">
    172                 <span class="dashicons dashicons-no-alt"></span> <?php echo __('Clear', 'bonus-for-woo'); ?>
     172                <span class="dashicons dashicons-no-alt"></span> <?php echo esc_html__('Clear', 'bonus-for-woo'); ?>
    173173            </a>
    174174        </form>
    … …  
    177177            <div class="bfw-log-notice">
    178178                <span class="dashicons dashicons-info"></span>
    179                 <?php echo __('The last 500 entries are displayed.', 'bonus-for-woo'); ?>
     179                <?php echo esc_html__('The last 500 entries are displayed.', 'bonus-for-woo'); ?>
    180180            </div>
    181181        <?php endif; ?>
  • bonus-for-woo/trunk/pages/statistic.php

    r3784081 r3795776  
    154154
    155155<div class="wrap bfw-stat-wrap">
    156     <h1 style="font-weight: 700; margin-bottom: 25px;"><?php _e('Advanced Business Analytics', 'bonus-for-woo'); ?></h1>
     156    <h1 style="font-weight: 700; margin-bottom: 25px;"><?php esc_html_e('Advanced Business Analytics', 'bonus-for-woo'); ?></h1>
    157157
    158158    <?php
    … …  
    163163            <div class="bfw-badge-time">
    164164                <span class="dashicons dashicons-calendar-alt"></span>
    165                 <?php _e('Range:', 'bonus-for-woo'); ?>
    166                 <strong><?php echo !empty($stats['date_start']) ? esc_html($stats['date_start']) :  _e('All time', 'bonus-for-woo'); ?> - <?php echo !empty($stats['date_end']) ? esc_html($stats['date_end']) : 'All time'; ?></strong>
    167                 <span style="font-size: 11px; margin-left:10px;">(<?php _e('Updated', 'bonus-for-woo'); ?>: <?php echo date_i18n('H:i', $stats['timestamp']); ?>)</span>
     165                <?php esc_html_e('Range:', 'bonus-for-woo'); ?>
     166                <strong><?php echo !empty($stats['date_start']) ? esc_html($stats['date_start']) :  esc_html_e('All time', 'bonus-for-woo'); ?> - <?php echo !empty($stats['date_end']) ? esc_html($stats['date_end']) : 'All time'; ?></strong>
     167                <span style="font-size: 11px; margin-left:10px;">(<?php esc_html_e('Updated', 'bonus-for-woo'); ?>: <?php echo esc_html(date_i18n('H:i', (int) $stats['timestamp'])); ?>)</span>
    168168            </div>
    169169            <div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
    … …  
    171171                <span>&mdash;</span>
    172172                <input type="date" id="bfw-date-end" class="regular-text" value="<?php echo esc_attr($stats['date_end'] ?? date('Y-m-d')); ?>" style="height:35px; width: 140px;">
    173                 <button id="bfw-clear-stats" class="button"><?php _e('Reset', 'bonus-for-woo'); ?></button>
    174                 <button id="bfw-recalc-start" class="button button-primary" style="height: auto; padding: 6px 20px;"><?php _e('Refresh Analytics', 'bonus-for-woo'); ?></button>
     173                <button id="bfw-clear-stats" class="button"><?php esc_html_e('Reset', 'bonus-for-woo'); ?></button>
     174                <button id="bfw-recalc-start" class="button button-primary" style="height: auto; padding: 6px 20px;"><?php esc_html_e('Refresh Analytics', 'bonus-for-woo'); ?></button>
    175175            </div>
    176176        </div>
    177177        <div id="bfw-recalc-progress" style="display:none;">
    178             <h3 style="margin-top: 0;"><?php _e('Deep Data Analysis', 'bonus-for-woo'); ?>...</h3>
     178            <h3 style="margin-top: 0;"><?php esc_html_e('Deep Data Analysis', 'bonus-for-woo'); ?>...</h3>
    179179            <div style="background: #f0f0f1; height: 10px; border-radius: 5px; overflow: hidden; margin: 15px 0;">
    180180                <div id="bfw-progress-inner" style="width: 0%; background: var(--bfw-primary); height: 100%; transition: width 0.3s;"></div>
    181181            </div>
    182             <p id="bfw-progress-text" style="font-weight: 600;"><?php _e('Processing...', 'bonus-for-woo'); ?></p>
     182            <p id="bfw-progress-text" style="font-weight: 600;"><?php esc_html_e('Processing...', 'bonus-for-woo'); ?></p>
    183183        </div>
    184184        <!-- Ряд 1: Главные деньги -->
    … …  
    186186            <div class="bfw-card">
    187187                <div class="bfw-card-header">
    188                     <span class="bfw-card-title"><?php _e('Lifetime Value (LTV)', 'bonus-for-woo'); ?></span>
     188                    <span class="bfw-card-title"><?php esc_html_e('Lifetime Value (LTV)', 'bonus-for-woo'); ?></span>
    189189                    <div class="bfw-card-icon"><span class="dashicons dashicons-id"></span></div>
    190190                </div>
    191                 <div class="bfw-card-value"><?php echo wc_price($stats['ltv_bonus'] ?? 0); ?></div>
     191                <div class="bfw-card-value"><?php echo wp_kses_post(wc_price($stats['ltv_bonus'] ?? 0)); ?></div>
    192192                <div class="bfw-card-footer">
    193                     <?php echo __('Average total revenue from each participant of the bonus system.', 'bonus-for-woo'); ?>
     193                    <?php echo esc_html__('Average total revenue from each participant of the bonus system.', 'bonus-for-woo'); ?>
    194194                </div>
    195195            </div>
    … …  
    197197            <div class="bfw-card">
    198198                <div class="bfw-card-header">
    199                     <span class="bfw-card-title"><?php _e('Referral Revenue', 'bonus-for-woo'); ?></span>
     199                    <span class="bfw-card-title"><?php esc_html_e('Referral Revenue', 'bonus-for-woo'); ?></span>
    200200                    <div class="bfw-card-icon"><span class="dashicons dashicons-groups"></span></div>
    201201                </div>
    202                 <div class="bfw-card-value"><?php echo wc_price($stats['referral_revenue'] ?? 0); ?></div>
     202                <div class="bfw-card-value"><?php echo wp_kses_post(wc_price($stats['referral_revenue'] ?? 0)); ?></div>
    203203                <div class="bfw-card-footer">
    204                     <?php _e('Total money brought by invited friends.', 'bonus-for-woo'); ?>
     204                    <?php esc_html_e('Total money brought by invited friends.', 'bonus-for-woo'); ?>
    205205                </div>
    206206            </div>
    … …  
    208208            <div class="bfw-card">
    209209                <div class="bfw-card-header">
    210                     <span class="bfw-card-title"><?php _e('Customer Retention', 'bonus-for-woo'); ?></span>
     210                    <span class="bfw-card-title"><?php esc_html_e('Customer Retention', 'bonus-for-woo'); ?></span>
    211211                    <div class="bfw-card-icon"><span class="dashicons dashicons-update"></span></div>
    212212                </div>
    213                 <div class="bfw-card-value"><?php echo round($stats['retention_rate'] ?? 0, 1); ?>%</div>
     213                <div class="bfw-card-value"><?php echo esc_html(round((float) ($stats['retention_rate'] ?? 0), 1)); ?>%</div>
    214214                <div class="bfw-card-footer">
    215                     <?php _e('Percentage of loyal customers who make repeat purchases.', 'bonus-for-woo'); ?>
     215                    <?php esc_html_e('Percentage of loyal customers who make repeat purchases.', 'bonus-for-woo'); ?>
    216216                </div>
    217217            </div>
    … …  
    219219            <div class="bfw-card">
    220220                <div class="bfw-card-header">
    221                     <span class="bfw-card-title"><?php _e('Point Liability', 'bonus-for-woo'); ?></span>
     221                    <span class="bfw-card-title"><?php esc_html_e('Point Liability', 'bonus-for-woo'); ?></span>
    222222                    <div class="bfw-card-icon"><span class="dashicons dashicons-money-alt"></span></div>
    223223                </div>
    224                 <div class="bfw-card-value"><?php echo wc_price($stats['points_total'] ?? 0); ?></div>
     224                <div class="bfw-card-value"><?php echo wp_kses_post(wc_price($stats['points_total'] ?? 0)); ?></div>
    225225                <div class="bfw-card-footer">
    226                     <?php _e('Current financial "debt" in points sitting on user balances.', 'bonus-for-woo'); ?>
     226                    <?php esc_html_e('Current financial "debt" in points sitting on user balances.', 'bonus-for-woo'); ?>
    227227                </div>
    228228            </div>
    … …  
    230230            <div class="bfw-card">
    231231                <div class="bfw-card-header">
    232                     <span class="bfw-card-title"><?php _e('Pending Points', 'bonus-for-woo'); ?></span>
     232                    <span class="bfw-card-title"><?php esc_html_e('Pending Points', 'bonus-for-woo'); ?></span>
    233233                    <div class="bfw-card-icon"><span class="dashicons dashicons-clock"></span></div>
    234234                </div>
    235235                <div class="bfw-card-value" style="color: var(--bfw-warning);"><?php echo esc_html(BfwPoints::formatNumber((float) ($stats['pending_points_total'] ?? 0))); ?></div>
    236236                <div class="bfw-card-footer">
    237                     <?php _e('Points awaiting accrual — orders currently in Processing status.', 'bonus-for-woo'); ?>
     237                    <?php esc_html_e('Points awaiting accrual — orders currently in Processing status.', 'bonus-for-woo'); ?>
    238238                </div>
    239239            </div>
    … …  
    241241            <div class="bfw-card">
    242242                <div class="bfw-card-header">
    243                     <span class="bfw-card-title"><?php _e('Burn Rate', 'bonus-for-woo'); ?></span>
     243                    <span class="bfw-card-title"><?php esc_html_e('Burn Rate', 'bonus-for-woo'); ?></span>
    244244                    <div class="bfw-card-icon"><span class="dashicons dashicons-chart-line"></span></div>
    245245                </div>
    246246                <?php $burn = round($stats['prr'] ?? 0, 1); ?>
    247                 <div class="bfw-card-value" style="color: <?php echo $burn >= 20 && $burn <= 50 ? 'var(--bfw-success)' : 'var(--bfw-warning)'; ?>"><?php echo $burn; ?>%</div>
     247                <div class="bfw-card-value" style="color: <?php echo $burn >= 20 && $burn <= 50 ? 'var(--bfw-success)' : 'var(--bfw-warning)'; ?>"><?php echo esc_html(round((float) $burn, 1)); ?>%</div>
    248248                <div class="bfw-card-footer">
    249                     <?php _e('20-40% is a healthy figure for most stores.', 'bonus-for-woo'); ?>
     249                    <?php esc_html_e('20-40% is a healthy figure for most stores.', 'bonus-for-woo'); ?>
    250250                </div>
    251251            </div>
    … …  
    253253            <div class="bfw-card">
    254254                <div class="bfw-card-header">
    255                     <span class="bfw-card-title"><?php _e('Program Penetration', 'bonus-for-woo'); ?></span>
     255                    <span class="bfw-card-title"><?php esc_html_e('Program Penetration', 'bonus-for-woo'); ?></span>
    256256                    <div class="bfw-card-icon"><span class="dashicons dashicons-admin-users"></span></div>
    257257                </div>
    258                 <div class="bfw-card-value" style="color: var(--bfw-primary);"><?php echo round($stats['penetration'] ?? 0, 1); ?>%</div>
     258                <div class="bfw-card-value" style="color: var(--bfw-primary);"><?php echo esc_html(round((float) ($stats['penetration'] ?? 0), 1)); ?>%</div>
    259259                <div class="bfw-card-footer">
    260                     <?php _e('Share of all active customers using the bonus system.', 'bonus-for-woo'); ?>
     260                    <?php esc_html_e('Share of all active customers using the bonus system.', 'bonus-for-woo'); ?>
    261261                </div>
    262262            </div>
    … …  
    266266            <!-- Здоровье системы -->
    267267            <div class="bfw-card">
    268                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Ecosystem Health', 'bonus-for-woo'); ?></h3>
     268                <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Ecosystem Health', 'bonus-for-woo'); ?></h3>
    269269                <div style="margin-bottom: 25px;">
    270270                    <div style="display: flex; justify-content: space-between; align-items: baseline;">
    271                         <span style="font-weight: 600;"><?php _e('Redemption Rate', 'bonus-for-woo'); ?></span>
    272                         <span style="font-size: 24px; font-weight: 800; color: var(--bfw-primary);"><?php echo round($stats['prr'] ?? 0, 1); ?>%</span>
     271                        <span style="font-weight: 600;"><?php esc_html_e('Redemption Rate', 'bonus-for-woo'); ?></span>
     272                        <span style="font-size: 24px; font-weight: 800; color: var(--bfw-primary);"><?php echo esc_html(round((float) ($stats['prr'] ?? 0), 1)); ?>%</span>
    273273                    </div>
    274                     <div class="bfw-health-bar"><div class="bfw-health-fill" style="width: <?php echo min(100, $stats['prr'] ?? 0); ?>%"></div></div>
     274                    <div class="bfw-health-bar"><div class="bfw-health-fill" style="width: <?php echo esc_attr(min(100, (float) ($stats['prr'] ?? 0))); ?>%"></div></div>
    275275                    <p style="font-size: 13px; color: var(--bfw-text-muted);">
    276                         <?php _e('This shows how actively users spend their points. 20-40% is healthy for most stores.', 'bonus-for-woo'); ?>
     276                        <?php esc_html_e('This shows how actively users spend their points. 20-40% is healthy for most stores.', 'bonus-for-woo'); ?>
    277277                    </p>
    278278                </div>
    … …  
    280280                <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; padding-top: 15px; border-top: 1px solid var(--bfw-border);">
    281281                    <div>
    282                         <small style="color: var(--bfw-text-muted); display: block; margin-bottom: 4px;"><?php _e('Total Issued', 'bonus-for-woo'); ?></small>
     282                        <small style="color: var(--bfw-text-muted); display: block; margin-bottom: 4px;"><?php esc_html_e('Total Issued', 'bonus-for-woo'); ?></small>
    283283                        <strong style="font-size: 16px;"><?php echo esc_html(BfwPoints::formatNumber((float) ($stats['issued_total_life'] ?? 0))); ?></strong>
    284284                    </div>
    285285                    <div>
    286                         <small style="color: var(--bfw-text-muted); display: block; margin-bottom: 4px;"><?php _e('Total Spent', 'bonus-for-woo'); ?></small>
     286                        <small style="color: var(--bfw-text-muted); display: block; margin-bottom: 4px;"><?php esc_html_e('Total Spent', 'bonus-for-woo'); ?></small>
    287287                        <strong style="font-size: 16px;"><?php echo esc_html(BfwPoints::formatNumber((float) ($stats['spent_total_life'] ?? 0))); ?></strong>
    288288                    </div>
    … …  
    292292            <!-- Распределение уровней -->
    293293            <div class="bfw-card">
    294                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Customer Segmentation', 'bonus-for-woo'); ?></h3>
     294                <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Customer Segmentation', 'bonus-for-woo'); ?></h3>
    295295                <?php
    296296                $labels = []; $counts = [];
    … …  
    312312             <!-- Распределение баллов -->
    313313             <div class="bfw-card">
    314                  <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Points Distribution', 'bonus-for-woo'); ?></h3>
     314                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Points Distribution', 'bonus-for-woo'); ?></h3>
    315315                 <div id="bfw-distribution-loading" style="text-align: center; padding: 20px;">
    316316                     <span class="dashicons dashicons-spinner is-active"></span>
    317                      <?php _e('Loading...', 'bonus-for-woo'); ?>
     317                     <?php esc_html_e('Loading...', 'bonus-for-woo'); ?>
    318318                 </div>
    319319                 <div id="bfw-distribution-content" style="display: none;">
    … …  
    323323            <!-- Топ клиентов -->
    324324            <div class="bfw-card">
    325                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Top 5 VIP Customers', 'bonus-for-woo'); ?></h3>
     325                <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Top 5 VIP Customers', 'bonus-for-woo'); ?></h3>
    326326                <table style="width: 100%; border-collapse: collapse; text-align: left;">
    327327                    <thead>
    328328                    <tr style="border-bottom: 2px solid var(--bfw-border); color: var(--bfw-text-muted);">
    329                         <th style="padding: 10px 5px;"><?php _e('User', 'bonus-for-woo'); ?></th>
    330                         <th style="padding: 10px 5px;"><?php _e('Revenue', 'bonus-for-woo'); ?></th>
    331                         <th style="padding: 10px 5px;"><?php _e('Orders', 'bonus-for-woo'); ?></th>
     329                        <th style="padding: 10px 5px;"><?php esc_html_e('User', 'bonus-for-woo'); ?></th>
     330                        <th style="padding: 10px 5px;"><?php esc_html_e('Revenue', 'bonus-for-woo'); ?></th>
     331                        <th style="padding: 10px 5px;"><?php esc_html_e('Orders', 'bonus-for-woo'); ?></th>
    332332                    </tr>
    333333                    </thead>
    … …  
    339339                        <tr style="border-bottom: 1px solid #eee;">
    340340                            <td style="padding: 12px 5px; font-weight: 600;"><?php echo esc_html($name); ?></td>
    341                             <td style="padding: 12px 5px; color: var(--bfw-success);"><?php echo wc_price($vip['revenue']); ?></td>
    342                             <td style="padding: 12px 5px;"><?php echo $vip['orders']; ?></td>
     341                            <td style="padding: 12px 5px; color: var(--bfw-success);"><?php echo wp_kses_post(wc_price($vip['revenue'])); ?></td>
     342                            <td style="padding: 12px 5px;"><?php echo esc_html((int) $vip['orders']); ?></td>
    343343                        </tr>
    344344                    <?php endforeach; else: ?>
    345                         <tr><td colspan="3" style="padding: 15px; text-align: center;"><?php _e('Not enough data.', 'bonus-for-woo'); ?></td></tr>
     345                        <tr><td colspan="3" style="padding: 15px; text-align: center;"><?php esc_html_e('Not enough data.', 'bonus-for-woo'); ?></td></tr>
    346346                    <?php endif; ?>
    347347                    </tbody>
    … …  
    355355             <div class="bfw-card">
    356356                 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
    357                      <h3 style="margin: 0;"><?php _e('Points Trend Analytics', 'bonus-for-woo'); ?></h3>
     357                     <h3 style="margin: 0;"><?php esc_html_e('Points Trend Analytics', 'bonus-for-woo'); ?></h3>
    358358                     <select id="bfw-trend-period" style="padding: 6px 12px; border-radius: 4px; border: 1px solid var(--bfw-border);">
    359                          <option value="7"><?php _e('Last 7 days', 'bonus-for-woo'); ?></option>
    360                          <option value="30" selected><?php _e('Last 30 days', 'bonus-for-woo'); ?></option>
    361                          <option value="90"><?php _e('Last 90 days', 'bonus-for-woo'); ?></option>
    362                          <option value="365"><?php _e('Last year', 'bonus-for-woo'); ?></option>
     359                         <option value="7"><?php esc_html_e('Last 7 days', 'bonus-for-woo'); ?></option>
     360                         <option value="30" selected><?php esc_html_e('Last 30 days', 'bonus-for-woo'); ?></option>
     361                         <option value="90"><?php esc_html_e('Last 90 days', 'bonus-for-woo'); ?></option>
     362                         <option value="365"><?php esc_html_e('Last year', 'bonus-for-woo'); ?></option>
    363363                     </select>
    364364                 </div>
    365365                 <div id="bfw-trend-loading" style="text-align: center; padding: 20px;">
    366366                     <span class="dashicons dashicons-spinner is-active"></span>
    367                      <?php _e('Loading...', 'bonus-for-woo'); ?>
     367                     <?php esc_html_e('Loading...', 'bonus-for-woo'); ?>
    368368                 </div>
    369369                 <div id="bfw-trend-content" style="display: none;">
    … …  
    376376             <!-- Overall Stats -->
    377377             <div class="bfw-card">
    378                  <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Overall System Statistics', 'bonus-for-woo'); ?></h3>
     378                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Overall System Statistics', 'bonus-for-woo'); ?></h3>
    379379                 <div id="bfw-overall-loading" style="text-align: center; padding: 20px;">
    380380                     <span class="dashicons dashicons-spinner is-active"></span>
    381                      <?php _e('Loading...', 'bonus-for-woo'); ?>
     381                     <?php esc_html_e('Loading...', 'bonus-for-woo'); ?>
    382382                 </div>
    383383                 <div id="bfw-overall-content" style="display: none;">
    384384                     <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px;">
    385385                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    386                              <small style="color: var(--bfw-text-muted);"><?php _e('Total Points', 'bonus-for-woo'); ?></small>
     386                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Total Points', 'bonus-for-woo'); ?></small>
    387387                             <div style="font-size: 24px; font-weight: 700; color: var(--bfw-primary);" id="bfw-total-points">0</div>
    388388                         </div>
    389389                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    390                              <small style="color: var(--bfw-text-muted);"><?php _e('Users with Points', 'bonus-for-woo'); ?></small>
     390                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Users with Points', 'bonus-for-woo'); ?></small>
    391391                             <div style="font-size: 24px; font-weight: 700; color: var(--bfw-primary);" id="bfw-users-with-points">0</div>
    392392                         </div>
    393393                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    394                              <small style="color: var(--bfw-text-muted);"><?php _e('Total Earned', 'bonus-for-woo'); ?></small>
     394                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Total Earned', 'bonus-for-woo'); ?></small>
    395395                             <div style="font-size: 24px; font-weight: 700; color: var(--bfw-success);" id="bfw-total-earned">0</div>
    396396                         </div>
    397397                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    398                              <small style="color: var(--bfw-text-muted);"><?php _e('Total Spent', 'bonus-for-woo'); ?></small>
     398                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Total Spent', 'bonus-for-woo'); ?></small>
    399399                             <div style="font-size: 24px; font-weight: 700; color: var(--bfw-warning);" id="bfw-total-spent">0</div>
    400400                         </div>
    401401                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px; grid-column: span 2;">
    402                              <small style="color: var(--bfw-text-muted);"><?php _e('Active Users (30 days)', 'bonus-for-woo'); ?></small>
     402                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Active Users (30 days)', 'bonus-for-woo'); ?></small>
    403403                             <div style="font-size: 24px; font-weight: 700; color: var(--bfw-primary);" id="bfw-active-users">0</div>
    404404                         </div>
    … …  
    409409             <!-- Top Users -->
    410410             <div class="bfw-card">
    411                  <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Top Users by Points', 'bonus-for-woo'); ?></h3>
     411                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Top Users by Points', 'bonus-for-woo'); ?></h3>
    412412                 <div id="bfw-top-users-loading" style="text-align: center; padding: 20px;">
    413413                     <span class="dashicons dashicons-spinner is-active"></span>
    414                      <?php _e('Loading...', 'bonus-for-woo'); ?>
     414                     <?php esc_html_e('Loading...', 'bonus-for-woo'); ?>
    415415                 </div>
    416416                 <div id="bfw-top-users-content" style="display: none;">
    … …  
    418418                         <thead>
    419419                             <tr style="border-bottom: 2px solid var(--bfw-border); color: var(--bfw-text-muted);">
    420                                  <th style="padding: 10px; text-align: left;"><?php _e('User', 'bonus-for-woo'); ?></th>
    421                                  <th style="padding: 10px; text-align: right;"><?php _e('Points', 'bonus-for-woo'); ?></th>
     420                                 <th style="padding: 10px; text-align: left;"><?php esc_html_e('User', 'bonus-for-woo'); ?></th>
     421                                 <th style="padding: 10px; text-align: right;"><?php esc_html_e('Points', 'bonus-for-woo'); ?></th>
    422422                             </tr>
    423423                         </thead>
    … …  
    431431             <!-- Coupon Stats -->
    432432             <div class="bfw-card">
    433                  <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Coupon Statistics', 'bonus-for-woo'); ?></h3>
     433                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Coupon Statistics', 'bonus-for-woo'); ?></h3>
    434434                 <div id="bfw-coupon-loading" style="text-align: center; padding: 20px;">
    435435                     <span class="dashicons dashicons-spinner is-active"></span>
    436                      <?php _e('Loading...', 'bonus-for-woo'); ?>
     436                     <?php esc_html_e('Loading...', 'bonus-for-woo'); ?>
    437437                 </div>
    438438                 <div id="bfw-coupon-content" style="display: none;">
    439439                     <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px;">
    440440                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    441                              <small style="color: var(--bfw-text-muted);"><?php _e('Total Coupons', 'bonus-for-woo'); ?></small>
     441                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Total Coupons', 'bonus-for-woo'); ?></small>
    442442                             <div style="font-size: 20px; font-weight: 700; color: var(--bfw-primary);" id="bfw-total-coupons">0</div>
    443443                         </div>
    444444                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    445                              <small style="color: var(--bfw-text-muted);"><?php _e('Active Coupons', 'bonus-for-woo'); ?></small>
     445                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Active Coupons', 'bonus-for-woo'); ?></small>
    446446                             <div style="font-size: 20px; font-weight: 700; color: var(--bfw-success);" id="bfw-active-coupons">0</div>
    447447                         </div>
    448448                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    449                              <small style="color: var(--bfw-text-muted);"><?php _e('Used Coupons', 'bonus-for-woo'); ?></small>
     449                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Used Coupons', 'bonus-for-woo'); ?></small>
    450450                             <div style="font-size: 20px; font-weight: 700; color: var(--bfw-warning);" id="bfw-used-coupons">0</div>
    451451                         </div>
    452452                         <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    453                              <small style="color: var(--bfw-text-muted);"><?php _e('Total Coupon Sum', 'bonus-for-woo'); ?></small>
     453                             <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Total Coupon Sum', 'bonus-for-woo'); ?></small>
    454454                             <div style="font-size: 20px; font-weight: 700; color: var(--bfw-primary);" id="bfw-total-coupon-sum">0</div>
    455455                         </div>
    … …  
    464464             <!-- Сводка оборота -->
    465465             <div class="bfw-card">
    466                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Revenue Breakdown', 'bonus-for-woo'); ?></h3>
     466                <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Revenue Breakdown', 'bonus-for-woo'); ?></h3>
    467467                <div style="margin-bottom: 15px;">
    468468                    <div style="display: flex; justify-content: space-between; margin-bottom: 6px;">
    469                         <span><?php _e('Total Store Revenue', 'bonus-for-woo'); ?></span>
    470                         <strong><?php echo wc_price($stats['total_revenue'] ?? 0); ?></strong>
     469                        <span><?php esc_html_e('Total Store Revenue', 'bonus-for-woo'); ?></span>
     470                        <strong><?php echo wp_kses_post(wc_price($stats['total_revenue'] ?? 0)); ?></strong>
    471471                    </div>
    472472                    <div style="display: flex; justify-content: space-between; font-size: 13px; color: var(--bfw-text-muted);">
    473                         <span><?php _e('From orders with bonus discount', 'bonus-for-woo'); ?></span>
    474                         <span><?php echo wc_price($stats['total_revenue'] - ($stats['rev_no_bonus'] ?? ($stats['total_revenue'] / 2))); ?></span>
     473                        <span><?php esc_html_e('From orders with bonus discount', 'bonus-for-woo'); ?></span>
     474                        <span><?php echo wp_kses_post(wc_price($stats['total_revenue'] - ($stats['rev_no_bonus'] ?? ($stats['total_revenue'] / 2)))); ?></span>
    475475                    </div>
    476476                </div>
    … …  
    478478                <div style="padding-top: 15px; border-top: 1px solid var(--bfw-border);">
    479479                     <div style="display: flex; justify-content: space-between; align-items: center;">
    480                         <span style="font-weight: 600;"><?php _e('AOV Increase', 'bonus-for-woo'); ?></span>
     480                        <span style="font-weight: 600;"><?php esc_html_e('AOV Increase', 'bonus-for-woo'); ?></span>
    481481                        <?php
    482482                        $aov_no = $stats['aov_no_bonus'] ?? 0;
    … …  
    486486                        $trend_sign = $increase >= 0 ? '+' : '';
    487487                        ?>
    488                         <span class="bfw-trend <?php echo $trend_class; ?>" style="font-size: 14px;"><?php echo $trend_sign . round($increase, 1); ?>%</span>
     488                        <span class="bfw-trend <?php echo esc_attr($trend_class); ?>" style="font-size: 14px;"><?php echo esc_html($trend_sign . round((float) $increase, 1)); ?>%</span>
    489489                     </div>
    490490                     <p style="font-size: 12px; color: var(--bfw-text-muted); margin-top: 5px;">
    … …  
    496496             <!-- Реферальная активность -->
    497497             <div class="bfw-card">
    498                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Viral Growth (Referrals)', 'bonus-for-woo'); ?></h3>
     498                <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Viral Growth (Referrals)', 'bonus-for-woo'); ?></h3>
    499499                <?php if (BfwRoles::isPro() && BfwSetting::get('referal-system')) : ?>
    500500                    <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; margin-bottom: 20px;">
    501501                        <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    502                             <small style="color: var(--bfw-text-muted);"><?php _e('Ambassadors', 'bonus-for-woo'); ?></small>
    503                             <div style="font-size: 20px; font-weight: 700; color: var(--bfw-primary);"><?php echo $stats['referrals_total'] ?? 0; ?></div>
     502                            <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Ambassadors', 'bonus-for-woo'); ?></small>
     503                            <div style="font-size: 20px; font-weight: 700; color: var(--bfw-primary);"><?php echo esc_html((int) ($stats['referrals_total'] ?? 0)); ?></div>
    504504                        </div>
    505505                        <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    506                             <small style="color: var(--bfw-text-muted);"><?php _e('Friends Invited', 'bonus-for-woo'); ?></small>
    507                             <div style="font-size: 20px; font-weight: 700; color: var(--bfw-success);"><?php echo $stats['referrals_invited'] ?? 0; ?></div>
     506                            <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Friends Invited', 'bonus-for-woo'); ?></small>
     507                            <div style="font-size: 20px; font-weight: 700; color: var(--bfw-success);"><?php echo esc_html((int) ($stats['referrals_invited'] ?? 0)); ?></div>
    508508                        </div>
    509509                        <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    510                             <small style="color: var(--bfw-text-muted);"><?php _e('Users with Referral Link', 'bonus-for-woo'); ?></small>
     510                            <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Users with Referral Link', 'bonus-for-woo'); ?></small>
    511511                            <div style="font-size: 20px; font-weight: 700; color: var(--bfw-primary);" id="bfw-referral-users">0</div>
    512512                        </div>
    513513                        <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
    514                             <small style="color: var(--bfw-text-muted);"><?php _e('Referral Revenue', 'bonus-for-woo'); ?></small>
    515                             <div style="font-size: 20px; font-weight: 700; color: var(--bfw-success);"><?php echo wc_price($stats['referral_revenue'] ?? 0); ?></div>
     514                            <small style="color: var(--bfw-text-muted);"><?php esc_html_e('Referral Revenue', 'bonus-for-woo'); ?></small>
     515                            <div style="font-size: 20px; font-weight: 700; color: var(--bfw-success);"><?php echo wp_kses_post(wc_price($stats['referral_revenue'] ?? 0)); ?></div>
    516516                        </div>
    517517                    </div>
    518                     <h4 style="margin-bottom: 10px;"><?php _e('Top Referrers', 'bonus-for-woo'); ?></h4>
     518                    <h4 style="margin-bottom: 10px;"><?php esc_html_e('Top Referrers', 'bonus-for-woo'); ?></h4>
    519519                    <table style="width: 100%; border-collapse: collapse;">
    520520                        <thead>
    521521                            <tr style="border-bottom: 2px solid var(--bfw-border); color: var(--bfw-text-muted);">
    522                                 <th style="padding: 10px; text-align: left;"><?php _e('User', 'bonus-for-woo'); ?></th>
    523                                 <th style="padding: 10px; text-align: right;"><?php _e('Invitations', 'bonus-for-woo'); ?></th>
     522                                <th style="padding: 10px; text-align: left;"><?php esc_html_e('User', 'bonus-for-woo'); ?></th>
     523                                <th style="padding: 10px; text-align: right;"><?php esc_html_e('Invitations', 'bonus-for-woo'); ?></th>
    524524                            </tr>
    525525                        </thead>
    … …  
    528528                    <div id="bfw-referral-loading" style="text-align: center; padding: 20px; display: none;">
    529529                        <span class="dashicons dashicons-spinner is-active"></span>
    530                         <?php _e('Loading...', 'bonus-for-woo'); ?>
     530                        <?php esc_html_e('Loading...', 'bonus-for-woo'); ?>
    531531                    </div>
    532532                <?php else: ?>
    533533                    <p style="color: var(--bfw-text-muted); text-align: center; padding: 20px 0;">
    534                         <?php _e('Referral system is currently disabled.', 'bonus-for-woo'); ?>
     534                        <?php esc_html_e('Referral system is currently disabled.', 'bonus-for-woo'); ?>
    535535                    </p>
    536536                <?php endif; ?>
    … …  
    542542            <!-- Export Buttons -->
    543543            <div class="bfw-card">
    544                 <h3 style="margin-top: 0; margin-bottom: 20px;"><?php _e('Export Analytics', 'bonus-for-woo'); ?></h3>
     544                <h3 style="margin-top: 0; margin-bottom: 20px;"><?php esc_html_e('Export Analytics', 'bonus-for-woo'); ?></h3>
    545545                <div style="display: flex; gap: 10px; flex-wrap: wrap;">
    546                     <button class="button" id="bfw-export-distribution"><?php _e('Export Distribution', 'bonus-for-woo'); ?></button>
    547                     <button class="button" id="bfw-export-trend"><?php _e('Export Trend', 'bonus-for-woo'); ?></button>
    548                     <button class="button" id="bfw-export-overall"><?php _e('Export Overall Stats', 'bonus-for-woo'); ?></button>
     546                    <button class="button" id="bfw-export-distribution"><?php esc_html_e('Export Distribution', 'bonus-for-woo'); ?></button>
     547                    <button class="button" id="bfw-export-trend"><?php esc_html_e('Export Trend', 'bonus-for-woo'); ?></button>
     548                    <button class="button" id="bfw-export-overall"><?php esc_html_e('Export Overall Stats', 'bonus-for-woo'); ?></button>
    549549                </div>
    550550            </div>
    … …  
    612612                                    labels: labels,
    613613                                    datasets: [{
    614                                         label: '<?php _e('Number of Users', 'bonus-for-woo'); ?>',
     614                                        label: '<?php esc_html_e('Number of Users', 'bonus-for-woo'); ?>',
    615615                                        data: data,
    616616                                        backgroundColor: '#2271b1',
    … …  
    627627                            });
    628628                        } else {
    629                             $('#bfw-distribution-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     629                            $('#bfw-distribution-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    630630                        }
    631631                    }).fail(function(xhr, status, error) {
    632632                        console.error('Distribution AJAX failed:', status, error);
    633                         $('#bfw-distribution-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     633                        $('#bfw-distribution-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    634634                    });
    635635                }
    … …  
    668668                                        datasets: [
    669669                                            {
    670                                                 label: '<?php _e('Issued Points (+)', 'bonus-for-woo'); ?>',
     670                                                label: '<?php esc_html_e('Issued Points (+)', 'bonus-for-woo'); ?>',
    671671                                                data: issuedData,
    672672                                                borderColor: '#00a65a',
    … …  
    677677                                            },
    678678                                            {
    679                                                 label: '<?php _e('Spent Points (-)', 'bonus-for-woo'); ?>',
     679                                                label: '<?php esc_html_e('Spent Points (-)', 'bonus-for-woo'); ?>',
    680680                                                data: spentData,
    681681                                                borderColor: '#f0b849',
    … …  
    696696                                });
    697697                            } else {
    698                                 $('#bfw-trend-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     698                                $('#bfw-trend-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    699699                            }
    700700                        }).fail(function() {
    701                             $('#bfw-trend-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     701                            $('#bfw-trend-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    702702                        });
    703703                    }
    … …  
    724724                        $('#bfw-active-users').text(response.data.active_users.toLocaleString());
    725725                    } else {
    726                         $('#bfw-overall-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     726                        $('#bfw-overall-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    727727                    }
    728728                }).fail(function() {
    729                     $('#bfw-overall-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     729                    $('#bfw-overall-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    730730                });
    731731
    … …  
    742742                        });
    743743                    } else {
    744                         $('#bfw-top-users-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     744                        $('#bfw-top-users-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    745745                    }
    746746                }).fail(function() {
    747                     $('#bfw-top-users-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     747                    $('#bfw-top-users-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    748748                });
    749749
    … …  
    763763                            });
    764764                        } else {
    765                             tbody.append('<tr><td colspan="2" style="padding: 15px; text-align: center;"><?php _e('No data', 'bonus-for-woo'); ?></td></tr>');
     765                            tbody.append('<tr><td colspan="2" style="padding: 15px; text-align: center;"><?php esc_html_e('No data', 'bonus-for-woo'); ?></td></tr>');
    766766                        }
    767767                    }
    768768                }).fail(function() {
    769                     $('#bfw-top-referrers-table').append('<tr><td colspan="2" style="padding: 15px; text-align: center;"><?php _e('Failed to load data', 'bonus-for-woo'); ?></td></tr>');
     769                    $('#bfw-top-referrers-table').append('<tr><td colspan="2" style="padding: 15px; text-align: center;"><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></td></tr>');
    770770                });
    771771
    … …  
    780780                        $('#bfw-total-coupon-sum').text(response.data.total_coupon_sum.toLocaleString());
    781781                    } else {
    782                         $('#bfw-coupon-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     782                        $('#bfw-coupon-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    783783                    }
    784784                }).fail(function() {
    785                     $('#bfw-coupon-loading').html('<p><?php _e('Failed to load data', 'bonus-for-woo'); ?></p>');
     785                    $('#bfw-coupon-loading').html('<p><?php esc_html_e('Failed to load data', 'bonus-for-woo'); ?></p>');
    786786                });
    787787
    … …  
    808808        <div style="text-align: center; padding: 80px 20px; background: #fff; border-radius: 12px; border: 1px solid var(--bfw-border);">
    809809            <span class="dashicons dashicons-analytics" style="font-size: 80px; width: 80px; height: 80px; color: #ccd0d4; margin-bottom: 20px;"></span>
    810             <h2><?php _e('Analytics is ready to work', 'bonus-for-woo'); ?></h2>
    811             <p><?php _e('Calculate your first report to unlock insights about LTV, Retention and Referral ROI.', 'bonus-for-woo'); ?></p>
    812             <button id="bfw-recalc-start" class="button button-primary button-large" style="margin-top: 20px; height: auto; padding: 10px 40px;"><?php _e('Start Initial Calculation', 'bonus-for-woo'); ?></button>
     810            <h2><?php esc_html_e('Analytics is ready to work', 'bonus-for-woo'); ?></h2>
     811            <p><?php esc_html_e('Calculate your first report to unlock insights about LTV, Retention and Referral ROI.', 'bonus-for-woo'); ?></p>
     812            <button id="bfw-recalc-start" class="button button-primary button-large" style="margin-top: 20px; height: auto; padding: 10px 40px;"><?php esc_html_e('Start Initial Calculation', 'bonus-for-woo'); ?></button>
    813813        </div>
    814814    <?php endif; ?>
    … …  
    831831        $('#bfw-progress-inner').css('width', percent + '%');
    832832        const msgs = [
    833             '<?php _e('Analyzing user statuses...', 'bonus-for-woo'); ?>',
    834             '<?php _e('Calculating Point Redemption Rate...', 'bonus-for-woo'); ?>',
    835             '<?php _e('Auditing Referral network and Revenue...', 'bonus-for-woo'); ?>',
    836             '<?php _e('Correlating Retention and LTV metrics...', 'bonus-for-woo'); ?>',
    837             '<?php _e('Rendering dynamic timeline...', 'bonus-for-woo'); ?>'
     833            '<?php esc_html_e('Analyzing user statuses...', 'bonus-for-woo'); ?>',
     834            '<?php esc_html_e('Calculating Point Redemption Rate...', 'bonus-for-woo'); ?>',
     835            '<?php esc_html_e('Auditing Referral network and Revenue...', 'bonus-for-woo'); ?>',
     836            '<?php esc_html_e('Correlating Retention and LTV metrics...', 'bonus-for-woo'); ?>',
     837            '<?php esc_html_e('Rendering dynamic timeline...', 'bonus-for-woo'); ?>'
    838838        ];
    839         $('#bfw-progress-text').text(msgs[step] || '<?php _e('Finalizing...', 'bonus-for-woo'); ?>');
     839        $('#bfw-progress-text').text(msgs[step] || '<?php esc_html_e('Finalizing...', 'bonus-for-woo'); ?>');
    840840    }
    841841
    … …  
    861861                    setTimeout(runStep, 100);
    862862                } else {
    863                     $('#bfw-progress-text').text('✅ <?php _e('Analysis Complete! Reloading...', 'bonus-for-woo'); ?>');
     863                    $('#bfw-progress-text').text('✅ <?php esc_html_e('Analysis Complete! Reloading...', 'bonus-for-woo'); ?>');
    864864                    setTimeout(() => window.location.reload(), 1000);
    865865                }
    … …  
    872872
    873873    $('#bfw-clear-stats').on('click', function () {
    874         if (!confirm('<?php _e('Clear stats cache?', 'bonus-for-woo'); ?>')) return;
     874        if (!confirm('<?php esc_html_e('Clear stats cache?', 'bonus-for-woo'); ?>')) return;
    875875        $.post(ajaxurl, {
    876876            action: 'bfw_clear_stats',
  • bonus-for-woo/trunk/pages/tools.php

    r3784081 r3795776  
    228228        <h1>
    229229            <span class="dashicons dashicons-admin-tools" style="font-size: 32px; width: 32px; height: 32px;"></span>
    230             <?php echo __('Bonus for Woo', 'bonus-for-woo') . ' - ' . __('Tools', 'bonus-for-woo'); ?>
     230            <?php echo esc_html__('Bonus for Woo', 'bonus-for-woo') . ' - ' . __('Tools', 'bonus-for-woo'); ?>
    231231        </h1>
    232232    </div>
    … …  
    238238            <div class="bfw-tool-header">
    239239                <div class="bfw-tool-icon"><span class="dashicons dashicons-backup"></span></div>
    240                 <h2 class="bfw-tool-title"><?php _e('Accrual of points for previous orders.', 'bonus-for-woo'); ?></h2>
     240                <h2 class="bfw-tool-title"><?php esc_html_e('Accrual of points for previous orders.', 'bonus-for-woo'); ?></h2>
    241241            </div>
    242242            <p class="bfw-tool-desc">
    243                 <?php _e('We will award points to users for previous orders, before installing the bonus system plugin.', 'bonus-for-woo'); ?>
    244                 <br><a href="https://computy.ru/blog/docs/bonus-for-woo/instrumenty/nachislenie-ballov-za-predydushhie-zakazy/" target="_blank" style="text-decoration:none; color: var(--bfw-primary); font-size: 13px;"><?php _e('More details', 'bonus-for-woo'); ?> ↗</a>
     243                <?php esc_html_e('We will award points to users for previous orders, before installing the bonus system plugin.', 'bonus-for-woo'); ?>
     244                <br><a href="https://computy.ru/blog/docs/bonus-for-woo/instrumenty/nachislenie-ballov-za-predydushhie-zakazy/" target="_blank" style="text-decoration:none; color: var(--bfw-primary); font-size: 13px;"><?php esc_html_e('More details', 'bonus-for-woo'); ?> ↗</a>
    245245            </p>
    246246
    247247            <div class="bfw-alert bfw-alert-warning">
    248248                <span class="dashicons dashicons-warning"></span>
    249                 <span> <?php _e('Attention! The action is irreversible!', 'bonus-for-woo'); ?></span>
     249                <span> <?php esc_html_e('Attention! The action is irreversible!', 'bonus-for-woo'); ?></span>
    250250            </div>
    251251
    … …  
    255255                        <button id="startRecalculation" class="bfw-btn bfw-btn-danger">
    256256                            <span class="dashicons dashicons-controls-play"></span>
    257                             <?php _e('Recalculation', 'bonus-for-woo'); ?>
     257                            <?php esc_html_e('Recalculation', 'bonus-for-woo'); ?>
    258258                        </button>
    259259                        <div id="progress" style="font-weight: 600; color: var(--bfw-success); font-size: 13px;"></div>
    … …  
    263263                    </div>
    264264                <?php else : ?>
    265                     <span class="bfw-badge-pro"><?php _e('Available in PRO version.', 'bonus-for-woo'); ?></span>
     265                    <span class="bfw-badge-pro"><?php esc_html_e('Available in PRO version.', 'bonus-for-woo'); ?></span>
    266266                <?php endif; ?>
    267267            </div>
    … …  
    272272            <div class="bfw-tool-header">
    273273                <div class="bfw-tool-icon"><span class="dashicons dashicons-plus-alt"></span></div>
    274                 <h2 class="bfw-tool-title"><?php _e('Bulk adding points', 'bonus-for-woo'); ?></h2>
    275             </div>
    276             <p class="bfw-tool-desc"><?php _e('Works without creating a notification. It won\'t be sent to your email.', 'bonus-for-woo'); ?></p>
     274                <h2 class="bfw-tool-title"><?php esc_html_e('Bulk adding points', 'bonus-for-woo'); ?></h2>
     275            </div>
     276            <p class="bfw-tool-desc"><?php esc_html_e('Works without creating a notification. It won\'t be sent to your email.', 'bonus-for-woo'); ?></p>
    277277
    278278            <?php if (BfwRoles::isPro()) : ?>
    279279                <div class="bfw-tool-form">
    280280                    <div class="bfw-form-group">
    281                         <label><?php _e('How many points should I add?', 'bonus-for-woo'); ?></label>
     281                        <label><?php esc_html_e('How many points should I add?', 'bonus-for-woo'); ?></label>
    282282                        <input type="number" id="mass_points" class="bfw-input" value="100" min="1">
    283283                    </div>
    284284                    <div class="bfw-form-group">
    285                         <label><?php _e('Text in history', 'bonus-for-woo'); ?></label>
    286                         <input type="text" id="mass_text" class="bfw-input" placeholder="<?php _e('e.g. Holiday Gift', 'bonus-for-woo'); ?>">
     285                        <label><?php esc_html_e('Text in history', 'bonus-for-woo'); ?></label>
     286                        <input type="text" id="mass_text" class="bfw-input" placeholder="<?php esc_html_e('e.g. Holiday Gift', 'bonus-for-woo'); ?>">
    287287                    </div>
    288288                </div>
    … …  
    291291                        <button id="mass-start" class="bfw-btn bfw-btn-primary">
    292292                            <span class="dashicons dashicons-share-alt"></span>
    293                             <?php _e('Start adding points', 'bonus-for-woo'); ?>
     293                            <?php esc_html_e('Start adding points', 'bonus-for-woo'); ?>
    294294                        </button>
    295295                        <div id="mass-progress" style="font-size: 13px;"></div>
    … …  
    298298            <?php else : ?>
    299299                <div class="bfw-tool-footer">
    300                     <span class="bfw-badge-pro"><?php _e('Available in PRO version.', 'bonus-for-woo'); ?></span>
     300                    <span class="bfw-badge-pro"><?php esc_html_e('Available in PRO version.', 'bonus-for-woo'); ?></span>
    301301                </div>
    302302            <?php endif; ?>
    … …  
    307307            <div class="bfw-tool-header">
    308308                <div class="bfw-tool-icon"><span class="dashicons dashicons-media-spreadsheet"></span></div>
    309                 <h2 class="bfw-tool-title"><?php _e('Export/Import', 'bonus-for-woo'); ?></h2>
    310             </div>
    311             <p class="bfw-tool-desc"><?php _e('When you click the "Create CSV export file" button, a link to download the file will appear next to it. You can download the file and edit it and then import it in the form below. After that, the bonus points data will be updated.', 'bonus-for-woo'); ?></p>
     309                <h2 class="bfw-tool-title"><?php esc_html_e('Export/Import', 'bonus-for-woo'); ?></h2>
     310            </div>
     311            <p class="bfw-tool-desc"><?php esc_html_e('Download the CSV export, edit it in a spreadsheet, then upload it in the form below. After the import, the bonus points data will be updated.', 'bonus-for-woo'); ?></p>
    312312
    313313            <div class="bfw-tool-form">
    314314                <div style="display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 20px;">
    315                     <?php $filename = BfwFunctions::getExportFilePath(); ?>
    316                     <?php if (is_file($filename)) : ?>
    317                         <a class="bfw-btn bfw-btn-outline" href="<?php echo wp_nonce_url('?page=bonus-for-woo%2Fpages%2Ftools.php&export_bfw_points=true', 'bfw_export_points'); ?>">
    318                             <span class="dashicons dashicons-update"></span> <?php _e('Refresh CSV', 'bonus-for-woo'); ?>
    319                         </a>
    320                         <a class="bfw-btn bfw-btn-outline" href="<?php echo esc_url(wp_nonce_url(admin_url('admin-post.php?action=bfw_download_export'), 'bfw_download_export')); ?>" download>
    321                             <span class="dashicons dashicons-download"></span> <?php _e('Download CSV', 'bonus-for-woo'); ?>
    322                         </a>
    323                         <a title="<?php _e('Delete export file', 'bonus-for-woo'); ?>" class="bfw-btn bfw-btn-danger" style="padding: 10px;" href="<?php echo wp_nonce_url('?page=bonus-for-woo%2Fpages%2Ftools.php&remove_export_bfw_points=true', 'bfw_remove_export'); ?>">
    324                             <span class="dashicons dashicons-trash"></span>
    325                         </a>
    326                     <?php else : ?>
    327                         <a class="bfw-btn bfw-btn-outline" href="<?php echo wp_nonce_url('?page=bonus-for-woo%2Fpages%2Ftools.php&export_bfw_points=true', 'bfw_export_points'); ?>">
    328                              <span class="dashicons dashicons-media-spreadsheet"></span> <?php _e('Сreate CSV export file', 'bonus-for-woo'); ?>
    329                         </a>
    330                     <?php endif; ?>
     315                    <a class="bfw-btn bfw-btn-outline" href="<?php echo esc_url(wp_nonce_url('?page=bonus-for-woo%2Fpages%2Ftools.php&export_bfw_points=true', 'bfw_export_points')); ?>">
     316                        <span class="dashicons dashicons-download"></span> <?php esc_html_e('Download CSV export', 'bonus-for-woo'); ?>
     317                    </a>
    331318                </div>
    332319
    333320                <div style="padding: 15px; background: #fcfcfc; border: 1px dashed var(--bfw-border); border-radius: 8px;">
    334                     <label style="display:block; font-weight:600; margin-bottom:10px; font-size:13px;"><?php _e('Upload CSV file', 'bonus-for-woo'); ?></label>
     321                    <label style="display:block; font-weight:600; margin-bottom:10px; font-size:13px;"><?php esc_html_e('Upload CSV file', 'bonus-for-woo'); ?></label>
    335322                    <input name="file" type="file" id="bfw-file-export" style="font-size:12px; width:100%; margin-bottom:15px;">
    336323                   
    337324                    <div style="display:flex; gap:10px;">
    338325                        <select id="search_by" class="bfw-input" style="width: auto; height: 38px;">
    339                             <option value="by_id"><?php _e('by ID', 'bonus-for-woo'); ?></option>
    340                             <option value="by_email"><?php _e('by email', 'bonus-for-woo'); ?></option>
    341                             <option value="by_phone"><?php _e('by phone', 'bonus-for-woo'); ?></option>
     326                            <option value="by_id"><?php esc_html_e('by ID', 'bonus-for-woo'); ?></option>
     327                            <option value="by_email"><?php esc_html_e('by email', 'bonus-for-woo'); ?></option>
     328                            <option value="by_phone"><?php esc_html_e('by phone', 'bonus-for-woo'); ?></option>
    342329                        </select>
    343330                        <button class="bfw-btn bfw-btn-primary" onclick="upload(); return false;">
    344                             <span class="dashicons dashicons-upload"></span> <?php _e('import', 'bonus-for-woo'); ?>
     331                            <span class="dashicons dashicons-upload"></span> <?php esc_html_e('import', 'bonus-for-woo'); ?>
    345332                        </button>
    346333                    </div>
    … …  
    354341            <div class="bfw-tool-header">
    355342                <div class="bfw-tool-icon"><span class="dashicons dashicons-admin-users"></span></div>
    356                 <h2 class="bfw-tool-title"><?php _e('Update user statuses', 'bonus-for-woo'); ?></h2>
     343                <h2 class="bfw-tool-title"><?php esc_html_e('Update user statuses', 'bonus-for-woo'); ?></h2>
    357344            </div>
    358345            <div class="bfw-tool-desc">
    359346                 <div style="margin-bottom: 20px;">
    360                     <p style="font-size:13px; margin-bottom:12px;"><?php _e('Force update all loyalty statuses based on current user balance.', 'bonus-for-woo'); ?></p>
     347                    <p style="font-size:13px; margin-bottom:12px;"><?php esc_html_e('Force update all loyalty statuses based on current user balance.', 'bonus-for-woo'); ?></p>
    361348                     <div class="bfw-alert bfw-alert-warning">
    362349                         <span class="dashicons dashicons-warning"></span>
    363                          <span> <?php _e('Update all users statuses. Requires a lot of resources!', 'bonus-for-woo'); ?></span>
     350                         <span> <?php esc_html_e('Update all users statuses. Requires a lot of resources!', 'bonus-for-woo'); ?></span>
    364351                     </div>
    365352                    <form method="post" action="">
    … …  
    368355                        <button type="submit" class="bfw-btn bfw-btn-outline">
    369356                            <span class="dashicons dashicons-update-alt"></span>
    370                             <?php _e('Update user statuses', 'bonus-for-woo'); ?>
     357                            <?php esc_html_e('Update user statuses', 'bonus-for-woo'); ?>
    371358                        </button>
    372359                    </form>
    … …  
    391378            <div class="bfw-tool-header">
    392379                <div class="bfw-tool-icon"><span class="dashicons dashicons-list-view"></span></div>
    393                 <h2 class="bfw-tool-title"><?php _e('Transaction ledger', 'bonus-for-woo'); ?></h2>
     380                <h2 class="bfw-tool-title"><?php esc_html_e('Transaction ledger', 'bonus-for-woo'); ?></h2>
    394381            </div>
    395382            <div class="bfw-tool-desc">
    396383                <div style="margin-bottom: 20px;">
    397384                    <p style="font-size:13px; margin-bottom:12px;">
    398                         <?php _e('The ledger records every operation on a balance and makes repeated accrual impossible. Run the migration once after upgrading, then use the audit to find balances that disagree with the ledger.', 'bonus-for-woo'); ?>
     385                        <?php esc_html_e('The ledger records every operation on a balance and makes repeated accrual impossible. Run the migration once after upgrading, then use the audit to find balances that disagree with the ledger.', 'bonus-for-woo'); ?>
    399386                    </p>
    400387
    … …  
    404391                        <button type="submit" class="bfw-btn bfw-btn-outline">
    405392                            <span class="dashicons dashicons-database-import"></span>
    406                             <?php _e('Import history into ledger', 'bonus-for-woo'); ?>
     393                            <?php esc_html_e('Import history into ledger', 'bonus-for-woo'); ?>
    407394                        </button>
    408395                    </form>
    … …  
    413400                        <button type="submit" class="bfw-btn bfw-btn-outline">
    414401                            <span class="dashicons dashicons-search"></span>
    415                             <?php _e('Audit balances against ledger', 'bonus-for-woo'); ?>
     402                            <?php esc_html_e('Audit balances against ledger', 'bonus-for-woo'); ?>
    416403                        </button>
    417404                    </form>
    … …  
    491478            <div class="bfw-tool-header">
    492479                <div class="bfw-tool-icon"><span class="dashicons dashicons-editor-paragraph"></span></div>
    493                 <h2 class="bfw-tool-title"><?php _e('Rules and Conditions Generator', 'bonus-for-woo'); ?></h2>
     480                <h2 class="bfw-tool-title"><?php esc_html_e('Rules and Conditions Generator', 'bonus-for-woo'); ?></h2>
    494481            </div>
    495482            <div class="bfw-tool-desc">
    … …  
    498485                <div>
    499486
    500                     <p style="font-size:13px; margin-bottom:12px;"><?php _e('This generator will create text for you based on the plugin settings.', 'bonus-for-woo'); ?></p>
     487                    <p style="font-size:13px; margin-bottom:12px;"><?php esc_html_e('This generator will create text for you based on the plugin settings.', 'bonus-for-woo'); ?></p>
    501488                    <a class="bfw-btn bfw-btn-outline" href="?page=bonus-for-woo/pages/generator.php">
    502489                        <span class="dashicons dashicons-editor-paragraph"></span>
    503                         <?php _e('Rules and Conditions Generator', 'bonus-for-woo'); ?>
     490                        <?php esc_html_e('Rules and Conditions Generator', 'bonus-for-woo'); ?>
    504491                    </a>
    505492                </div>
    … …  
    528515            success: function(response) {
    529516                if (response.data && response.data.done) {
    530                     $('#mass-progress').html('<span style="color:var(--bfw-success); font-weight:700;">✅ ' + '<?php _e('Completed Successfully!', 'bonus-for-woo'); ?>' + '</span>');
     517                    $('#mass-progress').html('<span style="color:var(--bfw-success); font-weight:700;">✅ ' + '<?php esc_html_e('Completed Successfully!', 'bonus-for-woo'); ?>' + '</span>');
    531518                    $('#mass-start').prop('disabled', false);
    532519                    return;
    533520                }
    534521                if (response.data && response.data.next_page) {
    535                     $('#mass-progress').html('<?php _e('Processing page:', 'bonus-for-woo'); ?> ' + massPage);
     522                    $('#mass-progress').html('<?php esc_html_e('Processing page:', 'bonus-for-woo'); ?> ' + massPage);
    536523                    massPage = response.data.next_page;
    537524                    setTimeout(processMassBatch, 300);
    … …  
    542529
    543530    $('#mass-start').on('click', function(){
    544         if(!confirm('<?php _e('Add points to ALL users?', 'bonus-for-woo'); ?>')) return;
     531        if(!confirm('<?php esc_html_e('Add points to ALL users?', 'bonus-for-woo'); ?>')) return;
    545532        massPage = 1;
    546533        $(this).prop('disabled', true);
    547         $('#mass-progress').html('🚀 <?php _e('Initiating...', 'bonus-for-woo'); ?>');
     534        $('#mass-progress').html('🚀 <?php esc_html_e('Initiating...', 'bonus-for-woo'); ?>');
    548535        processMassBatch();
    549536    });
    … …  
    554541        let search_by = jQuery('#search_by').val();
    555542
    556         if (!fileInput.files.length) return alert("<?php _e('Select CSV file', 'bonus-for-woo'); ?>");
    557 
     543        if (!fileInput.files.length) return alert("<?php esc_html_e('Select CSV file', 'bonus-for-woo'); ?>");
     544
     545        /*
     546         * Файл уходит в наш обработчик как есть, без промежуточной загрузки в
     547         * медиатеку: иначе CSV с балансами клиентов оставался на сайте по
     548         * постоянному публичному URL.
     549         */
    558550        let formData = new FormData();
    559         formData.append("action", "upload-attachment");
    560         formData.append("async-upload", fileInput.files[0]);
    561         formData.append("name", fileInput.files[0].name);
    562         formData.append("_ajax_nonce", "<?php echo wp_create_nonce('media-form'); ?>");
     551        formData.append("action", "bfw_export_bonuses");
     552        formData.append("search_by", search_by);
     553        formData.append("offset", 0);
     554        formData.append("nonce", "<?php echo esc_js(wp_create_nonce('bfw_export_bonuses_nonce')); ?>");
     555        formData.append("bfw_import_file", fileInput.files[0]);
    563556
    564557        let resultBlock = jQuery('#bfw-file-export-result');
    565         resultBlock.html('<?php _e('Uploading...', 'bonus-for-woo'); ?>');
    566 
    567         fetch("/wp-admin/async-upload.php", { method: "POST", body: formData })
    568         .then(r => r.text())
    569         .then(responseText => {
    570             jQuery.ajax({
    571                 type: 'POST',
    572                 url: ajaxurl,
    573                 data: {
    574                     action: 'bfw_export_bonuses',
    575                     response: responseText,
    576                     search_by: search_by,
    577                     offset: 0,
    578                     nonce: '<?php echo wp_create_nonce("bfw_export_bonuses_nonce"); ?>'
    579                 },
    580                 success: function (init) {
    581                     if (!init.success) {
    582                         resultBlock.html('<span style="color:var(--bfw-danger);"><?php _e('Initialization error', 'bonus-for-woo'); ?></span>');
    583                         return;
    584                     }
    585                     processImportBatch(responseText, search_by, init.data.next_offset, init.data.total || 5000);
     558        resultBlock.html('<?php esc_html_e('Importing...', 'bonus-for-woo'); ?>');
     559
     560        fetch(ajaxurl, { method: "POST", body: formData, credentials: 'same-origin' })
     561            .then(r => r.json())
     562            .then(res => {
     563                if (!res.success) {
     564                    resultBlock.html('<span style="color:var(--bfw-danger);">' + (res.data && res.data.message ? res.data.message : '<?php esc_html_e('Import error', 'bonus-for-woo'); ?>') + '</span>');
     565                    return;
    586566                }
    587             });
    588         });
     567                resultBlock.html('<span style="color:var(--bfw-success); font-weight:700;">✅ <?php esc_html_e('Import Complete!', 'bonus-for-woo'); ?></span>');
     568                jQuery('#bfw-file-export').val('');
     569            })
     570            .catch(() => resultBlock.html('<span style="color:var(--bfw-danger);"><?php esc_html_e('Import error', 'bonus-for-woo'); ?></span>'));
    589571    };
    590 
    591     function processImportBatch(response, search_by, offset, total) {
    592         let resultBlock = jQuery('#bfw-file-export-result');
    593         jQuery.ajax({
    594             type: 'POST',
    595             url: ajaxurl,
    596             data: {
    597                 action: 'bfw_export_bonuses',
    598                 response: response,
    599                 search_by: search_by,
    600                 offset: offset,
    601                 nonce: '<?php echo wp_create_nonce("bfw_export_bonuses_nonce"); ?>'
    602             },
    603             success: function (res) {
    604                 if (!res.success) { resultBlock.html('<span style="color:var(--bfw-danger);"><?php _e('Import error', 'bonus-for-woo'); ?></span>'); return; }
    605                 if (!res.data.done) {
    606                     let nextOffset = res.data.next_offset;
    607                     let percent = Math.round((nextOffset / total) * 100);
    608                     resultBlock.html('<?php _e('Importing:', 'bonus-for-woo'); ?> ' + (percent > 100 ? 100 : percent) + '%');
    609                     processImportBatch(response, search_by, nextOffset, total);
    610                 } else {
    611                     resultBlock.html('<span style="color:var(--bfw-success); font-weight:700;">✅ <?php _e('Import Complete!', 'bonus-for-woo'); ?></span>');
    612                     jQuery('#bfw-file-export').val('');
    613                 }
    614             }
    615         });
    616     }
    617572});
    618573</script>
  • bonus-for-woo/trunk/readme.txt

    r3784081 r3795776  
    55Requires at least:  6.2
    66Tested up to:  7.1
    7 WC requires at least: 6.0
    8 WC tested up to: 11.0.1
    9 Stable tag: 8.5.0
     7WC requires at least: 6.2
     8WC tested up to: 11.2.1
     9Stable tag: 8.5.2
    1010Requires PHP: 7.4
    1111License: GPLv3 or later
    … …  
    8383== Installation ==
    8484
    85 1. Upload dir `bonus-for-woo-computy` to the `/wp-content/plugins/` directory
     851. Upload dir `bonus-for-woo` to the `/wp-content/plugins/` directory
    86862. Activate the plugin through the 'Plugins' menu in WordPress
    8787
     88
     89== External services ==
     90
     91This plugin connects to the plugin author's server in the following cases.
     92
     93= License activation (PRO) =
     94The PRO key is verified against `https://computy.ru/API/api.php`. The request sends the license key, the site domain, the plugin version and the product identifier. The server returns a signed token that is stored in the site options.
     95
     96* Service: computy.ru
     97* Data sent: license key, site domain, plugin version, product ID
     98* When: on activation and no more than once every 7 days
     99* Terms: https://computy.ru/privacy
     100
     101= Buy button =
     102The "Buy" button on the PRO settings screen posts the site URL, the plugin version and the site administrator email to `https://computy.ru/buy_plugin`.
     103
     104* Service: computy.ru
     105* Data sent: site URL, plugin version, admin email
     106* When: only when the administrator clicks the button
     107* Terms: https://computy.ru/privacy
     108
     109= Blog articles widget =
     110The plugin settings screen loads the list of the author's recent articles from `https://computy.ru/blog/tag/bfw/` and caches the result in a transient.
     111
     112* Service: computy.ru
     113* Data sent: no visitor data, an ordinary HTTP request
     114* When: when the plugin settings screen is opened while the cache is empty
     115* Terms: https://computy.ru/privacy
    88116
    89117== Screenshots ==
    … …  
    124152== Changelog ==
    125153
    126 = 8.5.0 - 2026-11-04 =
     154= 8.5.2 - 2026-11-04 =
     155* Security: the coupons list no longer interpolates the `order`/`orderby` query arguments; only an allowlist is accepted.
     156* Security: failed coupon code entries are rate-limited, so codes can no longer be brute-forced.
     157* Security: the points export is streamed and never written to disk; the download link is nonce-protected.
     158* Security: the redirect parameter is validated against the site host on every write-off entry point.
     159* Security: CSV import reads the uploaded file instead of a client-supplied path.
     160* Security: coupon actions require the plugin capability, not just a nonce.
     161* One capability (BFW_CAP_MANAGE) now drives both the menu and the AJAX handlers.
     162* Points export is batched (500 users per query) instead of loading every customer at once.
     163* Removed an unused coupon import handler and the leftover QR / DataTables demo files.
     164* A critical error that occurred when entering an empty coupon has been fixed.
     165
     166= 8.5.1 - 2026-11-04 =
    127167* Cashback percent, points and sums now follow the WooCommerce "Number of decimals" setting: 0 decimals shows 3%, 250; 2 decimals shows 3.50%, 250.75. Rounding of points uses the same setting.
    128168* Points export file moved from the plugin directory to uploads — a plugin installed from the admin screen could not write there. Download link is now nonce-protected.
    … …  
    224264== Upgrade Notice ==
    225265
    226 = 9.0.0 =
    227 * Super update.
     266= 8.5.2 =
     267* Points export is now streamed and is never written to the disk.
     268* Coupons: the list respects a strict ORDER BY whitelist, and repeated failed code entries are rate-limited.
     269* Cashback percent and points follow the WooCommerce number of decimals setting.
Note: See TracChangeset for help on using the changeset viewer.