Plugin Directory

Changeset 3764726


Ignore:
Timestamp:
10/25/2026 02:08:28 PM (3 hours ago)
Author:
cartnorm
Message:

Update to version 0.1.2 from GitHub

Location:
cartnorm-omnibus-lowest-price
Files:
14 edited
1 copied

Legend:

Unmodified
Added
Removed
  • cartnorm-omnibus-lowest-price/tags/0.1.2/cartnorm-omnibus-lowest-price.php

    r3764542 r3764726  
    44 * Plugin URI:        https://cartnorm.com/
    55 * Description:       Shows the lowest price from the 30 days before a discount, as required by the EU Omnibus Directive. Change-only price history that never bloats your database.
    6  * Version:           0.1.1
     6 * Version:           0.1.2
    77 * Requires at least: 6.4
    88 * Requires PHP:      7.4
    … …  
    2121defined( 'ABSPATH' ) || exit;
    2222
    23 define( 'CARTNORM_VERSION', '0.1.1' );
     23define( 'CARTNORM_VERSION', '0.1.2' );
    2424define( 'CARTNORM_FILE', __FILE__ );
    2525define( 'CARTNORM_DIR', plugin_dir_path( __FILE__ ) );
  • cartnorm-omnibus-lowest-price/tags/0.1.2/includes/class-admin.php

    r3764542 r3764726  
    138138                        echo '<table class="widefat striped" style="font-size:11px"><thead><tr><th>' . esc_html__( 'From', 'cartnorm-omnibus-lowest-price' ) . '</th><th>' . esc_html__( 'Price', 'cartnorm-omnibus-lowest-price' ) . '</th><th>' . esc_html__( 'Sale', 'cartnorm-omnibus-lowest-price' ) . '</th><th></th></tr></thead><tbody>';
    139139                        foreach ( $rows as $row ) {
    140                                 echo '<tr><td>' . esc_html( wp_date( get_option( 'date_format' ), Clock::from_mysql( $row->valid_from ) ) ) . '</td><td>' . wp_kses_post( wc_price( $row->price ) ) . '</td><td>' . ( (int) $row->on_sale ? '&#10003;' : '' ) . '</td><td>' . Corrections::link( $row ) . '</td></tr>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- link() escapes.
     140                                echo '<tr><td>' . esc_html( wp_date( get_option( 'date_format' ), Clock::from_mysql( $row->valid_from ) ) ) . '</td><td>' . wp_kses_post( wc_price( $row->price ) ) . '</td><td>' . ( (int) $row->on_sale ? '&#10003;' : '' ) . '</td><td>' . wp_kses_post( Corrections::link( $row ) ) . '</td></tr>';
    141141                        }
    142142                        echo '</tbody></table>';
  • cartnorm-omnibus-lowest-price/tags/0.1.2/includes/class-compliance-check.php

    r3764542 r3764726  
    169169                        )
    170170                ) . '</p>';
     171                Notices::render_pro_note();
    171172
    172173                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only filter.
  • cartnorm-omnibus-lowest-price/tags/0.1.2/includes/class-notices.php

    r3764542 r3764726  
    66 * - "Import price history": an inline, dismissible box inside this plugin's own
    77 *   settings section, shown only when another Omnibus plugin's history exists.
     8 * - A note about the optional Pro add-on: one small inline box inside this
     9 *   plugin's own settings section and Omnibus check page, for users who can
     10 *   manage WooCommerce. It can be dismissed for good (per user, works without
     11 *   JavaScript) and is never shown when the Pro add-on is active.
    812 * - The history-building status is a plain line in the settings section
    913 *   description (see Settings::history_status()), not a notice.
    … …  
    2832        const FIELD_TYPE   = 'cartnorm_notices';
    2933        const IMPORT_INFIX = 'import_';
     34        const PRO_KEY      = 'pro_info';
     35        const PRO_URL      = 'https://cartnorm.com/pricing/';
    3036
    3137        /**
    … …  
    95101         */
    96102        public static function keys() {
    97                 $keys = array();
     103                $keys = array( self::PRO_KEY );
    98104                foreach ( array_keys( Importer::source_labels() ) as $source ) {
    99105                        $keys[] = self::IMPORT_INFIX . $source;
    … …  
    131137                }
    132138                return true;
     139        }
     140
     141        /**
     142         * Whether Cartnorm Omnibus Pro is active.
     143         *
     144         * @return bool
     145         */
     146        public static function pro_active() {
     147                /**
     148                 * Whether Cartnorm Omnibus Pro is active.
     149                 *
     150                 * @param bool $active Active.
     151                 */
     152                return (bool) apply_filters( 'cartnorm_pro_active', defined( 'CARTNORM_PRO_VERSION' ) );
     153        }
     154
     155        /**
     156         * Whether a currency switcher supported by Cartnorm Omnibus Pro is active:
     157         * WPML / WooCommerce Multilingual in multi-currency mode, Aelia, CURCY or
     158         * FOX (WOOCS). Only checks for loaded classes, functions and globals.
     159         *
     160         * @return bool
     161         */
     162        public static function multi_currency_detected() {
     163                $detected = self::wcml_multi_currency()
     164                        || class_exists( 'WC_Aelia_CurrencySwitcher' ) || has_filter( 'wc_aelia_cs_convert' )
     165                        || function_exists( 'wmc_get_price' )
     166                        || ( isset( $GLOBALS['WOOCS'] ) && is_object( $GLOBALS['WOOCS'] ) ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName
     167
     168                /**
     169                 * Whether the shop uses a currency switcher supported by Cartnorm Omnibus Pro.
     170                 *
     171                 * @param bool $detected Detected.
     172                 */
     173                return (bool) apply_filters( 'cartnorm_multi_currency_detected', $detected );
     174        }
     175
     176        /**
     177         * WPML / WooCommerce Multilingual with multi-currency switched on.
     178         *
     179         * @return bool
     180         */
     181        private static function wcml_multi_currency() {
     182                if ( ! defined( 'WCML_VERSION' ) && ! class_exists( 'woocommerce_wpml' ) ) {
     183                        return false;
     184                }
     185                global $woocommerce_wpml;
     186                if ( is_object( $woocommerce_wpml ) && isset( $woocommerce_wpml->settings['enable_multi_currency'] ) ) {
     187                        $independent = defined( 'WCML_MULTI_CURRENCIES_INDEPENDENT' ) ? WCML_MULTI_CURRENCIES_INDEPENDENT : 2;
     188                        return (int) $woocommerce_wpml->settings['enable_multi_currency'] === (int) $independent;
     189                }
     190                return true;
     191        }
     192
     193        /**
     194         * No-JavaScript dismiss link for a notice.
     195         *
     196         * @param string $key Notice key.
     197         * @return string
     198         */
     199        private static function dismiss_link( $key ) {
     200                $url = wp_nonce_url(
     201                        add_query_arg(
     202                                array(
     203                                        'action' => self::ACTION,
     204                                        'notice' => $key,
     205                                ),
     206                                admin_url( 'admin-post.php' )
     207                        ),
     208                        self::ACTION
     209                );
     210                return ' <a class="hide-if-js" href="' . esc_url( $url ) . '">' . esc_html__( 'Dismiss', 'cartnorm-omnibus-lowest-price' ) . '</a>';
     211        }
     212
     213        /**
     214         * The note about Cartnorm Omnibus Pro, as HTML. Empty unless the current
     215         * user can manage WooCommerce, is on one of this plugin's own screens, has
     216         * not dismissed it, and the Pro add-on is not active.
     217         *
     218         * @return string
     219         */
     220        public static function pro_note_html() {
     221                if ( ! current_user_can( 'manage_woocommerce' ) || ! self::is_own_screen() || self::pro_active() || self::is_dismissed( self::PRO_KEY ) ) {
     222                        return '';
     223                }
     224                if ( self::multi_currency_detected() ) {
     225                        $text = __( 'Your shop shows prices in more than one currency. This plugin shows the lowest-price notice in your base currency only; Cartnorm Omnibus Pro shows the correct 30-day lowest price in each currency.', 'cartnorm-omnibus-lowest-price' );
     226                } else {
     227                        $text = __( 'Optional paid add-on: Cartnorm Omnibus Pro adds multi-currency support, a warning when the regular price was raised before a sale, a monthly email report, Google Shopping feed prices and an EU withdrawal button.', 'cartnorm-omnibus-lowest-price' );
     228                }
     229                return '<div class="notice notice-info inline is-dismissible cartnorm-notice cartnorm-pro-note" data-cartnorm-notice="' . esc_attr( self::PRO_KEY ) . '"><p>'
     230                        . esc_html( $text )
     231                        . ' <a href="' . esc_url( self::PRO_URL ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Learn more', 'cartnorm-omnibus-lowest-price' ) . '</a>'
     232                        . self::dismiss_link( self::PRO_KEY )
     233                        . '</p></div>';
     234        }
     235
     236        /**
     237         * Print the Pro note (Omnibus check page).
     238         */
     239        public static function render_pro_note() {
     240                echo wp_kses( self::pro_note_html(), Display::allowed_html() );
    133241        }
    134242
    … …  
    171279                        }
    172280
    173                         $dismiss_url = wp_nonce_url(
    174                                 add_query_arg(
    175                                         array(
    176                                                 'action' => self::ACTION,
    177                                                 'notice' => $key,
    178                                         ),
    179                                         admin_url( 'admin-post.php' )
    180                                 ),
    181                                 self::ACTION
    182                         );
    183281                        $out .= '<div class="notice notice-info inline is-dismissible cartnorm-notice" data-cartnorm-notice="' . esc_attr( $key ) . '"><p>'
    184282                                . esc_html( $text ) . ' ' . $button
    185                                 . ' <a class="hide-if-js" href="' . esc_url( $dismiss_url ) . '">' . esc_html__( 'Dismiss', 'cartnorm-omnibus-lowest-price' ) . '</a>'
     283                                . self::dismiss_link( $key )
    186284                                . '</p></div>';
    187285                }
    188                 return $out;
     286                return $out . self::pro_note_html();
    189287        }
    190288
    … …  
    200298                        return;
    201299                }
    202                 echo '<tr class="cartnorm-notices-row"><td colspan="2" style="padding:0 0 8px">' . $html . '</td></tr>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped in html().
    203         }
    204 
    205         /**
    206          * Script that stores the dismissal when the core dismiss button is clicked.
    207          * Loaded only on this plugin's own screens.
     300                echo '<tr class="cartnorm-notices-row"><td colspan="2" style="padding:0 0 8px">' . wp_kses( $html, Display::allowed_html() ) . '</td></tr>';
     301        }
     302
     303        /**
     304         * Script that stores the dismissal when the core dismiss button is clicked,
     305         * and a one-line style for the box. Loaded only on this plugin's own screens.
    208306         */
    209307        public static function enqueue() {
    … …  
    213311                $script = '(function(){document.addEventListener("click",function(e){var b=e.target.closest?e.target.closest(".cartnorm-notice .notice-dismiss"):null;if(!b){return;}var n=b.closest(".cartnorm-notice");var d=new FormData();d.append("action",' . wp_json_encode( self::ACTION ) . ');d.append("notice",n.getAttribute("data-cartnorm-notice"));d.append("_ajax_nonce",' . wp_json_encode( wp_create_nonce( self::ACTION ) ) . ');fetch(' . wp_json_encode( admin_url( 'admin-ajax.php' ) ) . ',{method:"POST",credentials:"same-origin",body:d});});})();';
    214312                wp_add_inline_script( 'common', $script );
     313                // WooCommerce screens override the core padding that keeps text clear of the dismiss button.
     314                wp_add_inline_style( 'common', '.wp-core-ui .cartnorm-notice.notice.is-dismissible{padding-right:48px}' );
    215315        }
    216316
    … …  
    248348                }
    249349                self::dismiss( self::requested_key() );
    250                 wp_safe_redirect( Settings::url() );
     350                $back = wp_get_referer();
     351                wp_safe_redirect( $back ? $back : Settings::url() );
    251352                exit;
    252353        }
  • cartnorm-omnibus-lowest-price/tags/0.1.2/includes/class-settings.php

    r3764542 r3764726  
    8080                add_filter( 'woocommerce_get_settings_products', array( __CLASS__, 'add_settings' ), 10, 2 );
    8181                add_filter( 'plugin_action_links_' . plugin_basename( CARTNORM_FILE ), array( __CLASS__, 'action_links' ) );
     82                add_filter( 'plugin_row_meta', array( __CLASS__, 'row_meta' ), 10, 2 );
    8283        }
    8384
    … …  
    9899         */
    99100        public static function action_links( $links ) {
    100                 array_unshift( $links, '<a href="' . esc_url( self::url() ) . '">' . esc_html__( 'Settings', 'cartnorm-omnibus-lowest-price' ) . '</a>' );
     101                array_unshift( $links, Display::kses( '<a href="' . esc_url( self::url() ) . '">' . esc_html__( 'Settings', 'cartnorm-omnibus-lowest-price' ) . '</a>' ) );
     102                return $links;
     103        }
     104
     105        /**
     106         * Add a "Pro version" link to this plugin's row on the Plugins screen
     107         * (only for this plugin, and not when the Pro add-on is active).
     108         *
     109         * @param array  $links Row meta links.
     110         * @param string $file  Plugin file.
     111         * @return array
     112         */
     113        public static function row_meta( $links, $file ) {
     114                if ( plugin_basename( CARTNORM_FILE ) !== $file || Notices::pro_active() ) {
     115                        return $links;
     116                }
     117                $links[] = Display::kses( '<a href="' . esc_url( Notices::PRO_URL ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Pro version', 'cartnorm-omnibus-lowest-price' ) . '</a>' );
    101118                return $links;
    102119        }
  • cartnorm-omnibus-lowest-price/tags/0.1.2/languages/cartnorm-omnibus-lowest-price.pot

    r3764542 r3764726  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: Cartnorm Omnibus Lowest Price 0.1.1\n"
     5"Project-Id-Version: Cartnorm Omnibus Lowest Price 0.1.2\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/cartnorm-omnibus-lowest-price\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
    … …  
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2026-10-25T08:45:23+00:00\n"
     12"POT-Creation-Date: 2026-10-25T14:03:29+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.12.0\n"
    1515"X-Domain: cartnorm-omnibus-lowest-price\n"
    1616
     17#: blocks/lowest-price/block.json
     18msgctxt "block title"
     19msgid "Lowest price (Omnibus)"
     20msgstr ""
     21
     22#: blocks/lowest-price/block.json
     23msgctxt "block description"
     24msgid "Shows the lowest price of the product in the 30 days before the discount."
     25msgstr ""
     26
     27#: blocks/lowest-price/block.json
     28msgctxt "block keyword"
     29msgid "omnibus"
     30msgstr ""
     31
     32#: blocks/lowest-price/block.json
     33msgctxt "block keyword"
     34msgid "lowest price"
     35msgstr ""
     36
     37#: blocks/lowest-price/block.json
     38msgctxt "block keyword"
     39msgid "30 days"
     40msgstr ""
     41
     42#: blocks/lowest-price/block.json
     43msgctxt "block keyword"
     44msgid "price history"
     45msgstr ""
     46
    1747#. Plugin Name of the plugin
    1848#: cartnorm-omnibus-lowest-price.php
    … …  
    4373msgstr ""
    4474
    45 #: includes/class-admin.php:68 includes/class-settings.php:111
     75#: includes/class-admin.php:68
     76#: includes/class-settings.php:128
    4677#: includes/integrations/class-elementor-widget.php:36
    4778msgid "Lowest price (Omnibus)"
    … …  
    102133msgstr ""
    103134
    104 #: includes/class-admin.php:161 includes/class-corrections.php:218
    105 #: includes/class-importer.php:337 includes/class-notices.php:247
     135#: includes/class-admin.php:161
     136#: includes/class-corrections.php:218
     137#: includes/class-importer.php:337
     138#: includes/class-notices.php:347
    106139msgid "You are not allowed to do this."
    107140msgstr ""
    … …  
    151184msgstr ""
    152185
    153 #: includes/class-compliance-check.php:198
     186#: includes/class-compliance-check.php:199
    154187msgid "No products are reduced at the moment."
    155188msgstr ""
    156189
    157 #: includes/class-compliance-check.php:203
     190#: includes/class-compliance-check.php:204
    158191#: includes/integrations/class-elementor-widget.php:72
    159192msgid "Product"
    160193msgstr ""
    161194
    162 #: includes/class-compliance-check.php:203
     195#: includes/class-compliance-check.php:204
    163196msgid "Status"
    164197msgstr ""
    165198
    166 #: includes/class-compliance-check.php:203
     199#: includes/class-compliance-check.php:204
    167200msgid "Sale price"
    168201msgstr ""
    169202
    170 #: includes/class-compliance-check.php:203
     203#: includes/class-compliance-check.php:204
    171204msgid "Lowest price, 30 days before"
    172205msgstr ""
    173206
    174 #: includes/class-compliance-check.php:203
     207#: includes/class-compliance-check.php:204
    175208msgid "Sale started"
    176209msgstr ""
    177210
    178 #: includes/class-corrections.php:35 includes/class-corrections.php:69
     211#: includes/class-corrections.php:35
     212#: includes/class-corrections.php:69
    179213msgid "Correct price record"
    180214msgstr ""
    … …  
    184218msgstr ""
    185219
    186 #: includes/class-corrections.php:78 includes/class-corrections.php:130
     220#: includes/class-corrections.php:78
     221#: includes/class-corrections.php:130
    187222#: includes/class-corrections.php:154
    188223msgid "This record does not exist."
    … …  
    199234msgstr ""
    200235
    201 #: includes/class-corrections.php:97 includes/class-corrections.php:131
     236#: includes/class-corrections.php:97
     237#: includes/class-corrections.php:131
    202238#: includes/class-corrections.php:157
    203239msgid "The current price cannot be corrected here. Change it in the product instead."
    … …  
    236272msgstr ""
    237273
    238 #: includes/class-corrections.php:129 includes/class-corrections.php:148
     274#: includes/class-corrections.php:129
     275#: includes/class-corrections.php:148
    239276msgid "Please give a reason for the correction."
    240277msgstr ""
    241278
    242 #: includes/class-corrections.php:132 includes/class-corrections.php:160
     279#: includes/class-corrections.php:132
     280#: includes/class-corrections.php:160
    243281msgid "Please enter a valid price."
    244282msgstr ""
    245283
     284#: includes/class-country-rules.php:64
     285msgid "EU default rules"
     286msgstr ""
     287
    246288#: includes/class-country-rules.php:65
    247 msgid "EU default rules"
     289msgid "Austria"
    248290msgstr ""
    249291
    250292#: includes/class-country-rules.php:66
    251 msgid "Austria"
     293msgid "Belgium"
    252294msgstr ""
    253295
    254296#: includes/class-country-rules.php:67
    255 msgid "Belgium"
     297msgid "Czech Republic"
    256298msgstr ""
    257299
    258300#: includes/class-country-rules.php:68
    259 msgid "Czech Republic"
     301msgid "France"
    260302msgstr ""
    261303
    262304#: includes/class-country-rules.php:69
    263 msgid "France"
     305msgid "Germany"
    264306msgstr ""
    265307
    266308#: includes/class-country-rules.php:70
    267 msgid "Germany"
     309msgid "Ireland"
    268310msgstr ""
    269311
    270312#: includes/class-country-rules.php:71
    271 msgid "Ireland"
     313msgid "Italy"
    272314msgstr ""
    273315
    274316#: includes/class-country-rules.php:72
    275 msgid "Italy"
     317msgid "Netherlands"
    276318msgstr ""
    277319
    278320#: includes/class-country-rules.php:73
    279 msgid "Netherlands"
     321msgid "Poland"
    280322msgstr ""
    281323
    282324#: includes/class-country-rules.php:74
    283 msgid "Poland"
     325msgid "Portugal"
    284326msgstr ""
    285327
    286328#: includes/class-country-rules.php:75
    287 msgid "Portugal"
     329msgid "Romania"
    288330msgstr ""
    289331
    290332#: includes/class-country-rules.php:76
    291 msgid "Romania"
     333msgid "Spain"
    292334msgstr ""
    293335
    294336#: includes/class-country-rules.php:77
    295 msgid "Spain"
    296 msgstr ""
    297 
    298 #: includes/class-country-rules.php:78
    299337msgid "Sweden"
    300338msgstr ""
    301339
    302 #: includes/class-country-rules.php:80
     340#: includes/class-country-rules.php:79
    303341msgid "Automatic (store country)"
    304342msgstr ""
    305343
    306 #: includes/class-notices.php:88
     344#: includes/class-notices.php:94
    307345msgid "Cartnorm Omnibus Lowest Price needs WooCommerce to be installed and active."
    308346msgstr ""
    309347
     348#: includes/class-notices.php:210
     349msgid "Dismiss"
     350msgstr ""
     351
     352#: includes/class-notices.php:225
     353msgid "Your shop shows prices in more than one currency. This plugin shows the lowest-price notice in your base currency only; Cartnorm Omnibus Pro shows the correct 30-day lowest price in each currency."
     354msgstr ""
     355
     356#: includes/class-notices.php:227
     357msgid "Optional paid add-on: Cartnorm Omnibus Pro adds multi-currency support, a warning when the regular price was raised before a sale, a monthly email report, Google Shopping feed prices and an EU withdrawal button."
     358msgstr ""
     359
     360#: includes/class-notices.php:231
     361msgid "Learn more"
     362msgstr ""
     363
    310364#. translators: %s: name of another plugin
    311 #: includes/class-notices.php:153
     365#: includes/class-notices.php:261
    312366#, php-format
    313367msgid "Importing price history from %s in the background."
    314368msgstr ""
    315369
    316 #: includes/class-notices.php:160
     370#: includes/class-notices.php:268
    317371msgid "Import price history"
    318372msgstr ""
    319373
    320374#. translators: %s: name of another plugin
    321 #: includes/class-notices.php:163
     375#: includes/class-notices.php:271
    322376#, php-format
    323377msgid "Price history from %s was found. Import it to keep your full 30-day history after switching."
    324378msgstr ""
    325379
    326 #: includes/class-notices.php:185
    327 msgid "Dismiss"
    328 msgstr ""
    329 
    330 #: includes/class-settings.php:100
     380#: includes/class-settings.php:101
    331381msgid "Settings"
    332382msgstr ""
    333383
     384#: includes/class-settings.php:117
     385msgid "Pro version"
     386msgstr ""
     387
    334388#. translators: 1: number of stored price changes, 2: date tracking started
    335 #: includes/class-settings.php:131
     389#: includes/class-settings.php:148
    336390#, php-format
    337391msgid "Price history: %1$s stored price changes since %2$s. Rows are written only when a price changes."
    338392msgstr ""
    339393
    340 #: includes/class-settings.php:141
     394#: includes/class-settings.php:158
    341395msgid "Lowest price before a discount (EU Omnibus Directive)"
    342396msgstr ""
    343397
    344 #: includes/class-settings.php:143
     398#: includes/class-settings.php:160
    345399msgid "When a price is reduced, EU law requires showing the lowest price of the product in the 30 days before the reduction, and calculating any strikethrough price or percentage from that price."
    346400msgstr ""
    347401
    348 #: includes/class-settings.php:151
     402#: includes/class-settings.php:168
    349403msgid "Enable"
    350404msgstr ""
    351405
    352 #: includes/class-settings.php:152
     406#: includes/class-settings.php:169
    353407msgid "Show the lowest price information in the shop"
    354408msgstr ""
    355409
    356 #: includes/class-settings.php:158
     410#: includes/class-settings.php:175
    357411msgid "Show on"
    358412msgstr ""
    359413
    360 #: includes/class-settings.php:159
     414#: includes/class-settings.php:176
    361415msgid "Single product pages (including each variation)"
    362416msgstr ""
    363417
    364 #: includes/class-settings.php:166
     418#: includes/class-settings.php:183
    365419msgid "Shop, category and other product lists"
    366420msgstr ""
    367421
    368 #: includes/class-settings.php:173
     422#: includes/class-settings.php:190
    369423msgid "Position on product pages"
    370424msgstr ""
    371425
    372 #: includes/class-settings.php:177
     426#: includes/class-settings.php:194
    373427msgid "If your theme or page builder shows prices in an unusual way, choose another position, or \"Nowhere\" and place the block, shortcode [cartnorm_lowest_price] or Elementor widget yourself."
    374428msgstr ""
    375429
    376 #: includes/class-settings.php:179
     430#: includes/class-settings.php:196
    377431msgid "Right under the price (recommended)"
    378432msgstr ""
    379433
    380 #: includes/class-settings.php:180
     434#: includes/class-settings.php:197
    381435msgid "Under the short description (classic themes)"
    382436msgstr ""
    383437
    384 #: includes/class-settings.php:181
     438#: includes/class-settings.php:198
    385439msgid "Under the add-to-cart button (classic themes)"
    386440msgstr ""
    387441
    388 #: includes/class-settings.php:182
     442#: includes/class-settings.php:199
    389443msgid "Nowhere (I place it myself)"
    390444msgstr ""
    391445
    392 #: includes/class-settings.php:186
     446#: includes/class-settings.php:203
    393447msgid "Show for"
    394448msgstr ""
    395449
    396 #: includes/class-settings.php:191
     450#: includes/class-settings.php:208
    397451msgid "Reduced products only (what the law requires)"
    398452msgstr ""
    399453
    400 #: includes/class-settings.php:192
     454#: includes/class-settings.php:209
    401455msgid "All products (lowest price in the last 30 days)"
    402456msgstr ""
    403457
    404 #: includes/class-settings.php:196
     458#: includes/class-settings.php:213
    405459msgid "Strikethrough price"
    406460msgstr ""
    407461
    408 #: includes/class-settings.php:200
     462#: includes/class-settings.php:217
    409463msgid "The CJEU (case C-330/23) ruled that an announced reduction must be calculated from the lowest price of the previous 30 days."
    410464msgstr ""
    411465
    412 #: includes/class-settings.php:202
     466#: includes/class-settings.php:219
    413467msgid "Lowest price of the 30 days before the reduction (recommended)"
    414468msgstr ""
    415469
    416 #: includes/class-settings.php:203
     470#: includes/class-settings.php:220
    417471msgid "Regular price (WooCommerce default)"
    418472msgstr ""
    419473
    420 #: includes/class-settings.php:207
     474#: includes/class-settings.php:224
    421475msgid "Sale badge"
    422476msgstr ""
    423477
    424 #: includes/class-settings.php:212
     478#: includes/class-settings.php:229
    425479msgid "Theme default (\"Sale!\")"
    426480msgstr ""
    427481
    428 #: includes/class-settings.php:213
     482#: includes/class-settings.php:230
    429483msgid "Percentage calculated from the 30-day lowest price, rounded down (e.g. \"-20%\")"
    430484msgstr ""
    431485
    432 #: includes/class-settings.php:217
     486#: includes/class-settings.php:234
    433487msgid "Fake discount guard"
    434488msgstr ""
    435489
    436 #: includes/class-settings.php:218
     490#: includes/class-settings.php:235
    437491msgid "Hide the strikethrough price, the sale badge and the notice when the sale price is not actually below the 30-day lowest price"
    438492msgstr ""
    439493
    440 #: includes/class-settings.php:224
     494#: includes/class-settings.php:241
    441495msgid "Country rules"
    442496msgstr ""
    443497
    444 #: includes/class-settings.php:229
     498#: includes/class-settings.php:246
    445499msgid "Member states differ in details such as progressive reductions and new products. Choose the country whose law applies to your shop."
    446500msgstr ""
    447501
    448 #: includes/class-settings.php:232
     502#: includes/class-settings.php:249
    449503msgid "Progressive reductions"
    450504msgstr ""
    451505
    452 #: includes/class-settings.php:236
     506#: includes/class-settings.php:253
    453507msgid "Whether uninterrupted, increasing reductions keep the reference price of the first reduction."
    454508msgstr ""
    455509
    456 #: includes/class-settings.php:238
     510#: includes/class-settings.php:255
    457511msgid "Follow the country rules"
    458512msgstr ""
    459513
    460 #: includes/class-settings.php:239
     514#: includes/class-settings.php:256
    461515msgid "Never (each further reduction gets a new reference)"
    462516msgstr ""
    463517
    464 #: includes/class-settings.php:240
     518#: includes/class-settings.php:257
    465519msgid "Always (one uninterrupted campaign)"
    466520msgstr ""
    467521
    468 #: includes/class-settings.php:244
     522#: includes/class-settings.php:261
    469523msgid "When history is too short"
    470524msgstr ""
    471525
    472 #: includes/class-settings.php:248
     526#: includes/class-settings.php:265
    473527msgid "Used while the plugin is still building history, for example right after installation."
    474528msgstr ""
    475529
    476 #: includes/class-settings.php:250
     530#: includes/class-settings.php:267
    477531msgid "Use the regular price"
    478532msgstr ""
    479533
    480 #: includes/class-settings.php:251
     534#: includes/class-settings.php:268
    481535msgid "Use the lowest price recorded so far"
    482536msgstr ""
    483537
    484 #: includes/class-settings.php:252
     538#: includes/class-settings.php:269
    485539msgid "Show nothing"
    486540msgstr ""
    487541
    488 #: includes/class-settings.php:256
     542#: includes/class-settings.php:273
    489543msgid "Look-back period (days)"
    490544msgstr ""
    491545
    492 #: includes/class-settings.php:261
     546#: includes/class-settings.php:278
    493547msgid "At least 30 days. Some member states allow or require longer periods in specific cases."
    494548msgstr ""
    495549
    496 #: includes/class-settings.php:264
     550#: includes/class-settings.php:281
    497551msgid "Custom text"
    498552msgstr ""
    499553
    500 #: includes/class-settings.php:269
     554#: includes/class-settings.php:286
    501555msgid "Leave empty to use the default text in the shop language (built in for German, Polish, French, Italian, Spanish and Dutch). Use {price} for the price and {days} for the number of days."
    502556msgstr ""
    503557
    504 #: includes/class-settings.php:272
     558#: includes/class-settings.php:289
    505559msgid "Keep history for (days)"
    506560msgstr ""
    507561
    508 #: includes/class-settings.php:277
     562#: includes/class-settings.php:294
    509563msgid "Older price changes are removed automatically. The price that was valid at the start of the look-back window is always kept."
    510564msgstr ""
    511565
    512566#. translators: 1: number of days of the look-back period, 2: number of days left
    513 #: includes/class-settings.php:302
     567#: includes/class-settings.php:319
    514568#, php-format
    515569msgid "The full %1$d-day history will be available in %2$d day. Until then, the fallback chosen under \"When history is too short\" is used."
    … …  
    519573
    520574#. translators: Customer-facing notice shown next to a reduced price (EU Omnibus Directive). {price} is replaced by the price, {days} by the number of days. Keep both placeholders.
    521 #: includes/class-settings.php:320
     575#: includes/class-settings.php:337
    522576msgid "Lowest price in the {days} days before the discount: {price}"
    523577msgstr ""
    524578
    525579#. translators: Customer-facing notice shown next to a price that is not reduced. {price} is replaced by the price, {days} by the number of days. Keep both placeholders.
    526 #: includes/class-settings.php:331
     580#: includes/class-settings.php:348
    527581msgid "Lowest price in the last {days} days: {price}"
    528582msgstr ""
    529583
    530584#. translators: Placeholder for the lowest-price notice in the block editor and page builders.
    531 #: includes/class-settings.php:342 blocks/lowest-price/editor.js:16
     585#: includes/class-settings.php:359
     586#: blocks/lowest-price/editor.js:16
    532587msgid "Lowest price in the 30 days before the discount (shown when the product is reduced)"
    533588msgstr ""
    … …  
    564619msgid "Right"
    565620msgstr ""
    566 
    567 #: blocks/lowest-price/block.json
    568 msgctxt "block title"
    569 msgid "Lowest price (Omnibus)"
    570 msgstr ""
    571 
    572 #: blocks/lowest-price/block.json
    573 msgctxt "block description"
    574 msgid "Shows the lowest price of the product in the 30 days before the discount."
    575 msgstr ""
    576 
    577 #: blocks/lowest-price/block.json
    578 msgctxt "block keyword"
    579 msgid "omnibus"
    580 msgstr ""
    581 
    582 #: blocks/lowest-price/block.json
    583 msgctxt "block keyword"
    584 msgid "lowest price"
    585 msgstr ""
    586 
    587 #: blocks/lowest-price/block.json
    588 msgctxt "block keyword"
    589 msgid "30 days"
    590 msgstr ""
    591 
    592 #: blocks/lowest-price/block.json
    593 msgctxt "block keyword"
    594 msgid "price history"
    595 msgstr ""
  • cartnorm-omnibus-lowest-price/tags/0.1.2/readme.txt

    r3764542 r3764726  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 0.1.1
     7Stable tag: 0.1.2
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    8484Product pages, shop and category pages, a block for block themes, an **Elementor widget** (with color, typography and alignment), and the `[cartnorm_lowest_price]` shortcode. Choose where the notice appears on product pages: under the price, under the short description, under the add-to-cart button, or nowhere (place it yourself). On variable products, the general notice names its variation and is replaced by the selected variation's own notice. The customer-facing notice is built in for German, Polish, French, Italian, Spanish and Dutch shops, using the wording recommended by national regulators (for example "Najniższa cena z 30 dni przed obniżką" in Poland).
    8585
     86= Free, with nothing locked =
     87
     88Every feature described above is included in this free plugin and works without an account or licence. An optional paid add-on, Cartnorm Omnibus Pro, adds tools for multi-currency shops and a few extras (see "Is there a Pro version?" in the FAQ).
     89
    8690= Not legal advice =
    8791
    … …  
    122126The notice shown to shoppers is built in for German (Germany, Austria, Switzerland), Polish, French (France, Belgium), Italian, Spanish and Dutch (Netherlands, Belgium), so it appears in the shop language from day one. You can replace it with your own text under "Custom text" in the settings. When a translation from translate.wordpress.org is installed, it is used instead. All other texts can be translated on translate.wordpress.org.
    123127
     128= Is there a Pro version? =
     129
     130Yes. Cartnorm Omnibus Pro is an optional paid add-on. This free plugin stays fully functional without it; nothing in it is locked or time-limited. Pro adds:
     131
     132* Multi-currency: the lowest-price notice in the currency the customer sees (WPML / WooCommerce Multilingual, Aelia, CURCY, FOX or your own rates), with a separate 30-day history for each currency.
     133* A warning when the regular price was raised in the 30 days before a sale.
     134* A monthly Omnibus report by email.
     135* Google Shopping feed prices that match the 30-day lowest price shown in your shop.
     136* An EU withdrawal button (Directive (EU) 2023/2673).
     137
     138Details and prices: [cartnorm.com/pricing](https://cartnorm.com/pricing/). The free plugin shows a "Pro version" link on the Plugins screen and one small note in its own settings, which you can dismiss for good. Both disappear when Pro is active.
     139
    124140= What happens when I uninstall the plugin? =
    125141
    … …  
    136152== Changelog ==
    137153
     154= 0.1.2 =
     155* New: Pro link on the Plugins screen and an optional, dismissible note in the plugin's own settings.
     156
    138157= 0.1.1 =
    139158* Fix: in block themes (such as Twenty Twenty-Five) the sale badge in shop and category lists now follows the plugin settings too: it is hidden on discounts that are not genuine, and shows the percentage when that option is on. Older product grid blocks are covered as well.
  • cartnorm-omnibus-lowest-price/trunk/cartnorm-omnibus-lowest-price.php

    r3764542 r3764726  
    44 * Plugin URI:        https://cartnorm.com/
    55 * Description:       Shows the lowest price from the 30 days before a discount, as required by the EU Omnibus Directive. Change-only price history that never bloats your database.
    6  * Version:           0.1.1
     6 * Version:           0.1.2
    77 * Requires at least: 6.4
    88 * Requires PHP:      7.4
    … …  
    2121defined( 'ABSPATH' ) || exit;
    2222
    23 define( 'CARTNORM_VERSION', '0.1.1' );
     23define( 'CARTNORM_VERSION', '0.1.2' );
    2424define( 'CARTNORM_FILE', __FILE__ );
    2525define( 'CARTNORM_DIR', plugin_dir_path( __FILE__ ) );
  • cartnorm-omnibus-lowest-price/trunk/includes/class-admin.php

    r3764542 r3764726  
    138138                        echo '<table class="widefat striped" style="font-size:11px"><thead><tr><th>' . esc_html__( 'From', 'cartnorm-omnibus-lowest-price' ) . '</th><th>' . esc_html__( 'Price', 'cartnorm-omnibus-lowest-price' ) . '</th><th>' . esc_html__( 'Sale', 'cartnorm-omnibus-lowest-price' ) . '</th><th></th></tr></thead><tbody>';
    139139                        foreach ( $rows as $row ) {
    140                                 echo '<tr><td>' . esc_html( wp_date( get_option( 'date_format' ), Clock::from_mysql( $row->valid_from ) ) ) . '</td><td>' . wp_kses_post( wc_price( $row->price ) ) . '</td><td>' . ( (int) $row->on_sale ? '&#10003;' : '' ) . '</td><td>' . Corrections::link( $row ) . '</td></tr>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- link() escapes.
     140                                echo '<tr><td>' . esc_html( wp_date( get_option( 'date_format' ), Clock::from_mysql( $row->valid_from ) ) ) . '</td><td>' . wp_kses_post( wc_price( $row->price ) ) . '</td><td>' . ( (int) $row->on_sale ? '&#10003;' : '' ) . '</td><td>' . wp_kses_post( Corrections::link( $row ) ) . '</td></tr>';
    141141                        }
    142142                        echo '</tbody></table>';
  • cartnorm-omnibus-lowest-price/trunk/includes/class-compliance-check.php

    r3764542 r3764726  
    169169                        )
    170170                ) . '</p>';
     171                Notices::render_pro_note();
    171172
    172173                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only filter.
  • cartnorm-omnibus-lowest-price/trunk/includes/class-notices.php

    r3764542 r3764726  
    66 * - "Import price history": an inline, dismissible box inside this plugin's own
    77 *   settings section, shown only when another Omnibus plugin's history exists.
     8 * - A note about the optional Pro add-on: one small inline box inside this
     9 *   plugin's own settings section and Omnibus check page, for users who can
     10 *   manage WooCommerce. It can be dismissed for good (per user, works without
     11 *   JavaScript) and is never shown when the Pro add-on is active.
    812 * - The history-building status is a plain line in the settings section
    913 *   description (see Settings::history_status()), not a notice.
    … …  
    2832        const FIELD_TYPE   = 'cartnorm_notices';
    2933        const IMPORT_INFIX = 'import_';
     34        const PRO_KEY      = 'pro_info';
     35        const PRO_URL      = 'https://cartnorm.com/pricing/';
    3036
    3137        /**
    … …  
    95101         */
    96102        public static function keys() {
    97                 $keys = array();
     103                $keys = array( self::PRO_KEY );
    98104                foreach ( array_keys( Importer::source_labels() ) as $source ) {
    99105                        $keys[] = self::IMPORT_INFIX . $source;
    … …  
    131137                }
    132138                return true;
     139        }
     140
     141        /**
     142         * Whether Cartnorm Omnibus Pro is active.
     143         *
     144         * @return bool
     145         */
     146        public static function pro_active() {
     147                /**
     148                 * Whether Cartnorm Omnibus Pro is active.
     149                 *
     150                 * @param bool $active Active.
     151                 */
     152                return (bool) apply_filters( 'cartnorm_pro_active', defined( 'CARTNORM_PRO_VERSION' ) );
     153        }
     154
     155        /**
     156         * Whether a currency switcher supported by Cartnorm Omnibus Pro is active:
     157         * WPML / WooCommerce Multilingual in multi-currency mode, Aelia, CURCY or
     158         * FOX (WOOCS). Only checks for loaded classes, functions and globals.
     159         *
     160         * @return bool
     161         */
     162        public static function multi_currency_detected() {
     163                $detected = self::wcml_multi_currency()
     164                        || class_exists( 'WC_Aelia_CurrencySwitcher' ) || has_filter( 'wc_aelia_cs_convert' )
     165                        || function_exists( 'wmc_get_price' )
     166                        || ( isset( $GLOBALS['WOOCS'] ) && is_object( $GLOBALS['WOOCS'] ) ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName
     167
     168                /**
     169                 * Whether the shop uses a currency switcher supported by Cartnorm Omnibus Pro.
     170                 *
     171                 * @param bool $detected Detected.
     172                 */
     173                return (bool) apply_filters( 'cartnorm_multi_currency_detected', $detected );
     174        }
     175
     176        /**
     177         * WPML / WooCommerce Multilingual with multi-currency switched on.
     178         *
     179         * @return bool
     180         */
     181        private static function wcml_multi_currency() {
     182                if ( ! defined( 'WCML_VERSION' ) && ! class_exists( 'woocommerce_wpml' ) ) {
     183                        return false;
     184                }
     185                global $woocommerce_wpml;
     186                if ( is_object( $woocommerce_wpml ) && isset( $woocommerce_wpml->settings['enable_multi_currency'] ) ) {
     187                        $independent = defined( 'WCML_MULTI_CURRENCIES_INDEPENDENT' ) ? WCML_MULTI_CURRENCIES_INDEPENDENT : 2;
     188                        return (int) $woocommerce_wpml->settings['enable_multi_currency'] === (int) $independent;
     189                }
     190                return true;
     191        }
     192
     193        /**
     194         * No-JavaScript dismiss link for a notice.
     195         *
     196         * @param string $key Notice key.
     197         * @return string
     198         */
     199        private static function dismiss_link( $key ) {
     200                $url = wp_nonce_url(
     201                        add_query_arg(
     202                                array(
     203                                        'action' => self::ACTION,
     204                                        'notice' => $key,
     205                                ),
     206                                admin_url( 'admin-post.php' )
     207                        ),
     208                        self::ACTION
     209                );
     210                return ' <a class="hide-if-js" href="' . esc_url( $url ) . '">' . esc_html__( 'Dismiss', 'cartnorm-omnibus-lowest-price' ) . '</a>';
     211        }
     212
     213        /**
     214         * The note about Cartnorm Omnibus Pro, as HTML. Empty unless the current
     215         * user can manage WooCommerce, is on one of this plugin's own screens, has
     216         * not dismissed it, and the Pro add-on is not active.
     217         *
     218         * @return string
     219         */
     220        public static function pro_note_html() {
     221                if ( ! current_user_can( 'manage_woocommerce' ) || ! self::is_own_screen() || self::pro_active() || self::is_dismissed( self::PRO_KEY ) ) {
     222                        return '';
     223                }
     224                if ( self::multi_currency_detected() ) {
     225                        $text = __( 'Your shop shows prices in more than one currency. This plugin shows the lowest-price notice in your base currency only; Cartnorm Omnibus Pro shows the correct 30-day lowest price in each currency.', 'cartnorm-omnibus-lowest-price' );
     226                } else {
     227                        $text = __( 'Optional paid add-on: Cartnorm Omnibus Pro adds multi-currency support, a warning when the regular price was raised before a sale, a monthly email report, Google Shopping feed prices and an EU withdrawal button.', 'cartnorm-omnibus-lowest-price' );
     228                }
     229                return '<div class="notice notice-info inline is-dismissible cartnorm-notice cartnorm-pro-note" data-cartnorm-notice="' . esc_attr( self::PRO_KEY ) . '"><p>'
     230                        . esc_html( $text )
     231                        . ' <a href="' . esc_url( self::PRO_URL ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Learn more', 'cartnorm-omnibus-lowest-price' ) . '</a>'
     232                        . self::dismiss_link( self::PRO_KEY )
     233                        . '</p></div>';
     234        }
     235
     236        /**
     237         * Print the Pro note (Omnibus check page).
     238         */
     239        public static function render_pro_note() {
     240                echo wp_kses( self::pro_note_html(), Display::allowed_html() );
    133241        }
    134242
    … …  
    171279                        }
    172280
    173                         $dismiss_url = wp_nonce_url(
    174                                 add_query_arg(
    175                                         array(
    176                                                 'action' => self::ACTION,
    177                                                 'notice' => $key,
    178                                         ),
    179                                         admin_url( 'admin-post.php' )
    180                                 ),
    181                                 self::ACTION
    182                         );
    183281                        $out .= '<div class="notice notice-info inline is-dismissible cartnorm-notice" data-cartnorm-notice="' . esc_attr( $key ) . '"><p>'
    184282                                . esc_html( $text ) . ' ' . $button
    185                                 . ' <a class="hide-if-js" href="' . esc_url( $dismiss_url ) . '">' . esc_html__( 'Dismiss', 'cartnorm-omnibus-lowest-price' ) . '</a>'
     283                                . self::dismiss_link( $key )
    186284                                . '</p></div>';
    187285                }
    188                 return $out;
     286                return $out . self::pro_note_html();
    189287        }
    190288
    … …  
    200298                        return;
    201299                }
    202                 echo '<tr class="cartnorm-notices-row"><td colspan="2" style="padding:0 0 8px">' . $html . '</td></tr>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped in html().
    203         }
    204 
    205         /**
    206          * Script that stores the dismissal when the core dismiss button is clicked.
    207          * Loaded only on this plugin's own screens.
     300                echo '<tr class="cartnorm-notices-row"><td colspan="2" style="padding:0 0 8px">' . wp_kses( $html, Display::allowed_html() ) . '</td></tr>';
     301        }
     302
     303        /**
     304         * Script that stores the dismissal when the core dismiss button is clicked,
     305         * and a one-line style for the box. Loaded only on this plugin's own screens.
    208306         */
    209307        public static function enqueue() {
    … …  
    213311                $script = '(function(){document.addEventListener("click",function(e){var b=e.target.closest?e.target.closest(".cartnorm-notice .notice-dismiss"):null;if(!b){return;}var n=b.closest(".cartnorm-notice");var d=new FormData();d.append("action",' . wp_json_encode( self::ACTION ) . ');d.append("notice",n.getAttribute("data-cartnorm-notice"));d.append("_ajax_nonce",' . wp_json_encode( wp_create_nonce( self::ACTION ) ) . ');fetch(' . wp_json_encode( admin_url( 'admin-ajax.php' ) ) . ',{method:"POST",credentials:"same-origin",body:d});});})();';
    214312                wp_add_inline_script( 'common', $script );
     313                // WooCommerce screens override the core padding that keeps text clear of the dismiss button.
     314                wp_add_inline_style( 'common', '.wp-core-ui .cartnorm-notice.notice.is-dismissible{padding-right:48px}' );
    215315        }
    216316
    … …  
    248348                }
    249349                self::dismiss( self::requested_key() );
    250                 wp_safe_redirect( Settings::url() );
     350                $back = wp_get_referer();
     351                wp_safe_redirect( $back ? $back : Settings::url() );
    251352                exit;
    252353        }
  • cartnorm-omnibus-lowest-price/trunk/includes/class-settings.php

    r3764542 r3764726  
    8080                add_filter( 'woocommerce_get_settings_products', array( __CLASS__, 'add_settings' ), 10, 2 );
    8181                add_filter( 'plugin_action_links_' . plugin_basename( CARTNORM_FILE ), array( __CLASS__, 'action_links' ) );
     82                add_filter( 'plugin_row_meta', array( __CLASS__, 'row_meta' ), 10, 2 );
    8283        }
    8384
    … …  
    9899         */
    99100        public static function action_links( $links ) {
    100                 array_unshift( $links, '<a href="' . esc_url( self::url() ) . '">' . esc_html__( 'Settings', 'cartnorm-omnibus-lowest-price' ) . '</a>' );
     101                array_unshift( $links, Display::kses( '<a href="' . esc_url( self::url() ) . '">' . esc_html__( 'Settings', 'cartnorm-omnibus-lowest-price' ) . '</a>' ) );
     102                return $links;
     103        }
     104
     105        /**
     106         * Add a "Pro version" link to this plugin's row on the Plugins screen
     107         * (only for this plugin, and not when the Pro add-on is active).
     108         *
     109         * @param array  $links Row meta links.
     110         * @param string $file  Plugin file.
     111         * @return array
     112         */
     113        public static function row_meta( $links, $file ) {
     114                if ( plugin_basename( CARTNORM_FILE ) !== $file || Notices::pro_active() ) {
     115                        return $links;
     116                }
     117                $links[] = Display::kses( '<a href="' . esc_url( Notices::PRO_URL ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Pro version', 'cartnorm-omnibus-lowest-price' ) . '</a>' );
    101118                return $links;
    102119        }
  • cartnorm-omnibus-lowest-price/trunk/languages/cartnorm-omnibus-lowest-price.pot

    r3764542 r3764726  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: Cartnorm Omnibus Lowest Price 0.1.1\n"
     5"Project-Id-Version: Cartnorm Omnibus Lowest Price 0.1.2\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/cartnorm-omnibus-lowest-price\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
    … …  
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2026-10-25T08:45:23+00:00\n"
     12"POT-Creation-Date: 2026-10-25T14:03:29+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.12.0\n"
    1515"X-Domain: cartnorm-omnibus-lowest-price\n"
    1616
     17#: blocks/lowest-price/block.json
     18msgctxt "block title"
     19msgid "Lowest price (Omnibus)"
     20msgstr ""
     21
     22#: blocks/lowest-price/block.json
     23msgctxt "block description"
     24msgid "Shows the lowest price of the product in the 30 days before the discount."
     25msgstr ""
     26
     27#: blocks/lowest-price/block.json
     28msgctxt "block keyword"
     29msgid "omnibus"
     30msgstr ""
     31
     32#: blocks/lowest-price/block.json
     33msgctxt "block keyword"
     34msgid "lowest price"
     35msgstr ""
     36
     37#: blocks/lowest-price/block.json
     38msgctxt "block keyword"
     39msgid "30 days"
     40msgstr ""
     41
     42#: blocks/lowest-price/block.json
     43msgctxt "block keyword"
     44msgid "price history"
     45msgstr ""
     46
    1747#. Plugin Name of the plugin
    1848#: cartnorm-omnibus-lowest-price.php
    … …  
    4373msgstr ""
    4474
    45 #: includes/class-admin.php:68 includes/class-settings.php:111
     75#: includes/class-admin.php:68
     76#: includes/class-settings.php:128
    4677#: includes/integrations/class-elementor-widget.php:36
    4778msgid "Lowest price (Omnibus)"
    … …  
    102133msgstr ""
    103134
    104 #: includes/class-admin.php:161 includes/class-corrections.php:218
    105 #: includes/class-importer.php:337 includes/class-notices.php:247
     135#: includes/class-admin.php:161
     136#: includes/class-corrections.php:218
     137#: includes/class-importer.php:337
     138#: includes/class-notices.php:347
    106139msgid "You are not allowed to do this."
    107140msgstr ""
    … …  
    151184msgstr ""
    152185
    153 #: includes/class-compliance-check.php:198
     186#: includes/class-compliance-check.php:199
    154187msgid "No products are reduced at the moment."
    155188msgstr ""
    156189
    157 #: includes/class-compliance-check.php:203
     190#: includes/class-compliance-check.php:204
    158191#: includes/integrations/class-elementor-widget.php:72
    159192msgid "Product"
    160193msgstr ""
    161194
    162 #: includes/class-compliance-check.php:203
     195#: includes/class-compliance-check.php:204
    163196msgid "Status"
    164197msgstr ""
    165198
    166 #: includes/class-compliance-check.php:203
     199#: includes/class-compliance-check.php:204
    167200msgid "Sale price"
    168201msgstr ""
    169202
    170 #: includes/class-compliance-check.php:203
     203#: includes/class-compliance-check.php:204
    171204msgid "Lowest price, 30 days before"
    172205msgstr ""
    173206
    174 #: includes/class-compliance-check.php:203
     207#: includes/class-compliance-check.php:204
    175208msgid "Sale started"
    176209msgstr ""
    177210
    178 #: includes/class-corrections.php:35 includes/class-corrections.php:69
     211#: includes/class-corrections.php:35
     212#: includes/class-corrections.php:69
    179213msgid "Correct price record"
    180214msgstr ""
    … …  
    184218msgstr ""
    185219
    186 #: includes/class-corrections.php:78 includes/class-corrections.php:130
     220#: includes/class-corrections.php:78
     221#: includes/class-corrections.php:130
    187222#: includes/class-corrections.php:154
    188223msgid "This record does not exist."
    … …  
    199234msgstr ""
    200235
    201 #: includes/class-corrections.php:97 includes/class-corrections.php:131
     236#: includes/class-corrections.php:97
     237#: includes/class-corrections.php:131
    202238#: includes/class-corrections.php:157
    203239msgid "The current price cannot be corrected here. Change it in the product instead."
    … …  
    236272msgstr ""
    237273
    238 #: includes/class-corrections.php:129 includes/class-corrections.php:148
     274#: includes/class-corrections.php:129
     275#: includes/class-corrections.php:148
    239276msgid "Please give a reason for the correction."
    240277msgstr ""
    241278
    242 #: includes/class-corrections.php:132 includes/class-corrections.php:160
     279#: includes/class-corrections.php:132
     280#: includes/class-corrections.php:160
    243281msgid "Please enter a valid price."
    244282msgstr ""
    245283
     284#: includes/class-country-rules.php:64
     285msgid "EU default rules"
     286msgstr ""
     287
    246288#: includes/class-country-rules.php:65
    247 msgid "EU default rules"
     289msgid "Austria"
    248290msgstr ""
    249291
    250292#: includes/class-country-rules.php:66
    251 msgid "Austria"
     293msgid "Belgium"
    252294msgstr ""
    253295
    254296#: includes/class-country-rules.php:67
    255 msgid "Belgium"
     297msgid "Czech Republic"
    256298msgstr ""
    257299
    258300#: includes/class-country-rules.php:68
    259 msgid "Czech Republic"
     301msgid "France"
    260302msgstr ""
    261303
    262304#: includes/class-country-rules.php:69
    263 msgid "France"
     305msgid "Germany"
    264306msgstr ""
    265307
    266308#: includes/class-country-rules.php:70
    267 msgid "Germany"
     309msgid "Ireland"
    268310msgstr ""
    269311
    270312#: includes/class-country-rules.php:71
    271 msgid "Ireland"
     313msgid "Italy"
    272314msgstr ""
    273315
    274316#: includes/class-country-rules.php:72
    275 msgid "Italy"
     317msgid "Netherlands"
    276318msgstr ""
    277319
    278320#: includes/class-country-rules.php:73
    279 msgid "Netherlands"
     321msgid "Poland"
    280322msgstr ""
    281323
    282324#: includes/class-country-rules.php:74
    283 msgid "Poland"
     325msgid "Portugal"
    284326msgstr ""
    285327
    286328#: includes/class-country-rules.php:75
    287 msgid "Portugal"
     329msgid "Romania"
    288330msgstr ""
    289331
    290332#: includes/class-country-rules.php:76
    291 msgid "Romania"
     333msgid "Spain"
    292334msgstr ""
    293335
    294336#: includes/class-country-rules.php:77
    295 msgid "Spain"
    296 msgstr ""
    297 
    298 #: includes/class-country-rules.php:78
    299337msgid "Sweden"
    300338msgstr ""
    301339
    302 #: includes/class-country-rules.php:80
     340#: includes/class-country-rules.php:79
    303341msgid "Automatic (store country)"
    304342msgstr ""
    305343
    306 #: includes/class-notices.php:88
     344#: includes/class-notices.php:94
    307345msgid "Cartnorm Omnibus Lowest Price needs WooCommerce to be installed and active."
    308346msgstr ""
    309347
     348#: includes/class-notices.php:210
     349msgid "Dismiss"
     350msgstr ""
     351
     352#: includes/class-notices.php:225
     353msgid "Your shop shows prices in more than one currency. This plugin shows the lowest-price notice in your base currency only; Cartnorm Omnibus Pro shows the correct 30-day lowest price in each currency."
     354msgstr ""
     355
     356#: includes/class-notices.php:227
     357msgid "Optional paid add-on: Cartnorm Omnibus Pro adds multi-currency support, a warning when the regular price was raised before a sale, a monthly email report, Google Shopping feed prices and an EU withdrawal button."
     358msgstr ""
     359
     360#: includes/class-notices.php:231
     361msgid "Learn more"
     362msgstr ""
     363
    310364#. translators: %s: name of another plugin
    311 #: includes/class-notices.php:153
     365#: includes/class-notices.php:261
    312366#, php-format
    313367msgid "Importing price history from %s in the background."
    314368msgstr ""
    315369
    316 #: includes/class-notices.php:160
     370#: includes/class-notices.php:268
    317371msgid "Import price history"
    318372msgstr ""
    319373
    320374#. translators: %s: name of another plugin
    321 #: includes/class-notices.php:163
     375#: includes/class-notices.php:271
    322376#, php-format
    323377msgid "Price history from %s was found. Import it to keep your full 30-day history after switching."
    324378msgstr ""
    325379
    326 #: includes/class-notices.php:185
    327 msgid "Dismiss"
    328 msgstr ""
    329 
    330 #: includes/class-settings.php:100
     380#: includes/class-settings.php:101
    331381msgid "Settings"
    332382msgstr ""
    333383
     384#: includes/class-settings.php:117
     385msgid "Pro version"
     386msgstr ""
     387
    334388#. translators: 1: number of stored price changes, 2: date tracking started
    335 #: includes/class-settings.php:131
     389#: includes/class-settings.php:148
    336390#, php-format
    337391msgid "Price history: %1$s stored price changes since %2$s. Rows are written only when a price changes."
    338392msgstr ""
    339393
    340 #: includes/class-settings.php:141
     394#: includes/class-settings.php:158
    341395msgid "Lowest price before a discount (EU Omnibus Directive)"
    342396msgstr ""
    343397
    344 #: includes/class-settings.php:143
     398#: includes/class-settings.php:160
    345399msgid "When a price is reduced, EU law requires showing the lowest price of the product in the 30 days before the reduction, and calculating any strikethrough price or percentage from that price."
    346400msgstr ""
    347401
    348 #: includes/class-settings.php:151
     402#: includes/class-settings.php:168
    349403msgid "Enable"
    350404msgstr ""
    351405
    352 #: includes/class-settings.php:152
     406#: includes/class-settings.php:169
    353407msgid "Show the lowest price information in the shop"
    354408msgstr ""
    355409
    356 #: includes/class-settings.php:158
     410#: includes/class-settings.php:175
    357411msgid "Show on"
    358412msgstr ""
    359413
    360 #: includes/class-settings.php:159
     414#: includes/class-settings.php:176
    361415msgid "Single product pages (including each variation)"
    362416msgstr ""
    363417
    364 #: includes/class-settings.php:166
     418#: includes/class-settings.php:183
    365419msgid "Shop, category and other product lists"
    366420msgstr ""
    367421
    368 #: includes/class-settings.php:173
     422#: includes/class-settings.php:190
    369423msgid "Position on product pages"
    370424msgstr ""
    371425
    372 #: includes/class-settings.php:177
     426#: includes/class-settings.php:194
    373427msgid "If your theme or page builder shows prices in an unusual way, choose another position, or \"Nowhere\" and place the block, shortcode [cartnorm_lowest_price] or Elementor widget yourself."
    374428msgstr ""
    375429
    376 #: includes/class-settings.php:179
     430#: includes/class-settings.php:196
    377431msgid "Right under the price (recommended)"
    378432msgstr ""
    379433
    380 #: includes/class-settings.php:180
     434#: includes/class-settings.php:197
    381435msgid "Under the short description (classic themes)"
    382436msgstr ""
    383437
    384 #: includes/class-settings.php:181
     438#: includes/class-settings.php:198
    385439msgid "Under the add-to-cart button (classic themes)"
    386440msgstr ""
    387441
    388 #: includes/class-settings.php:182
     442#: includes/class-settings.php:199
    389443msgid "Nowhere (I place it myself)"
    390444msgstr ""
    391445
    392 #: includes/class-settings.php:186
     446#: includes/class-settings.php:203
    393447msgid "Show for"
    394448msgstr ""
    395449
    396 #: includes/class-settings.php:191
     450#: includes/class-settings.php:208
    397451msgid "Reduced products only (what the law requires)"
    398452msgstr ""
    399453
    400 #: includes/class-settings.php:192
     454#: includes/class-settings.php:209
    401455msgid "All products (lowest price in the last 30 days)"
    402456msgstr ""
    403457
    404 #: includes/class-settings.php:196
     458#: includes/class-settings.php:213
    405459msgid "Strikethrough price"
    406460msgstr ""
    407461
    408 #: includes/class-settings.php:200
     462#: includes/class-settings.php:217
    409463msgid "The CJEU (case C-330/23) ruled that an announced reduction must be calculated from the lowest price of the previous 30 days."
    410464msgstr ""
    411465
    412 #: includes/class-settings.php:202
     466#: includes/class-settings.php:219
    413467msgid "Lowest price of the 30 days before the reduction (recommended)"
    414468msgstr ""
    415469
    416 #: includes/class-settings.php:203
     470#: includes/class-settings.php:220
    417471msgid "Regular price (WooCommerce default)"
    418472msgstr ""
    419473
    420 #: includes/class-settings.php:207
     474#: includes/class-settings.php:224
    421475msgid "Sale badge"
    422476msgstr ""
    423477
    424 #: includes/class-settings.php:212
     478#: includes/class-settings.php:229
    425479msgid "Theme default (\"Sale!\")"
    426480msgstr ""
    427481
    428 #: includes/class-settings.php:213
     482#: includes/class-settings.php:230
    429483msgid "Percentage calculated from the 30-day lowest price, rounded down (e.g. \"-20%\")"
    430484msgstr ""
    431485
    432 #: includes/class-settings.php:217
     486#: includes/class-settings.php:234
    433487msgid "Fake discount guard"
    434488msgstr ""
    435489
    436 #: includes/class-settings.php:218
     490#: includes/class-settings.php:235
    437491msgid "Hide the strikethrough price, the sale badge and the notice when the sale price is not actually below the 30-day lowest price"
    438492msgstr ""
    439493
    440 #: includes/class-settings.php:224
     494#: includes/class-settings.php:241
    441495msgid "Country rules"
    442496msgstr ""
    443497
    444 #: includes/class-settings.php:229
     498#: includes/class-settings.php:246
    445499msgid "Member states differ in details such as progressive reductions and new products. Choose the country whose law applies to your shop."
    446500msgstr ""
    447501
    448 #: includes/class-settings.php:232
     502#: includes/class-settings.php:249
    449503msgid "Progressive reductions"
    450504msgstr ""
    451505
    452 #: includes/class-settings.php:236
     506#: includes/class-settings.php:253
    453507msgid "Whether uninterrupted, increasing reductions keep the reference price of the first reduction."
    454508msgstr ""
    455509
    456 #: includes/class-settings.php:238
     510#: includes/class-settings.php:255
    457511msgid "Follow the country rules"
    458512msgstr ""
    459513
    460 #: includes/class-settings.php:239
     514#: includes/class-settings.php:256
    461515msgid "Never (each further reduction gets a new reference)"
    462516msgstr ""
    463517
    464 #: includes/class-settings.php:240
     518#: includes/class-settings.php:257
    465519msgid "Always (one uninterrupted campaign)"
    466520msgstr ""
    467521
    468 #: includes/class-settings.php:244
     522#: includes/class-settings.php:261
    469523msgid "When history is too short"
    470524msgstr ""
    471525
    472 #: includes/class-settings.php:248
     526#: includes/class-settings.php:265
    473527msgid "Used while the plugin is still building history, for example right after installation."
    474528msgstr ""
    475529
    476 #: includes/class-settings.php:250
     530#: includes/class-settings.php:267
    477531msgid "Use the regular price"
    478532msgstr ""
    479533
    480 #: includes/class-settings.php:251
     534#: includes/class-settings.php:268
    481535msgid "Use the lowest price recorded so far"
    482536msgstr ""
    483537
    484 #: includes/class-settings.php:252
     538#: includes/class-settings.php:269
    485539msgid "Show nothing"
    486540msgstr ""
    487541
    488 #: includes/class-settings.php:256
     542#: includes/class-settings.php:273
    489543msgid "Look-back period (days)"
    490544msgstr ""
    491545
    492 #: includes/class-settings.php:261
     546#: includes/class-settings.php:278
    493547msgid "At least 30 days. Some member states allow or require longer periods in specific cases."
    494548msgstr ""
    495549
    496 #: includes/class-settings.php:264
     550#: includes/class-settings.php:281
    497551msgid "Custom text"
    498552msgstr ""
    499553
    500 #: includes/class-settings.php:269
     554#: includes/class-settings.php:286
    501555msgid "Leave empty to use the default text in the shop language (built in for German, Polish, French, Italian, Spanish and Dutch). Use {price} for the price and {days} for the number of days."
    502556msgstr ""
    503557
    504 #: includes/class-settings.php:272
     558#: includes/class-settings.php:289
    505559msgid "Keep history for (days)"
    506560msgstr ""
    507561
    508 #: includes/class-settings.php:277
     562#: includes/class-settings.php:294
    509563msgid "Older price changes are removed automatically. The price that was valid at the start of the look-back window is always kept."
    510564msgstr ""
    511565
    512566#. translators: 1: number of days of the look-back period, 2: number of days left
    513 #: includes/class-settings.php:302
     567#: includes/class-settings.php:319
    514568#, php-format
    515569msgid "The full %1$d-day history will be available in %2$d day. Until then, the fallback chosen under \"When history is too short\" is used."
    … …  
    519573
    520574#. translators: Customer-facing notice shown next to a reduced price (EU Omnibus Directive). {price} is replaced by the price, {days} by the number of days. Keep both placeholders.
    521 #: includes/class-settings.php:320
     575#: includes/class-settings.php:337
    522576msgid "Lowest price in the {days} days before the discount: {price}"
    523577msgstr ""
    524578
    525579#. translators: Customer-facing notice shown next to a price that is not reduced. {price} is replaced by the price, {days} by the number of days. Keep both placeholders.
    526 #: includes/class-settings.php:331
     580#: includes/class-settings.php:348
    527581msgid "Lowest price in the last {days} days: {price}"
    528582msgstr ""
    529583
    530584#. translators: Placeholder for the lowest-price notice in the block editor and page builders.
    531 #: includes/class-settings.php:342 blocks/lowest-price/editor.js:16
     585#: includes/class-settings.php:359
     586#: blocks/lowest-price/editor.js:16
    532587msgid "Lowest price in the 30 days before the discount (shown when the product is reduced)"
    533588msgstr ""
    … …  
    564619msgid "Right"
    565620msgstr ""
    566 
    567 #: blocks/lowest-price/block.json
    568 msgctxt "block title"
    569 msgid "Lowest price (Omnibus)"
    570 msgstr ""
    571 
    572 #: blocks/lowest-price/block.json
    573 msgctxt "block description"
    574 msgid "Shows the lowest price of the product in the 30 days before the discount."
    575 msgstr ""
    576 
    577 #: blocks/lowest-price/block.json
    578 msgctxt "block keyword"
    579 msgid "omnibus"
    580 msgstr ""
    581 
    582 #: blocks/lowest-price/block.json
    583 msgctxt "block keyword"
    584 msgid "lowest price"
    585 msgstr ""
    586 
    587 #: blocks/lowest-price/block.json
    588 msgctxt "block keyword"
    589 msgid "30 days"
    590 msgstr ""
    591 
    592 #: blocks/lowest-price/block.json
    593 msgctxt "block keyword"
    594 msgid "price history"
    595 msgstr ""
  • cartnorm-omnibus-lowest-price/trunk/readme.txt

    r3764542 r3764726  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 0.1.1
     7Stable tag: 0.1.2
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    8484Product pages, shop and category pages, a block for block themes, an **Elementor widget** (with color, typography and alignment), and the `[cartnorm_lowest_price]` shortcode. Choose where the notice appears on product pages: under the price, under the short description, under the add-to-cart button, or nowhere (place it yourself). On variable products, the general notice names its variation and is replaced by the selected variation's own notice. The customer-facing notice is built in for German, Polish, French, Italian, Spanish and Dutch shops, using the wording recommended by national regulators (for example "Najniższa cena z 30 dni przed obniżką" in Poland).
    8585
     86= Free, with nothing locked =
     87
     88Every feature described above is included in this free plugin and works without an account or licence. An optional paid add-on, Cartnorm Omnibus Pro, adds tools for multi-currency shops and a few extras (see "Is there a Pro version?" in the FAQ).
     89
    8690= Not legal advice =
    8791
    … …  
    122126The notice shown to shoppers is built in for German (Germany, Austria, Switzerland), Polish, French (France, Belgium), Italian, Spanish and Dutch (Netherlands, Belgium), so it appears in the shop language from day one. You can replace it with your own text under "Custom text" in the settings. When a translation from translate.wordpress.org is installed, it is used instead. All other texts can be translated on translate.wordpress.org.
    123127
     128= Is there a Pro version? =
     129
     130Yes. Cartnorm Omnibus Pro is an optional paid add-on. This free plugin stays fully functional without it; nothing in it is locked or time-limited. Pro adds:
     131
     132* Multi-currency: the lowest-price notice in the currency the customer sees (WPML / WooCommerce Multilingual, Aelia, CURCY, FOX or your own rates), with a separate 30-day history for each currency.
     133* A warning when the regular price was raised in the 30 days before a sale.
     134* A monthly Omnibus report by email.
     135* Google Shopping feed prices that match the 30-day lowest price shown in your shop.
     136* An EU withdrawal button (Directive (EU) 2023/2673).
     137
     138Details and prices: [cartnorm.com/pricing](https://cartnorm.com/pricing/). The free plugin shows a "Pro version" link on the Plugins screen and one small note in its own settings, which you can dismiss for good. Both disappear when Pro is active.
     139
    124140= What happens when I uninstall the plugin? =
    125141
    … …  
    136152== Changelog ==
    137153
     154= 0.1.2 =
     155* New: Pro link on the Plugins screen and an optional, dismissible note in the plugin's own settings.
     156
    138157= 0.1.1 =
    139158* Fix: in block themes (such as Twenty Twenty-Five) the sale badge in shop and category lists now follows the plugin settings too: it is hidden on discounts that are not genuine, and shows the percentage when that option is on. Older product grid blocks are covered as well.
Note: See TracChangeset for help on using the changeset viewer.