├── .distignore ├── .github └── workflows │ └── release.yml ├── .gitignore ├── Gruntfile.js ├── README.md ├── composer.json ├── package.json ├── phpcs.xml └── wc-add-to-cart-form-shortcode.php /.distignore: -------------------------------------------------------------------------------- 1 | /.wordpress-org 2 | /.git 3 | /.github 4 | /node_modules 5 | /build 6 | /vendor 7 | /deploy 8 | /src 9 | 10 | *.sublime-project 11 | *.afdesign 12 | 13 | .distignore 14 | .gitignore 15 | Gruntfile.js 16 | package.json 17 | package-lock.json 18 | readme.md 19 | composer.json 20 | composer.lock 21 | webpack.common.js 22 | webpack.dev.js 23 | webpack.prod.js 24 | phpcs.xml 25 | .jshintrc 26 | .babelrc -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Add build zip to GitHub releases. 2 | # Version: 1.1.0 3 | 4 | name: Release 5 | 6 | on: 7 | push: 8 | tags: 9 | # Semver (https://semver.org/) release pattern. 10 | - '[0-9]+.[0-9]+.[0-9]+*' 11 | 12 | jobs: 13 | # Build distribution release. 14 | # 15 | # Performs the following steps: 16 | # - Checks out the repository. 17 | # - Sets up PHP with Composer. 18 | # - Logs debug information. 19 | # - Get Composer Cache Directory. 20 | # - Sets up Composer Caching.. 21 | # - Logs debug information. 22 | # - Install Composer dependencies with development dependencies. 23 | # - Setup NodeJS. 24 | # - Get NPM cache directory. 25 | # - Sets up NPM Caching. 26 | # - Install NPM dependencies. 27 | # - Get release version. 28 | # - Logs debug information. 29 | # - Build release. 30 | # - Add package to GitHub releases. 31 | 32 | dist: 33 | name: "Build distribution release." 34 | runs-on: ubuntu-latest 35 | steps: 36 | 37 | - name: "Checks out the repository." 38 | uses: "actions/checkout@v2" 39 | 40 | - name: "Sets up PHP with Composer." 41 | uses: shivammathur/setup-php@v2 42 | with: 43 | php-version: '7.4' 44 | tools: composer 45 | env: 46 | COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | 48 | - name: "Get Composer Cache Directory." 49 | id: composer-cache 50 | run: | 51 | echo "::set-output name=dir::$(composer config cache-files-dir)" 52 | 53 | - name: "Sets up Composer Caching." 54 | uses: "actions/cache@v2" 55 | with: 56 | path: ${{ steps.composer-cache.outputs.dir }} 57 | key: ${{ runner.os }}-php-composer-build-${{ hashFiles('**/composer.lock') }} 58 | restore-keys: | 59 | ${{ runner.os }}-php-composer-build- 60 | 61 | - name: "Logs debug information." 62 | run: | 63 | php --version 64 | composer --version 65 | 66 | - name: "Install Composer dependencies without development dependencies." 67 | run: | 68 | composer install --no-dev --no-interaction --prefer-dist --no-scripts 69 | 70 | - name: "Setup NodeJS." 71 | uses: actions/setup-node@v2 72 | with: 73 | node-version: '14' 74 | 75 | - name: "Get NPM cache directory." 76 | id: npm-cache-dir 77 | run: | 78 | echo "::set-output name=dir::$(npm config get cache)" 79 | 80 | - name: "Sets up NPM Caching." 81 | uses: actions/cache@v2 82 | with: 83 | path: ${{ steps.npm-cache-dir.outputs.dir }} 84 | key: ${{ runner.os }}-node-npm-build-${{ hashFiles('**/package-lock.json') }} 85 | restore-keys: | 86 | ${{ runner.os }}-node-npm-build- 87 | 88 | - name: "Install NPM dependencies." 89 | run: npm install 90 | 91 | - name: "Get release version." 92 | run: | 93 | echo "release_tag=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV 94 | echo "release_name=$(node -p -e "require('./package.json').name")" >> $GITHUB_ENV 95 | 96 | - name: "Logs debug information." 97 | run: | 98 | node --version 99 | npm --version 100 | echo ${{ env.release_tag }} 101 | 102 | - name: "Build release." 103 | run: | 104 | npm run release 105 | 106 | - name: Rename output file 107 | run: mv ./deploy/${{ env.release_tag }}/${{ env.release_name }}.zip ./deploy/${{ env.release_tag }}/${{ env.release_name }}-${{ env.release_tag }}.zip 108 | 109 | - name: "Add package to GitHub releases." 110 | uses: softprops/action-gh-release@v1 111 | with: 112 | files: ./deploy/${{ env.release_tag }}/${{ env.release_name }}-${{ env.release_tag }}.zip 113 | env: 114 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 115 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/* 2 | composer.lock 3 | node_modules/* 4 | package-lock.json 5 | deploy/* 6 | *.bak 7 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Build automation scripts. 3 | * 4 | * @package WooCommerce Mix and Match 5 | */ 6 | 7 | module.exports = function(grunt) { 8 | 9 | require( 'load-grunt-tasks' )( grunt ); 10 | 11 | // Project configuration. 12 | grunt.initConfig( 13 | { 14 | pkg: grunt.file.readJSON( 'package.json' ), 15 | 16 | // # Build and release 17 | 18 | // Remove any files in zip destination and build folder. 19 | clean: { 20 | main: ['build/**'] 21 | }, 22 | 23 | // Copy the plugin into the build directory. 24 | copy: { 25 | main: { 26 | src: [ 27 | '**', 28 | '!node_modules/**', 29 | '!build/**', 30 | '!deploy/**', 31 | '!svn/**', 32 | '!**/*.zip', 33 | '!**/*.bak', 34 | '!wp-assets/**', 35 | '!package-lock.json', 36 | '!nyp-logo.png', 37 | '!screenshots/**', 38 | '!.git/**', 39 | '!**.md', 40 | '!Gruntfile.js', 41 | '!package.json', 42 | '!gitcreds.json', 43 | '!.gitcreds', 44 | '!.gitignore', 45 | '!.gitmodules', 46 | '!.code-workspace', 47 | '!sftp-config.json', 48 | '!**.sublime-workspace', 49 | '!**.code-workspace', 50 | '!**.sublime-project', 51 | '!deploy.sh', 52 | '!**/*~', 53 | '!phpcs.xml', 54 | '!composer.json', 55 | '!composer.lock', 56 | '!vendor/**', 57 | '!none', 58 | '!.nvmrc', 59 | '!.jshintrc', 60 | '!.distignore', 61 | '!**/*.scss', 62 | '!assets//scss/**' 63 | ], 64 | dest: 'build/' 65 | } 66 | }, 67 | 68 | // Make a zipfile. 69 | compress: { 70 | main: { 71 | options: { 72 | mode: 'zip', 73 | archive: 'deploy/<%= pkg.version %>/<%= pkg.name %>.zip' 74 | }, 75 | expand: true, 76 | cwd: 'build/', 77 | src: ['**/*'], 78 | dest: '/<%= pkg.name %>' 79 | } 80 | }, 81 | 82 | // Bump version numbers (replace with version in package.json). 83 | replace: { 84 | version: { 85 | src: [ 86 | 'readme.txt', 87 | '<%= pkg.name %>.php' 88 | ], 89 | overwrite: true, 90 | replacements: [ 91 | { 92 | from: /Stable tag:.*$/m, 93 | to: "Stable tag: <%= pkg.version %>" 94 | }, 95 | { 96 | from: /Version:.*$/m, 97 | to: "Version: <%= pkg.version %>" 98 | }, 99 | { 100 | from: /public \$version = \'.*.'/m, 101 | to: "public $version = '<%= pkg.version %>'" 102 | }, 103 | { 104 | from: /public \$version = \'.*.'/m, 105 | to: "public $version = '<%= pkg.version %>'" 106 | } 107 | ] 108 | } 109 | } 110 | 111 | } 112 | ); 113 | 114 | // Register tasks. 115 | grunt.registerTask( 116 | 'zip', 117 | [ 118 | 'clean', 119 | 'copy', 120 | 'compress' 121 | ] 122 | ); 123 | 124 | grunt.registerTask( 'release', [ 'replace', 'zip', 'clean' ] ); 125 | 126 | }; 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Add to Cart Form Shortcode for WooCommerce 2 | 3 | Add `[add_to_cart_form]` shortcode that display a single product add to cart form for WooCommerce. 4 | 5 | The default WooCommerce shortcodes can be a little lacking for complex product types or for products with custom add-ons such as Name Your Price. For example: `[add_to_cart id="99"]` shows only the loop add to cart button, which lacks the hooks that a plugin such as Name Your Price needs to display it's own input. 6 | 7 | And the product page shortcode, `[product_page id="99"]` displays the entire product page, which may or may not work well for the layout you are trying to build.... especially with page builders that don't have good WooCommerce modules. 8 | 9 | ## The [add_to_cart_form] solution 10 | 11 | The newly added `[add_to_cart_form]` shortcode lives in between the `[add_to_cart]` and `[product_page]` shortcodes. It displays the entire add to cart form from the single product page, but *only* the form. 12 | 13 | ## How to Use 14 | 15 | Like any other shortcode you can paste it into your page/post content. Or use it in a page builder's text editor module. The shortcode supports product identification by either product ID: 16 | 17 | `[add_to_cart_form id="99"]` 18 | 19 | or product SKU: 20 | 21 | `[add_to_cart_form sku="GUACAMOLE99"]` 22 | 23 | To use in a template you would use: 24 | 25 | `echo do_shortcode( '[add_to_cart_form id="99"]' );` 26 | 27 | ## Additional Parameters 28 | 29 | ### show_price 30 | 31 | By default, the price template will be displayed just prior to the add to cart template. You can hide it by setting the parameter to "false". 32 | 33 | `[add_to_cart_form id="99" show_price="false"]` 34 | 35 | ### hide_quantity 36 | 37 | The quantity buttons can look a little weird when displayed outside the product page. You can hide them, by setting the parameter to "true". Effectively, this sets the quantity to 1. 38 | `[add_to_cart_form id="99" hide_quantity="true"]` 39 | 40 | ### allow_form_action 41 | 42 | This form action is disabled by default so the page will refresh in place instead of redirecting to the single product page. However, the form action is filterable and so some users may wish to allow the page to redirect (for example, directly to the cart). To enable the form action, you can use `[add_to_cart_form id="99" allow_form_action="true"]` 43 | 44 | ## Screenshot 45 | 46 | Here's an example of the shortcode used on a simple Name Your Price product 47 | 48 | ![Add to cart form, displays Name Your Price text input](https://user-images.githubusercontent.com/507025/35475356-b88013fa-0362-11e8-8659-e7a9168065cf.png) 49 | 50 | ## Download 51 | [Download latest release](https://github.com/helgatheviking/add-to-cart-form-shortcode/releases/latest/download/add-to-cart-form-shortcode.zip) 52 | 53 | ## Donations 54 | If you find this plugin helpful, please consider donating to the [USA Women's National Handball Team](https://chuffed.org/fundraiser/pagq2023-kathy). 55 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "helgatheviking/add-to-cart-form-shortcode", 3 | "description": "Add [add_to_cart_form] shortcode that display a single WooCommerce product's add to cart form.", 4 | "homepage": "https://github.com/helgatheviking/add-to-cart-form-shortcode/", 5 | "type": "wordpress-plugin", 6 | "license": "GPL-3.0-or-later", 7 | "require-dev": { 8 | "woocommerce/woocommerce-sniffs": "0.1.3" 9 | }, 10 | "config": { 11 | "allow-plugins": { 12 | "dealerdirect/phpcodesniffer-composer-installer": true 13 | } 14 | }, 15 | "scripts": { 16 | "phpcs": [ 17 | "phpcs . -s -p -n" 18 | ], 19 | "phpcs-pre-commit": [ 20 | "phpcs . -s -p -n" 21 | ], 22 | "phpcb": [ 23 | "phpcbf . " 24 | ] 25 | }, 26 | "extra": { 27 | "scripts-description": { 28 | "phpcs": "Analyze code against the WordPress coding standards with PHP_CodeSniffer", 29 | "phpcb": "Fix coding standards warnings/errors automatically with PHP Code Beautifier" 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Add to Cart Form Shortcode for WooCommerce", 3 | "name": "wc-add-to-cart-form-shortcode", 4 | "version": "3.0.2", 5 | "description": "Add [add_to_cart_form] shortcode that display a single WooCommerce product's add to cart form.", 6 | "main": "Gruntfile.js", 7 | "license": "GPL-3.0", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/helgatheviking/add-to-cart-form-shortcode" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/helgatheviking/add-to-cart-form-shortcode/issues" 14 | }, 15 | "author": "helgatheviking", 16 | "devDependencies": { 17 | "grunt": "~1.5.2", 18 | "grunt-contrib-clean": "^2.0.1", 19 | "grunt-contrib-compress": "^2.0.0", 20 | "grunt-contrib-copy": "^1.0.0", 21 | "grunt-text-replace": "^0.4.0", 22 | "grunt-wp-i18n": "~1.0.3", 23 | "load-grunt-tasks": "~5.1.0" 24 | }, 25 | "scripts": { 26 | "build": "grunt build", 27 | "dev": "grunt dev", 28 | "release": "grunt release" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Kathy is Awesome Coding Standard 4 | 5 | 6 | 7 | 8 | 9 | 10 | build/ 11 | deploy/ 12 | vendor/ 13 | 14 | -------------------------------------------------------------------------------- /wc-add-to-cart-form-shortcode.php: -------------------------------------------------------------------------------- 1 | '', 67 | 'sku' => '', 68 | 'status' => 'publish', 69 | 'show_price' => 'true', 70 | 'hide_quantity' => 'false', 71 | 'allow_form_action' => 'false', 72 | ), 73 | $atts, 74 | 'product_add_to_cart_form' 75 | ); 76 | 77 | $query_args = array( 78 | 'posts_per_page' => 1, 79 | 'post_type' => 'product', 80 | 'post_status' => $atts['status'], 81 | 'ignore_sticky_posts' => 1, 82 | 'no_found_rows' => 1, 83 | ); 84 | 85 | if ( ! empty( $atts['sku'] ) ) { 86 | $query_args['meta_query'][] = array( 87 | 'key' => '_sku', 88 | 'value' => sanitize_text_field( $atts['sku'] ), 89 | 'compare' => '=', 90 | ); 91 | 92 | $query_args['post_type'] = array( 'product', 'product_variation' ); 93 | } 94 | 95 | if ( ! empty( $atts['id'] ) ) { 96 | $query_args['p'] = absint( $atts['id'] ); 97 | } 98 | 99 | // Hide quantity input if desired. 100 | if ( 'true' === $atts['hide_quantity'] ) { 101 | add_filter( 'woocommerce_quantity_input_min', 'kia_add_to_cart_form_return_one' ); 102 | add_filter( 'woocommerce_quantity_input_max', 'kia_add_to_cart_form_return_one' ); 103 | } 104 | 105 | // Change form action to avoid redirect. 106 | if ( 'false' === $atts[ 'allow_form_action' ] ) { 107 | add_filter( 'woocommerce_add_to_cart_form_action', '__return_empty_string' ); 108 | } 109 | 110 | $single_product = new WP_Query( $query_args ); 111 | 112 | $preselected_id = '0'; 113 | 114 | // Check if sku is a variation. 115 | if ( ! empty( $atts['sku'] ) && $single_product->have_posts() && 'product_variation' === $single_product->post->post_type ) { 116 | 117 | $variation = new WC_Product_Variation( $single_product->post->ID ); 118 | $attributes = $variation->get_attributes(); 119 | 120 | // Set preselected id to be used by JS to provide context. 121 | $preselected_id = $single_product->post->ID; 122 | 123 | // Get the parent product object. 124 | $query_args = array( 125 | 'posts_per_page' => 1, 126 | 'post_type' => 'product', 127 | 'post_status' => 'publish', 128 | 'ignore_sticky_posts' => 1, 129 | 'no_found_rows' => 1, 130 | 'p' => $single_product->post->post_parent, 131 | ); 132 | 133 | $single_product = new WP_Query( $query_args ); 134 | ?> 135 | 144 | is_single = true; 149 | 150 | ob_start(); 151 | 152 | global $wp_query, $previous_wp_query; 153 | 154 | // Backup query object so following loops think this is a product page. 155 | $previous_wp_query = $wp_query; 156 | // @codingStandardsIgnoreStart 157 | $wp_query = $single_product; 158 | // @codingStandardsIgnoreEnd 159 | 160 | wp_enqueue_script( 'wc-single-product' ); 161 | 162 | while ( $single_product->have_posts() ) { 163 | $single_product->the_post(); 164 | 165 | ?> 166 |
167 | 168 | 173 | 174 | 175 |
176 | ' . ob_get_clean() . ''; 197 | } 198 | } 199 | add_shortcode( 'add_to_cart_form', 'kia_add_to_cart_form_shortcode' ); 200 | 201 | if ( ! function_exists( 'kia_add_to_cart_form_redirect' ) ) { 202 | /** 203 | * Redirect to same page 204 | * 205 | * @return string 206 | */ 207 | function kia_add_to_cart_form_redirect( $url ) { 208 | return get_permalink(); 209 | } 210 | } 211 | 212 | 213 | 214 | if ( ! function_exists( 'kia_add_to_cart_form_return_one' ) ) { 215 | /** 216 | * Return integer 217 | * 218 | * @return int 219 | */ 220 | function kia_add_to_cart_form_return_one() { 221 | return 1; 222 | } 223 | } 224 | --------------------------------------------------------------------------------