Plugin Directory

Changeset 3755015


Ignore:
Timestamp:
10/19/2026 02:48:39 PM (3 weeks ago)
Author:
wp-buy
Message:

This update makes site monitoring and content protection simpler.

You can now view insights directly from your site’s top bar without opening the dashboard. A new red counter flags only genuinely new issues without slowing down page loading.

A one-click "Ask AI" tool lets you copy a clear summary of top errors directly into your clipboard, making it effortless to paste into ChatGPT for easy, step-by-step troubleshooting.

The settings area is also less cluttered, hiding distracting alerts behind a toggle.

Finally, content protection is stronger: right-click alerts now show immediately, Mac shortcut bypasses are blocked, and search pages are covered.

Location:
wp-content-copy-protector
Files:
83 added
12 edited

Legend:

Unmodified
Added
Removed
  • wp-content-copy-protector/trunk/activity-panel.php

    r3707909 r3755015  
    2020$wccp_free_ac_labels  = wccp_free_activity_labels();
    2121$wccp_free_ac_started = wccp_free_activity_started();
    22 $wccp_free_ac_upgrade = add_query_arg(array('utm_source' => 'plugin', 'utm_medium' => 'protection-center'), $wccp_free_pro_url);
    2322
    2423if (! function_exists('wccp_free_ac_num')) {
    … …  
    166165                                                <p><?php esc_html_e('See 30 days, 90 days or 12 months of activity, spot seasonal peaks and follow how each page trends over time.', 'wp-content-copy-protector'); ?></p>
    167166                                        </div>
    168                                         <a class="wpb-btn wpb-btn--primary" target="_blank" rel="noopener" href="<?php echo esc_url($wccp_free_ac_upgrade); ?>"><?php wccp_free_icon('sparkles'); ?><?php esc_html_e('See PRO', 'wp-content-copy-protector'); ?></a>
     167                                        <a class="wpb-btn wpb-btn--primary" target="_blank" rel="noopener" href="<?php echo esc_url(wccp_free_utm_url('protection-center-history-btn-primary')); ?>"><?php wccp_free_icon('sparkles'); ?><?php esc_html_e('See PRO', 'wp-content-copy-protector'); ?></a>
    169168                                </div>
    170169                        </div>
    … …  
    401400                                                </div>
    402401                                                <div class="wpb-ac-hint__acts">
    403                                                         <a class="wpb-btn wpb-btn--primary" target="_blank" rel="noopener" href="<?php echo esc_url($wccp_free_ac_upgrade); ?>"><?php echo esc_html($wccp_free_ac_hint['cta']); ?></a>
     402                                                        <a class="wpb-btn wpb-btn--primary" target="_blank" rel="noopener" href="<?php echo esc_url(wccp_free_utm_url('protection-center-hint-btn-primary')); ?>"><?php echo esc_html($wccp_free_ac_hint['cta']); ?></a>
    404403                                                        <button type="button" class="wpb-btn wpb-btn--quiet" data-ac-hide-hint><?php esc_html_e('Not now', 'wp-content-copy-protector'); ?></button>
    405404                                                </div>
  • wp-content-copy-protector/trunk/activity.php

    r3707909 r3755015  
    656656        $table = wccp_free_activity_table();
    657657
     658        // hits is not in the primary key, so sorting by (hour, hits) over the whole
     659        // table forces a filesort. Pin the latest hour via the index, then rank only its rows.
    658660        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- Own table, no input.
    659         $row = $wpdb->get_row("SELECT hour, object_id FROM $table ORDER BY hour DESC, hits DESC LIMIT 1", ARRAY_A);
     661        $row = $wpdb->get_row("SELECT hour, object_id FROM $table WHERE hour = (SELECT MAX(hour) FROM $table) ORDER BY hits DESC LIMIT 1", ARRAY_A);
    660662
    661663        if (! $row) {
  • wp-content-copy-protector/trunk/admin-bar-insights.php

    r3707909 r3755015  
    33/**
    44 * Admin bar insights: the card that opens when an admin hovers the plugin's
    5  * admin bar icon inside wp-admin.
     5 * admin bar icon, in wp-admin and on the front end, plus the "new errors"
     6 * count shown on the icon.
    67 *
    78 * It is a teaser, not a report: one headline number, a 7 day sparkline, error
    … …  
    2223 * - nothing is computed while a page loads except the icon dot, which reads
    2324 *   the same cached count as the admin menu bubble and a stored error snapshot
    24  *   (rate-limited, see wccp_free_abi_dot());
     25 *   (rate-limited, see wccp_free_abi_dot()), and the error count, which reads
     26 *   that stored snapshot too; a stale snapshot is refreshed over admin-ajax
     27 *   after the page has loaded (wccp_free_abi_ajax_count());
    2528 * - the card's data comes over admin-ajax on first hover, cached per user for
    2629 *   WCCP_FREE_ABI_CACHE; log files are read at most every WCCP_FREE_ABI_ERR_TTL.
    … …  
    3841
    3942/**
    40  * Can the current screen show the card at all?
     43 * Can the current screen show the card at all? wp-admin and the front end alike.
    4144 */
    4245function wccp_free_abi_available()
    … …  
    4447        global $wccp_settings;
    4548
    46         if (! is_admin() || ! is_admin_bar_showing() || ! current_user_can('manage_options')) {
     49        if (! is_admin_bar_showing() || is_customize_preview() || ! current_user_can('manage_options')) {
    4750                return false;
    4851        }
    … …  
    208211
    209212/**
     213 * The icon's error count: issues new since the admin last opened the Error
     214 * Monitor (or in the last 24 hours), the same number as the card's "New
     215 * issues" tile. Reads the stored snapshot only, so it can run on every page.
     216 *
     217 * @param bool $fresh Rescan the logs if the snapshot is stale (admin-ajax only).
     218 * @return array{count:int, stale:bool, text:string, label:string}
     219 */
     220function wccp_free_abi_error_badge($fresh = false)
     221{
     222        $snapshot = wccp_free_abi_errors($fresh);
     223        $count    = 0;
     224        $stale    = true;
     225
     226        if (is_array($snapshot)) {
     227                $today = (new DateTime('now', wccp_free_em_site_zone()))->format('Y-m-d');
     228                $stale = $snapshot['day'] !== $today || $snapshot['time'] <= time() - WCCP_FREE_ABI_ERR_TTL;
     229                $count = wccp_free_abi_new_issues($snapshot, (int) get_user_meta(get_current_user_id(), 'wccp_free_em_seen', true));
     230        }
     231
     232        return array(
     233                'count' => $count,
     234                'stale' => $stale,
     235                'text'  => $count > 99 ? '99+' : number_format_i18n($count),
     236                /* translators: %s: number of new error issues. */
     237                'label' => sprintf(_n('%s new error', '%s new errors', $count, 'wp-content-copy-protector'), number_format_i18n($count)),
     238        );
     239}
     240
     241/**
    210242 * Numbers and stories from the error logs.
    211243 *
    … …  
    777809
    778810/**
     811 * The page rendered the error count from a stale snapshot: rescan (at most
     812 * every WCCP_FREE_ABI_ERR_TTL, shared by every admin) and return the new count.
     813 */
     814function wccp_free_abi_ajax_count()
     815{
     816        check_ajax_referer('wccp_free_abi');
     817
     818        if (! wccp_free_em_can()) {
     819                wp_send_json_error(null, 403);
     820        }
     821
     822        $badge = wccp_free_abi_error_badge(true);
     823        unset($badge['stale']);
     824
     825        // The card's cached tiles predate the rescan.
     826        wccp_free_abi_forget();
     827
     828        wp_send_json_success($badge);
     829}
     830add_action('wp_ajax_wccp_free_abi_count', 'wccp_free_abi_ajax_count');
     831
     832/**
     833 * Append the error count to the plugin's admin bar item. The element is always
     834 * there (hidden at 0) so the script can fill it after a refresh.
     835 */
     836function wccp_free_abi_bar_count($admin_bar)
     837{
     838        if (! wccp_free_abi_available() || ! wccp_free_em_can()) {
     839                return;
     840        }
     841
     842        $node = $admin_bar->get_node('wccp_free_top_button');
     843        if (! $node) {
     844                return;
     845        }
     846
     847        $badge = wccp_free_abi_error_badge();
     848
     849        $admin_bar->add_node(array(
     850                'id'    => 'wccp_free_top_button',
     851                'title' => $node->title . sprintf(
     852                        '<span class="wccp-abi-count count-%1$d"><span aria-hidden="true">%2$s</span><span class="screen-reader-text">%3$s</span></span>',
     853                        $badge['count'],
     854                        esc_html($badge['text']),
     855                        esc_html($badge['label'])
     856                ),
     857        ));
     858}
     859add_action('admin_bar_menu', 'wccp_free_abi_bar_count', 41);
     860
     861/**
    779862 * Should the icon carry a "something new" dot on this page load?
    780863 *
    … …  
    857940}
    858941
    859 function wccp_free_abi_enqueue($hook)
    860 {
    861         // The plugin page itself is where the card would send you.
    862         if ($hook === 'toplevel_page_wccpoptionspro' || ! wccp_free_abi_available()) {
     942/**
     943 * @param string $hook Admin page hook; empty on the front end.
     944 */
     945function wccp_free_abi_enqueue($hook = '')
     946{
     947        if (! wccp_free_abi_available()) {
    863948                return;
    864949        }
    865950
     951        // The error count is on every page.
    866952        wp_enqueue_style(
    867953                'wccp-admin-bar-insights',
    … …  
    870956                WCCP_FREE_VERSION
    871957        );
     958
     959        // The plugin page itself is where the card would send you.
     960        if ($hook === 'toplevel_page_wccpoptionspro') {
     961                return;
     962        }
     963
     964        $badge = wccp_free_em_can() ? wccp_free_abi_error_badge() : null;
    872965
    873966        wp_enqueue_script(
    … …  
    883976                'nonce'   => wp_create_nonce('wccp_free_abi'),
    884977                'dot'     => wccp_free_abi_dot(),
     978                'stale'   => $badge && $badge['stale'],
    885979                'i18n'    => array(
    886980                        'loading'  => __('Loading your numbers…', 'wp-content-copy-protector'),
    … …  
    895989}
    896990add_action('admin_enqueue_scripts', 'wccp_free_abi_enqueue', 30);
     991add_action('wp_enqueue_scripts', 'wccp_free_abi_enqueue', 30);
  • wp-content-copy-protector/trunk/admin-core.php

    r3707909 r3755015  
    212212
    213213$wccp_free_admin_email = get_bloginfo('admin_email');
    214 $wccp_free_pro_url     = 'https://www.wp-buy.com/product/wp-content-copy-protection-pro/';
     214$wccp_free_pro_url     = WCCP_FREE_PRO_URL;
    215215$wccp_free_docs_url    = 'https://www.wp-buy.com/category/plugins-support/copy-protection-plugin-support/';
    216216$wccp_free_support_url = 'https://www.wp-buy.com/contact-us/';
    … …  
    244244                if (localStorage.getItem('wpccp_subscribed') != 'wpccp_subsbc_user') {
    245245                        var admin_email_wccp = document.getElementById('admin_email_wccp').value;
    246                         window.open('https://www.wp-buy.com/wpccp-subscribe/?email=' + admin_email_wccp, '_blank');
     246                        window.open(<?php echo wp_json_encode(wccp_free_utm_url('header-subscribe-popup')); ?> + '&email=' + encodeURIComponent(admin_email_wccp), '_blank');
    247247                }
    248248        }
    … …  
    289289                <div class="wpb-featured__buy">
    290290                        <div class="wpb-featured__price"><?php esc_html_e('Live Demo', 'wp-content-copy-protector'); ?></div>
    291                         <a class="wpb-featured__btn" target="_blank" rel="noopener" href="<?php echo esc_url($wccp_free_pro_url); ?>"><?php esc_html_e('View Details', 'wp-content-copy-protector'); ?></a>
     291                        <a class="wpb-featured__btn" target="_blank" rel="noopener" href="<?php echo esc_url(wccp_free_utm_url('header-featured-banner-btn')); ?>"><?php esc_html_e('View Details', 'wp-content-copy-protector'); ?></a>
    292292                        <span class="wpb-featured__mbg"><?php esc_html_e('30-Day Money Back', 'wp-content-copy-protector'); ?></span>
    293293                </div>
    … …  
    383383                                                <span class="wpb-field__label"><?php echo wp_kses(__('Exclude <u>Admin</u> from protection', 'wp-content-copy-protector'), $wccp_free_allowed_tags); ?></span>
    384384                                                <span class="wpb-field__locked">
    385                                                         <a class="wpb-tag wpb-tag--pro" target="_blank" rel="noopener" href="<?php echo esc_url($wccp_free_pro_url); ?>"><?php wccp_free_icon('lock'); ?><?php esc_html_e('Premium', 'wp-content-copy-protector'); ?></a>
     385                                                        <a class="wpb-tag wpb-tag--pro" target="_blank" rel="noopener" href="<?php echo esc_url(wccp_free_utm_url('main-settings-btn-exclude-admin')); ?>"><?php wccp_free_icon('lock'); ?><?php esc_html_e('Premium', 'wp-content-copy-protector'); ?></a>
    386386                                                </span>
    387387                                                <p class="wpb-field__hint"><?php echo wp_kses(__('If <u>Yes</u>, The protection functions will be inactive for the admin when he is logged in', 'wp-content-copy-protector'), $wccp_free_allowed_tags); ?></p>
    … …  
    405405                                                <span class="wpb-chip"><?php wccp_free_icon('phone'); ?><?php esc_html_e('Desktop & mobile', 'wp-content-copy-protector'); ?></span>
    406406                                        </div>
    407                                         <a class="wpb-btn wpb-btn--ghost" target="_blank" rel="noopener" href="<?php echo esc_url($wccp_free_pro_url); ?>"><?php wccp_free_icon('sparkles'); ?><?php esc_html_e('Unlock more options', 'wp-content-copy-protector'); ?></a>
     407                                        <a class="wpb-btn wpb-btn--ghost" target="_blank" rel="noopener" href="<?php echo esc_url(wccp_free_utm_url('main-settings-btn-unlock-options')); ?>"><?php wccp_free_icon('sparkles'); ?><?php esc_html_e('Unlock more options', 'wp-content-copy-protector'); ?></a>
    408408                                </div>
    409409                        </div>
    … …  
    451451                                                <span class="wpb-field__label"><?php echo wp_kses(__('Disable <u>RightClick</u> on', 'wp-content-copy-protector'), $wccp_free_allowed_tags); ?></span>
    452452                                                <span class="wpb-field__locked">
    453                                                         <a class="wpb-tag wpb-tag--pro" target="_blank" rel="noopener" href="<?php echo esc_url($wccp_free_pro_url); ?>"><?php wccp_free_icon('lock'); ?><?php esc_html_e('Any post type', 'wp-content-copy-protector'); ?></a>
     453                                                        <a class="wpb-tag wpb-tag--pro" target="_blank" rel="noopener" href="<?php echo esc_url(wccp_free_utm_url('rightclick-tag-pro-post-types')); ?>"><?php wccp_free_icon('lock'); ?><?php esc_html_e('Any post type', 'wp-content-copy-protector'); ?></a>
    454454                                                </span>
    455455                                                <p class="wpb-field__hint"><?php echo wp_kses(__('<b>PRO</b> unlocks the same right-click lock on <u>any</u> post type you pick - WooCommerce products, listings, portfolios, courses, downloads. Sell on a marketplace? Your product photos and descriptions stop being one right-click away from a competitor.', 'wp-content-copy-protector'), $wccp_free_allowed_tags); ?></p>
    … …  
    467467
    468468                                <div class="wpb-shots">
    469                                         <a class="wpb-shot" target="_blank" rel="noopener" href="<?php echo esc_url($wccp_free_pro_url); ?>">
     469                                        <a class="wpb-shot" target="_blank" rel="noopener" href="<?php echo esc_url(wccp_free_utm_url('rightclick-premium-preview-image')); ?>">
    470470                                                <img src="<?php echo esc_url($wccp_free_pluginsurl); ?>/images/right-click-protection.webp" alt="<?php esc_attr_e('Right click protection options in the PRO version', 'wp-content-copy-protector'); ?>">
    471471                                        </a>
    … …  
    476476                                                <span class="wpb-chip"><?php wccp_free_icon('star'); ?><?php esc_html_e('Preview & Pricing', 'wp-content-copy-protector'); ?></span>
    477477                                        </div>
    478                                         <a class="wpb-btn wpb-btn--primary" target="_blank" rel="noopener" href="<?php echo esc_url($wccp_free_pro_url); ?>"><?php wccp_free_icon('sparkles'); ?><?php esc_html_e('Get the PRO version', 'wp-content-copy-protector'); ?></a>
     478                                        <a class="wpb-btn wpb-btn--primary" target="_blank" rel="noopener" href="<?php echo esc_url(wccp_free_utm_url('rightclick-btn-primary-get-pro')); ?>"><?php wccp_free_icon('sparkles'); ?><?php esc_html_e('Get the PRO version', 'wp-content-copy-protector'); ?></a>
    479479                                </div>
    480480                        </div>
    … …  
    510510                                                <span class="wpb-field__label"><?php echo wp_kses(__('<b>Pages</b> Protection by CSS', 'wp-content-copy-protector'), $wccp_free_allowed_tags); ?></span>
    511511                                                <span class="wpb-field__locked">
    512                                                         <a class="wpb-tag wpb-tag--pro" target="_blank" rel="noopener" href="<?php echo esc_url($wccp_free_pro_url); ?>"><?php wccp_free_icon('lock'); ?><?php esc_html_e('Premium', 'wp-content-copy-protector'); ?></a>
     512                                                        <a class="wpb-tag wpb-tag--pro" target="_blank" rel="noopener" href="<?php echo esc_url(wccp_free_utm_url('css-protection-tag-pro')); ?>"><?php wccp_free_icon('lock'); ?><?php esc_html_e('Premium', 'wp-content-copy-protector'); ?></a>
    513513                                                </span>
    514514                                                <p class="wpb-field__hint"><?php esc_html_e('Protect your static pages by CSS tricks', 'wp-content-copy-protector'); ?></p>
    … …  
    520520                                                <span class="wpb-chip wpb-chip--safe"><?php wccp_free_icon('check'); ?><?php esc_html_e('Keeps working with JavaScript disabled', 'wp-content-copy-protector'); ?></span>
    521521                                        </div>
    522                                         <a class="wpb-btn wpb-btn--ghost" target="_blank" rel="noopener" href="<?php echo esc_url($wccp_free_pro_url); ?>"><?php wccp_free_icon('lock'); ?><?php esc_html_e('Unlock pages protection', 'wp-content-copy-protector'); ?></a>
     522                                        <a class="wpb-btn wpb-btn--ghost" target="_blank" rel="noopener" href="<?php echo esc_url(wccp_free_utm_url('css-protection-btn-unlock-pages')); ?>"><?php wccp_free_icon('lock'); ?><?php esc_html_e('Unlock pages protection', 'wp-content-copy-protector'); ?></a>
    523523                                </div>
    524524                        </div>
    … …  
    700700                                        <!-- Icons the script clones into the markup it builds. -->
    701701                                        <template id="wpb-em-icons">
    702                                                 <?php foreach (array('alert', 'check', 'info', 'clock', 'message', 'download', 'trash', 'server', 'file', 'copy') as $wccp_free_em_icon) : ?>
     702                                                <?php foreach (array('alert', 'check', 'info', 'clock', 'message', 'download', 'trash', 'server', 'file', 'copy', 'sparkles', 'lock') as $wccp_free_em_icon) : ?>
    703703                                                        <span data-icon="<?php echo esc_attr($wccp_free_em_icon); ?>"><?php wccp_free_icon($wccp_free_em_icon); ?></span>
    704704                                                <?php endforeach; ?>
    … …  
    743743
    744744                                <div class="wpb-shots">
    745                                         <a class="wpb-shot" target="_blank" rel="noopener" href="<?php echo esc_url($wccp_free_pro_url); ?>">
     745                                        <a class="wpb-shot" target="_blank" rel="noopener" href="<?php echo esc_url(wccp_free_utm_url('more-pro-shot-smartphones')); ?>">
    746746                                                <img src="<?php echo esc_url($wccp_free_pluginsurl); ?>/images/smart-phones-protection.webp" alt="<?php esc_attr_e('Smartphone protection in the PRO version', 'wp-content-copy-protector'); ?>">
    747747                                        </a>
    748                                         <a class="wpb-shot" target="_blank" rel="noopener" href="<?php echo esc_url($wccp_free_pro_url); ?>">
     748                                        <a class="wpb-shot" target="_blank" rel="noopener" href="<?php echo esc_url(wccp_free_utm_url('more-pro-shot-watermark')); ?>">
    749749                                                <img src="<?php echo esc_url($wccp_free_pluginsurl); ?>/images/watermark-adv.webp" alt="<?php esc_attr_e('Image watermarking in the PRO version', 'wp-content-copy-protector'); ?>">
    750750                                        </a>
    751                                         <a class="wpb-shot" target="_blank" rel="noopener" href="<?php echo esc_url($wccp_free_pro_url); ?>">
     751                                        <a class="wpb-shot" target="_blank" rel="noopener" href="<?php echo esc_url(wccp_free_utm_url('more-pro-shot-watermark-examples')); ?>">
    752752                                                <img src="<?php echo esc_url($wccp_free_pluginsurl); ?>/images/watermarking-adv-examples.webp" alt="<?php esc_attr_e('Watermarking examples from the PRO version', 'wp-content-copy-protector'); ?>">
    753753                                        </a>
    … …  
    760760                                                <span class="wpb-chip"><?php wccp_free_icon('star'); ?><?php esc_html_e('Rated 4.9 by hundreds', 'wp-content-copy-protector'); ?></span>
    761761                                        </div>
    762                                         <a class="wpb-btn wpb-btn--primary" target="_blank" rel="noopener" href="<?php echo esc_url($wccp_free_pro_url); ?>"><?php wccp_free_icon('sparkles'); ?><?php esc_html_e('See it now', 'wp-content-copy-protector'); ?></a>
     762                                        <a class="wpb-btn wpb-btn--primary" target="_blank" rel="noopener" href="<?php echo esc_url(wccp_free_utm_url('more-pro-btn-primary-see-it-now')); ?>"><?php wccp_free_icon('sparkles'); ?><?php esc_html_e('See it now', 'wp-content-copy-protector'); ?></a>
    763763                                </div>
    764764                        </div>
    … …  
    784784                                                <span class="wpb-plans__promo-ic"><?php wccp_free_icon('zap'); ?></span>
    785785                                                <p class="wpb-plans__promo-txt">
    786                                                         <b><?php esc_html_e('These are the full prices, with no discount applied.', 'wp-content-copy-protector'); ?></b>
    787786                                                        <?php esc_html_e('A discount of up to 50% is waiting for you on our website - check the running offer before you buy.', 'wp-content-copy-protector'); ?>
    788787                                                </p>
    … …  
    800799                                                        </div>
    801800
    802                                                         <p class="wpb-plan__price">
    803                                                                 <span class="wpb-plan__amount"><span class="wpb-plan__cur">$</span>0</span>
    804                                                                 <span class="wpb-plan__per"><?php esc_html_e('/ forever', 'wp-content-copy-protector'); ?></span>
    805                                                         </p>
    806                                                         <span class="wpb-tag wpb-tag--free wpb-plan__badge"><?php wccp_free_icon('check'); ?><?php esc_html_e('Current plan', 'wp-content-copy-protector'); ?></span>
     801                                                        <p class="wpb-plan__pitch"><?php esc_html_e('Everyday protection that keeps casual copying off your pages.', 'wp-content-copy-protector'); ?></p>
     802                                                        <span class="wpb-tag wpb-tag--free wpb-plan__badge"><?php wccp_free_icon('check'); ?><?php esc_html_e('Current plan / forever', 'wp-content-copy-protector'); ?></span>
    807803
    808804                                                        <ul class="wpb-plan__list">
    … …  
    826822                                                        </div>
    827823
    828                                                         <p class="wpb-plan__price">
    829                                                                 <span class="wpb-plan__amount"><span class="wpb-plan__cur">$</span>58</span>
    830                                                                 <span class="wpb-plan__per"><?php esc_html_e('one-time', 'wp-content-copy-protector'); ?></span>
    831                                                         </p>
    832                                                         <span class="wpb-tag wpb-tag--layer wpb-plan__badge"><?php wccp_free_icon('clock'); ?><?php esc_html_e('Lifetime license', 'wp-content-copy-protector'); ?></span>
     824                                                        <p class="wpb-plan__pitch"><?php esc_html_e('Take full command of who sees, copies and prints your content.', 'wp-content-copy-protector'); ?></p>
     825                                                        <span class="wpb-tag wpb-tag--layer wpb-plan__badge"><?php wccp_free_icon('clock'); ?><?php esc_html_e('Lifetime license / one time payment', 'wp-content-copy-protector'); ?></span>
    833826                                                        <p class="wpb-plan__care"><?php wccp_free_icon('refresh'); ?><?php esc_html_e('Support & updates $1 / month, paid as $9 per year.', 'wp-content-copy-protector'); ?></p>
    834827
    … …  
    843836                                                        </ul>
    844837
    845                                                         <a class="wpb-btn wpb-btn--ghost wpb-plan__cta" target="_blank" rel="noopener" href="<?php echo esc_url($wccp_free_pro_url); ?>"><?php esc_html_e('Upgrade to ESSENTIAL', 'wp-content-copy-protector'); ?></a>
     838                                                        <a class="wpb-btn wpb-btn--ghost wpb-plan__cta" target="_blank" rel="noopener" href="<?php echo esc_url(wccp_free_utm_url('more-pro-plan-essential-btn')); ?>"><?php esc_html_e('Explore the essential version', 'wp-content-copy-protector'); ?></a>
    846839                                                </div>
    847840
    … …  
    857850                                                        </div>
    858851
    859                                                         <p class="wpb-plan__price">
    860                                                                 <span class="wpb-plan__amount"><span class="wpb-plan__cur">$</span>98</span>
    861                                                                 <span class="wpb-plan__per"><?php esc_html_e('one-time', 'wp-content-copy-protector'); ?></span>
    862                                                         </p>
    863                                                         <span class="wpb-tag wpb-tag--layer wpb-plan__badge"><?php wccp_free_icon('clock'); ?><?php esc_html_e('Lifetime license', 'wp-content-copy-protector'); ?></span>
     852                                                        <p class="wpb-plan__pitch"><?php esc_html_e('Every protection layer, plus watermarks that follow your work everywhere.', 'wp-content-copy-protector'); ?></p>
     853                                                        <span class="wpb-tag wpb-tag--layer wpb-plan__badge"><?php wccp_free_icon('clock'); ?><?php esc_html_e('Lifetime license / one time payment', 'wp-content-copy-protector'); ?></span>
    864854                                                        <p class="wpb-plan__care"><?php wccp_free_icon('refresh'); ?><?php esc_html_e('Support & updates $1 / month, paid as $9 per year.', 'wp-content-copy-protector'); ?></p>
    865855
    … …  
    877867                                                        </ul>
    878868
    879                                                         <a class="wpb-btn wpb-btn--primary wpb-plan__cta" target="_blank" rel="noopener" href="<?php echo esc_url($wccp_free_pro_url); ?>"><?php esc_html_e('Upgrade to COMPLETE', 'wp-content-copy-protector'); ?></a>
     869                                                        <a class="wpb-btn wpb-btn--primary wpb-plan__cta" target="_blank" rel="noopener" href="<?php echo esc_url(wccp_free_utm_url('more-pro-plan-complete-btn')); ?>"><?php esc_html_e('Explore the complete version', 'wp-content-copy-protector'); ?></a>
    880870                                                </div>
    881871
    … …  
    10521042                                                        /* translators: %s: link to the mailing list sign-up page. */
    10531043                                                        esc_html__('%s to our mailing list to get flash discounts', 'wp-content-copy-protector'),
    1054                                                         '<a target="_blank" rel="noopener" href="https://www.wp-buy.com/wpccp-subscribe">' . esc_html__('Subscribe', 'wp-content-copy-protector') . '</a>'
     1044                                                        '<a target="_blank" rel="noopener" href="' . esc_url(wccp_free_utm_url('more-pro-footer-subscribe-link', 'https://www.wp-buy.com/wpccp-subscribe/')) . '">' . esc_html__('Subscribe', 'wp-content-copy-protector') . '</a>'
    10551045                                                );
    10561046                                                ?>
    10571047                                        </p>
    1058                                         <a class="wpb-btn wpb-btn--primary" target="_blank" rel="noopener" href="<?php echo esc_url($wccp_free_pro_url); ?>"><?php wccp_free_icon('sparkles'); ?><?php esc_html_e('See the premium plans', 'wp-content-copy-protector'); ?></a>
     1048                                        <a class="wpb-btn wpb-btn--primary" target="_blank" rel="noopener" href="<?php echo esc_url(wccp_free_utm_url('more-pro-footer-btn-primary-plans')); ?>"><?php wccp_free_icon('sparkles'); ?><?php esc_html_e('See the premium plans', 'wp-content-copy-protector'); ?></a>
    10591049                                </div>
    10601050                        </div>
  • wp-content-copy-protector/trunk/css/admin-bar-insights.css

    r3707909 r3755015  
    5252}
    5353
     54/* On the front end the theme's styles apply too. */
     55#wccp-abi {
     56        text-align: start;
     57        letter-spacing: normal;
     58        text-transform: none;
     59}
     60
     61#wccp-abi li {
     62        list-style: none;
     63}
     64
     65#wccp-abi li::before,
     66#wccp-abi li::after {
     67        content: none;
     68}
     69
     70#wccp-abi a {
     71        border: 0;
     72        box-shadow: none;
     73}
     74
    5475/* Hover bridge over the gap between the icon and the card. */
    5576#wccp-abi::after {
    … …  
    421442}
    422443
     444/* New errors since the Error Monitor was last opened. */
     445#wpadminbar #wp-admin-bar-wccp_free_top_button .wccp-abi-count {
     446        display: inline-block;
     447        box-sizing: border-box;
     448        min-width: 18px;
     449        height: 18px;
     450        margin-inline-start: 6px;
     451        padding: 0 5px;
     452        border-radius: 9px;
     453        background: #d63638;
     454        color: #fff;
     455        font-size: 11px;
     456        font-weight: 600;
     457        line-height: 18px;
     458        text-align: center;
     459        vertical-align: middle;
     460}
     461
     462/* #wpadminbar * sets a 32px line-height and 13px font on every element. */
     463#wpadminbar #wp-admin-bar-wccp_free_top_button .wccp-abi-count > span[aria-hidden] {
     464        display: block;
     465        color: inherit;
     466        font-size: inherit;
     467        font-weight: inherit;
     468        line-height: inherit;
     469}
     470
     471#wpadminbar #wp-admin-bar-wccp_free_top_button .wccp-abi-count.count-0 {
     472        display: none;
     473}
     474
     475/* Pulsing yellow ring around the item while the error count is above 0.
     476   Driven by the count's class, so it follows the AJAX refresh too; the
     477   upgrade hint's purple ring takes precedence while that card is open. */
     478#wpadminbar #wp-admin-bar-wccp_free_top_button:not(.wccp-hint-target) > .ab-item:has(.wccp-abi-count:not(.count-0)) {
     479        position: relative;
     480}
     481
     482#wpadminbar #wp-admin-bar-wccp_free_top_button:not(.wccp-hint-target) > .ab-item:has(.wccp-abi-count:not(.count-0))::after {
     483        content: "";
     484        position: absolute;
     485        inset: 3px;
     486        border-radius: 6px;
     487        box-shadow: 0 0 0 0 rgba(250, 204, 21, .9);
     488        animation: wccp-abi-alert 10s ease-out infinite;
     489        pointer-events: none;
     490}
     491
    423492#wpadminbar #wp-admin-bar-wccp_free_top_button.wccp-abi-open > .ab-item {
    424493        background: #2c3338;
    425494        color: #72aee6;
     495}
     496
     497/* Keep the item still on the plugin's own settings page (every tab): the
     498   admin is already where the ring and dot are pointing. */
     499body.toplevel_page_wccpoptionspro #wpadminbar #wp-admin-bar-wccp_free_top_button .wccp-abi-dot,
     500body.toplevel_page_wccpoptionspro #wpadminbar #wp-admin-bar-wccp_free_top_button > .ab-item,
     501body.toplevel_page_wccpoptionspro #wpadminbar #wp-admin-bar-wccp_free_top_button > .ab-item::after {
     502        animation: none !important;
     503        transition: none !important;
     504}
     505
     506body.toplevel_page_wccpoptionspro #wpadminbar #wp-admin-bar-wccp_free_top_button > .ab-item::after {
     507        box-shadow: none !important;
    426508}
    427509
    … …  
    441523        70% { box-shadow: 0 0 0 2px #1d2327, 0 0 0 9px rgba(249, 115, 22, 0); }
    442524        100% { box-shadow: 0 0 0 2px #1d2327, 0 0 0 2px rgba(249, 115, 22, 0); }
     525}
     526
     527/* Two 1.6s pulses back to back, then rest until the 10s cycle repeats. */
     528@keyframes wccp-abi-alert {
     529        0%, 16% { box-shadow: 0 0 0 0 rgba(250, 204, 21, .9); }
     530        11.2%, 27.2% { box-shadow: 0 0 0 10px rgba(250, 204, 21, 0); }
     531        15.9%, 32%, 100% { box-shadow: 0 0 0 0 rgba(250, 204, 21, 0); }
    443532}
    444533
    … …  
    460549        #wccp-abi .wccp-abi__pulse,
    461550        #wccp-abi .wccp-abi__skeleton,
    462         #wpadminbar #wp-admin-bar-wccp_free_top_button .wccp-abi-dot {
     551        #wpadminbar #wp-admin-bar-wccp_free_top_button .wccp-abi-dot,
     552        #wpadminbar #wp-admin-bar-wccp_free_top_button:not(.wccp-hint-target) > .ab-item:has(.wccp-abi-count:not(.count-0))::after {
    463553                animation: none;
    464554                transition: none;
  • wp-content-copy-protector/trunk/css/error-monitor.css

    r3707909 r3755015  
    546546}
    547547
     548/* "Ask AI for help" PRO teaser - the amber .wpb-tag--pro chip, with the
     549   lock dimmed like the locked range buttons in the Protection Center. */
     550#wpb-admin .wpb-em-issue__ai {
     551        flex-shrink: 0;
     552        margin-top: 1px;
     553}
     554
     555#wpb-admin .wpb-em-issue__ai:focus-visible {
     556        outline: 2px solid var(--wpb-primary);
     557        outline-offset: 2px;
     558}
     559
     560/* Unlocked rows: the same chip as a real button that copies the AI prompt. */
     561#wpb-admin .wpb-em-issue__ai--free {
     562        border: 0;
     563        font-family: inherit;
     564        line-height: inherit;
     565        cursor: pointer;
     566}
     567
     568#wpb-admin .wpb-em-issue__ai--free:hover {
     569        background: rgba(108, 99, 255, .18);
     570}
     571
     572#wpb-admin .wpb-em-issue__ai--free.wpb-tag--free:hover {
     573        background: rgba(16, 185, 129, .2);
     574}
     575
     576#wpb-admin .wpb-em-issue__ai--free svg {
     577        width: 12px;
     578        height: 12px;
     579}
     580
     581#wpb-admin .wpb-tag--pro.wpb-em-issue__ai svg:last-child {
     582        width: 11px;
     583        height: 11px;
     584        opacity: .7;
     585}
     586
    548587.wpb-em-issue__count strong {
    549588        display: inline-block;
    … …  
    894933        .wpb-em-issue__sum {
    895934                gap: 8px;
     935                flex-wrap: wrap;
     936        }
     937
     938        .wpb-em-issue__main {
     939                flex-basis: 100%;
     940        }
     941
     942        #wpb-admin .wpb-em-issue__ai {
     943                margin-inline-start: auto;
    896944        }
    897945
  • wp-content-copy-protector/trunk/css/wpb-admin.css

    r3693765 r3755015  
    12491249}
    12501250
    1251 #wpb-admin .wpb-plan__price {
    1252         display: flex;
    1253         align-items: baseline;
    1254         gap: 6px;
    1255         flex-wrap: wrap;
    1256         margin: 0 0 12px;
     1251/* One-line pitch under the plan title - accent rule on the left, no prices. */
     1252#wpb-admin .wpb-plan__pitch {
     1253        margin: 0 0 14px;
     1254        padding-left: 10px;
     1255        border-left: 3px solid var(--wpb-border-soft);
     1256        font-size: .84rem;
     1257        font-weight: 600;
     1258        line-height: 1.45;
    12571259        color: var(--wpb-secondary);
    12581260}
    12591261
    1260 .wpb-plan__from,
    1261 .wpb-plan__per {
    1262         font-size: .78rem;
    1263         font-weight: 600;
    1264         color: var(--wpb-text-secondary);
    1265 }
    1266 
    1267 .wpb-plan__amount {
    1268         font-size: 1.7rem;
    1269         font-weight: 800;
    1270         letter-spacing: -0.02em;
    1271         line-height: 1.1;
    1272 }
    1273 
    1274 .wpb-plan__cur {
    1275         font-size: 1.05rem;
    1276         font-weight: 700;
    1277         margin-right: 1px;
    1278 }
    1279 
     1262#wpb-admin .wpb-plan--popular .wpb-plan__pitch {
     1263        border-left-color: var(--wpb-primary);
     1264}
     1265
     1266/* Longer badge copy may wrap in narrow columns instead of overflowing the card. */
    12801267#wpb-admin .wpb-plan__badge {
    12811268        align-self: flex-start;
    1282         margin: -4px 0 12px;
    1283 }
    1284 
    1285 /* Lifetime licences still carry an optional support line - it sits under the price. */
     1269        max-width: 100%;
     1270        margin: 0 0 12px;
     1271        white-space: normal;
     1272        line-height: 1.35;
     1273        border-radius: 12px;
     1274}
     1275
     1276/* Lifetime licences still carry an optional support line - it sits under the badge. */
    12861277#wpb-admin .wpb-plan__care {
    12871278        display: flex;
  • wp-content-copy-protector/trunk/error-monitor.php

    r3707909 r3755015  
    234234                $names = array();
    235235                foreach (get_plugins() as $plugin_file => $plugin) {
    236                         $names[strtolower(strtok($plugin_file, '/'))] = $plugin['Name'];
     236                        $names[strtolower(strtok($plugin_file, '/'))] = array($plugin['Name'], $plugin['Version']);
    237237                }
    238238                $mu_names = array();
    239239                foreach (get_mu_plugins() as $plugin_file => $plugin) {
    240                         $mu_names[strtolower($plugin_file)] = $plugin['Name'];
     240                        $mu_names[strtolower($plugin_file)] = array($plugin['Name'], $plugin['Version']);
    241241                }
    242242
    … …  
    267267                $low  = strtolower($slug);
    268268
    269                 if ($kind === 'plugin') {
    270                         $name = isset($cache['plugins'][$low]) ? $cache['plugins'][$low] : $slug;
    271                 } elseif ($kind === 'mu') {
    272                         $name = isset($cache['mu'][$low]) ? $cache['mu'][$low] : $slug;
     269                $name    = $slug;
     270                $version = '';
     271
     272                if ($kind === 'theme') {
     273                        $theme = wp_get_theme($slug);
     274                        if ($theme->exists()) {
     275                                $name    = $theme->get('Name');
     276                                $version = $theme->get('Version');
     277                        }
    273278                } else {
    274                         $theme = wp_get_theme($slug);
    275                         $name  = $theme->exists() ? $theme->get('Name') : $slug;
    276                 }
    277 
    278                 return array('key' => $kind . ':' . $low, 'kind' => $kind, 'name' => $name);
     279                        $known = $kind === 'plugin' ? $cache['plugins'] : $cache['mu'];
     280                        if (isset($known[$low])) {
     281                                list($name, $version) = $known[$low];
     282                        }
     283                }
     284
     285                // The version goes into the "Ask AI for help" prompt.
     286                return array('key' => $kind . ':' . $low, 'kind' => $kind, 'name' => $name, 'version' => (string) $version);
    279287        }
    280288
    … …  
    638646                'locale'     => str_replace('_', '-', get_user_locale()),
    639647                'tailLabel'  => size_format(WCCP_FREE_EM_TAIL_BYTES),
     648                'proUrl'     => wccp_free_utm_url('error-monitor-ai-analysis-link'),
     649                // Site facts for the "Ask AI for help" prompt.
     650                'env'        => array(
     651                        'wp'        => get_bloginfo('version'),
     652                        'php'       => PHP_VERSION,
     653                        'theme'     => trim(wp_get_theme()->get('Name') . ' ' . wp_get_theme()->get('Version')),
     654                        'multisite' => is_multisite(),
     655                ),
    640656                'i18n'       => array(
    641657                        'levels'       => array(
    … …  
    701717                        'copy'         => __('Copy', 'wp-content-copy-protector'),
    702718                        'copied'       => __('Copied', 'wp-content-copy-protector'),
     719                        'askAi'        => __('Ask AI for help', 'wp-content-copy-protector'),
     720                        'askAiPro'     => __('PRO feature - get an AI explanation and fix for this error', 'wp-content-copy-protector'),
     721                        'askAiFree'    => __('Copy a ready-made prompt about this error, then paste it into ChatGPT, Claude or any AI assistant', 'wp-content-copy-protector'),
     722                        'aiCopied'     => __('Ready prompt copied', 'wp-content-copy-protector'),
     723                        'aiCopyFailed' => __('Copy failed', 'wp-content-copy-protector'),
     724                        'yes'          => __('yes', 'wp-content-copy-protector'),
     725                        'no'           => __('no', 'wp-content-copy-protector'),
     726                        'aiNotGiven'   => __('not given in the log', 'wp-content-copy-protector'),
     727                        /* translators: 1: file path, 2: line number. */
     728                        'aiFileLine'   => __('%1$s (line %2$s)', 'wp-content-copy-protector'),
     729                        /* translators: 1: plugin or theme name, 2: kind, e.g. "Plugin", 3: version number. */
     730                        'aiSourceVer'  => __('%1$s (%2$s, version %3$s)', 'wp-content-copy-protector'),
     731                        /* translators: 1: plugin or theme name, 2: kind, e.g. "Plugin". */
     732                        'aiSourceKind' => __('%1$s (%2$s)', 'wp-content-copy-protector'),
     733                        'aiOnce'       => __('once', 'wp-content-copy-protector'),
     734                        /* translators: %s: number of times (2 or more). */
     735                        'aiTimes'      => __('%s times', 'wp-content-copy-protector'),
     736                        /* translators: 1: e.g. "20 times", 2: number of days, 3: e.g. "35 times". */
     737                        'aiOften'      => __('%1$s in the last %2$s days (%3$s in the whole log)', 'wp-content-copy-protector'),
     738                        /* translators: The prompt copied by the "Ask AI for help" button. Keep the {placeholders} and line breaks unchanged. */
     739                        'aiPrompt'     => __("I run a WordPress website and its error log shows the PHP error below. Please help me understand it and fix it.\n\n== Error details ==\nSeverity: {severity}\nMessage:\n{message}\nFile: {file}\nComes from: {source}\nHow often: {often}\nFirst seen: {first}\nLast seen: {last}\n\n== Site environment ==\nWordPress version: {wp}\nPHP version: {php}\nActive theme: {theme}\nMultisite: {multisite}\n\n== What I need from you ==\n1. Explain in plain language what this error means and whether it can break my site or is only a warning.\n2. Tell me the most likely cause, based on the message, the file and the plugin or theme it comes from.\n3. Give me a step-by-step fix, starting with the safest options (updating, reconfiguring or replacing the plugin/theme) before any code change.\n4. If a code change is really needed, show the exact change, which file to edit, and how to back up the site first. Prefer a fix that survives plugin/theme updates (a child theme or a small custom plugin) over editing the original files.\n5. Tell me how to check that the error is gone and how to stop it from coming back.\n6. If you need more information (for example the code around that line or a full stack trace), tell me exactly what to send you.\n\nThis error was detected by the WP Content Copy Protection & No Right Click free WordPress plugin.", 'wp-content-copy-protector'),
    703740                ),
    704741        ));
  • wp-content-copy-protector/trunk/js/admin-bar-insights.js

    r3707909 r3755015  
    2222                return;
    2323        }
     24
     25        // Error count rendered by wccp_free_abi_bar_count() (absent without Error Monitor access).
     26        var badge = link.querySelector( '.wccp-abi-count' );
     27        var linkLabel = ( badge ? link.textContent.replace( badge.textContent, '' ) : link.textContent ).trim();
    2428
    2529        var t = cfg.i18n;
    … …  
    200204                card.id = 'wccp-abi';
    201205                card.setAttribute( 'role', 'region' );
    202                 card.setAttribute( 'aria-label', link.textContent.trim() );
     206                card.setAttribute( 'aria-label', linkLabel );
    203207
    204208                arrow = h( 'span', 'wccp-abi__arrow' );
    … …  
    501505        link.setAttribute( 'aria-expanded', 'false' );
    502506
     507        function badgeCount() {
     508                return badge ? parseInt( badge.className.replace( /.*\bcount-(\d+).*/, '$1' ), 10 ) || 0 : 0;
     509        }
     510
     511        // The page was rendered from an error snapshot older than its TTL: rescan after load.
     512        if ( badge && cfg.stale ) {
     513                var countParams = new URLSearchParams( { action: 'wccp_free_abi_count', _ajax_nonce: cfg.nonce } );
     514
     515                window.fetch( cfg.ajaxUrl, { method: 'POST', credentials: 'same-origin', body: countParams } )
     516                        .then( function ( response ) {
     517                                return response.json();
     518                        } )
     519                        .then( function ( json ) {
     520                                if ( ! json || ! json.success ) {
     521                                        return;
     522                                }
     523                                badge.className = 'wccp-abi-count count-' + json.data.count;
     524                                badge.firstChild.textContent = json.data.text;
     525                                badge.lastChild.textContent = json.data.label;
     526                        } )
     527                        .catch( function () {} );
     528        }
     529
    503530        // cfg.dot: seconds the dot may still show in this hour's allowance.
    504         if ( cfg.dot > 0 ) {
     531        // A visible error count already says "something new".
     532        if ( cfg.dot > 0 && ! badgeCount() ) {
    505533                var dot = h( 'span', 'wccp-abi-dot' );
    506534                dot.setAttribute( 'role', 'img' );
  • wp-content-copy-protector/trunk/js/error-monitor.js

    r3707909 r3755015  
    3232        var LEVEL_ICONS = { fatal: 'alert', warning: 'alert', notice: 'info', deprecated: 'clock', other: 'message' };
    3333        var PAGE = 25;
     34        // "Ask AI for help" copies a prompt on this many top rows; the rest stay a PRO teaser.
     35        var AI_FREE_ROWS = 3;
    3436
    3537        var state = {
    … …  
    816818        }
    817819
     820        function aiPrompt( row ) {
     821                var issue = row.issue;
     822                var env = cfg.env || {};
     823                var kind = t.kinds[ issue.source.kind ];
     824                var times = function ( n ) {
     825                        return n === 1 ? t.aiOnce : fill( t.aiTimes, num( n ) );
     826                };
     827                var source = issue.source.name;
     828
     829                if ( kind && issue.source.version ) {
     830                        source = fill( t.aiSourceVer, source, kind, issue.source.version );
     831                } else if ( kind ) {
     832                        source = fill( t.aiSourceKind, source, kind );
     833                }
     834
     835                var values = {
     836                        severity: t.levels[ issue.level ],
     837                        message: ( issue.type && issue.level === 'other' ? issue.type + ': ' : '' ) + issue.message,
     838                        file: issue.file ? ( issue.line ? fill( t.aiFileLine, issue.file, issue.line ) : issue.file ) : t.aiNotGiven,
     839                        source: source,
     840                        often: fill( t.aiOften, times( row.count ), num( state.range ), times( issue.count ) ),
     841                        first: issue.firstLabel,
     842                        last: issue.lastLabel,
     843                        wp: env.wp,
     844                        php: env.php,
     845                        theme: env.theme,
     846                        multisite: env.multisite ? t.yes : t.no
     847                };
     848
     849                // One pass, so a {token} inside a log message is never expanded.
     850                return t.aiPrompt.replace( /\{(\w+)\}/g, function ( match, key ) {
     851                        return Object.prototype.hasOwnProperty.call( values, key ) ? String( values[ key ] ) : match;
     852                } );
     853        }
     854
     855        // navigator.clipboard only exists on HTTPS / localhost; plain-http admins
     856        // fall back to a hidden textarea and execCommand.
     857        function copyText( text ) {
     858                if ( navigator.clipboard && window.isSecureContext ) {
     859                        return navigator.clipboard.writeText( text );
     860                }
     861
     862                return new Promise( function ( resolve, reject ) {
     863                        var area = h( 'textarea' );
     864
     865                        area.value = text;
     866                        area.setAttribute( 'readonly', '' );
     867                        area.style.position = 'fixed';
     868                        area.style.top = '-1000px';
     869                        document.body.appendChild( area );
     870                        area.select();
     871
     872                        var ok = false;
     873
     874                        try {
     875                                ok = document.execCommand( 'copy' );
     876                        } catch ( e ) {}
     877
     878                        document.body.removeChild( area );
     879                        ( ok ? resolve : reject )();
     880                } );
     881        }
     882
     883        function aiButton( row ) {
     884                var button = h( 'button', 'wpb-tag wpb-em-issue__ai wpb-em-issue__ai--free' );
     885                var label = h( 'span', '', t.askAi );
     886
     887                button.type = 'button';
     888                button.title = t.askAiFree;
     889                button.appendChild( icon( 'sparkles' ) );
     890                button.appendChild( label );
     891
     892                button.addEventListener( 'click', function ( event ) {
     893                        // Inside <summary>: copy only, do not open/close the row.
     894                        event.preventDefault();
     895                        event.stopPropagation();
     896
     897                        copyText( aiPrompt( row ) ).then( function () {
     898                                label.textContent = t.aiCopied;
     899                                button.classList.add( 'wpb-tag--free' );
     900                        }, function () {
     901                                label.textContent = t.aiCopyFailed;
     902                        } );
     903                } );
     904
     905                return button;
     906        }
     907
    818908        function renderIssues( rows ) {
    819909                clear( el.issues );
    … …  
    846936                } );
    847937
    848                 rows.slice( 0, state.limit ).forEach( function ( row ) {
     938                rows.slice( 0, state.limit ).forEach( function ( row, index ) {
    849939                        var issue = row.issue;
    850940                        var item = h( 'li', 'wpb-em-issue wpb-em-issue--' + issue.level );
    … …  
    874964                        count.appendChild( h( 'strong', '', '×' + num( row.count ) ) );
    875965
     966                        var ai;
     967
     968                        if ( index < AI_FREE_ROWS ) {
     969                                ai = aiButton( row );
     970                        } else {
     971                                // PRO teaser: a plain link to the upgrade page. A link is
     972                                // interactive content, so clicking it does not toggle the row.
     973                                ai = h( 'a', 'wpb-tag wpb-tag--pro wpb-em-issue__ai' );
     974                                ai.href = cfg.proUrl;
     975                                ai.target = '_blank';
     976                                ai.rel = 'noopener';
     977                                ai.title = t.askAiPro;
     978                                ai.appendChild( icon( 'sparkles' ) );
     979                                ai.appendChild( document.createTextNode( t.askAi ) );
     980                                ai.appendChild( icon( 'lock' ) );
     981                        }
     982
    876983                        summary.appendChild( main );
     984                        summary.appendChild( ai );
    877985                        summary.appendChild( count );
    878986                        details.appendChild( summary );
  • wp-content-copy-protector/trunk/preventer-index.php

    r3707909 r3755015  
    44Plugin URI: https://wordpress.org/plugins/wp-content-copy-protector/
    55Description: This wp plugin protect the posts content from being copied by any other web site author , you dont want your content to spread without your permission!!
    6 Version: 4.2
     6Version: 4.3
    77Author: wp-buy
    88Text Domain: wp-content-copy-protector
    … …  
    1717//define all variables the needed alot
    1818define('WCCP_FREE_PLUGIN_FILE', __FILE__);
    19 define('WCCP_FREE_VERSION', '4.2'); // keep in sync with the plugin header above
     19define('WCCP_FREE_VERSION', '4.3'); // keep in sync with the plugin header above
     20define('WCCP_FREE_PRO_URL', 'https://www.wp-buy.com/product/wp-content-copy-protection-pro/');
     21
     22/**
     23 * Tag a promotional link with a utm_element parameter so each click can be
     24 * traced back to the button, tag or banner it came from.
     25 *
     26 * @param string $element Slug naming the element and where it sits (e.g. 'main-settings-btn-exclude-admin').
     27 * @param string $url     Destination; defaults to the PRO product page. An existing query string is kept.
     28 * @return string
     29 */
     30function wccp_free_utm_url($element, $url = WCCP_FREE_PRO_URL)
     31{
     32        return add_query_arg('utm_element', sanitize_key($element), $url);
     33}
    2034include 'the_globals.php';
    2135include_once('notifications.php');
    … …  
    2539include_once('activity.php');
    2640include_once('admin-bar-insights.php');
     41include_once('notice-filter.php');
    2742$wccp_settings = wccp_read_options();
    2843
    … …  
    7994function wccp_free_disable_right_click()
    8095{
     96        global $wccp_settings;
    8197?>
    8298        <script id="wpcp_disable_Right_Click" type="text/javascript">
     99                var wpcp_right_click_msg = "<?php echo esc_js($wccp_settings['smessage']); ?>";
     100
    83101                document.ondragstart = function() {
    84102                        return false;
    … …  
    86104
    87105                function nocontext(e) {
     106                        // show_wpcp_message() is printed in the footer, so guard against an early right click
     107                        if (typeof show_wpcp_message === "function") show_wpcp_message(wpcp_right_click_msg);
    88108                        return false;
    89109                }
    … …  
    111131                        }
    112132
    113                         if (e.ctrlKey) {
     133                        if (e.ctrlKey || e.metaKey) { // metaKey = Cmd on macOS
    114134                                var key;
    115135                                if (window.event)
    … …  
    289309                        window.addEventListener("touchend", touchend, false);
    290310                });
     311
     312                //Safari on macOS: a trackpad Force Click selects the word and opens Look Up
     313                document.addEventListener("webkitmouseforcewillbegin", function(e) {
     314                        var elemtype = (e.target.nodeName || "").toUpperCase();
     315                        if (elemtype == "TEXTAREA" || elemtype == "INPUT" || elemtype == "SELECT" || elemtype == "OPTION" || elemtype == "EMBED") return;
     316                        if (wccp_free_iscontenteditable(e)) return;
     317                        e.preventDefault();
     318                }, false);
    291319
    292320                function wccp_pro_is_passive() {
    … …  
    476504        global $wccp_settings;
    477505        if (!wccp_free_visitor_is_protected()) return false;
    478         if ((is_home() || is_front_page() || is_archive() || is_post_type_archive() ||  is_404() || is_attachment() || is_author() || is_category() || is_feed()) && $wccp_settings['home_css_protection'] == 'Enabled') return true;
     506        if ((is_home() || is_front_page() || is_archive() || is_post_type_archive() ||  is_404() || is_attachment() || is_author() || is_category() || is_feed() || is_search()) && $wccp_settings['home_css_protection'] == 'Enabled') return true;
    479507        if (is_single() && $wccp_settings['posts_css_protection'] == 'Enabled') return true;
    480508        if (is_page() && !is_front_page() && $wccp_settings['pages_css_protection'] == 'Enabled') return true;
    … …  
    496524        global $wccp_settings;
    497525        if (!wccp_free_visitor_is_protected()) return false;
    498         if ((is_home() || is_front_page() || is_archive() || is_post_type_archive() ||  is_404() || is_attachment() || is_author() || is_category() || is_feed()) && $wccp_settings['right_click_protection_homepage'] == 'checked') return true;
     526        if ((is_home() || is_front_page() || is_archive() || is_post_type_archive() ||  is_404() || is_attachment() || is_author() || is_category() || is_feed() || is_search()) && $wccp_settings['right_click_protection_homepage'] == 'checked') return true;
    499527        if (is_single() && $wccp_settings['right_click_protection_posts'] == 'checked') return true;
    500528        if (is_page() && !is_front_page() && $wccp_settings['right_click_protection_posts'] == 'checked') return true;
    … …  
    655683        array_push($links, $settings_link);
    656684
    657         $go_pro_link = '<a title="' . esc_attr__('Upgrade to PRO version Now', 'wp-content-copy-protector') . '" target="_blank" style="font-weight:bold;color: chocolate;" href="https://www.wp-buy.com/product/wp-content-copy-protection-pro/">' . __('Go PRO', 'wp-content-copy-protector') . '</a>';
     685        $go_pro_link = '<a title="' . esc_attr__('Upgrade to PRO version Now', 'wp-content-copy-protector') . '" target="_blank" style="font-weight:bold;color: chocolate;" href="' . esc_url(wccp_free_utm_url('plugins-list-go-pro-link')) . '">' . __('Go PRO', 'wp-content-copy-protector') . '</a>';
    658686        array_push($links, $go_pro_link);
    659687
    … …  
    678706
    679707        $class_name = sanitize_html_class(dirname($plugin_file));
    680         $p_url      = 'https://www.wp-buy.com/product/wp-content-copy-protection-pro/';
     708        $p_url      = wccp_free_utm_url('plugins-list-update-notice-cta');
    681709
    682710        $messages = [
    … …  
    924952                        transform: rotate(45deg);
    925953                }
     954
     955                /* Green glow while hovered, keyboard focused or its insights card is open.
     956                   Inset, so it stays inside the bar and never fights the ::after rings. */
     957                #wpadminbar #wp-admin-bar-wccp_free_top_button > .ab-item,
     958                #wpadminbar #wp-admin-bar-wccp_free_top_button .ab-icon:before {
     959                        transition: box-shadow .2s ease, text-shadow .2s ease;
     960                }
     961
     962                #wpadminbar:not(.mobile) #wp-admin-bar-wccp_free_top_button:hover > .ab-item,
     963                #wpadminbar #wp-admin-bar-wccp_free_top_button > .ab-item:focus-visible,
     964                #wpadminbar #wp-admin-bar-wccp_free_top_button.wccp-abi-open > .ab-item {
     965                        box-shadow: inset 0 0 0 1px rgba(2, 202, 2, .45), inset 0 0 12px rgba(2, 202, 2, .35);
     966                }
     967
     968                #wpadminbar:not(.mobile) #wp-admin-bar-wccp_free_top_button:hover .ab-icon:before,
     969                #wpadminbar #wp-admin-bar-wccp_free_top_button > .ab-item:focus-visible .ab-icon:before,
     970                #wpadminbar #wp-admin-bar-wccp_free_top_button.wccp-abi-open .ab-icon:before {
     971                        text-shadow: 0 0 6px rgba(2, 202, 2, .9);
     972                }
     973
     974                @media (prefers-reduced-motion: reduce) {
     975                        #wpadminbar #wp-admin-bar-wccp_free_top_button > .ab-item,
     976                        #wpadminbar #wp-admin-bar-wccp_free_top_button .ab-icon:before {
     977                                transition: none;
     978                        }
     979                }
    926980        </style>
    927981<?php
  • wp-content-copy-protector/trunk/readme.txt

    r3707909 r3755015  
    55License URI: http://www.gnu.org/licenses/gpl-2.0.html
    66Requires at least: 4.8
    7 Tested up to: 7.1.1
    8 Stable tag: 4.2
     7Tested up to: 7.1.3
     8Stable tag: 4.3
    99Requires PHP: 7.4
    1010
    … …  
    2020Are you tired of seeing your original content copied and spread across the web without your permission? The WP Content Copy Protection & No Right Click plugin is here to safeguard your posts and home page from unauthorized copying.
    2121
    22 ✨ *Key Features:*
    23 🛡️ *Content Protection:*
     22==Key Features==
     23🛡️ **Content Protection:**
    2424Ensure your hard work remains yours. This plugin employs advanced content-protection techniques to keep your text and images safe from being copied by other web authors.
    2525
    26 🔒 *Copy Protection:*
     26🔒 **Copy Protection:**
    2727With robust copy protection mechanisms, this plugin prevents unauthorized duplication of your content. It effectively disables common methods used for content theft, ensuring your intellectual property remains secure.
    2828
    29 ©️ *Copyright Enforcement:*
     29©️ **Copyright Enforcement:**
    3030Maintain control over your copyrighted material. The plugin acts as a virtual guardian, enforcing copyright rules and deterring potential infringers.
    3131
    32 🖱️ *Disable Right Click:*
     32🖱️ **Disable Right Click:**
    3333Stop users from right-clicking on your content. By disabling right-click functionality, this plugin adds an extra layer of security, preventing easy access to the context menu used for copying content.
    3434
    35 🚫 *Deny Copy Paste:*
     35🚫 **Deny Copy Paste:**
    3636Block the ability to copy and paste your content. This plugin uses a combination of JavaScript and CSS techniques to make copying and pasting from your site nearly impossible.
    3737
    38 👥 *Control Who Can Copy:*
     38👥 **Control Who Can Copy:**
    3939Take charge of your content distribution. With this plugin, you have the power to control who can and cannot copy your content, giving you peace of mind that your work is protected.
    4040
    41 💧 *Watermark Images (Pro Version):*
     41💧 **Watermark Images (Pro Version):**
    4242Upgrade to the Pro version to add watermarks to your images. This feature ensures that even if your images are copied, they will still carry your watermark, maintaining your brand visibility and deterring unauthorized use.
    4343
    44 📊 *Protection Center (New, free):*
     44📊 **Protection Center (New, free):**
    4545See your protection working instead of guessing. The Protection Center counts every blocked action on your site - blocked right-clicks, blocked text selections, blocked copy shortcuts, image interactions and print attempts - and shows what happened since your last visit: visits with blocked actions, a daily chart for the last 7 days, the pages people try to copy the most, the busiest hours and the mix between text, images and right-clicks. Built on privacy: no IP address, no cookie and no personal data is ever stored, logged-in editors and admins are never counted, everything stays in your own database, and you can switch it off or wipe it with one click at any time.
    4646
    47 📧 *Weekly Protection Report (New, free):*
     47📧 **Weekly Protection Report (New, free):**
    4848Do not want to log in to check? Turn on the weekly digest and the plugin emails your site admin a short summary every Monday - only in weeks that actually had activity - with the number of visits with blocked copy actions and how that compares to the week before.
    4949
    50 🔔 *Admin Bar Insights (New, free):*
     50🔔 **Admin Bar Insights (New, free):**
    5151A small card that opens from the plugin icon in the WordPress admin bar, wherever you are in wp-admin. One headline number, a 7-day sparkline, your error count, and two short insights that change on every visit ("copy attempts are up 38% this week", "one page drew 41% of the activity"). It costs nothing on page load - the numbers are fetched only when you open the card, and cached.
    5252
    53 🩺 *Error Monitor (New, free):*
     53🩺 **Error Monitor (New, free):**
    5454Not only protection: the plugin now finds the error logs your site writes to (wp-content/debug.log, the PHP log set by your server, and error_log files in the site root, wp-admin and wp-content) and explains what is going wrong in plain language. Repeated errors are folded into single issues with a counter, each issue names the plugin, theme or part of WordPress behind it with the file and line, and charts show errors per day by severity and which plugins produce the most. It also warns you when your log file can be downloaded by anyone from the web and shows how to block it. Only the newest part of each log is read, so even huge log files do not slow down your admin.
    5555
    56 🌍 *Speaks Your Language (New):*
     56🌍 **Speaks Your Language (New)**
    5757The whole plugin - settings, help notes, Protection Center and Error Monitor - is now fully translated into 11 languages besides English: Arabic, German, Spanish, French, Italian, Japanese, Dutch, Portuguese (Brazil and Portugal), Russian and Simplified Chinese, with full right-to-left (RTL) support.
    5858
    59 *Unique Benefits:*
     59==Unique Benefits:==
    6060Unparalleled Protection: Unlike other WordPress plugins, our unique combination of JavaScript and CSS techniques offers a level of protection not found elsewhere.
    6161Free to Use: Enjoy all these powerful features without any cost. Protect your content with the free version of our plugin, and upgrade to the Pro version for additional benefits like image watermarking.
    … …  
    118118
    119119== Changelog ==
     120= 4.3 =
     121<ul>
     122<li>Admin Bar Insights on the front end: the insights card now also opens when you hover the plugin icon in the admin bar while viewing your site, not only inside wp-admin. It stays hidden in the Customizer preview and on small screens.</li>
     123<li>New feature: New errors count on the admin bar icon. A red counter next to "Protection" shows how many new error issues appeared since you last opened the Error Monitor (or in the last 24 hours if you have not opened it yet), the same number as the "New issues" tile in the card. It shows "99+" above 99, is hidden when there is nothing new, and is reset once you open the Error Monitor.</li>
     124<li>New errors count: repeated lines of an error the logs already had are not counted again, so old noise such as the same deprecation notice logged every day does not keep the counter up. Only problems that were not happening before are counted.</li>
     125<li>New errors count performance: page loads only read the stored error snapshot. When the snapshot is older than 10 minutes the count is refreshed in the background after the page has loaded, so your pages are never slowed down by reading log files.</li>
     126<li>Notifications: the "New activity" dot is no longer shown while the new errors count is visible, so the icon never carries two signals at once.</li>
     127<li>New feature: "Ask AI for help" in the Error Monitor, free for the top 3 issues in the list. One click copies a ready-made prompt about that error, ready to paste into ChatGPT, Claude or any AI assistant, and the button changes to "Ready prompt copied". The button is still a PRO feature for the other issues.</li>
     128<li>Ask AI for help: the prompt includes the severity, the full message, the file and line, the plugin or theme behind the error with its version, how often it happened in the selected period and in the whole log, and the first and last seen dates. It also lists your WordPress version, PHP version, active theme and whether the site is a multisite, and asks for a plain-language explanation and a safe, step-by-step fix.</li>
     129<li>Ask AI for help: copying also works on admin pages served over plain http (no https), and the prompt text is translatable.</li>
     130<li>Interface: the insights card is protected against front end theme styles (list bullets, link borders, text alignment), so it looks the same on your site as in wp-admin.</li>
     131<li>Interface: a cleaner settings page. Notifications from other plugins, themes and WordPress are hidden on the plugin settings page only, behind a "Show hidden notifications" button that shows how many there are. One click shows them again, and dismiss buttons keep working. The plugin's own notices stay visible, and every other admin page is unchanged.</li>
     132<li>Fix: a single right-click now shows your alert message. Before, the context menu was blocked silently and the message only appeared on a double-click.</li>
     133<li>Fix for Mac users: copy shortcuts are now blocked with the Cmd key too (Cmd+A, Cmd+C, Cmd+S, Cmd+U and the Safari source and developer tools shortcuts), not only with Ctrl. Text fields and editable areas still accept them.</li>
     134<li>Fix for Mac users: in Safari, a Force Click on the trackpad no longer selects a word and opens the Look Up popup on protected pages. Text fields and editable areas are not affected.</li>
     135<li>Fix: right-click protection and CSS protection now also cover search results pages, using the same setting as the homepage and archives. Selection protection already did.</li>
     136</ul>
    120137= 4.2 =
    121138<ul>
Note: See TracChangeset for help on using the changeset viewer.