├── assets └── js │ ├── wc-pd-writepanels.min.js │ └── wc-pd-writepanels.js ├── .gitignore ├── package.json ├── README.md ├── class-wc-pd-core-compatibility.php ├── Gruntfile.js ├── class-wc-pd-helpers.php ├── readme.txt ├── languages └── woocommerce-product-dependencies.pot └── woocommerce-product-dependencies.php /assets/js/wc-pd-writepanels.min.js: -------------------------------------------------------------------------------- 1 | jQuery(function(e){function d(){"product_ids"==c.val()?(n.show(),i.hide()):(i.show(),n.hide())}var c=e("#product_dependencies_dropdown"),n=e("#product_ids_dependencies_choice"),i=e("#category_ids_dependencies_choice");d(),c.change(function(){d()})}); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Editors 2 | project.xml 3 | project.properties 4 | /nbproject/private/ 5 | .buildpath 6 | .project 7 | .settings* 8 | sftp-config.json 9 | .idea 10 | 11 | # Grunt 12 | /node_modules/ 13 | /vendor/ 14 | /deploy/ 15 | npm-debug.log 16 | package-lock.json 17 | 18 | # Sass 19 | .sass-cache/ 20 | 21 | # OS X metadata 22 | .DS_Store 23 | 24 | # Windows junk 25 | Thumbs.db 26 | 27 | # ApiGen 28 | /wc-apidocs/ 29 | 30 | # Unit tests 31 | /tmp 32 | /tests/coverage/ 33 | 34 | # Logs 35 | /logs -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "woocommerce-product-dependencies", 3 | "title": "WooCommerce Product Dependencies", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/woocommerce/woocommerce-product-dependencies.git" 7 | }, 8 | "license": "GPL-3.0+", 9 | "version": "2.0.1", 10 | "main": "Gruntfile.js", 11 | "devDependencies": { 12 | "grunt": "~1.0.1", 13 | "grunt-checktextdomain": "~1.0.0", 14 | "grunt-contrib-jshint": "~1.1.0", 15 | "grunt-contrib-uglify": "~3.0.0", 16 | "grunt-contrib-watch": "^1.1.0", 17 | "grunt-wp-i18n": "~0.5.4" 18 | }, 19 | "engines": { 20 | "node": ">=6.9.4", 21 | "npm": ">=1.1.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /assets/js/wc-pd-writepanels.js: -------------------------------------------------------------------------------- 1 | jQuery( function( $ ) { 2 | 3 | var $product_dependencies_dropdown = $( '#product_dependencies_dropdown' ), 4 | $product_ids_dependencies_choice = $( '#product_ids_dependencies_choice' ), 5 | $category_ids_dependencies_choice = $( '#category_ids_dependencies_choice' ); 6 | 7 | function initialize() { 8 | if ( $product_dependencies_dropdown.val() == 'product_ids' ) { 9 | $product_ids_dependencies_choice.show(); 10 | $category_ids_dependencies_choice.hide(); 11 | 12 | } else { 13 | $category_ids_dependencies_choice.show(); 14 | $product_ids_dependencies_choice.hide(); 15 | } 16 | } 17 | 18 | initialize(); 19 | 20 | $product_dependencies_dropdown.change( function() { 21 | initialize(); 22 | } ); 23 | 24 | } ); 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [WooCommerce Product Dependencies](https://wordpress.org/plugins/woocommerce-product-dependencies/) 2 | 3 | ## Description 4 | 5 | Lightweight WooCommerce plugin that allows you to restrict access to any product, depending on the ownership and/or purchase of other, required products. 6 | 7 |

8 | screenshot-2 9 |

10 | 11 | Features: 12 | 13 | * **Conditional product access** based on the ownership and/or purchase of required products. 14 | * Streamlined admin interface – product dependencies are entered in a dedicated **Dependencies** tab. 15 | * Support for multiple product dependencies. 16 | * Support for "ownership", "purchase" and "ownership/purchase" dependency types. 17 | 18 |
19 |

20 | screenshot-1 21 |

22 |
23 | 24 | **Important**: Requires WooCommerce 2.2+. WooCommerce 3.0+ or higher recommended. 25 | 26 | **Important**: The code in this plugin is provided "as is". Support via the WordPress.org forum is provided on a **voluntary** basis only. 27 | -------------------------------------------------------------------------------- /class-wc-pd-core-compatibility.php: -------------------------------------------------------------------------------- 1 | =' ); 38 | } 39 | 40 | /** 41 | * Returns true if the installed version of WooCommerce is greater than $version. 42 | * 43 | * @param string $version the version to compare 44 | * @return boolean true if the installed version of WooCommerce is > $version 45 | */ 46 | public static function is_wc_version_gt( $version ) { 47 | return self::get_wc_version() && version_compare( self::get_wc_version(), $version, '>' ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | module.exports = function( grunt ) { 3 | 'use strict'; 4 | 5 | grunt.initConfig({ 6 | 7 | // setting folder templates 8 | dirs: { 9 | css: 'assets/css', 10 | js: 'assets/js' 11 | }, 12 | 13 | // JavaScript linting with JSHint. 14 | jshint: { 15 | options: { 16 | 'force': true, 17 | 'boss': true, 18 | 'curly': true, 19 | 'eqeqeq': false, 20 | 'eqnull': true, 21 | 'es3': false, 22 | 'expr': false, 23 | 'immed': true, 24 | 'noarg': true, 25 | 'onevar': true, 26 | 'quotmark': 'single', 27 | 'trailing': true, 28 | 'undef': true, 29 | 'unused': true, 30 | 'sub': false, 31 | 'browser': true, 32 | 'maxerr': 1000, 33 | globals: { 34 | 'jQuery': false, 35 | '$': false, 36 | 'Backbone': false, 37 | '_': false, 38 | 'wc_bundle_params': false, 39 | 'wc_pb_number_round': false 40 | }, 41 | }, 42 | all: [ 43 | 'Gruntfile.js', 44 | '<%= dirs.js %>/*.js', 45 | '!<%= dirs.js %>/*.min.js' 46 | ] 47 | }, 48 | 49 | // Minify .js files. 50 | uglify: { 51 | options: { 52 | preserveComments: false 53 | }, 54 | jsfiles: { 55 | files: [{ 56 | expand: true, 57 | cwd: '<%= dirs.js %>', 58 | src: [ 59 | '*.js', 60 | '!*.min.js' 61 | ], 62 | dest: '<%= dirs.js %>', 63 | ext: '.min.js' 64 | }] 65 | } 66 | }, 67 | 68 | // Watch changes for assets. 69 | watch: { 70 | js: { 71 | files: [ 72 | '<%= dirs.js %>/*js' 73 | ], 74 | tasks: ['uglify'] 75 | } 76 | }, 77 | 78 | // Generate POT files. 79 | makepot: { 80 | options: { 81 | type: 'wp-plugin', 82 | domainPath: 'languages' 83 | }, 84 | go: { 85 | options: { 86 | potFilename: 'woocommerce-product-dependencies.pot', 87 | exclude: [ 88 | 'languages/.*', 89 | 'assets/.*', 90 | 'node-modules/.*', 91 | 'woo-includes/.*' 92 | ] 93 | } 94 | } 95 | }, 96 | 97 | // Check textdomain errors. 98 | checktextdomain: { 99 | options:{ 100 | text_domain: [ 'woocommerce-product-dependencies', 'woocommerce' ], 101 | keywords: [ 102 | '__:1,2d', 103 | '_e:1,2d', 104 | '_x:1,2c,3d', 105 | 'esc_html__:1,2d', 106 | 'esc_html_e:1,2d', 107 | 'esc_html_x:1,2c,3d', 108 | 'esc_attr__:1,2d', 109 | 'esc_attr_e:1,2d', 110 | 'esc_attr_x:1,2c,3d', 111 | '_ex:1,2c,3d', 112 | '_n:1,2,4d', 113 | '_nx:1,2,4c,5d', 114 | '_n_noop:1,2,3d', 115 | '_nx_noop:1,2,3c,4d' 116 | ] 117 | }, 118 | files: { 119 | src: [ 120 | '**/*.php', // Include all files 121 | '!apigen/**', // Exclude apigen/ 122 | '!deploy/**', // Exclude deploy/ 123 | '!node_modules/**' // Exclude node_modules/ 124 | ], 125 | expand: true 126 | } 127 | } 128 | }); 129 | 130 | // Load NPM tasks to be used here. 131 | grunt.loadNpmTasks( 'grunt-contrib-jshint' ); 132 | grunt.loadNpmTasks( 'grunt-contrib-uglify' ); 133 | grunt.loadNpmTasks( 'grunt-contrib-watch' ); 134 | grunt.loadNpmTasks( 'grunt-wp-i18n' ); 135 | grunt.loadNpmTasks( 'grunt-checktextdomain' ); 136 | 137 | // Register tasks. 138 | grunt.registerTask( 'dev', [ 139 | 'checktextdomain', 140 | 'uglify' 141 | ]); 142 | 143 | grunt.registerTask( 'default', [ 144 | 'dev', 145 | 'makepot' 146 | ]); 147 | 148 | grunt.registerTask( 'domain', [ 149 | 'checktextdomain' 150 | ]); 151 | }; 152 | -------------------------------------------------------------------------------- /class-wc-pd-helpers.php: -------------------------------------------------------------------------------- 1 | $product ) { 62 | 63 | $part_to_merge = self::get_part_to_merge_expression( $loop, count( $products ), $relationship ); 64 | $product_permalink = $product->is_visible() ? $product->get_permalink() : ''; 65 | $product_title = $product->get_name(); 66 | 67 | if ( $product_permalink ) { 68 | $part_to_merge = sprintf( $part_to_merge, sprintf( '"%2$s"', esc_url( $product_permalink ), $product_title ) ); 69 | } else { 70 | $part_to_merge = sprintf( $part_to_merge, sprintf( '"%s"', $product_title ) ); 71 | } 72 | 73 | $parts_to_merge[] = $part_to_merge; 74 | 75 | $loop++; 76 | } 77 | } 78 | 79 | if ( is_rtl() ) { 80 | $parts_to_merge = array_reverse( $parts_to_merge ); 81 | } 82 | 83 | return implode( '', $parts_to_merge ); 84 | } 85 | 86 | /** 87 | * Merges category titles. 88 | * 89 | * @since 1.2.0 90 | * 91 | * @param array $category_ids 92 | * @param string $relationship 93 | * @return string 94 | */ 95 | public static function merge_category_titles( $category_ids, $relationship ) { 96 | 97 | $parts_to_merge = array(); 98 | 99 | if ( ! empty( $category_ids ) ) { 100 | 101 | $loop = 0; 102 | 103 | foreach ( $category_ids as $category_id ) { 104 | 105 | $part_to_merge = self::get_part_to_merge_expression( $loop, count( $category_ids ), $relationship ); 106 | $category_permalink = get_term_link( $category_id, 'product_cat' ); 107 | 108 | if ( $term = get_term_by( 'id', $category_id, 'product_cat' ) ){ 109 | $category_title = $term->name; 110 | } else { 111 | continue; 112 | } 113 | 114 | if ( $category_permalink ) { 115 | $part_to_merge = sprintf( $part_to_merge, sprintf( '"%2$s"', esc_url( $category_permalink ), $category_title ) ); 116 | } else { 117 | $part_to_merge = sprintf( $part_to_merge, sprintf( '"%s"', $category_title ) ); 118 | } 119 | 120 | $parts_to_merge[] = $part_to_merge; 121 | 122 | $loop++; 123 | } 124 | } 125 | 126 | if ( is_rtl() ) { 127 | $parts_to_merge = array_reverse( $parts_to_merge ); 128 | } 129 | 130 | return implode( '', $parts_to_merge ); 131 | } 132 | 133 | /** 134 | * Return a formatted product title. 135 | * 136 | * @param WC_Product|int $product 137 | * @param string $title 138 | * @return string 139 | */ 140 | public static function get_product_title( $product, $title = '' ) { 141 | 142 | if ( ! is_object( $product ) ) { 143 | $product = wc_get_product( $product ); 144 | } 145 | 146 | if ( ! $product ) { 147 | return false; 148 | } 149 | 150 | return $product->get_formatted_name(); 151 | } 152 | 153 | /** 154 | * Format a product title. 155 | * 156 | * @param string $title 157 | * @param string $identifier 158 | * @param string $meta 159 | * @param string $paren 160 | * @return string 161 | */ 162 | public static function format_product_title( $title, $identifier = '', $meta = '', $paren = false ) { 163 | 164 | if ( $identifier && $meta ) { 165 | if ( $paren ) { 166 | $title = sprintf( _x( '%1$s – %2$s (%3$s)', 'product title followed by meta and sku in parenthesis', 'woocommerce-product-dependencies' ), $title, $meta, $identifier ); 167 | } else { 168 | $title = sprintf( _x( '%1$s – %2$s – %3$s', 'sku followed by product title and meta', 'woocommerce-product-dependencies' ), $identifier, $title, $meta ); 169 | } 170 | } elseif ( $identifier ) { 171 | if ( $paren ) { 172 | $title = sprintf( _x( '%1$s (%2$s)', 'product title followed by sku in parenthesis', 'woocommerce-product-dependencies' ), $title, $identifier ); 173 | } else { 174 | $title = sprintf( _x( '%1$s – %2$s', 'sku followed by product title', 'woocommerce-product-dependencies' ), $identifier, $title ); 175 | } 176 | } elseif ( $meta ) { 177 | if ( $paren ) { 178 | $title = sprintf( _x( '%1$s (%2$s)', 'product title followed by meta in parenthesis', 'woocommerce-product-dependencies' ), $title, $meta ); 179 | } else { 180 | $title = sprintf( _x( '%1$s – %2$s', 'product title followed by meta', 'woocommerce-product-dependencies' ), $title, $meta ); 181 | } 182 | } 183 | 184 | return $title; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === WooCommerce Product Dependencies === 2 | 3 | Contributors: automattic, woocommerce, SomewhereWarm 4 | Tags: woocommerce, products, dependencies, prerequisite, restrict 5 | Requires at least: 6.2 6 | Tested up to: 6.6 7 | WC requires at least: 8.2 8 | WC tested up to: 9.1 9 | Stable tag: 2.0.1 10 | License: GPLv2 or later 11 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 12 | 13 | Restrict access to any WooCommerce product, depending on the ownership and/or purchase of other required products. 14 | 15 | 16 | == Description == 17 | 18 | Looking for a way to restrict product access in WooCommerce? Don't want to set up a full-fledged [memberships](https://woocommerce.com/products/woocommerce-memberships/) site? 19 | 20 | This tiny plugin allows you to restrict access to any WooCommerce product, depending on the ownership or purchase of other, required products. 21 | 22 | Features: 23 | 24 | * **Conditional product access** based on the ownership and/or purchase of other required products. 25 | * Support for "ownership", "purchase" and "ownership/purchase" dependency types. 26 | 27 | Developers can checkout and contribute to the source code on the plugin's [GitHub Repository](https://github.com/woocommerce/woocommerce-product-dependencies/). 28 | 29 | **Important**: Requires WooCommerce 2.2+. WooCommerce 3.0+ or higher recommended. 30 | 31 | Like this plugin? You'll love our official WooCommerce Extensions: 32 | 33 | * [WooCommerce Product Bundles](https://woocommerce.com/products/product-bundles/) 34 | * [WooCommerce Composite Products](https://woocommerce.com/products/composite-products/) 35 | * [WooCommerce Conditional Shipping and Payments](https://woocommerce.com/products/conditional-shipping-and-payments/) 36 | * [WooCommerce Product Recommendations](https://woocommerce.com/products/product-recommendations/) 37 | * [All Products for WooCommerce Subscriptions](https://woocommerce.com/products/all-products-for-woocommerce-subscriptions/) 38 | 39 | 40 | == Installation == 41 | 42 | 1. Download the plugin. 43 | 2. Go to your WordPress Dashboard and then click **Plugins > Add New**. 44 | 3. Click **Upload Plugin** at the top. 45 | 4. Click **Choose File** and select the .zip file you downloaded in **Step 1**. 46 | 5. Click **Install Now** and **Activate** the plugin. 47 | 48 | 49 | == Documentation == 50 | 51 | Dependencies are evaluated when customers attempt to add a product to their cart. If validation fails, the product cannot be added to the cart and a notice is displayed. In order to evaluate "Ownership"-type dependencies, customers are prompted to log in. 52 | 53 | = Creating Dependencies = 54 | 55 | To add dependencies to a product: 56 | 57 | * Go to the **Product Data > Dependencies** tab. 58 | * Use the **Product Dependencies** field to search for and add some products and/or variations. 59 | * Choose a **Dependency Type**. 60 | * **Update** to save your changes. 61 | 62 | The "Ownership" dependency type is evaluated by checking if the customer has purchased a required product in a previous order. The "Purchase" dependency type requires the customer to have a required product in the cart in order to purchase the dependent one. 63 | 64 | = Ownership vs Purchase = 65 | 66 | The plugin allows you to select between 3 different dependency types: 67 | 68 | * **Ownership**: Access is granted only to customers that already own any of the products added to the Product Dependencies field. 69 | * **Purchase**: The product can be purchased only in combination with any of the items added to the Product Dependencies field. Ownership is not taken into account. 70 | * **Either**: Access is granted with ownership or purchase of any item added to the Product Dependencies field. 71 | 72 | 73 | == Screenshots == 74 | 75 | 1. Product dependencies can be created from the **Dependencies** tab, found under **Product Data**, and adding products/variations to the **Product Dependencies** field. 76 | 2. If the ownership and/or purchase conditions are not met, products with dependencies cannot be added to the cart and a notification is displayed. 77 | 78 | 79 | == Changelog == 80 | 81 | = 2.0.0 = 82 | * Tweak - Updated author links. 83 | 84 | = 2.0.0 = 85 | * Important - New: PHP 7.4+ is now required. 86 | * Important - New: WooCommerce 8.2+ is now required. 87 | * Important - New: WordPress 6.2+ is now required. 88 | 89 | = 1.2.8 = 90 | * Feature - Declared compatibility with the new High-Performance Order Tables. 91 | 92 | = 1.2.7 = 93 | * Fix - Prevent products from satisfying their own dependencies. 94 | 95 | = 1.2.6 = 96 | * Tweak - Declared support for WooCommerce 5.0. 97 | 98 | = 1.2.5 = 99 | * Tweak - Declared support for WooCommerce 4.2. 100 | 101 | = 1.2.4 = 102 | * Tweak - Declared support for WC 4.1 and WP 5.4. 103 | 104 | = 1.2.3 = 105 | * Tweak - Updated plugin headers to declare support for WC 3.9 and WP 5.3. 106 | 107 | = 1.2.2 = 108 | * Fix - Remove esc_html when rendering custom notices. 109 | 110 | = 1.2.1 = 111 | * Fix - Products with "Purchase"-type category dependencies cannot be added to the cart although a variation of a product that belongs to the required category has been added to the cart. 112 | 113 | = 1.2.0 = 114 | * Feature - Introduced category-based dependencies. 115 | * Feature - Added custom dependency notices. 116 | * Feature - Added AND/OR dependency relationship. Can be activated for specific products using the 'wc_pd_dependency_relationship' filter. 117 | * Tweak - Tweaked default notices. 118 | 119 | = 1.1.3 = 120 | * Tweak - Updated description. 121 | * Tweak - Added WooCommerce version headers. 122 | 123 | = 1.1.2 = 124 | * Fix - Variable product dependencies not validated correctly in the cart. 125 | 126 | = 1.1.1 = 127 | * Fix - Dependencies not working under WC < 3.0 after last update. Fixed! 128 | 129 | = 1.1.0 = 130 | * Refactored and cleaned up plugin. 131 | * Fix - Added support for WooCommerce 3.0. 132 | * Tweak - Add links to dependent products in notices. 133 | 134 | = 1.0.7 = 135 | * Fix - Stray "or" in dependent products list when only one dependency is present. 136 | * Localization - Added Brazilian Portuguese translation. 137 | 138 | = 1.0.6 = 139 | * Fix - PHP array_values warning. 140 | 141 | = 1.0.5 = 142 | * Fix - WC 2.3 support. 143 | 144 | = 1.0.4 = 145 | * Localization - Added Brazilian translation (robertopc) 146 | 147 | = 1.0.3 = 148 | * Fix - Saving bug 149 | 150 | = 1.0.2 = 151 | * Fix - WC detection fix 152 | 153 | = 1.0.1 = 154 | * Tweak - Styling support for WooCommerce v2 write-panels 155 | 156 | = 1.0 = 157 | * Initial release 158 | 159 | 160 | == Upgrade Notice == 161 | 162 | = 1.2.7 = 163 | Prevent products from satisfying their own dependencies. 164 | -------------------------------------------------------------------------------- /languages/woocommerce-product-dependencies.pot: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2024 WooCommerce 2 | # This file is distributed under the GNU General Public License v3.0. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: WooCommerce Product Dependencies 2.0.1\n" 6 | "Report-Msgid-Bugs-To: " 7 | "https://wordpress.org/support/plugin/woocommerce-product-dependencies\n" 8 | "POT-Creation-Date: 2024-09-19 06:41:32+00:00\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=utf-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "PO-Revision-Date: 2024-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "X-Generator: grunt-wp-i18n 0.5.4\n" 16 | 17 | #: class-wc-pd-helpers.php:36 18 | msgid "%s" 19 | msgstr "" 20 | 21 | #: class-wc-pd-helpers.php:38 22 | msgid " and %s" 23 | msgstr "" 24 | 25 | #: class-wc-pd-helpers.php:38 26 | msgid " or %s" 27 | msgstr "" 28 | 29 | #: class-wc-pd-helpers.php:40 30 | msgid ", %s" 31 | msgstr "" 32 | 33 | #: woocommerce-product-dependencies.php:81 34 | #: woocommerce-product-dependencies.php:90 35 | msgid "Foul!" 36 | msgstr "" 37 | 38 | #: woocommerce-product-dependencies.php:423 39 | msgid "all required products" 40 | msgstr "" 41 | 42 | #: woocommerce-product-dependencies.php:424 43 | msgid "these products" 44 | msgstr "" 45 | 46 | #: woocommerce-product-dependencies.php:426 47 | msgid "a required product" 48 | msgstr "" 49 | 50 | #: woocommerce-product-dependencies.php:438 51 | msgid "one or more products from the %s categories" 52 | msgstr "" 53 | 54 | #: woocommerce-product-dependencies.php:439 55 | msgid "one or more products from all required categories" 56 | msgstr "" 57 | 58 | #: woocommerce-product-dependencies.php:441 59 | #: woocommerce-product-dependencies.php:445 60 | msgid "a product from the %s category" 61 | msgstr "" 62 | 63 | #: woocommerce-product-dependencies.php:442 64 | msgid "a qualifying product" 65 | msgstr "" 66 | 67 | #: woocommerce-product-dependencies.php:453 68 | msgid "" 69 | "Access to "%1$s" is restricted to customers who have previously " 70 | "purchased %2$s." 71 | msgstr "" 72 | 73 | #: woocommerce-product-dependencies.php:455 74 | msgid "" 75 | "Access to "%1$s" is restricted to customers who have previously " 76 | "purchased %2$s. Please log in to validate ownership " 77 | "and try again." 78 | msgstr "" 79 | 80 | #: woocommerce-product-dependencies.php:474 81 | msgid "a product" 82 | msgid_plural "some products" 83 | msgstr[0] "" 84 | msgstr[1] "" 85 | 86 | #: woocommerce-product-dependencies.php:475 87 | msgid "%1$s from the %2$s category" 88 | msgid_plural "%1$s from the %2$s categories" 89 | msgstr[0] "" 90 | msgstr[1] "" 91 | 92 | #: woocommerce-product-dependencies.php:478 93 | msgid "one or more products from the %s category" 94 | msgid_plural "one or more products from the %s categories" 95 | msgstr[0] "" 96 | msgstr[1] "" 97 | 98 | #: woocommerce-product-dependencies.php:481 99 | msgid "" 100 | ""%1$s" requires purchasing %2$s. Please add %3$s to your cart and " 101 | "try again (you have already purchased %4$s)." 102 | msgstr "" 103 | 104 | #: woocommerce-product-dependencies.php:487 105 | msgid "" 106 | ""%1$s" requires purchasing %2$s. To get access to this product " 107 | "now, please add %3$s to your cart." 108 | msgstr "" 109 | 110 | #: woocommerce-product-dependencies.php:494 111 | msgid "" 112 | ""%1$s" requires purchasing %2$s. If you have previously purchased " 113 | "%3$s, please log in to verify ownership and try again. " 114 | "Alternatively, get access to "%1$s" now by adding %4$s to your " 115 | "cart." 116 | msgstr "" 117 | 118 | #: woocommerce-product-dependencies.php:501 119 | msgid "" 120 | ""%1$s" is only available in combination with %2$s. To purchase " 121 | "this product, please add %3$s to your cart." 122 | msgstr "" 123 | 124 | #: woocommerce-product-dependencies.php:697 125 | #: woocommerce-product-dependencies.php:709 126 | msgid "Dependencies" 127 | msgstr "" 128 | 129 | #: woocommerce-product-dependencies.php:762 130 | #: woocommerce-product-dependencies.php:772 131 | msgid "Product dependencies" 132 | msgstr "" 133 | 134 | #: woocommerce-product-dependencies.php:764 135 | msgid "Select products" 136 | msgstr "" 137 | 138 | #: woocommerce-product-dependencies.php:765 139 | msgid "Select categories" 140 | msgstr "" 141 | 142 | #: woocommerce-product-dependencies.php:779 143 | msgid "Search for products and variations…" 144 | msgstr "" 145 | 146 | #: woocommerce-product-dependencies.php:791 147 | msgid "" 148 | "Restrict product access based on the ownership or purchase of " 149 | "any product or variation added to this list." 150 | msgstr "" 151 | 152 | #: woocommerce-product-dependencies.php:799 153 | msgid "Select product categories…" 154 | msgstr "" 155 | 156 | #: woocommerce-product-dependencies.php:814 157 | msgid "Dependency type" 158 | msgstr "" 159 | 160 | #: woocommerce-product-dependencies.php:817 161 | msgid "Ownership" 162 | msgstr "" 163 | 164 | #: woocommerce-product-dependencies.php:818 165 | msgid "Purchase" 166 | msgstr "" 167 | 168 | #: woocommerce-product-dependencies.php:819 169 | msgid "Either" 170 | msgstr "" 171 | 172 | #: woocommerce-product-dependencies.php:827 173 | msgid "Custom notice" 174 | msgstr "" 175 | 176 | #: woocommerce-product-dependencies.php:828 177 | msgid "Notice to display instead of the default one." 178 | msgstr "" 179 | 180 | #. Plugin Name of the plugin/theme 181 | msgid "WooCommerce Product Dependencies" 182 | msgstr "" 183 | 184 | #. Author URI of the plugin/theme 185 | msgid "https://woocommerce.com/" 186 | msgstr "" 187 | 188 | #. Description of the plugin/theme 189 | msgid "" 190 | "Restrict access to WooCommerce products, depending on the ownership and/or " 191 | "purchase of other, required products." 192 | msgstr "" 193 | 194 | #. Author of the plugin/theme 195 | msgid "WooCommerce" 196 | msgstr "" 197 | 198 | #: class-wc-pd-helpers.php:166 199 | msgctxt "product title followed by meta and sku in parenthesis" 200 | msgid "%1$s – %2$s (%3$s)" 201 | msgstr "" 202 | 203 | #: class-wc-pd-helpers.php:168 204 | msgctxt "sku followed by product title and meta" 205 | msgid "%1$s – %2$s – %3$s" 206 | msgstr "" 207 | 208 | #: class-wc-pd-helpers.php:172 209 | msgctxt "product title followed by sku in parenthesis" 210 | msgid "%1$s (%2$s)" 211 | msgstr "" 212 | 213 | #: class-wc-pd-helpers.php:174 214 | msgctxt "sku followed by product title" 215 | msgid "%1$s – %2$s" 216 | msgstr "" 217 | 218 | #: class-wc-pd-helpers.php:178 219 | msgctxt "product title followed by meta in parenthesis" 220 | msgid "%1$s (%2$s)" 221 | msgstr "" 222 | 223 | #: class-wc-pd-helpers.php:180 224 | msgctxt "product title followed by meta" 225 | msgid "%1$s – %2$s" 226 | msgstr "" -------------------------------------------------------------------------------- /woocommerce-product-dependencies.php: -------------------------------------------------------------------------------- 1 | plugin_url() . '/assets/js/wc-pd-writepanels' . $suffix . '.js', array(), $this->version ); 185 | 186 | $screen = get_current_screen(); 187 | $screen_id = $screen ? $screen->id : ''; 188 | 189 | if ( in_array( $screen_id, array( 'product' ) ) ) { 190 | wp_enqueue_script( 'wc-pd-writepanels' ); 191 | } 192 | 193 | } 194 | 195 | /** 196 | * Validates a product when adding to cart. 197 | * 198 | * @param boolean $add 199 | * @param int $item_id 200 | * @param int $quantity 201 | * @return boolean 202 | */ 203 | public function add_to_cart_validation( $add, $item_id, $quantity ) { 204 | return $add && $this->evaluate_dependencies( $item_id ); 205 | } 206 | 207 | /** 208 | * Validates cart contents. 209 | */ 210 | public function check_cart_items() { 211 | 212 | $cart_items = WC()->cart->cart_contents; 213 | 214 | foreach ( $cart_items as $cart_item ) { 215 | $product = $cart_item[ 'data' ]; 216 | $this->evaluate_dependencies( $product ); 217 | } 218 | } 219 | 220 | /** 221 | * Check conditions. 222 | * 223 | * @param mixed $item 224 | * @return boolean 225 | */ 226 | public function evaluate_dependencies( $item ) { 227 | 228 | if ( is_a( $item, 'WC_Product' ) ) { 229 | 230 | if ( $item->is_type( 'variation' ) ) { 231 | $product_id = $item->get_parent_id(); 232 | $product = wc_get_product( $product_id ); 233 | } else { 234 | $product_id = $item->get_id(); 235 | $product = $item; 236 | } 237 | 238 | } else { 239 | $product_id = absint( $item ); 240 | $product = wc_get_product( $product_id ); 241 | } 242 | 243 | if ( ! $product ) { 244 | return; 245 | } 246 | 247 | $tied_product_ids = $this->get_tied_product_ids( $product ); 248 | $tied_category_ids = $this->get_tied_category_ids( $product ); 249 | $dependency_selection_type = $this->get_dependency_selection_type( $product ); 250 | $dependency_notice = $this->get_dependency_notice( $product ); 251 | 252 | $product_title = $product->get_title(); 253 | $tied_products = array(); 254 | $dependencies_exist = false; 255 | 256 | // Ensure dependencies exist. 257 | if ( 'product_ids' === $dependency_selection_type ) { 258 | 259 | if ( ! empty( $tied_product_ids ) ) { 260 | foreach ( $tied_product_ids as $id ) { 261 | 262 | $tied_product = wc_get_product( $id ); 263 | 264 | if ( $tied_product ) { 265 | $tied_products[ $id ] = $tied_product; 266 | $dependencies_exist = true; 267 | } 268 | } 269 | } 270 | 271 | } else { 272 | 273 | if ( ! empty( $tied_category_ids ) ) { 274 | 275 | $product_categories = (array) get_terms( 'product_cat', array( 'get' => 'all' ) ); 276 | $product_category_ids = wp_list_pluck( $product_categories, 'term_id' ); 277 | $tied_category_ids = array_intersect( $product_category_ids, $tied_category_ids ); 278 | $dependencies_exist = sizeof( $tied_category_ids ) > 0; 279 | } 280 | } 281 | 282 | if ( $dependencies_exist ) { 283 | 284 | $purchase_dependency_result = false; 285 | $ownership_dependency_result = false; 286 | 287 | $purchased_product_ids = array(); 288 | $purchased_cat_ids = array(); 289 | 290 | $dependency_type = $this->get_dependency_type( $product ); 291 | $dependency_relationship = $this->get_dependency_relationship( $product ); 292 | 293 | $tied_product_ids = array_keys( $tied_products ); 294 | $has_multiple = 'product_ids' === $dependency_selection_type ? sizeof( $tied_products ) > 1 : sizeof( $tied_category_ids ) > 1; 295 | $tied_ids = 'product_ids' === $dependency_selection_type ? $tied_product_ids : $tied_category_ids; 296 | $owned_ids = array(); 297 | 298 | // Check cart. 299 | if ( in_array( $dependency_type, array( self::DEPENDENCY_TYPE_PURCHASE, self::DEPENDENCY_TYPE_EITHER ) ) ) { 300 | 301 | $cart_contents = WC()->cart->cart_contents; 302 | $item_id = $product_id; 303 | 304 | foreach ( $cart_contents as $cart_item ) { 305 | 306 | $product_id = $cart_item[ 'product_id' ]; 307 | $variation_id = $cart_item[ 'variation_id' ]; 308 | 309 | if ( $product_id === $item_id || $variation_id === $item_id ) { 310 | continue; 311 | } 312 | 313 | if ( 'product_ids' === $dependency_selection_type ) { 314 | 315 | if ( in_array( $product_id, $tied_product_ids ) || in_array( $variation_id, $tied_product_ids ) ) { 316 | 317 | if ( 'or' === $dependency_relationship ) { 318 | $purchase_dependency_result = true; 319 | break; 320 | } else { 321 | $purchased_product_ids = array_unique( array_merge( $purchased_product_ids, array_filter( array( $product_id, $variation_id ) ) ) ); 322 | } 323 | } 324 | 325 | } else { 326 | 327 | $cart_item_product = $cart_item[ 'data' ]->is_type( 'variation' ) ? wc_get_product( $cart_item[ 'data' ]->get_parent_id() ) : $cart_item[ 'data' ]; 328 | $cart_item_cat_ids = $cart_item_product->get_category_ids(); 329 | 330 | $matching_cat_ids = array_intersect( $cart_item_cat_ids, $tied_category_ids ); 331 | 332 | if ( sizeof( $matching_cat_ids ) ) { 333 | 334 | if ( 'or' === $dependency_relationship ) { 335 | $purchase_dependency_result = true; 336 | break; 337 | } else { 338 | $purchased_cat_ids = array_unique( array_merge( $purchased_cat_ids, array_filter( $matching_cat_ids ) ) ); 339 | $purchased_product_ids = array_unique( array_merge( $purchased_product_ids, array_filter( array( $product_id, $variation_id ) ) ) ); 340 | } 341 | } 342 | } 343 | } 344 | 345 | $purchased_ids = 'product_ids' === $dependency_selection_type ? $purchased_product_ids : $purchased_cat_ids; 346 | 347 | if ( 'and' === $dependency_relationship ) { 348 | if ( sizeof( $purchased_ids ) >= sizeof( $tied_ids ) ) { 349 | $purchase_dependency_result = true; 350 | } 351 | } 352 | } 353 | 354 | // Check ownership. 355 | if ( in_array( $dependency_type, array( self::DEPENDENCY_TYPE_OWNERSHIP, self::DEPENDENCY_TYPE_EITHER ) ) ) { 356 | 357 | if ( is_user_logged_in() ) { 358 | 359 | $current_user = wp_get_current_user(); 360 | 361 | if ( 'category_ids' === $dependency_selection_type ) { 362 | $tied_product_ids = $this->get_product_ids_in_categories( $tied_category_ids ); 363 | } 364 | 365 | if ( $dependency_type === self::DEPENDENCY_TYPE_EITHER && $purchase_dependency_result ) { 366 | 367 | $ownership_dependency_result = true; 368 | 369 | } else { 370 | 371 | $owned_product_ids = $this->customer_bought_products( $current_user->user_email, $current_user->ID, $tied_product_ids ); 372 | 373 | // Find all categories that these products belong to and then compare against the set of required categories. 374 | if ( 'and' === $dependency_relationship && $has_multiple ) { 375 | 376 | if ( $dependency_type === self::DEPENDENCY_TYPE_EITHER ) { 377 | $owned_product_ids = array_unique( array_merge( $owned_product_ids, $purchased_product_ids ) ); 378 | } 379 | 380 | if ( 'product_ids' === $dependency_selection_type ) { 381 | $owned_ids = $owned_product_ids; 382 | } else { 383 | $owned_ids = array_unique( wp_get_object_terms( $owned_product_ids, 'product_cat', array( 'fields' => 'ids' ) ) ); 384 | } 385 | 386 | $owned_ids = array_intersect( $owned_ids, $tied_ids ); 387 | 388 | $ownership_dependency_result = sizeof( $owned_ids ) >= sizeof( $tied_ids ); 389 | 390 | if ( $ownership_dependency_result ) { 391 | $purchase_dependency_result = true; 392 | } 393 | 394 | } else { 395 | $ownership_dependency_result = sizeof( $owned_product_ids ); 396 | } 397 | } 398 | } 399 | } 400 | 401 | $result = $ownership_dependency_result || $purchase_dependency_result; 402 | 403 | // Show notice. 404 | if ( false === $result ) { 405 | 406 | if ( ! WC()->session->has_session() ) { 407 | // Generate a random customer ID. 408 | WC()->session->set_customer_session_cookie( true ); 409 | } 410 | 411 | if ( $dependency_notice ) { 412 | 413 | wc_add_notice( $dependency_notice, 'error' ); 414 | 415 | } else { 416 | 417 | if ( 'product_ids' === $dependency_selection_type ) { 418 | 419 | $required_msg = WC_PD_Helpers::merge_product_titles( $tied_products, $dependency_relationship ); 420 | 421 | if ( $has_multiple ) { 422 | if ( 'and' === $dependency_relationship ) { 423 | $action_msg = __( 'all required products', 'woocommerce-product-dependencies' ); 424 | $action_msg_2 = __( 'these products', 'woocommerce-product-dependencies' ); 425 | } else { 426 | $action_msg = __( 'a required product', 'woocommerce-product-dependencies' ); 427 | } 428 | } else { 429 | $action_msg = $required_msg; 430 | } 431 | 432 | } else { 433 | 434 | $merged_category_titles = WC_PD_Helpers::merge_category_titles( $tied_category_ids, $dependency_relationship ); 435 | 436 | if ( $has_multiple ) { 437 | if ( 'and' === $dependency_relationship ) { 438 | $required_msg = sprintf( __( 'one or more products from the %s categories', 'woocommerce-product-dependencies' ), $merged_category_titles ); 439 | $action_msg = __( 'one or more products from all required categories', 'woocommerce-product-dependencies' ); 440 | } else { 441 | $required_msg = sprintf( __( 'a product from the %s category', 'woocommerce-product-dependencies' ), $merged_category_titles ); 442 | $action_msg = __( 'a qualifying product', 'woocommerce-product-dependencies' ); 443 | } 444 | } else { 445 | $required_msg = sprintf( __( 'a product from the %s category', 'woocommerce-product-dependencies' ), $merged_category_titles ); 446 | $action_msg = $required_msg; 447 | } 448 | } 449 | 450 | if ( $dependency_type === self::DEPENDENCY_TYPE_OWNERSHIP ) { 451 | 452 | if ( is_user_logged_in() ) { 453 | $msg = __( 'Access to "%1$s" is restricted to customers who have previously purchased %2$s.', 'woocommerce-product-dependencies' ); 454 | } else { 455 | $msg = __( 'Access to "%1$s" is restricted to customers who have previously purchased %2$s. Please log in to validate ownership and try again.', 'woocommerce-product-dependencies' ); 456 | } 457 | 458 | wc_add_notice( sprintf( $msg, $product_title, $required_msg, wp_login_url() ), 'error' ); 459 | 460 | } elseif ( $dependency_type === self::DEPENDENCY_TYPE_EITHER ) { 461 | 462 | if ( is_user_logged_in() ) { 463 | 464 | if ( 'and' === $dependency_relationship && $has_multiple && sizeof( $owned_ids ) ) { 465 | 466 | if ( 'product_ids' === $dependency_selection_type ) { 467 | 468 | $owned_msg = WC_PD_Helpers::merge_product_titles( array_intersect_key( $tied_products, array_flip( $owned_product_ids ) ), 'and' ); 469 | $action_msg = WC_PD_Helpers::merge_product_titles( array_intersect_key( $tied_products, array_flip( array_diff( $tied_ids, $owned_product_ids ) ) ), 'and' ); 470 | 471 | } else { 472 | 473 | $owned_category_titles = WC_PD_Helpers::merge_category_titles( $owned_ids, 'and' ); 474 | $owned_products_msg = _n( 'a product', 'some products', sizeof( $owned_product_ids ), 'woocommerce-product-dependencies' ); 475 | $owned_msg = sprintf( _n( '%1$s from the %2$s category', '%1$s from the %2$s categories', sizeof( $owned_category_titles ), 'woocommerce-product-dependencies' ), $owned_products_msg, $owned_category_titles ); 476 | 477 | $action_category_titles = WC_PD_Helpers::merge_category_titles( array_diff( $tied_ids, $owned_ids ), 'and' ); 478 | $action_msg = sprintf( _n( 'one or more products from the %s category', 'one or more products from the %s categories', sizeof( $action_category_titles ), 'woocommerce-product-dependencies' ), $action_category_titles ); 479 | } 480 | 481 | $msg = __( '"%1$s" requires purchasing %2$s. Please add %3$s to your cart and try again (you have already purchased %4$s).', 'woocommerce-product-dependencies' ); 482 | 483 | wc_add_notice( sprintf( $msg, $product_title, $required_msg, $action_msg, $owned_msg ), 'error' ); 484 | 485 | } else { 486 | 487 | $msg = __( '"%1$s" requires purchasing %2$s. To get access to this product now, please add %3$s to your cart.', 'woocommerce-product-dependencies' ); 488 | 489 | wc_add_notice( sprintf( $msg, $product_title, $required_msg, $action_msg ), 'error' ); 490 | } 491 | 492 | } else { 493 | 494 | $msg = __( '"%1$s" requires purchasing %2$s. If you have previously purchased %3$s, please log in to verify ownership and try again. Alternatively, get access to "%1$s" now by adding %4$s to your cart.', 'woocommerce-product-dependencies' ); 495 | 496 | wc_add_notice( sprintf( $msg, $product_title, $required_msg, isset( $action_msg_2 ) ? $action_msg_2 : $action_msg, $action_msg, wp_login_url() ), 'error' ); 497 | } 498 | 499 | } else { 500 | 501 | $msg = __( '"%1$s" is only available in combination with %2$s. To purchase this product, please add %3$s to your cart.', 'woocommerce-product-dependencies' ); 502 | 503 | wc_add_notice( sprintf( $msg, $product_title, $required_msg, $action_msg ), 'error' ); 504 | } 505 | } 506 | } 507 | 508 | return $result; 509 | } 510 | 511 | return true; 512 | } 513 | 514 | /** 515 | * Dependency relationship: 516 | * - 'or': Ownership/purchase of any product required. 517 | * - 'and': Ownership/purhase of all products required. 518 | * 519 | * @param WC_Product $product 520 | * @return string 521 | */ 522 | public function get_dependency_relationship( $product ) { 523 | return apply_filters( 'wc_pd_dependency_relationship', 'or', $product ); 524 | } 525 | 526 | /** 527 | * Returns an array with all dependent product ids. 528 | * 529 | * @since 1.2.0 530 | * 531 | * @param WC_Product $product 532 | * @return array $dependent_ids 533 | */ 534 | public function get_tied_product_ids( $product ) { 535 | $dependent_ids = $product->get_meta( '_tied_products', true ); 536 | return empty( $dependent_ids ) ? array() : array_unique( $dependent_ids ); 537 | } 538 | 539 | /** 540 | * Returns an array with all saved category ids. 541 | * 542 | * @since 1.2.0 543 | * 544 | * @param WC_Product $product 545 | * @return array $category_ids 546 | */ 547 | public function get_tied_category_ids( $product ) { 548 | $category_ids = $product->get_meta( '_tied_categories', true ); 549 | return empty( $category_ids ) ? array() : array_unique( $category_ids ); 550 | } 551 | 552 | /** 553 | * Returns the product dependency selection type. 554 | * 555 | * @since 1.2.0 556 | * 557 | * @param WC_Product $product 558 | * @return string $selection_type 559 | */ 560 | public function get_dependency_selection_type( $product ) { 561 | $selection_type = $product->get_meta( '_dependency_selection_type', true ); 562 | $selection_type = in_array( $selection_type, array( 'product_ids', 'category_ids' ) ) ? $selection_type : 'product_ids'; 563 | 564 | return $selection_type; 565 | } 566 | 567 | /** 568 | * Returns the custom dependency notice. 569 | * 570 | * @since 1.2.0 571 | * 572 | * @param WC_Product $product 573 | * @return string $notice 574 | */ 575 | public function get_dependency_notice( $product ) { 576 | $notice = $product->get_meta( '_dependency_notice', true ); 577 | return $notice; 578 | } 579 | 580 | /** 581 | * Returns the product dependency type. 582 | * 583 | * @since 1.2.0 584 | * 585 | * @param WC_Product $product 586 | * @return string $type 587 | */ 588 | public function get_dependency_type( $product ) { 589 | $type = absint( $product->get_meta( '_dependency_type', true ) ); 590 | $type = in_array( $type, array( self::DEPENDENCY_TYPE_OWNERSHIP, self::DEPENDENCY_TYPE_PURCHASE, self::DEPENDENCY_TYPE_EITHER ) ) ? $type : self::DEPENDENCY_TYPE_EITHER; 591 | 592 | return $type; 593 | } 594 | 595 | /** 596 | * Get all product IDs that belong to the specified categories. 597 | * 598 | * @param array $category_ids 599 | * @return array 600 | */ 601 | private function get_product_ids_in_categories( $category_ids ) { 602 | 603 | $query_results = new WP_Query( array( 604 | 'post_type' => array( 'product', 'product_variation' ), 605 | 'fields' => 'ids', 606 | 'tax_query' => array( 607 | array( 608 | 'taxonomy' => 'product_cat', 609 | 'field' => 'term_id', 610 | 'terms' => $category_ids, 611 | 'operator' => 'IN', 612 | ) 613 | ) 614 | ) ); 615 | 616 | return $query_results->posts; 617 | } 618 | 619 | /** 620 | * Re-implementation of 'wc_customer_bought_product' with support for array input. 621 | * 622 | * @since 1.2.0 623 | * 624 | * @param string $customer_email 625 | * @param int $user_id 626 | * @param array $product_ids 627 | * @return array 628 | */ 629 | private function customer_bought_products( $customer_email, $user_id, $product_ids ) { 630 | 631 | global $wpdb; 632 | 633 | $results = apply_filters( 'wc_pd_pre_customer_bought_products', null, $customer_email, $user_id, $product_ids ); 634 | 635 | if ( null !== $results ) { 636 | return $results; 637 | } 638 | 639 | $transient_name = 'wc_cbp_' . md5( $customer_email . $user_id . WC_Cache_Helper::get_transient_version( 'orders' ) ); 640 | 641 | if ( false === ( $results = get_transient( $transient_name ) ) ) { 642 | 643 | $customer_data = array( $user_id ); 644 | 645 | if ( $user_id ) { 646 | 647 | $user = get_user_by( 'id', $user_id ); 648 | 649 | if ( isset( $user->user_email ) ) { 650 | $customer_data[] = $user->user_email; 651 | } 652 | } 653 | 654 | if ( is_email( $customer_email ) ) { 655 | $customer_data[] = $customer_email; 656 | } 657 | 658 | $customer_data = array_map( 'esc_sql', array_filter( array_unique( $customer_data ) ) ); 659 | $statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() ); 660 | 661 | if ( sizeof( $customer_data ) == 0 ) { 662 | return false; 663 | } 664 | 665 | $results = $wpdb->get_col( " 666 | SELECT im.meta_value FROM {$wpdb->posts} AS p 667 | INNER JOIN {$wpdb->postmeta} AS pm ON p.ID = pm.post_id 668 | INNER JOIN {$wpdb->prefix}woocommerce_order_items AS i ON p.ID = i.order_id 669 | INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS im ON i.order_item_id = im.order_item_id 670 | WHERE p.post_status IN ( 'wc-" . implode( "','wc-", $statuses ) . "' ) 671 | AND pm.meta_key IN ( '_billing_email', '_customer_user' ) 672 | AND im.meta_key IN ( '_product_id', '_variation_id' ) 673 | AND im.meta_value != 0 674 | AND pm.meta_value IN ( '" . implode( "','", $customer_data ) . "' ) 675 | " ); 676 | 677 | $results = array_map( 'absint', $results ); 678 | 679 | set_transient( $transient_name, $results, DAY_IN_SECONDS * 30 ); 680 | } 681 | 682 | return array_intersect( $results, $product_ids ); 683 | } 684 | 685 | /* 686 | |-------------------------------------------------------------------------- 687 | | Admin Filters. 688 | |-------------------------------------------------------------------------- 689 | */ 690 | 691 | /** 692 | * Add Product Data tab. 693 | * 694 | * @return void 695 | */ 696 | public function dependencies_product_data_panel_tab() { 697 | echo ''; 698 | } 699 | 700 | /** 701 | * Add the "Product Dependencies" panel tab. 702 | * 703 | * @param array $tabs 704 | * @return array 705 | */ 706 | public function dependencies_product_data_tab( $tabs ) { 707 | 708 | $tabs[ 'dependencies' ] = array( 709 | 'label' => __( 'Dependencies', 'woocommerce-product-dependencies' ), 710 | 'target' => 'tied_products_data', 711 | 'class' => array( 'show_if_simple', 'show_if_variable', 'show_if_bundle', 'show_if_composite', 'linked_product_options', 'show_if_booking', 'show_if_phive_booking' ) 712 | ); 713 | 714 | return $tabs; 715 | } 716 | 717 | /** 718 | * Add Product Data tab section. 719 | * 720 | * @return void 721 | */ 722 | public function dependencies_product_data_panel() { 723 | 724 | global $post, $product_object; 725 | 726 | if ( empty( $product_object ) || ! is_a( $product_object, 'WC_Product' ) ) { 727 | $product_object = wc_get_product( $post->ID ); 728 | } 729 | 730 | if ( ! $product_object ) { 731 | return; 732 | } 733 | 734 | $tied_products = $this->get_tied_product_ids( $product_object ); 735 | $tied_categories = $this->get_tied_category_ids( $product_object ); 736 | $dependency_type = $this->get_dependency_type( $product_object ); 737 | $selection_type = $this->get_dependency_selection_type( $product_object ); 738 | $dependency_notice = $this->get_dependency_notice( $product_object ); 739 | 740 | $product_id_options = array(); 741 | $product_categories = (array) get_terms( 'product_cat', array( 'get' => 'all' ) ); 742 | $selection_type = in_array( $selection_type, array( 'product_ids', 'category_ids' ) ) ? $selection_type : 'product_ids'; 743 | 744 | if ( $tied_products ) { 745 | foreach ( $tied_products as $item_id ) { 746 | 747 | $title = WC_PD_Helpers::get_product_title( $item_id ); 748 | 749 | if ( $title ) { 750 | $product_id_options[ $item_id ] = $title; 751 | } 752 | } 753 | } 754 | 755 | ?> 756 |
757 | 758 | 'product_dependencies_dropdown', 762 | 'label' => __( 'Product dependencies', 'woocommerce-product-dependencies' ), 763 | 'options' => array( 764 | 'product_ids' => __( 'Select products', 'woocommerce-product-dependencies' ), 765 | 'category_ids' => __( 'Select categories', 'woocommerce-product-dependencies' ) 766 | ), 767 | 'value' => $selection_type 768 | ) ); 769 | ?> 770 | 771 | 774 | 775 |
776 |

777 | any product or variation added to this list.', 'woocommerce-product-dependencies' ) ); 792 | 793 | ?> 794 |

795 |
796 | 797 |
798 |

799 | 809 |

810 |
811 | 812 |
813 |

814 | 816 | 821 |

822 |
'dependency_notice', 826 | 'value' => $dependency_notice, 827 | 'label' => __( 'Custom notice', 'woocommerce-product-dependencies' ), 828 | 'description' => __( 'Notice to display instead of the default one.', 'woocommerce-product-dependencies' ), 829 | 'desc_tip' => true 830 | ) ); 831 | 832 | ?>
833 | = 2.7. 838 | * 839 | * @param WC_Product $product 840 | * @return void 841 | */ 842 | public function process_product_data( $product ) { 843 | 844 | if ( ! empty( $_POST[ 'tied_categories' ] ) && is_array( $_POST[ 'tied_categories' ] ) ) { 845 | 846 | $tied_categories = array_map( 'intval', $_POST[ 'tied_categories' ] ); 847 | 848 | $product->update_meta_data( '_tied_categories', $tied_categories ); 849 | 850 | } else { 851 | 852 | $product->delete_meta_data( '_tied_categories' ); 853 | } 854 | 855 | if ( ! isset( $_POST[ 'tied_products' ] ) || empty( $_POST[ 'tied_products' ] ) ) { 856 | 857 | $product->delete_meta_data( '_tied_products' ); 858 | 859 | } elseif ( isset( $_POST[ 'tied_products' ] ) && ! empty( $_POST[ 'tied_products' ] ) ) { 860 | 861 | $tied_ids = $_POST[ 'tied_products' ]; 862 | 863 | if ( is_array( $tied_ids ) ) { 864 | $tied_ids = array_map( 'intval', $tied_ids ); 865 | } else { 866 | $tied_ids = array_filter( array_map( 'intval', explode( ',', $tied_ids ) ) ); 867 | } 868 | 869 | $product->update_meta_data( '_tied_products', $tied_ids ); 870 | } 871 | 872 | if ( ! empty( $_POST[ 'dependency_type' ] ) ) { 873 | $product->update_meta_data( '_dependency_type', stripslashes( $_POST[ 'dependency_type' ] ) ); 874 | } 875 | 876 | if ( ! empty( $_POST[ 'product_dependencies_dropdown' ] ) ) { 877 | $product->update_meta_data( '_dependency_selection_type', stripslashes( $_POST[ 'product_dependencies_dropdown' ] ) ); 878 | } 879 | 880 | if ( ! empty( $_POST[ 'dependency_notice' ] ) ) { 881 | $product->update_meta_data( '_dependency_notice', wp_kses_post( stripslashes( $_POST[ 'dependency_notice' ] ) ) ); 882 | } else { 883 | $product->delete_meta_data( '_dependency_notice' ); 884 | } 885 | } 886 | 887 | /** 888 | * Save dependencies meta. WC <= 2.6. 889 | * 890 | * @param int $post_id 891 | * @param WC_Post $post 892 | * @return void 893 | */ 894 | public function process_meta( $post_id, $post ) { 895 | 896 | global $post; 897 | 898 | if ( ! empty( $_POST[ 'tied_categories' ] ) && is_array( $_POST[ 'tied_categories' ] ) ) { 899 | 900 | $tied_categories = array_map( 'intval', $_POST[ 'tied_categories' ] ); 901 | 902 | update_post_meta( $post_id, '_tied_categories', $tied_categories ); 903 | 904 | } else { 905 | 906 | delete_post_meta( $post_id, '_tied_categories' ); 907 | } 908 | 909 | if ( ! isset( $_POST[ 'tied_products' ] ) || empty( $_POST[ 'tied_products' ] ) ) { 910 | 911 | delete_post_meta( $post_id, '_tied_products' ); 912 | 913 | } elseif ( isset( $_POST[ 'tied_products' ] ) && ! empty( $_POST[ 'tied_products' ] ) ) { 914 | 915 | $tied_ids = $_POST[ 'tied_products' ]; 916 | 917 | if ( is_array( $tied_ids ) ) { 918 | $tied_ids = array_map( 'intval', $tied_ids ); 919 | } else { 920 | $tied_ids = array_filter( array_map( 'intval', explode( ',', $tied_ids ) ) ); 921 | } 922 | 923 | update_post_meta( $post_id, '_tied_products', $tied_ids ); 924 | } 925 | 926 | if ( ! empty( $_POST[ 'dependency_type' ] ) ) { 927 | update_post_meta( $post_id, '_dependency_type', stripslashes( $_POST[ 'dependency_type' ] ) ); 928 | } 929 | 930 | if ( ! empty( $_POST[ 'product_dependencies_dropdown' ] ) ) { 931 | update_post_meta( $post_id, '_dependency_selection_type', stripslashes( $_POST[ 'product_dependencies_dropdown' ] ) ); 932 | } 933 | 934 | if ( ! empty( $_POST[ 'dependency_notice' ] ) ) { 935 | update_post_meta( $post_id, '_dependency_notice', wp_kses_post( stripslashes( $_POST[ 'dependency_notice' ] ) ) ); 936 | } else { 937 | 938 | delete_post_meta( $post_id, '_dependency_notice' ); 939 | } 940 | } 941 | 942 | /* 943 | |-------------------------------------------------------------------------- 944 | | Deprecated methods. 945 | |-------------------------------------------------------------------------- 946 | */ 947 | 948 | /** 949 | * Check conditions. 950 | * 951 | * @deprecated 1.1.0 952 | * 953 | * @param int $item_id 954 | * @return boolean 955 | */ 956 | public function woo_tied_evaluate_access( $id ) { 957 | _deprecated_function( __METHOD__ . '()', '1.1.0', __CLASS__ . '::evaluate_access()' ); 958 | return WC_Product_Dependencies()->evaluate_dependencies( $id ); 959 | } 960 | } 961 | 962 | /** 963 | * Returns the main instance of WC_Product_Dependencies to prevent the need to use globals. 964 | * 965 | * @since 1.1.0 966 | * @return WC_Product_Dependencies 967 | */ 968 | function WC_Product_Dependencies() { 969 | return WC_Product_Dependencies::instance(); 970 | } 971 | 972 | // Backwards compatibility with v1.0. 973 | $GLOBALS[ 'woocommerce_restricted_access' ] = WC_Product_Dependencies(); 974 | --------------------------------------------------------------------------------