Plugin Directory

Changeset 3625812


Ignore:
Timestamp:
07/28/2026 11:26:07 AM (3 months ago)
Author:
wpedgar
Message:

Update to version 1.7.3 from GitHub

Location:
priceblueprint-for-woocommerce
Files:
4 added
20 edited
1 copied

Legend:

Unmodified
Added
Removed
  • priceblueprint-for-woocommerce/tags/1.7.3/assets/css/welcome-screen.css

    r3597567 r3625812  
    132132}
    133133
    134 /* ── Actions ── */
    135 .prbp-welcome-actions {
    136         display: flex;
    137         align-items: center;
    138         gap: 16px;
    139         padding: 0 4px;
    140 }
    141 .prbp-welcome-cta {
     134/* ── Get started card (demo import = primary, create/skip = secondary) ── */
     135.prbp-welcome-getstarted {
     136        background: #fff;
     137        border: 1px solid #E2E8F0;
     138        border-radius: 12px;
     139        padding: 32px 40px;
     140        margin-bottom: 12px;
     141        box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04), 0 4px 12px rgba(15, 23, 42, 0.04);
     142}
     143
     144/* Shared button treatments */
     145.prbp-welcome-btn-primary,
     146.prbp-welcome-btn-secondary {
    142147        display: inline-flex;
    143148        align-items: center;
    144149        gap: 6px;
    145         background: #0D9488;
    146         color: #fff !important;
    147         border: none;
    148150        border-radius: 6px;
    149         padding: 10px 22px;
    150         font-size: 14px;
    151151        font-weight: 600;
    152152        text-decoration: none !important;
    153153        cursor: pointer;
     154        line-height: 1.4;
     155        font-family: inherit;
     156        white-space: nowrap;
     157}
     158.prbp-welcome-btn-primary {
     159        flex-shrink: 0;
     160        background: #0D9488;
     161        color: #fff !important;
     162        border: none;
     163        padding: 10px 22px;
     164        font-size: 14px;
    154165        transition: background .15s;
    155         line-height: 1.4;
    156 }
    157 .prbp-welcome-cta:hover {
     166}
     167.prbp-welcome-btn-primary:hover {
    158168        background: #0a7a70;
    159169        color: #fff !important;
     170}
     171.prbp-welcome-btn-primary:disabled {
     172        cursor: not-allowed;
     173        opacity: 0.6;
     174}
     175.prbp-welcome-btn-secondary {
     176        background: #fff;
     177        color: #1d2327 !important;
     178        border: 1px solid #c3c4c7;
     179        padding: 9px 20px;
     180        font-size: 13px;
     181        font-weight: 500;
     182        transition: border-color .15s, color .15s;
     183}
     184.prbp-welcome-btn-secondary:hover {
     185        border-color: #0a7a70;
     186        color: #0a7a70 !important;
    160187}
    161188.prbp-welcome-skip {
    … …  
    169196}
    170197
    171 /* ── Demo Import ── */
    172 .prbp-welcome-demo {
    173         background: #fff;
    174         border: 1px solid #E2E8F0;
    175         border-radius: 12px;
    176         padding: 28px 40px;
    177         margin-top: 12px;
    178         box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04), 0 4px 12px rgba(15, 23, 42, 0.04);
    179 }
     198/* ── Demo Import (primary action) ── */
    180199.prbp-welcome-demo .prbp-welcome-steps-title {
    181200        margin-bottom: 12px;
    … …  
    193212        line-height: 1.5;
    194213}
    195 .prbp-welcome-demo-btn {
    196         flex-shrink: 0;
    197         display: inline-flex;
    198         align-items: center;
    199         gap: 8px;
    200         background: #fff;
    201         color: #1d2327;
    202         border: 1px solid #c3c4c7;
    203         border-radius: 6px;
    204         padding: 9px 20px;
    205         font-size: 13px;
    206         font-weight: 500;
    207         cursor: pointer;
    208         transition: border-color .15s, color .15s;
    209         line-height: 1.4;
    210         font-family: inherit;
    211         white-space: nowrap;
    212 }
    213 .prbp-welcome-demo-btn:hover {
    214         border-color: #0a7a70;
    215         color: #0a7a70;
    216 }
    217 .prbp-welcome-demo-btn:disabled {
    218         cursor: not-allowed;
    219         opacity: 0.6;
     214
     215/* ── Divider between primary and secondary actions ── */
     216.prbp-welcome-divider {
     217        height: 1px;
     218        background: #E2E8F0;
     219        margin: 24px 0;
     220}
     221
     222/* ── Secondary row: create-from-scratch + skip ── */
     223.prbp-welcome-secondary-row {
     224        display: flex;
     225        align-items: center;
     226        flex-wrap: wrap;
     227        gap: 16px;
     228}
     229.prbp-welcome-secondary-label {
     230        font-size: 13px;
     231        color: #64748B;
     232        margin-right: 4px;
    220233}
    221234@keyframes prbp-spin {
  • priceblueprint-for-woocommerce/tags/1.7.3/priceblueprint-for-woocommerce.php

    r3605639 r3625812  
    33 * Plugin Name:       WooCommerce Attribute Pricing — PriceBlueprint
    44 * Description:       Reusable pricing blueprints for WooCommerce. Assign one blueprint to multiple products — define attribute-based pricing rules once, update everywhere instantly.
    5  * Version:           1.7.1
     5 * Version:           1.7.3
    66 * Requires at least: 6.0
    77 * Requires PHP:      7.4
    … …  
    4343                'has_paid_plans'      => false,
    4444                'is_org_compliant'    => true,
     45                'enable_anonymous'    => true,
    4546                'menu'                => array(
    4647                    'first-path'     => 'plugins.php',
    … …  
    5051                ),
    5152            ) );
     53
     54            // Reassuring, plain-language opt-in message so first-time activators
     55            // understand what's shared (and that "Skip" is a perfectly fine choice).
     56            $pfwp_fs->add_filter( 'connect_message', function () {
     57                $plugin_name = esc_html__( 'PriceBlueprint', 'priceblueprint-for-woocommerce' );
     58
     59                return sprintf(
     60                    /* translators: 1: opening <strong>PriceBlueprint</strong> tag, 2: plugin name */
     61                    esc_html__( 'Opt in to get security & feature updates for %1$s, plus help us keep it compatible with your store by sharing basic, non-personal environment info (WordPress/PHP/WooCommerce version, site URL). No spam, we never sell your data, and you can change your mind anytime from Account settings. Rather not share anything? Click "Skip" below — %2$s works exactly the same either way.', 'priceblueprint-for-woocommerce' ),
     62                    '<strong>' . $plugin_name . '</strong>',
     63                    $plugin_name
     64                );
     65            } );
    5266        }
    5367
    … …  
    7084// wp i18n make-pot . languages/priceblueprint.pot --domain=priceblueprint --exclude=vendor,node_modules
    7185
    72 define( 'PRBP_VERSION',    '1.7.1' );
     86define( 'PRBP_VERSION',    '1.7.3' );
    7387define( 'PRBP_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
    7488define( 'PRBP_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
  • priceblueprint-for-woocommerce/tags/1.7.3/readme.txt

    r3605639 r3625812  
    44Requires at least: 6.0
    55Tested up to: 7.0
    6 Stable tag: 1.7.1
     6Stable tag: 1.7.3
    77Requires PHP: 7.4
    88License: GPL-2.0-or-later
    … …  
    8484== Changelog ==
    8585
     86= 1.7.3 =
     87* Improved the Freemius connection prompt with a clearer explanation of what's shared and why.
     88* Reordered the welcome screen so trying the demo data comes first.
     89
     90= 1.7.2 =
     91* Security: Add-to-cart validation now verifies every selected option against the blueprint's actual rule values — previously a tampered form submission could add a product to the cart without paying for its selected options.
     92* Fix: The plugin could not be deleted from the Plugins screen — uninstall.php referenced a Freemius helper that is unavailable during uninstall, causing a fatal error. Uninstall now also removes plugin options and correctly clears leftover transients.
     93* Fix: Fatal parse error on PHP 7.4 — a PHP 8-only union type in the gallery placeholder handler broke sites running the minimum supported PHP version.
     94* Fix: Pricing rules containing non-Latin characters (ö, ü, ł, Cyrillic, etc.) or quotes in term labels were corrupted on save due to missing slashing around the stored JSON. Affected blueprints need to be re-saved once after updating.
     95* Fix: Moving a blueprint to the Trash no longer wipes prices and attributes of linked products. Trashing now only unpublishes linked products (fully reversible); permanent deletion additionally unlinks them, leaving all other product data intact.
     96
    8697= 1.7.1 =
    8798* Fix: "Import Demo Data" could fatal error (500) on some stores due to an unguarded wp_insert_term() call — now handled gracefully with a proper error message.
  • priceblueprint-for-woocommerce/tags/1.7.3/src/Admin/SaveHandler.php

    r3597567 r3625812  
    6969                }
    7070
    71                 update_post_meta( $post_id, 'prbp_template_rules', wp_json_encode( self::sanitizeSections( $sections ) ) );
     71                // wp_slash() — update_post_meta() unslashes its value, which would strip the
     72                // backslashes wp_json_encode() uses for \uXXXX and \" escapes and corrupt the JSON.
     73                update_post_meta( $post_id, 'prbp_template_rules', wp_slash( wp_json_encode( self::sanitizeSections( $sections ) ) ) );
    7274                RulesCache::flush( $post_id );
    7375        }
  • priceblueprint-for-woocommerce/tags/1.7.3/src/Ajax/ImportDemo.php

    r3605639 r3625812  
    183183                }
    184184
    185                 update_post_meta( $blueprint_id, 'prbp_template_rules', wp_json_encode( $rules ) );
     185                // wp_slash() — update_post_meta() unslashes, which would corrupt JSON escape sequences.
     186                update_post_meta( $blueprint_id, 'prbp_template_rules', wp_slash( wp_json_encode( $rules ) ) );
    186187
    187188                return $blueprint_id;
  • priceblueprint-for-woocommerce/tags/1.7.3/src/CPT/Blueprint.php

    r3552429 r3625812  
    1616        public static function register(): void {
    1717                add_action( 'init',               [ self::class, 'registerCPT' ],  10 );
    18                 add_action( 'before_delete_post', [ self::class, 'handleRemoval' ], 10, 1 );
    19                 add_action( 'wp_trash_post',      [ self::class, 'handleRemoval' ], 10, 1 );
     18                add_action( 'before_delete_post', [ self::class, 'handleDelete' ], 10, 1 );
     19                add_action( 'wp_trash_post',      [ self::class, 'handleTrash' ], 10, 1 );
    2020        }
    2121
    … …  
    4949
    5050        /**
    51          * When a blueprint is trashed or permanently deleted, move all linked products
    52          * to draft and clear their price, attributes, and blueprint reference.
     51         * Trashing a blueprint is reversible, so it must not destroy product data:
     52         * linked products are only unpublished (draft). Prices, attributes, and the
     53         * prbp_template_id link stay intact — restoring the blueprint from Trash lets
     54         * the merchant simply republish the products.
    5355         *
    5456         * @param int $post_id
    5557         */
    56         public static function handleRemoval( int $post_id ): void {
     58        public static function handleTrash( int $post_id ): void {
    5759                if ( get_post_type( $post_id ) !== 'price_blueprint' ) {
    5860                        return;
    5961                }
    6062
    61                 $linked_ids = get_posts( [
     63                foreach ( self::linkedProductIds( $post_id ) as $product_id ) {
     64                        self::unpublishProduct( $product_id );
     65                }
     66        }
     67
     68        /**
     69         * Permanent deletion: unpublish linked products and remove the blueprint
     70         * reference. Prices and attributes are deliberately left untouched — they
     71         * may include data the merchant set manually for other purposes.
     72         *
     73         * @param int $post_id
     74         */
     75        public static function handleDelete( int $post_id ): void {
     76                if ( get_post_type( $post_id ) !== 'price_blueprint' ) {
     77                        return;
     78                }
     79
     80                foreach ( self::linkedProductIds( $post_id ) as $product_id ) {
     81                        self::unpublishProduct( $product_id );
     82                        delete_post_meta( $product_id, 'prbp_template_id' );
     83                }
     84        }
     85
     86        /**
     87         * @param  int $post_id Blueprint post ID.
     88         * @return int[]        Product IDs linked to this blueprint.
     89         */
     90        private static function linkedProductIds( int $post_id ): array {
     91                return get_posts( [
    6292                        'post_type'      => 'product',
    6393                        'post_status'    => 'any',
    … …  
    72102                        ],
    73103                ] );
     104        }
    74105
    75                 foreach ( $linked_ids as $product_id ) {
    76                         $product = wc_get_product( $product_id );
    77                         if ( ! $product ) {
    78                                 continue;
    79                         }
     106        /**
     107         * Move a published product to draft without touching any of its other data.
     108         *
     109         * @param int $product_id
     110         */
     111        private static function unpublishProduct( int $product_id ): void {
     112                if ( 'publish' !== get_post_status( $product_id ) ) {
     113                        return;
     114                }
    80115
    81                         $product->set_status( 'draft' );
    82                         $product->set_regular_price( '' );
    83                         $product->set_price( '' );
    84                         $product->set_sale_price( '' );
    85                         $product->set_attributes( [] );
    86                         $product->save();
     116                $product = wc_get_product( $product_id );
     117                if ( ! $product ) {
     118                        return;
     119                }
    87120
    88                         delete_post_meta( $product_id, 'prbp_template_id' );
    89                 }
     121                $product->set_status( 'draft' );
     122                $product->save();
    90123        }
    91124}
  • priceblueprint-for-woocommerce/tags/1.7.3/src/Cart/CartItemMeta.php

    r3587316 r3625812  
    2424
    2525        /**
    26          * Server-side validation: all attributes from the template must be selected.
     26         * Server-side validation: every attribute from the template must carry a
     27         * selection that matches one of that attribute's rule value slugs. A bare
     28         * "not empty" check is not enough — a tampered slug would silently skip
     29         * rule matching in capture() and the option's price would never be added.
    2730         *
    2831         * @param bool $passed
    … …  
    5255
    5356                foreach ( $attributes as $attr ) {
    54                         if ( empty( $selections[ $attr ] ) ) {
     57                        $slug     = $selections[ $attr ] ?? '';
     58                        $is_valid = false;
     59
     60                        if ( '' !== $slug ) {
     61                                foreach ( $rules as $rule ) {
     62                                        if ( $rule['attribute'] === $attr
     63                                                && in_array( $slug, (array) ( $rule['value_slugs'] ?? [] ), true )
     64                                        ) {
     65                                                $is_valid = true;
     66                                                break;
     67                                        }
     68                                }
     69                        }
     70
     71                        if ( ! $is_valid ) {
    5572                                wc_add_notice(
    5673                                        sprintf(
  • priceblueprint-for-woocommerce/tags/1.7.3/src/Frontend/ProductPage.php

    r3597567 r3625812  
    181181         * inside .woocommerce-product-gallery__wrapper; it offsets flexslider's horizontal
    182182         * slide positions, making the main viewport appear blank.
    183          */
    184         public static function suppressGalleryPlaceholder( string $html, int|string $attachment_id ): string {
     183         *
     184         * @param string     $html
     185         * @param int|string $attachment_id
     186         */
     187        public static function suppressGalleryPlaceholder( string $html, $attachment_id ): string {
    185188                if ( $attachment_id ) {
    186189                        return $html;
  • priceblueprint-for-woocommerce/tags/1.7.3/templates/welcome-screen.php

    r3561869 r3625812  
    4747        </div>
    4848
    49         <div class="prbp-welcome-actions">
    50                 <a href="<?php echo esc_url( admin_url( 'post-new.php?post_type=price_blueprint' ) ); ?>"
    51                    class="prbp-welcome-cta">
    52                         <?php esc_html_e( 'Create your first Blueprint', 'priceblueprint-for-woocommerce' ); ?> &rarr;
    53                 </a>
    54                 <a href="<?php echo esc_url( admin_url( 'edit.php?post_type=price_blueprint' ) ); ?>"
    55                    class="prbp-welcome-skip">
    56                         <?php esc_html_e( 'Skip for now', 'priceblueprint-for-woocommerce' ); ?>
    57                 </a>
    58         </div>
     49        <div class="prbp-welcome-getstarted">
    5950
    60         <div class="prbp-welcome-demo" id="prbp-demo-wrap">
    61                 <p class="prbp-welcome-steps-title"><?php esc_html_e( 'Try it first', 'priceblueprint-for-woocommerce' ); ?></p>
    62                 <div class="prbp-welcome-demo-row">
    63                         <p class="prbp-welcome-demo-desc"><?php esc_html_e( 'Import a sample blueprint and product to see how PriceBlueprint works before building your own.', 'priceblueprint-for-woocommerce' ); ?></p>
    64                         <button type="button" id="prbp-import-demo" class="prbp-welcome-demo-btn">
    65                                 <?php esc_html_e( 'Import Demo Data', 'priceblueprint-for-woocommerce' ); ?>
    66                         </button>
     51                <div class="prbp-welcome-demo" id="prbp-demo-wrap">
     52                        <p class="prbp-welcome-steps-title"><?php esc_html_e( 'Try it first', 'priceblueprint-for-woocommerce' ); ?></p>
     53                        <div class="prbp-welcome-demo-row">
     54                                <p class="prbp-welcome-demo-desc"><?php esc_html_e( 'Import a sample blueprint and product to see how PriceBlueprint works before building your own — the fastest way to see a real price change on your storefront.', 'priceblueprint-for-woocommerce' ); ?></p>
     55                                <button type="button" id="prbp-import-demo" class="prbp-welcome-btn-primary">
     56                                        <?php esc_html_e( 'Import Demo Data', 'priceblueprint-for-woocommerce' ); ?> &rarr;
     57                                </button>
     58                        </div>
    6759                </div>
     60
     61                <div class="prbp-welcome-divider"></div>
     62
     63                <div class="prbp-welcome-secondary-row">
     64                        <span class="prbp-welcome-secondary-label"><?php esc_html_e( 'Prefer to start from scratch?', 'priceblueprint-for-woocommerce' ); ?></span>
     65                        <a href="<?php echo esc_url( admin_url( 'post-new.php?post_type=price_blueprint' ) ); ?>"
     66                           class="prbp-welcome-btn-secondary">
     67                                <?php esc_html_e( 'Create your first Blueprint', 'priceblueprint-for-woocommerce' ); ?>
     68                        </a>
     69                        <a href="<?php echo esc_url( admin_url( 'edit.php?post_type=price_blueprint' ) ); ?>"
     70                           class="prbp-welcome-skip">
     71                                <?php esc_html_e( 'Skip for now', 'priceblueprint-for-woocommerce' ); ?>
     72                        </a>
     73                </div>
     74
    6875        </div>
    6976
  • priceblueprint-for-woocommerce/tags/1.7.3/uninstall.php

    r3557112 r3625812  
    2828delete_post_meta_by_key( 'prbp_template_id' );
    2929
     30// Delete plugin options.
     31delete_option( 'prbp_show_welcome' );
     32delete_option( 'prbp_demo_blueprint_id' );
     33delete_option( 'prbp_demo_product_id' );
     34
    3035// Clean up any lingering transients (save error transients use user IDs).
    3136global $wpdb;
    3237// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    3338$wpdb->query(
    34         "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transientprbp_save_error_%' OR option_name LIKE '_transient_timeoutprbp_save_error_%'"
     39        "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_prbp_save_error_%' OR option_name LIKE '_transient_timeout_prbp_save_error_%'"
    3540);
    3641
    37 // Not like register_uninstall_hook(), you do NOT have to use a static function.
    38 pfwp_fs()->add_action('after_uninstall', 'pfwp_fs_uninstall_cleanup');
     42// NOTE: Do not call pfwp_fs() here — the main plugin file is NOT loaded when
     43// WordPress runs uninstall.php, so the Freemius helper does not exist and the
     44// call would fatal, blocking plugin deletion. Freemius account data is shared
     45// SDK state and is left untouched intentionally.
  • priceblueprint-for-woocommerce/trunk/assets/css/welcome-screen.css

    r3597567 r3625812  
    132132}
    133133
    134 /* ── Actions ── */
    135 .prbp-welcome-actions {
    136         display: flex;
    137         align-items: center;
    138         gap: 16px;
    139         padding: 0 4px;
    140 }
    141 .prbp-welcome-cta {
     134/* ── Get started card (demo import = primary, create/skip = secondary) ── */
     135.prbp-welcome-getstarted {
     136        background: #fff;
     137        border: 1px solid #E2E8F0;
     138        border-radius: 12px;
     139        padding: 32px 40px;
     140        margin-bottom: 12px;
     141        box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04), 0 4px 12px rgba(15, 23, 42, 0.04);
     142}
     143
     144/* Shared button treatments */
     145.prbp-welcome-btn-primary,
     146.prbp-welcome-btn-secondary {
    142147        display: inline-flex;
    143148        align-items: center;
    144149        gap: 6px;
    145         background: #0D9488;
    146         color: #fff !important;
    147         border: none;
    148150        border-radius: 6px;
    149         padding: 10px 22px;
    150         font-size: 14px;
    151151        font-weight: 600;
    152152        text-decoration: none !important;
    153153        cursor: pointer;
     154        line-height: 1.4;
     155        font-family: inherit;
     156        white-space: nowrap;
     157}
     158.prbp-welcome-btn-primary {
     159        flex-shrink: 0;
     160        background: #0D9488;
     161        color: #fff !important;
     162        border: none;
     163        padding: 10px 22px;
     164        font-size: 14px;
    154165        transition: background .15s;
    155         line-height: 1.4;
    156 }
    157 .prbp-welcome-cta:hover {
     166}
     167.prbp-welcome-btn-primary:hover {
    158168        background: #0a7a70;
    159169        color: #fff !important;
     170}
     171.prbp-welcome-btn-primary:disabled {
     172        cursor: not-allowed;
     173        opacity: 0.6;
     174}
     175.prbp-welcome-btn-secondary {
     176        background: #fff;
     177        color: #1d2327 !important;
     178        border: 1px solid #c3c4c7;
     179        padding: 9px 20px;
     180        font-size: 13px;
     181        font-weight: 500;
     182        transition: border-color .15s, color .15s;
     183}
     184.prbp-welcome-btn-secondary:hover {
     185        border-color: #0a7a70;
     186        color: #0a7a70 !important;
    160187}
    161188.prbp-welcome-skip {
    … …  
    169196}
    170197
    171 /* ── Demo Import ── */
    172 .prbp-welcome-demo {
    173         background: #fff;
    174         border: 1px solid #E2E8F0;
    175         border-radius: 12px;
    176         padding: 28px 40px;
    177         margin-top: 12px;
    178         box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04), 0 4px 12px rgba(15, 23, 42, 0.04);
    179 }
     198/* ── Demo Import (primary action) ── */
    180199.prbp-welcome-demo .prbp-welcome-steps-title {
    181200        margin-bottom: 12px;
    … …  
    193212        line-height: 1.5;
    194213}
    195 .prbp-welcome-demo-btn {
    196         flex-shrink: 0;
    197         display: inline-flex;
    198         align-items: center;
    199         gap: 8px;
    200         background: #fff;
    201         color: #1d2327;
    202         border: 1px solid #c3c4c7;
    203         border-radius: 6px;
    204         padding: 9px 20px;
    205         font-size: 13px;
    206         font-weight: 500;
    207         cursor: pointer;
    208         transition: border-color .15s, color .15s;
    209         line-height: 1.4;
    210         font-family: inherit;
    211         white-space: nowrap;
    212 }
    213 .prbp-welcome-demo-btn:hover {
    214         border-color: #0a7a70;
    215         color: #0a7a70;
    216 }
    217 .prbp-welcome-demo-btn:disabled {
    218         cursor: not-allowed;
    219         opacity: 0.6;
     214
     215/* ── Divider between primary and secondary actions ── */
     216.prbp-welcome-divider {
     217        height: 1px;
     218        background: #E2E8F0;
     219        margin: 24px 0;
     220}
     221
     222/* ── Secondary row: create-from-scratch + skip ── */
     223.prbp-welcome-secondary-row {
     224        display: flex;
     225        align-items: center;
     226        flex-wrap: wrap;
     227        gap: 16px;
     228}
     229.prbp-welcome-secondary-label {
     230        font-size: 13px;
     231        color: #64748B;
     232        margin-right: 4px;
    220233}
    221234@keyframes prbp-spin {
  • priceblueprint-for-woocommerce/trunk/priceblueprint-for-woocommerce.php

    r3605639 r3625812  
    33 * Plugin Name:       WooCommerce Attribute Pricing — PriceBlueprint
    44 * Description:       Reusable pricing blueprints for WooCommerce. Assign one blueprint to multiple products — define attribute-based pricing rules once, update everywhere instantly.
    5  * Version:           1.7.1
     5 * Version:           1.7.3
    66 * Requires at least: 6.0
    77 * Requires PHP:      7.4
    … …  
    4343                'has_paid_plans'      => false,
    4444                'is_org_compliant'    => true,
     45                'enable_anonymous'    => true,
    4546                'menu'                => array(
    4647                    'first-path'     => 'plugins.php',
    … …  
    5051                ),
    5152            ) );
     53
     54            // Reassuring, plain-language opt-in message so first-time activators
     55            // understand what's shared (and that "Skip" is a perfectly fine choice).
     56            $pfwp_fs->add_filter( 'connect_message', function () {
     57                $plugin_name = esc_html__( 'PriceBlueprint', 'priceblueprint-for-woocommerce' );
     58
     59                return sprintf(
     60                    /* translators: 1: opening <strong>PriceBlueprint</strong> tag, 2: plugin name */
     61                    esc_html__( 'Opt in to get security & feature updates for %1$s, plus help us keep it compatible with your store by sharing basic, non-personal environment info (WordPress/PHP/WooCommerce version, site URL). No spam, we never sell your data, and you can change your mind anytime from Account settings. Rather not share anything? Click "Skip" below — %2$s works exactly the same either way.', 'priceblueprint-for-woocommerce' ),
     62                    '<strong>' . $plugin_name . '</strong>',
     63                    $plugin_name
     64                );
     65            } );
    5266        }
    5367
    … …  
    7084// wp i18n make-pot . languages/priceblueprint.pot --domain=priceblueprint --exclude=vendor,node_modules
    7185
    72 define( 'PRBP_VERSION',    '1.7.1' );
     86define( 'PRBP_VERSION',    '1.7.3' );
    7387define( 'PRBP_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
    7488define( 'PRBP_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
  • priceblueprint-for-woocommerce/trunk/readme.txt

    r3605639 r3625812  
    44Requires at least: 6.0
    55Tested up to: 7.0
    6 Stable tag: 1.7.1
     6Stable tag: 1.7.3
    77Requires PHP: 7.4
    88License: GPL-2.0-or-later
    … …  
    8484== Changelog ==
    8585
     86= 1.7.3 =
     87* Improved the Freemius connection prompt with a clearer explanation of what's shared and why.
     88* Reordered the welcome screen so trying the demo data comes first.
     89
     90= 1.7.2 =
     91* Security: Add-to-cart validation now verifies every selected option against the blueprint's actual rule values — previously a tampered form submission could add a product to the cart without paying for its selected options.
     92* Fix: The plugin could not be deleted from the Plugins screen — uninstall.php referenced a Freemius helper that is unavailable during uninstall, causing a fatal error. Uninstall now also removes plugin options and correctly clears leftover transients.
     93* Fix: Fatal parse error on PHP 7.4 — a PHP 8-only union type in the gallery placeholder handler broke sites running the minimum supported PHP version.
     94* Fix: Pricing rules containing non-Latin characters (ö, ü, ł, Cyrillic, etc.) or quotes in term labels were corrupted on save due to missing slashing around the stored JSON. Affected blueprints need to be re-saved once after updating.
     95* Fix: Moving a blueprint to the Trash no longer wipes prices and attributes of linked products. Trashing now only unpublishes linked products (fully reversible); permanent deletion additionally unlinks them, leaving all other product data intact.
     96
    8697= 1.7.1 =
    8798* Fix: "Import Demo Data" could fatal error (500) on some stores due to an unguarded wp_insert_term() call — now handled gracefully with a proper error message.
  • priceblueprint-for-woocommerce/trunk/src/Admin/SaveHandler.php

    r3597567 r3625812  
    6969                }
    7070
    71                 update_post_meta( $post_id, 'prbp_template_rules', wp_json_encode( self::sanitizeSections( $sections ) ) );
     71                // wp_slash() — update_post_meta() unslashes its value, which would strip the
     72                // backslashes wp_json_encode() uses for \uXXXX and \" escapes and corrupt the JSON.
     73                update_post_meta( $post_id, 'prbp_template_rules', wp_slash( wp_json_encode( self::sanitizeSections( $sections ) ) ) );
    7274                RulesCache::flush( $post_id );
    7375        }
  • priceblueprint-for-woocommerce/trunk/src/Ajax/ImportDemo.php

    r3605639 r3625812  
    183183                }
    184184
    185                 update_post_meta( $blueprint_id, 'prbp_template_rules', wp_json_encode( $rules ) );
     185                // wp_slash() — update_post_meta() unslashes, which would corrupt JSON escape sequences.
     186                update_post_meta( $blueprint_id, 'prbp_template_rules', wp_slash( wp_json_encode( $rules ) ) );
    186187
    187188                return $blueprint_id;
  • priceblueprint-for-woocommerce/trunk/src/CPT/Blueprint.php

    r3552429 r3625812  
    1616        public static function register(): void {
    1717                add_action( 'init',               [ self::class, 'registerCPT' ],  10 );
    18                 add_action( 'before_delete_post', [ self::class, 'handleRemoval' ], 10, 1 );
    19                 add_action( 'wp_trash_post',      [ self::class, 'handleRemoval' ], 10, 1 );
     18                add_action( 'before_delete_post', [ self::class, 'handleDelete' ], 10, 1 );
     19                add_action( 'wp_trash_post',      [ self::class, 'handleTrash' ], 10, 1 );
    2020        }
    2121
    … …  
    4949
    5050        /**
    51          * When a blueprint is trashed or permanently deleted, move all linked products
    52          * to draft and clear their price, attributes, and blueprint reference.
     51         * Trashing a blueprint is reversible, so it must not destroy product data:
     52         * linked products are only unpublished (draft). Prices, attributes, and the
     53         * prbp_template_id link stay intact — restoring the blueprint from Trash lets
     54         * the merchant simply republish the products.
    5355         *
    5456         * @param int $post_id
    5557         */
    56         public static function handleRemoval( int $post_id ): void {
     58        public static function handleTrash( int $post_id ): void {
    5759                if ( get_post_type( $post_id ) !== 'price_blueprint' ) {
    5860                        return;
    5961                }
    6062
    61                 $linked_ids = get_posts( [
     63                foreach ( self::linkedProductIds( $post_id ) as $product_id ) {
     64                        self::unpublishProduct( $product_id );
     65                }
     66        }
     67
     68        /**
     69         * Permanent deletion: unpublish linked products and remove the blueprint
     70         * reference. Prices and attributes are deliberately left untouched — they
     71         * may include data the merchant set manually for other purposes.
     72         *
     73         * @param int $post_id
     74         */
     75        public static function handleDelete( int $post_id ): void {
     76                if ( get_post_type( $post_id ) !== 'price_blueprint' ) {
     77                        return;
     78                }
     79
     80                foreach ( self::linkedProductIds( $post_id ) as $product_id ) {
     81                        self::unpublishProduct( $product_id );
     82                        delete_post_meta( $product_id, 'prbp_template_id' );
     83                }
     84        }
     85
     86        /**
     87         * @param  int $post_id Blueprint post ID.
     88         * @return int[]        Product IDs linked to this blueprint.
     89         */
     90        private static function linkedProductIds( int $post_id ): array {
     91                return get_posts( [
    6292                        'post_type'      => 'product',
    6393                        'post_status'    => 'any',
    … …  
    72102                        ],
    73103                ] );
     104        }
    74105
    75                 foreach ( $linked_ids as $product_id ) {
    76                         $product = wc_get_product( $product_id );
    77                         if ( ! $product ) {
    78                                 continue;
    79                         }
     106        /**
     107         * Move a published product to draft without touching any of its other data.
     108         *
     109         * @param int $product_id
     110         */
     111        private static function unpublishProduct( int $product_id ): void {
     112                if ( 'publish' !== get_post_status( $product_id ) ) {
     113                        return;
     114                }
    80115
    81                         $product->set_status( 'draft' );
    82                         $product->set_regular_price( '' );
    83                         $product->set_price( '' );
    84                         $product->set_sale_price( '' );
    85                         $product->set_attributes( [] );
    86                         $product->save();
     116                $product = wc_get_product( $product_id );
     117                if ( ! $product ) {
     118                        return;
     119                }
    87120
    88                         delete_post_meta( $product_id, 'prbp_template_id' );
    89                 }
     121                $product->set_status( 'draft' );
     122                $product->save();
    90123        }
    91124}
  • priceblueprint-for-woocommerce/trunk/src/Cart/CartItemMeta.php

    r3587316 r3625812  
    2424
    2525        /**
    26          * Server-side validation: all attributes from the template must be selected.
     26         * Server-side validation: every attribute from the template must carry a
     27         * selection that matches one of that attribute's rule value slugs. A bare
     28         * "not empty" check is not enough — a tampered slug would silently skip
     29         * rule matching in capture() and the option's price would never be added.
    2730         *
    2831         * @param bool $passed
    … …  
    5255
    5356                foreach ( $attributes as $attr ) {
    54                         if ( empty( $selections[ $attr ] ) ) {
     57                        $slug     = $selections[ $attr ] ?? '';
     58                        $is_valid = false;
     59
     60                        if ( '' !== $slug ) {
     61                                foreach ( $rules as $rule ) {
     62                                        if ( $rule['attribute'] === $attr
     63                                                && in_array( $slug, (array) ( $rule['value_slugs'] ?? [] ), true )
     64                                        ) {
     65                                                $is_valid = true;
     66                                                break;
     67                                        }
     68                                }
     69                        }
     70
     71                        if ( ! $is_valid ) {
    5572                                wc_add_notice(
    5673                                        sprintf(
  • priceblueprint-for-woocommerce/trunk/src/Frontend/ProductPage.php

    r3597567 r3625812  
    181181         * inside .woocommerce-product-gallery__wrapper; it offsets flexslider's horizontal
    182182         * slide positions, making the main viewport appear blank.
    183          */
    184         public static function suppressGalleryPlaceholder( string $html, int|string $attachment_id ): string {
     183         *
     184         * @param string     $html
     185         * @param int|string $attachment_id
     186         */
     187        public static function suppressGalleryPlaceholder( string $html, $attachment_id ): string {
    185188                if ( $attachment_id ) {
    186189                        return $html;
  • priceblueprint-for-woocommerce/trunk/templates/welcome-screen.php

    r3561869 r3625812  
    4747        </div>
    4848
    49         <div class="prbp-welcome-actions">
    50                 <a href="<?php echo esc_url( admin_url( 'post-new.php?post_type=price_blueprint' ) ); ?>"
    51                    class="prbp-welcome-cta">
    52                         <?php esc_html_e( 'Create your first Blueprint', 'priceblueprint-for-woocommerce' ); ?> &rarr;
    53                 </a>
    54                 <a href="<?php echo esc_url( admin_url( 'edit.php?post_type=price_blueprint' ) ); ?>"
    55                    class="prbp-welcome-skip">
    56                         <?php esc_html_e( 'Skip for now', 'priceblueprint-for-woocommerce' ); ?>
    57                 </a>
    58         </div>
     49        <div class="prbp-welcome-getstarted">
    5950
    60         <div class="prbp-welcome-demo" id="prbp-demo-wrap">
    61                 <p class="prbp-welcome-steps-title"><?php esc_html_e( 'Try it first', 'priceblueprint-for-woocommerce' ); ?></p>
    62                 <div class="prbp-welcome-demo-row">
    63                         <p class="prbp-welcome-demo-desc"><?php esc_html_e( 'Import a sample blueprint and product to see how PriceBlueprint works before building your own.', 'priceblueprint-for-woocommerce' ); ?></p>
    64                         <button type="button" id="prbp-import-demo" class="prbp-welcome-demo-btn">
    65                                 <?php esc_html_e( 'Import Demo Data', 'priceblueprint-for-woocommerce' ); ?>
    66                         </button>
     51                <div class="prbp-welcome-demo" id="prbp-demo-wrap">
     52                        <p class="prbp-welcome-steps-title"><?php esc_html_e( 'Try it first', 'priceblueprint-for-woocommerce' ); ?></p>
     53                        <div class="prbp-welcome-demo-row">
     54                                <p class="prbp-welcome-demo-desc"><?php esc_html_e( 'Import a sample blueprint and product to see how PriceBlueprint works before building your own — the fastest way to see a real price change on your storefront.', 'priceblueprint-for-woocommerce' ); ?></p>
     55                                <button type="button" id="prbp-import-demo" class="prbp-welcome-btn-primary">
     56                                        <?php esc_html_e( 'Import Demo Data', 'priceblueprint-for-woocommerce' ); ?> &rarr;
     57                                </button>
     58                        </div>
    6759                </div>
     60
     61                <div class="prbp-welcome-divider"></div>
     62
     63                <div class="prbp-welcome-secondary-row">
     64                        <span class="prbp-welcome-secondary-label"><?php esc_html_e( 'Prefer to start from scratch?', 'priceblueprint-for-woocommerce' ); ?></span>
     65                        <a href="<?php echo esc_url( admin_url( 'post-new.php?post_type=price_blueprint' ) ); ?>"
     66                           class="prbp-welcome-btn-secondary">
     67                                <?php esc_html_e( 'Create your first Blueprint', 'priceblueprint-for-woocommerce' ); ?>
     68                        </a>
     69                        <a href="<?php echo esc_url( admin_url( 'edit.php?post_type=price_blueprint' ) ); ?>"
     70                           class="prbp-welcome-skip">
     71                                <?php esc_html_e( 'Skip for now', 'priceblueprint-for-woocommerce' ); ?>
     72                        </a>
     73                </div>
     74
    6875        </div>
    6976
  • priceblueprint-for-woocommerce/trunk/uninstall.php

    r3557112 r3625812  
    2828delete_post_meta_by_key( 'prbp_template_id' );
    2929
     30// Delete plugin options.
     31delete_option( 'prbp_show_welcome' );
     32delete_option( 'prbp_demo_blueprint_id' );
     33delete_option( 'prbp_demo_product_id' );
     34
    3035// Clean up any lingering transients (save error transients use user IDs).
    3136global $wpdb;
    3237// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    3338$wpdb->query(
    34         "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transientprbp_save_error_%' OR option_name LIKE '_transient_timeoutprbp_save_error_%'"
     39        "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_prbp_save_error_%' OR option_name LIKE '_transient_timeout_prbp_save_error_%'"
    3540);
    3641
    37 // Not like register_uninstall_hook(), you do NOT have to use a static function.
    38 pfwp_fs()->add_action('after_uninstall', 'pfwp_fs_uninstall_cleanup');
     42// NOTE: Do not call pfwp_fs() here — the main plugin file is NOT loaded when
     43// WordPress runs uninstall.php, so the Freemius helper does not exist and the
     44// call would fatal, blocking plugin deletion. Freemius account data is shared
     45// SDK state and is left untouched intentionally.
Note: See TracChangeset for help on using the changeset viewer.