├── .wordpress-org ├── banner-772x250.png ├── icon-128x128.png ├── icon-256x256.png ├── screenshot-1.gif ├── screenshot-2.gif ├── screenshot-3.jpg ├── screenshot-4.jpg ├── screenshot-5.jpg ├── screenshot-6.jpg ├── banner-1544x500.png └── banner-1880x609.png ├── postcss.config.js ├── languages └── art-woocommerce-order-one-click-ru_RU.mo ├── .eslintignore ├── wpml-config.xml ├── src ├── scss │ ├── public │ │ ├── _mixins.scss │ │ ├── _blockMsg.scss │ │ ├── _preload.scss │ │ ├── _skeleton.scss │ │ └── _popup.scss │ ├── awooc-styles.scss │ └── admin-style.scss └── js │ ├── public │ ├── main.js │ ├── components │ │ ├── Integrations │ │ │ ├── Woodmart.js │ │ │ └── VariationSwatchesByCartFlows.js │ │ ├── EventBus.js │ │ ├── Popup.js │ │ ├── RequestProcessing │ │ │ ├── DataCollector.js │ │ │ └── UpdateQuantity.js │ │ ├── Request.js │ │ ├── Form.js │ │ └── Buttons.js │ ├── AppCore.js │ ├── helpers.js │ └── config.js │ └── admin │ └── script.js ├── .prettierrc.json ├── .github └── workflows │ ├── deploy-readme-assets-to-wp-org.yml │ ├── deploy-to-wp-org.yml │ └── create-release.yml ├── .eslintrc.json ├── .distignore ├── templates ├── button.php ├── add-to-cart │ ├── single │ │ ├── simple-catalog.php │ │ ├── variable-catalog.php │ │ ├── variable.php │ │ ├── variable-normal.php │ │ ├── variable-special.php │ │ ├── variable-preorder.php │ │ ├── simple-normal.php │ │ ├── simple-special.php │ │ ├── simple-preorder.php │ │ └── simple.php │ └── loop │ │ ├── normal.php │ │ ├── preorder.php │ │ ├── special.php │ │ └── catalog.php ├── popup.php ├── quantity-input.php └── email.php ├── classes ├── Prepare │ ├── Analytics.php │ ├── Mail.php │ ├── Popup.php │ └── Prepare.php ├── Templater.php ├── Mode.php ├── Uninstall.php ├── Product │ └── Meta.php ├── RequestProcessing │ ├── OrderCreator.php │ └── EmailModifier.php ├── Admin │ ├── Create_Form.php │ └── Settings_Fields.php ├── Requirements.php ├── RequestHandler.php ├── Enqueue.php └── Main.php ├── composer.json ├── includes ├── helpers.php ├── create-cf7-field.php └── template-functions.php ├── package.json ├── art-woo-order-one-click.php ├── webpack.config.js ├── phpcs.xml └── CHANGELOG.md /.wordpress-org/banner-772x250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artikus11/art-woocommerce-order-one-click/HEAD/.wordpress-org/banner-772x250.png -------------------------------------------------------------------------------- /.wordpress-org/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artikus11/art-woocommerce-order-one-click/HEAD/.wordpress-org/icon-128x128.png -------------------------------------------------------------------------------- /.wordpress-org/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artikus11/art-woocommerce-order-one-click/HEAD/.wordpress-org/icon-256x256.png -------------------------------------------------------------------------------- /.wordpress-org/screenshot-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artikus11/art-woocommerce-order-one-click/HEAD/.wordpress-org/screenshot-1.gif -------------------------------------------------------------------------------- /.wordpress-org/screenshot-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artikus11/art-woocommerce-order-one-click/HEAD/.wordpress-org/screenshot-2.gif -------------------------------------------------------------------------------- /.wordpress-org/screenshot-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artikus11/art-woocommerce-order-one-click/HEAD/.wordpress-org/screenshot-3.jpg -------------------------------------------------------------------------------- /.wordpress-org/screenshot-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artikus11/art-woocommerce-order-one-click/HEAD/.wordpress-org/screenshot-4.jpg -------------------------------------------------------------------------------- /.wordpress-org/screenshot-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artikus11/art-woocommerce-order-one-click/HEAD/.wordpress-org/screenshot-5.jpg -------------------------------------------------------------------------------- /.wordpress-org/screenshot-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artikus11/art-woocommerce-order-one-click/HEAD/.wordpress-org/screenshot-6.jpg -------------------------------------------------------------------------------- /.wordpress-org/banner-1544x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artikus11/art-woocommerce-order-one-click/HEAD/.wordpress-org/banner-1544x500.png -------------------------------------------------------------------------------- /.wordpress-org/banner-1880x609.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artikus11/art-woocommerce-order-one-click/HEAD/.wordpress-org/banner-1880x609.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file postcss config 3 | * @type {{plugins: *[]}} 4 | */ 5 | module.exports = { 6 | plugins: [ require( 'autoprefixer' ) ], 7 | }; 8 | -------------------------------------------------------------------------------- /languages/art-woocommerce-order-one-click-ru_RU.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artikus11/art-woocommerce-order-one-click/HEAD/languages/art-woocommerce-order-one-click-ru_RU.mo -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .cache 2 | build 3 | build-module 4 | build-types 5 | node_modules 6 | packages/block-serialization-spec-parser/parser.js 7 | packages/react-native-editor/bundle 8 | vendor 9 | !.*.js -------------------------------------------------------------------------------- /wpml-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/scss/public/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin maxWidth569px { 2 | @media (max-width: 569px) { 3 | @content; 4 | } 5 | } 6 | 7 | @mixin minWidth568px { 8 | @media (min-width: 568px){ 9 | @content; 10 | } 11 | } -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "useTabs": true, 4 | "tabWidth": 2, 5 | "printWidth": 120, 6 | "jsxSingleQuote": true, 7 | "bracketSpacing": true, 8 | "parenSpacing": true, 9 | "trailingComma": "es5", 10 | "arrowParens": "avoid" 11 | } -------------------------------------------------------------------------------- /src/js/public/main.js: -------------------------------------------------------------------------------- 1 | import AppCore from './AppCore'; 2 | import { setupAwoocEventHandling } from './helpers'; 3 | 4 | ( function( $ ) { 5 | 'use strict'; 6 | 7 | setupAwoocEventHandling( $ ); 8 | 9 | $( document ).ready( () => { 10 | if ( ! window.AwoocAppCore ) { 11 | window.AwoocAppCore = new AppCore( $ ); 12 | } 13 | } ); 14 | }( jQuery ) ); 15 | -------------------------------------------------------------------------------- /src/scss/awooc-styles.scss: -------------------------------------------------------------------------------- 1 | @import "public/mixins"; 2 | @import "public/skeleton"; 3 | @import "public/popup"; 4 | @import "public/blockMsg"; 5 | 6 | .woocommerce{ 7 | .awooc-hide{ 8 | display: none !important; 9 | } 10 | button{ 11 | &.button{ 12 | &.awooc-hide{ 13 | display: none !important; 14 | } 15 | } 16 | } 17 | } 18 | .awooc-hide{ 19 | display: none !important; 20 | } -------------------------------------------------------------------------------- /src/scss/public/_blockMsg.scss: -------------------------------------------------------------------------------- 1 | .blockMsgAwooc { 2 | &::-webkit-scrollbar { 3 | width: 5px; 4 | border-radius: 5px; 5 | } 6 | 7 | &::-webkit-scrollbar-track { 8 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3); 9 | box-shadow: inset 0 0 6px rgba(0, 0, 0, .3) 10 | } 11 | 12 | &::-webkit-scrollbar-thumb { 13 | background-color: #a9a9a9; 14 | outline: 1px solid #708090; 15 | border-radius: 5px; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/deploy-readme-assets-to-wp-org.yml: -------------------------------------------------------------------------------- 1 | name: Plugin readme/assets update 2 | on: 3 | push: 4 | branches: 5 | - trunk 6 | jobs: 7 | trunk: 8 | name: Push to trunk 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@master 13 | 14 | - name: WordPress.org plugin asset/readme update 15 | uses: 10up/action-wordpress-plugin-asset-update@stable 16 | env: 17 | SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} 18 | SVN_USERNAME: ${{ secrets.SVN_USERNAME }} 19 | IGNORE_OTHER_FILES: true 20 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | // .eslintrc.json 2 | { 3 | "env": { 4 | "browser": true, 5 | "es2020": true, 6 | "jquery": true 7 | }, 8 | "extends": [ 9 | "plugin:@wordpress/eslint-plugin/recommended-with-formatting" 10 | ], 11 | "parserOptions": { 12 | "sourceType": "module" 13 | }, 14 | "rules": { 15 | "no-console": 1, 16 | "jsdoc/check-line-alignment": [ 17 | "warn", 18 | "never" 19 | ], 20 | "camelcase": [ 21 | "error", 22 | { 23 | "allow": [ 24 | "awooc_admin", 25 | "awooc_scripts_ajax", 26 | "awooc_scripts_translate", 27 | "awooc_scripts_settings", 28 | "wc_add_to_cart_variation_params" 29 | ] 30 | } 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.distignore: -------------------------------------------------------------------------------- 1 | # A set of files and directories you probably don't want in your WordPress.org distribution 2 | 3 | # Files 4 | /*.sql 5 | /*.tar.gz 6 | /*.zip 7 | /.DS_Store 8 | /.coveralls.yml 9 | /.distignore 10 | /.editorconfig 11 | /.eslintrc.json 12 | /.gitignore 13 | /.gitattributes 14 | /.phpcs.cache 15 | /.phpunit.result.cache 16 | /CHANGELOG.md 17 | /Makefile 18 | /README.md 19 | /Thumbs.db 20 | /auth.json 21 | /composer.json 22 | /composer.lock 23 | /package.json 24 | /package-lock.json 25 | /phpcs.xml 26 | /phpunit.xml 27 | /postcss.config.js 28 | /webpack.config.js 29 | /yarn.lock 30 | /.prettierrc.json 31 | /.eslintignore 32 | 33 | # Directories 34 | /.git 35 | /.github 36 | /.idea 37 | /.make 38 | /.wordpress-org 39 | /node_modules 40 | /src 41 | /tests 42 | -------------------------------------------------------------------------------- /src/js/public/components/Integrations/Woodmart.js: -------------------------------------------------------------------------------- 1 | export default class Woodmart { 2 | constructor( app ) { 3 | this.app = app; 4 | } 5 | 6 | init() { 7 | jQuery( document.body ).on( 'woodmart-quick-view-displayed', () => { 8 | const button = document.querySelector( '.product.quick-shop-loaded form.cart .awooc-button-js' ); 9 | 10 | if ( ! button ) { 11 | return; 12 | } 13 | 14 | const $product = jQuery( '.product.quick-shop-loaded' ); 15 | $product.find( '.variations_form' ) 16 | .on( 'hide_variation', () => this.app.buttons.toggleButtonClasses( [ button ], 'add', this.app.buttons.disableClasses ) ) 17 | .on( 'show_variation', ( event, variation, purchasable ) => this.app.buttons.updateButtonState( variation, purchasable, [ button ] ) ); 18 | } ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /templates/button.php: -------------------------------------------------------------------------------- 1 | 17 | 23 | 24 | get_front()->disable_loop(); 15 | ?> 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /templates/add-to-cart/loop/normal.php: -------------------------------------------------------------------------------- 1 | get_type() || class_exists( 'CFVSW\Plugin_Loader' ) ) : 25 | awooc_html_custom_add_to_cart(); 26 | endif; 27 | 28 | do_action( 'awooc_after_loop_add_to_cart_link', $product, $args ); 29 | -------------------------------------------------------------------------------- /classes/Prepare/Analytics.php: -------------------------------------------------------------------------------- 1 | $this->id(), 11 | 'title' => $this->title(), 12 | 'sku' => $this->sku(), 13 | 'price' => $this->price(), 14 | 'attr' => $this->attributes(), 15 | 'qty' => $this->get_qty(), 16 | 'category' => $this->product_category(), 17 | ]; 18 | 19 | if ( $this->main->get_mode()->is_mode_catalog() ) { 20 | unset( $data['qty'] ); 21 | } 22 | 23 | return $data; 24 | } 25 | 26 | 27 | /** 28 | * Получаем первый термин для аналитики 29 | * 30 | * @return bool|string 31 | * @since 3.0.0 32 | */ 33 | protected function product_category() { 34 | 35 | $terms = get_the_terms( $this->parent_id(), 'product_cat' ); 36 | 37 | if ( ! $terms || is_wp_error( $terms ) ) { 38 | return false; 39 | } 40 | 41 | return array_shift( $terms )->name; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/js/public/components/EventBus.js: -------------------------------------------------------------------------------- 1 | export default class EventBus { 2 | constructor() { 3 | this.events = {}; 4 | } 5 | 6 | on( event, callback ) { 7 | if ( ! this.events[ event ] ) { 8 | this.events[ event ] = []; 9 | } 10 | 11 | this.events[ event ].push( callback ); 12 | 13 | const listener = ( e ) => { 14 | this.events[ event ].forEach( ( cb ) => cb( e, e.detail ) ); 15 | }; 16 | 17 | this.events[ event ].listener = listener; 18 | 19 | document.addEventListener( event, listener ); 20 | } 21 | 22 | off( event, callback ) { 23 | if ( this.events[ event ] ) { 24 | this.events[ event ] = this.events[ event ].filter( ( cb ) => cb !== callback ); 25 | 26 | if ( ! this.events[ event ]?.length ) { 27 | document.removeEventListener( event, this.events[ event ].listener ); 28 | 29 | delete this.events[ event ].listener; 30 | } 31 | } 32 | } 33 | 34 | trigger( event, data = {} ) { 35 | const customEvent = new CustomEvent( event, { detail: data } ); 36 | 37 | document.dispatchEvent( customEvent ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /templates/add-to-cart/loop/preorder.php: -------------------------------------------------------------------------------- 1 | get_price() ) || ( $product->is_on_backorder( 1 ) ) || ! $product->is_in_stock() ) 23 | && ( 'variable' !== $product->get_type() || class_exists( 'CFVSW\Plugin_Loader' ) ) ) : 24 | awooc_html_custom_add_to_cart(); 25 | else : 26 | awooc_loop_add_to_cart_link( $product, $args ); 27 | endif; 28 | 29 | do_action( 'awooc_after_loop_add_to_cart_link', $product, $args ); 30 | -------------------------------------------------------------------------------- /templates/add-to-cart/loop/special.php: -------------------------------------------------------------------------------- 1 | get_type() || ( $product->is_in_stock() && $product->get_price() > 0 ) ) : 23 | awooc_loop_add_to_cart_link( $product, $args ); 24 | endif; 25 | 26 | if ( 'variable' !== $product->get_type() || class_exists( 'CFVSW\Plugin_Loader' ) ) : 27 | awooc_html_custom_add_to_cart(); 28 | endif; 29 | 30 | do_action( 'awooc_after_loop_add_to_cart_link', $product, $args ); 31 | -------------------------------------------------------------------------------- /templates/add-to-cart/loop/catalog.php: -------------------------------------------------------------------------------- 1 | get_type() ) : 25 | awooc_loop_add_to_cart_link( $product, $args ); 26 | else : 27 | awooc_html_custom_add_to_cart(); 28 | endif; 29 | 30 | do_action( 'awooc_after_loop_add_to_cart_link', $product, $args ); 31 | -------------------------------------------------------------------------------- /templates/add-to-cart/single/variable-catalog.php: -------------------------------------------------------------------------------- 1 | get_front()->disable_loop(); 17 | 18 | ?> 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "art/art-woocommerce-order-one-click", 3 | "description": "Plugin for WooCommerce. It includes the catalog mode in the store (there are no prices and the Buy button) and can turn on the Buy/Order button in one click. WooCommerce and Contact Form 7 are required for proper operation.", 4 | "license": "GPL-2.0", 5 | "keywords": [ 6 | "art", 7 | "woocommerce", 8 | "order" 9 | ], 10 | "homepage": "https://github.com/artikus11/art-woocommerce-order-one-click", 11 | "authors": [ 12 | { 13 | "name": "Artem Abramovich", 14 | "homepage": "https://wpruse.ru" 15 | } 16 | ], 17 | "autoload": { 18 | "psr-4": { 19 | "Art\\AWOOC\\": "classes/" 20 | } 21 | }, 22 | "require-dev": { 23 | "squizlabs/php_codesniffer": "^3.11", 24 | "wp-coding-standards/wpcs": "^3.1", 25 | "dealerdirect/phpcodesniffer-composer-installer": "^1.0", 26 | "phpcompatibility/phpcompatibility-wp": "*" 27 | }, 28 | "config": { 29 | "allow-plugins": { 30 | "dealerdirect/phpcodesniffer-composer-installer": true 31 | } 32 | }, 33 | "scripts": { 34 | "phpcs": "phpcs --standard=./phpcs.xml", 35 | "phpcbf": "phpcbf --standard=./phpcs.xml" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/scss/public/_preload.scss: -------------------------------------------------------------------------------- 1 | 2 | .awooc-preload-container * { 3 | box-sizing: border-box; 4 | } 5 | 6 | .awooc-preload-container { 7 | position: absolute !important; 8 | left: 0; 9 | right: 0; 10 | top: 0; 11 | bottom: 0; 12 | display: flex; 13 | align-items: center; 14 | } 15 | 16 | .awooc-ajax-loader { 17 | visibility: visible; 18 | display: inline-block; 19 | background-color: #23282d; /* Dark Gray 800 */ 20 | opacity: 0.75; 21 | width: 24px; 22 | height: 24px; 23 | border: none; 24 | border-radius: 100%; 25 | padding: 0; 26 | margin: 0 auto; 27 | position: relative; 28 | 29 | &:before { 30 | content: ''; 31 | position: absolute; 32 | background-color: #fbfbfc; /* Light Gray 100 */ 33 | top: 4px; 34 | left: 4px; 35 | width: 6px; 36 | height: 6px; 37 | border: none; 38 | border-radius: 100%; 39 | transform-origin: 8px 8px; 40 | animation-name: spin; 41 | animation-duration: 1000ms; 42 | animation-timing-function: linear; 43 | animation-iteration-count: infinite; 44 | 45 | } 46 | } 47 | 48 | 49 | @keyframes spin { 50 | from { 51 | transform: rotate(0deg); 52 | } 53 | 54 | to { 55 | transform: rotate(360deg); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/js/public/components/Popup.js: -------------------------------------------------------------------------------- 1 | import { settings } from '../config'; 2 | 3 | export default class Popup { 4 | constructor( app ) { 5 | this.app = app; 6 | this.bindEvent(); 7 | } 8 | 9 | bindEvent() { 10 | document.addEventListener( 'click', ( e ) => { 11 | this.unBlock( e ); 12 | } ); 13 | } 14 | 15 | showPopup( e ) { 16 | jQuery.blockUI( { 17 | message: settings.template, 18 | css: settings.popup.css, 19 | overlayCSS: settings.popup.overlay, 20 | fadeIn: settings.popup.fadeIn, 21 | fadeOut: settings.popup.fadeOut, 22 | focusInput: settings.popup.focusInput, 23 | bindEvents: false, 24 | timeout: 0, 25 | allowBodyStretch: true, 26 | centerX: true, 27 | centerY: true, 28 | blockMsgClass: 'blockMsg blockMsgAwooc', 29 | onBlock: () => { 30 | this.app.events.trigger( 'awooc_popup_open_trigger' ); 31 | this.app.request.sendRequest( e ); 32 | }, 33 | onUnblock: () => this.app.events.trigger( 'awooc_popup_close_trigger' ), 34 | onOverlayClick: () => document.documentElement.style.overflow = 'initial', 35 | } ); 36 | } 37 | 38 | unBlock( event ) { 39 | if ( event.target.classList.contains( 'awooc-close' ) || event.target.classList.contains( 'blockOverlay' ) ) { 40 | jQuery.unblockUI(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/js/admin/script.js: -------------------------------------------------------------------------------- 1 | jQuery( ( $ ) => { 2 | 'use strict'; 3 | /*global awooc_admin */ 4 | const AWOOCADMIN = { 5 | xhr: false, 6 | $selectMode: $( '#woocommerce_awooc_mode_catalog' ), 7 | analyticData: {}, 8 | 9 | init() { 10 | this.getDescription( this.$selectMode, this.$selectMode.val() ); 11 | 12 | $( document ).on( 'change', '#woocommerce_awooc_mode_catalog', ( event ) => { 13 | const selectedValue = $( event.target ).val(); 14 | this.getDescription( $( event.target ), selectedValue ); 15 | } ); 16 | }, 17 | // eslint-disable-next-line camelcase 18 | getDescription( $element, selectedValue ) { 19 | const $description = $element.closest( '.forminp-select' ).find( '.description' ); 20 | 21 | $description.css( { 22 | display: 'block', 23 | marginTop: '8px', 24 | maxWidth: '80%', 25 | } ); 26 | /* eslint-disable camelcase */ 27 | const descriptions = { 28 | dont_show_add_to_card: awooc_admin.mode_catalog, 29 | show_add_to_card: awooc_admin.mode_normal, 30 | in_stock_add_to_card: awooc_admin.mode_in_stock, 31 | no_stock_no_price: awooc_admin.mode_special, 32 | }; 33 | /* eslint-enable camelcase */ 34 | $description.text( descriptions[ selectedValue ] || '' ); 35 | }, 36 | }; 37 | 38 | AWOOCADMIN.init(); 39 | } ); 40 | -------------------------------------------------------------------------------- /classes/Templater.php: -------------------------------------------------------------------------------- 1 | template_path() . $template_name ); 22 | 23 | if ( ! $template_path ) { 24 | $template_path = sprintf( '%s/templates/%s', $this->plugin_path(), $template_name ); 25 | } 26 | 27 | return apply_filters( 'awooc_locate_template', $template_path ); 28 | } 29 | 30 | 31 | /** 32 | * @return string 33 | */ 34 | public function template_path(): string { 35 | 36 | return apply_filters( 'awooc_template_path', 'art-woocommerce-order-one-click/' ); 37 | } 38 | 39 | 40 | /** 41 | * @return string 42 | */ 43 | public function plugin_path(): string { 44 | 45 | return untrailingslashit( AWOOC_PLUGIN_DIR ); 46 | } 47 | 48 | 49 | /** 50 | * @return string 51 | */ 52 | public function plugin_url(): string { 53 | 54 | return untrailingslashit( plugins_url( '/', AWOOC_PLUGIN_FILE ) ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /.github/workflows/deploy-to-wp-org.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to WordPress.org 2 | on: 3 | workflow_run: 4 | workflows: ["Create release"] 5 | types: 6 | - completed 7 | 8 | jobs: 9 | tag: 10 | name: New release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 # Нужно для получения информации о тегах 17 | 18 | - name: Get version from Git tag 19 | id: get_version 20 | run: | 21 | # Получаем последний тег 22 | echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV 23 | 24 | - name: Webpack Build 25 | run: | 26 | npm install 27 | npm run build 28 | 29 | - name: Composer Autoloader 30 | run: | 31 | composer install --no-dev --prefer-dist --no-progress 32 | composer dump-autoload -o 33 | 34 | - name: WordPress Plugin Deploy 35 | id: deploy 36 | uses: 10up/action-wordpress-plugin-deploy@stable 37 | with: 38 | generate-zip: true 39 | env: 40 | SVN_USERNAME: ${{ secrets.SVN_USERNAME }} 41 | SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} 42 | SLUG: art-woocommerce-order-one-click 43 | VERSION: ${{ env.VERSION }} # Используем автоматически полученный тег -------------------------------------------------------------------------------- /classes/Mode.php: -------------------------------------------------------------------------------- 1 | get_mode_value(); 10 | } 11 | 12 | 13 | public function is_mode_catalog(): bool { 14 | 15 | return 'dont_show_add_to_card' === $this->get_mode_value(); 16 | } 17 | 18 | 19 | public function is_mode_preorder(): bool { 20 | 21 | return 'in_stock_add_to_card' === $this->get_mode_value(); 22 | } 23 | 24 | 25 | public function is_mode_normal(): bool { 26 | 27 | return 'show_add_to_card' === $this->get_mode_value(); 28 | } 29 | 30 | 31 | /** 32 | * Получение режима работы 33 | * 34 | * 'dont_show_add_to_card' => __( 'Catalog mode', 'art-woocommerce-order-one-click' ) 35 | * 'show_add_to_card' => __( 'Normal mode', 'art-woocommerce-order-one-click' ) 36 | * 'in_stock_add_to_card' => __( 'Pre-order mode', 'art-woocommerce-order-one-click' ) 37 | * 'no_stock_no_price' => __( 'Special mode', 'art-woocommerce-order-one-click' ) 38 | * 39 | * @return false|mixed|void 40 | * @since 3.0.0 41 | */ 42 | public function get_mode_value() { 43 | 44 | return get_option( 'woocommerce_awooc_mode_catalog' ); 45 | } 46 | 47 | 48 | public function get_modes(): array { 49 | 50 | return [ 51 | 'dont_show_add_to_card' => 'catalog', 52 | 'show_add_to_card' => 'normal', 53 | 'in_stock_add_to_card' => 'preorder', 54 | 'no_stock_no_price' => 'special', 55 | ]; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /classes/Uninstall.php: -------------------------------------------------------------------------------- 1 | query( 40 | $wpdb->prepare( 41 | "DELETE FROM $wpdb->options WHERE option_name LIKE %s", 42 | sprintf( '%s%s', $wpdb->esc_like( 'woocommerce_awooc_' ), '%' ) 43 | ) 44 | ); 45 | } 46 | 47 | 48 | public static function remove_post_meta(): void { 49 | 50 | global $wpdb; 51 | 52 | //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 53 | $wpdb->delete( $wpdb->postmeta, [ 'meta_key' => '_awooc_button' ], [ '%s' ] ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/scss/admin-style.scss: -------------------------------------------------------------------------------- 1 | .awooc-notice, 2 | .awooc-error, 3 | .awooc-updated { 4 | background: #fff; 5 | border-left: 4px solid #fff; 6 | box-shadow: 0 1px 1px 0 rgba(0, 0, 0, .1); 7 | margin: 5px 0 2px; 8 | padding: 1px 12px; 9 | } 10 | 11 | .notice-success { 12 | border-left-color: #018574; 13 | } 14 | 15 | .notice-warning { 16 | border-left-color: #FF8C00 17 | } 18 | 19 | .notice-error { 20 | border-left-color: #E81123 21 | } 22 | 23 | .notice-info { 24 | border-left-color: #0078D7 25 | } 26 | 27 | .awooc-row { 28 | width: 100%; 29 | max-width: 800px; 30 | display: flex; 31 | 32 | &:nth-of-type(5) .awooc-column:first-of-type { 33 | flex-grow: 2; 34 | flex-shrink: 2; 35 | flex-basis: 22px; 36 | } 37 | 38 | &:nth-of-type(6) .awooc-column:nth-of-type(2) { 39 | flex-grow: 4; 40 | flex-shrink: 4; 41 | flex-basis: 66px; 42 | } 43 | } 44 | 45 | .awooc-column { 46 | margin-right: 10px; 47 | flex-grow: 1; 48 | flex-shrink: 1; 49 | flex-basis: 0; 50 | 51 | label { 52 | line-height: 1.2; 53 | display: inherit; 54 | } 55 | 56 | input[type=text] { 57 | margin-left: 0; 58 | } 59 | 60 | &:first-child { 61 | margin-left: 0; 62 | } 63 | } 64 | 65 | 66 | .woocommerce { 67 | #poststuff { 68 | h2 { 69 | font-size: 1.3em; 70 | padding: 0; 71 | margin: 1em 0; 72 | line-height: inherit; 73 | 74 | span { 75 | font-size: 14px; 76 | padding: 0 12px; 77 | margin: 0; 78 | line-height: 1.4; 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /includes/helpers.php: -------------------------------------------------------------------------------- 1 | ', 69 | esc_html( AWOOC_PLUGIN_VER ), 70 | esc_html( awooc()->get_modes()[ get_option( 'woocommerce_awooc_mode_catalog' ) ] ) 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/js/public/components/RequestProcessing/DataCollector.js: -------------------------------------------------------------------------------- 1 | import { ajax } from '../../config'; 2 | 3 | export default class DataCollector { 4 | constructor( request ) { 5 | this.request = request; 6 | this.defaultAttributes = {}; 7 | } 8 | 9 | collectData( event ) { 10 | const data = this.getBaseData( event ); 11 | 12 | this.setAttributesOnCatalog( event, data ); 13 | this.serializeFormData( event, data ); 14 | 15 | return data; 16 | } 17 | 18 | getBaseData( event ) { 19 | return { 20 | id: this.request.getProductID( event ), 21 | action: 'awooc_ajax_product_form', 22 | nonce: ajax.nonce, 23 | attributes: { ...this.defaultAttributes }, 24 | }; 25 | } 26 | 27 | setAttributesOnCatalog( event, data ) { 28 | data.attributes = { ...this.defaultAttributes }; 29 | const { selectedVariant } = event.target.dataset; 30 | 31 | if ( selectedVariant ) { 32 | data.attributes = JSON.parse( selectedVariant ); 33 | } 34 | } 35 | 36 | serializeFormData( event, data ) { 37 | const form = event.target.closest( '.cart' ); 38 | 39 | if ( ! form ) { 40 | return; 41 | } 42 | 43 | data.attributes = { ...this.defaultAttributes }; 44 | 45 | this.processFormData( new FormData( form ), data ); 46 | } 47 | 48 | processFormData( formData, data ) { 49 | formData.forEach( ( value, name ) => { 50 | this.updateDataField( data, name, value ); 51 | 52 | if ( name.startsWith( 'attribute_' ) ) { 53 | this.updateDataField( data.attributes, name, value ); 54 | } 55 | } ); 56 | 57 | delete data[ 'add-to-cart' ]; 58 | } 59 | 60 | updateDataField( dataObject, name, value ) { 61 | dataObject[ name ] = dataObject[ name ] 62 | ? [].concat( dataObject[ name ], value ) 63 | : value; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/js/public/AppCore.js: -------------------------------------------------------------------------------- 1 | import EventBus from './components/EventBus'; 2 | import Popup from './components/Popup'; 3 | import Buttons from './components/Buttons'; 4 | import Request from './components/Request'; 5 | import Form from './components/Form'; 6 | import VariationSwatchesByCartFlows from './components/Integrations/VariationSwatchesByCartFlows'; 7 | import Woodmart from './components/Integrations/Woodmart'; 8 | 9 | export default class AppCore { 10 | constructor( $ ) { 11 | if ( ! this.validateGlobals() ) { 12 | return; 13 | } 14 | 15 | this.$ = $; 16 | this.xhr = false; 17 | this.analyticData = {}; 18 | 19 | this.events = new EventBus(); 20 | 21 | this.popup = new Popup( this ); 22 | this.buttons = new Buttons( this ); 23 | this.request = new Request( this ); 24 | this.form = new Form( this ); 25 | 26 | this.variationSwatches = new VariationSwatchesByCartFlows( this ); 27 | this.woodmart = new Woodmart( this ); 28 | 29 | this.init(); 30 | } 31 | 32 | init() { 33 | this.buttons.init(); 34 | this.form.init(); 35 | this.variationSwatches.init(); 36 | this.woodmart.init(); 37 | } 38 | 39 | validateGlobals() { 40 | const globals = { 41 | awooc_scripts_ajax: 'awooc_scripts_ajax not found', 42 | awooc_scripts_translate: 'awooc_scripts_translate not found', 43 | awooc_scripts_settings: 'awooc_scripts_settings not found', 44 | wpcf7: 'На странице не существует объекта wpcf7. Что-то не так с темой...', 45 | }; 46 | 47 | for ( const key in globals ) { 48 | if ( typeof window[ key ] === 'undefined' || window[ key ] === null ) { 49 | // eslint-disable-next-line no-console 50 | console.warn( globals[ key ] ); 51 | return false; 52 | } 53 | } 54 | 55 | return true; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /templates/add-to-cart/single/variable.php: -------------------------------------------------------------------------------- 1 | 14 |
15 | 16 | 17 | apply_filters( 'woocommerce_quantity_input_min', $product->get_min_purchase_quantity(), $product ), 23 | 'max_value' => apply_filters( 'woocommerce_quantity_input_max', $product->get_max_purchase_quantity(), $product ), 24 | //phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 25 | 'input_value' => isset( $_POST['quantity'] ) ? wc_stock_amount( wp_unslash( $_POST['quantity'] ) ) : $product->get_min_purchase_quantity(), 26 | 27 | ] 28 | ); 29 | 30 | do_action( 'woocommerce_after_add_to_cart_quantity' ); 31 | ?> 32 | 33 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
45 | -------------------------------------------------------------------------------- /templates/popup.php: -------------------------------------------------------------------------------- 1 | 26 | 27 |
28 |
×
29 |
30 | 40 |
41 |
42 | 43 | 57 | 58 |
59 |
60 | 61 | 69 | 70 |
71 |
72 | 80 | 81 |
82 |
83 | -------------------------------------------------------------------------------- /templates/add-to-cart/single/variable-normal.php: -------------------------------------------------------------------------------- 1 | get_front()->disable_loop(); 17 | 18 | ?> 19 |
20 | 21 | 22 | apply_filters( 'woocommerce_quantity_input_min', $product->get_min_purchase_quantity(), $product ), 28 | 'max_value' => apply_filters( 'woocommerce_quantity_input_max', $product->get_max_purchase_quantity(), $product ), 29 | //phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 30 | 'input_value' => isset( $_POST['quantity'] ) ? wc_stock_amount( wp_unslash( $_POST['quantity'] ) ) : $product->get_min_purchase_quantity(), 31 | 32 | ] 33 | ); 34 | 35 | do_action( 'woocommerce_after_add_to_cart_quantity' ); 36 | ?> 37 | 38 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 | -------------------------------------------------------------------------------- /templates/add-to-cart/single/variable-special.php: -------------------------------------------------------------------------------- 1 | get_front()->disable_loop(); 17 | 18 | ?> 19 |
20 | 21 | 22 | apply_filters( 'woocommerce_quantity_input_min', $product->get_min_purchase_quantity(), $product ), 28 | 'max_value' => apply_filters( 'woocommerce_quantity_input_max', $product->get_max_purchase_quantity(), $product ), 29 | //phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 30 | 'input_value' => isset( $_POST['quantity'] ) ? wc_stock_amount( wp_unslash( $_POST['quantity'] ) ) : $product->get_min_purchase_quantity(), 31 | 32 | ] 33 | ); 34 | 35 | do_action( 'woocommerce_after_add_to_cart_quantity' ); 36 | ?> 37 | 38 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 | -------------------------------------------------------------------------------- /templates/add-to-cart/single/variable-preorder.php: -------------------------------------------------------------------------------- 1 | get_front()->disable_loop(); 17 | 18 | ?> 19 |
20 | 21 | 22 | apply_filters( 'woocommerce_quantity_input_min', $product->get_min_purchase_quantity(), $product ), 28 | 'max_value' => apply_filters( 'woocommerce_quantity_input_max', $product->get_max_purchase_quantity(), $product ), 29 | //phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 30 | 'input_value' => isset( $_POST['quantity'] ) ? wc_stock_amount( wp_unslash( $_POST['quantity'] ) ) : $product->get_min_purchase_quantity(), 31 | 32 | ] 33 | ); 34 | 35 | do_action( 'woocommerce_after_add_to_cart_quantity' ); 36 | ?> 37 | 38 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 | -------------------------------------------------------------------------------- /classes/Product/Meta.php: -------------------------------------------------------------------------------- 1 | '_awooc_button', 46 | 'wrapper_class' => 'show_if_simple show_if_variable', 47 | 'label' => __( 'Disable Order One Click Button', 'art-woocommerce-order-one-click' ), 48 | 'description' => __( 49 | 'If enabled, then on this product the Order button will not be visible. Product will return to its original condition.', 50 | 'art-woocommerce-order-one-click' 51 | ), 52 | 'default' => 'no', 53 | ]; 54 | 55 | return array_slice( $options, 0, 0 ) + $new_option + $options; 56 | } 57 | 58 | 59 | /** 60 | * Сохраняем данные 61 | * 62 | * @param int $post_id ID продукта. 63 | * 64 | * @since 2.3.0 65 | */ 66 | public static function save_meta_box( int $post_id ): void { 67 | 68 | $product = wc_get_product( $post_id ); 69 | 70 | // @codingStandardsIgnoreStart 71 | $button = isset( $_POST['_awooc_button'] ) ? 'yes' : 'no'; 72 | // @codingStandardsIgnoreEnd 73 | 74 | $product->update_meta_data( '_awooc_button', $button ); 75 | 76 | $product->save(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /templates/add-to-cart/single/simple-normal.php: -------------------------------------------------------------------------------- 1 | is_purchasable() ) { 17 | return; 18 | } 19 | 20 | //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 21 | echo wc_get_stock_html( $product ); 22 | 23 | if ( $product->is_in_stock() ) : ?> 24 | 25 | 26 | 27 |
32 | 33 | 34 | 35 | apply_filters( 'woocommerce_quantity_input_min', $product->get_min_purchase_quantity(), $product ), 41 | 'max_value' => apply_filters( 'woocommerce_quantity_input_max', $product->get_max_purchase_quantity(), $product ), 42 | //phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 43 | 'input_value' => isset( $_POST['quantity'] ) ? wc_stock_amount( wp_unslash( $_POST['quantity'] ) ) : $product->get_min_purchase_quantity(), 44 | 45 | ] 46 | ); 47 | 48 | do_action( 'woocommerce_after_add_to_cart_quantity' ); 49 | ?> 50 | 51 | 58 | 59 | 60 | 61 | 62 |
63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /templates/add-to-cart/single/simple-special.php: -------------------------------------------------------------------------------- 1 | get_front()->disable_loop(); 21 | 22 | ?> 23 | 24 | 25 | 26 |
31 | 32 | 33 | 34 | apply_filters( 'woocommerce_quantity_input_min', $product->get_min_purchase_quantity(), $product ), 40 | 'max_value' => apply_filters( 'woocommerce_quantity_input_max', $product->get_max_purchase_quantity(), $product ), 41 | //phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 42 | 'input_value' => isset( $_POST['quantity'] ) ? wc_stock_amount( wp_unslash( $_POST['quantity'] ) ) : $product->get_min_purchase_quantity(), 43 | 44 | ] 45 | ); 46 | 47 | do_action( 'woocommerce_after_add_to_cart_quantity' ); 48 | ?> 49 | 50 | is_in_stock() && $product->get_price() > 0 ) : ?> 51 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /templates/add-to-cart/single/simple-preorder.php: -------------------------------------------------------------------------------- 1 | get_front()->disable_loop(); 17 | 18 | //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 19 | echo wc_get_stock_html( $product ); 20 | 21 | ?> 22 | 23 | 24 | 25 |
30 | 31 | 32 | 33 | apply_filters( 'woocommerce_quantity_input_min', $product->get_min_purchase_quantity(), $product ), 39 | 'max_value' => apply_filters( 'woocommerce_quantity_input_max', $product->get_max_purchase_quantity(), $product ), 40 | //phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 41 | 'input_value' => isset( $_POST['quantity'] ) ? wc_stock_amount( wp_unslash( $_POST['quantity'] ) ) : $product->get_min_purchase_quantity(), 42 | 43 | ] 44 | ); 45 | 46 | do_action( 'woocommerce_after_add_to_cart_quantity' ); 47 | ?> 48 | 49 | get_price() ) || ( $product->is_on_backorder( 1 ) ) || ! $product->is_in_stock() ) : ?> 50 | 51 | 52 | 53 | 54 | 61 | 62 | 63 | 64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "art-woocommerce-order-one-click", 3 | "version": "3.0.0", 4 | "description": "Плагин под WooCommerce. Включает четыре режима: каталог, заказать, предзаказ и специальный режим работы с отсутвием цен и запасов", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "cross-env NODE_ENV=development webpack --watch --mode development --progress", 8 | "watch": "cross-env NODE_ENV=development webpack --watch --progress", 9 | "start:open": "cross-env NODE_ENV=development webpack-dev-server --open", 10 | "build": "cross-env NODE_ENV=production webpack --mode production", 11 | "build:unminification": "cross-env NODE_ENV=development webpack", 12 | "build:custom": "wp-scripts build woocommerce.js main.js", 13 | "start": "wp-scripts start", 14 | "lint:js": "wp-scripts lint-js ./src", 15 | "sniff": "npm run lint:scss && npm run lint:js" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/artikus11/art-woocommerce-order-one-click.git" 20 | }, 21 | "keywords": [], 22 | "author": "", 23 | "license": "ISC", 24 | "bugs": { 25 | "url": "https://github.com/artikus11/art-woocommerce-order-one-click/issues" 26 | }, 27 | "homepage": "https://github.com/artikus11/art-woocommerce-order-one-click#readme", 28 | "devDependencies": { 29 | "@babel/core": "^7.26.0", 30 | "@babel/preset-env": "^7.26.0", 31 | "@wordpress/babel-preset-default": "^8.16.0", 32 | "@wordpress/eslint-plugin": "^22.2.0", 33 | "@wordpress/scripts": "^30.9.0", 34 | "autoprefixer": "^10.4.2", 35 | "babel-loader": "^9.2.1", 36 | "copy-webpack-plugin": "^10.2.1", 37 | "cross-env": "^7.0.3", 38 | "css-loader": "^6.5.1", 39 | "css-minimizer-webpack-plugin": "^3.4.1", 40 | "css-mqpacker": "^7.0.0", 41 | "cssnano": "^5.0.16", 42 | "eslint-config-prettier": "^9.1.0", 43 | "eslint-plugin-prettier": "^5.2.3", 44 | "glob": "^7.2.0", 45 | "mini-css-extract-plugin": "^2.5.3", 46 | "postcss": "^8.4.5", 47 | "postcss-cli": "^9.1.0", 48 | "postcss-loader": "^6.2.1", 49 | "postcss-preset-env": "^7.2.3", 50 | "prettier": "^3.4.2", 51 | "sass": "^1.49.0", 52 | "sass-loader": "^12.4.0", 53 | "style-loader": "^3.3.1", 54 | "terser-webpack-plugin": "^5.3.0", 55 | "thread-loader": "^3.0.4", 56 | "unminified-webpack-plugin": "^3.0.0", 57 | "webpack": "^5.67.0", 58 | "webpack-cli": "^4.9.2" 59 | }, 60 | "dependencies": { 61 | "webpack-remove-empty-scripts": "^1.0.4" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /.github/workflows/create-release.yml: -------------------------------------------------------------------------------- 1 | name: Create release 2 | 3 | on: 4 | push: 5 | branches: 6 | - dev 7 | - master 8 | 9 | jobs: 10 | create-release: 11 | name: Create release 12 | runs-on: "ubuntu-latest" 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | - name: Webpack Build 17 | run: | 18 | npm install 19 | npm run build 20 | 21 | - name: Composer Autoloader 22 | run: | 23 | composer install --no-dev --prefer-dist --no-progress 24 | composer dump-autoload -o 25 | 26 | - name: Create deployment artifact 27 | uses: thedoctor0/zip-release@master 28 | with: 29 | type: 'zip' 30 | filename: ${{ github.event.repository.name }}.zip 31 | exclusions: '*.git* /*node_modules/* .editorconfig /*src/* /*.wordpress-org/* .eslintrc.json .prettierrc.json gulpfile.js .stylelintrc.json package.json package-lock.json CHANGELOG.md README.md .gitattributes .gitignore .distignore .eslintignore .phpcs.xml phpcs.xml composer.json composer.lock postcss.config.js webpack.config.js assets/js/*.LICENSE.txt' 32 | 33 | - name: Store artifact for distribution 34 | uses: actions/upload-artifact@v4 35 | with: 36 | name: ${{ github.event.repository.name }} 37 | path: ${{ github.event.repository.name }}.zip 38 | overwrite: true 39 | 40 | - name: Download artifact 41 | uses: actions/download-artifact@v4 42 | with: 43 | name: ${{ github.event.repository.name }} 44 | merge-multiple: true 45 | 46 | - name: Get Changelog Entry 47 | id: changelog-reader 48 | uses: mindsers/changelog-reader-action@v2 49 | with: 50 | path: CHANGELOG.md 51 | 52 | - name: Create Release 53 | id: create-release 54 | uses: marvinpinto/action-automatic-releases@latest 55 | with: 56 | repo_token: ${{ secrets.GITHUB_TOKEN }} 57 | prerelease: false 58 | automatic_release_tag: ${{ steps.changelog-reader.outputs.version }} 59 | 60 | - name: Upload release assets 61 | id: upload-release-asset 62 | uses: actions/upload-release-asset@v1 63 | env: 64 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 65 | with: 66 | upload_url: ${{ steps.create-release.outputs.upload_url }} 67 | asset_path: ${{ github.event.repository.name }}.zip 68 | asset_name: ${{ github.event.repository.name }}.zip 69 | asset_content_type: application/zip -------------------------------------------------------------------------------- /src/js/public/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Выводим предупреждение об измении обработки события только в момент срабатывания события 3 | * @param {jQuery} $ - Объект jQuery. 4 | */ 5 | export function setupAwoocEventHandling( $ ) { 6 | const EVENT_PREFIX = 'awooc_'; 7 | const originalOn = $.fn.on; 8 | const originalTrigger = $.fn.trigger; 9 | 10 | // Патчим $.fn.on 11 | $.fn.on = function( event, selector, data, handler ) { 12 | // Нормализация аргументов 13 | if ( typeof selector === 'function' ) { 14 | handler = selector; 15 | selector = undefined; 16 | data = undefined; 17 | } else if ( typeof data === 'function' ) { 18 | handler = data; 19 | data = undefined; 20 | } 21 | 22 | // Если событие начинается с префикса "awooc_", добавляем обертку для обработчика 23 | if ( typeof event === 'string' && event.startsWith( EVENT_PREFIX ) && handler ) { 24 | const wrappedHandler = function( e ) { 25 | // Выводим предупреждение, если обработчик ожидает второй аргумент (data) 26 | if ( handler.length > 1 ) { 27 | // eslint-disable-next-line no-console 28 | console.warn( 29 | `[WARNING] '${ event }' передает данные через event.detail, но обработчик ожидает data. Используйте event.detail вместо второго аргумента.`, 30 | ); 31 | } 32 | // Вызываем оригинальный обработчик 33 | handler.call( this, e, e.detail ); 34 | }; 35 | return originalOn.call( this, event, selector, data, wrappedHandler ); 36 | } 37 | 38 | // Возвращаем оригинальный метод для других событий 39 | return originalOn.apply( this, arguments ); 40 | }; 41 | 42 | // Патчим $.fn.trigger 43 | $.fn.trigger = function( event, data ) { 44 | // Если событие начинается с префикса "awooc_", используем CustomEvent 45 | if ( typeof event === 'string' && event.startsWith( EVENT_PREFIX ) ) { 46 | const customEvent = new CustomEvent( event, { detail: data } ); 47 | document.dispatchEvent( customEvent ); 48 | return this; // Сохраняем цепочку вызовов jQuery 49 | } 50 | 51 | // Возвращаем оригинальный метод для других событий 52 | return originalTrigger.apply( this, arguments ); 53 | }; 54 | 55 | // Подписываемся на все события с префиксом "awooc_" 56 | document.addEventListener( 57 | EVENT_PREFIX, // Используем префикс как тип события 58 | function( event ) { 59 | // Проверяем, начинается ли тип события с префикса 60 | if ( event.type.startsWith( EVENT_PREFIX ) ) { 61 | // Передаем событие в jQuery 62 | $( document ).triggerHandler( event.type, event.detail ); 63 | } 64 | }, 65 | true, // Используем capture: true для перехвата событий на этапе захвата 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /templates/add-to-cart/single/simple.php: -------------------------------------------------------------------------------- 1 | is_purchasable() ) { 26 | return; 27 | } 28 | 29 | //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 30 | echo wc_get_stock_html( $product ); 31 | 32 | if ( $product->is_in_stock() ) : ?> 33 | 34 | 35 | 36 |
37 | 38 | 39 | apply_filters( 'woocommerce_quantity_input_min', $product->get_min_purchase_quantity(), $product ), 45 | 'max_value' => apply_filters( 'woocommerce_quantity_input_max', $product->get_max_purchase_quantity(), $product ), 46 | //phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 47 | 'input_value' => isset( $_POST['quantity'] ) ? wc_stock_amount( wp_unslash( $_POST['quantity'] ) ) : $product->get_min_purchase_quantity(), 48 | 49 | ] 50 | ); 51 | 52 | do_action( 'woocommerce_after_add_to_cart_quantity' ); 53 | ?> 54 | 55 | 62 | 63 | 64 | 65 |
66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/scss/public/_skeleton.scss: -------------------------------------------------------------------------------- 1 | .skeleton-loader { 2 | &:empty { 3 | width: 100%; 4 | height: 1em; 5 | display: block; 6 | border-radius: 3px; 7 | background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.5) 50%, rgba(255, 255, 255, 0) 80%), #eaeaea; 8 | background-repeat: repeat-y; 9 | background-size: 50px 500px; 10 | background-position: 0 0; 11 | animation: shine 3s infinite; 12 | 13 | } 14 | } 15 | 16 | .awooc-popup-image { 17 | &.skeleton-loader { 18 | &:empty { 19 | height: 230px; 20 | margin-bottom: 8px; 21 | } 22 | } 23 | } 24 | 25 | .awooc-popup-attr, 26 | .awooc-popup-qty, 27 | .awooc-popup-sku, 28 | .awooc-popup-sum, 29 | .awooc-popup-price { 30 | &.skeleton-loader { 31 | &:empty { 32 | height: 18px; 33 | margin-bottom: 8px; 34 | } 35 | } 36 | } 37 | 38 | .awooc-popup-price { 39 | &.skeleton-loader { 40 | &:empty { 41 | width: 30%; 42 | } 43 | } 44 | } 45 | 46 | .awooc-popup-sum { 47 | &.skeleton-loader { 48 | &:empty { 49 | width: 35%; 50 | } 51 | } 52 | } 53 | 54 | .awooc-popup-sku { 55 | &.skeleton-loader { 56 | &:empty { 57 | width: 25%; 58 | } 59 | } 60 | } 61 | 62 | .awooc-popup-qty { 63 | &.skeleton-loader { 64 | &:empty { 65 | width: 10%; 66 | } 67 | } 68 | } 69 | 70 | .awooc-popup-attr { 71 | &.skeleton-loader { 72 | &:empty { 73 | width: 50%; 74 | } 75 | } 76 | } 77 | 78 | @keyframes shine { 79 | to { 80 | background-position: 350% 0; 81 | } 82 | } 83 | 84 | .awooc-popup-form { 85 | &.skeleton-loader { 86 | &:empty { 87 | height: 300px; 88 | animation: loading 3s infinite; 89 | background-repeat: no-repeat; 90 | background-image: linear-gradient(#f9f9f9 100%, transparent 0), 91 | linear-gradient(#f9f9f9 100%, transparent 0), 92 | linear-gradient(#f9f9f9 100%, transparent 0), 93 | linear-gradient(#f3f3f3 100%, transparent 0), 94 | linear-gradient(to right, rgba(255, 255, 255, 0) -40%, rgba(255, 255, 255, 0.5) 45%, rgba(255, 255, 255, 0) 100%), 95 | linear-gradient(#eaeaea 100%, transparent 0); 96 | background-size: calc(100% - 3rem) 2.6rem, 97 | calc(100% - 3rem) 2.6rem, 98 | calc(100% - 3rem) 2.6rem, 99 | calc(100% - 3rem) 2.6rem, 100 | 30px, 300px, 101 | 100% 100%; 102 | background-position: 1.5rem 32px, 103 | 1.5rem 92px, 104 | 1.5rem 152px, 105 | 1.5rem 225px, 106 | -150% 0, 107 | 0 0; 108 | } 109 | 110 | 111 | } 112 | } 113 | 114 | 115 | @keyframes loading { 116 | to { 117 | background-position: 1.5rem 32px, 118 | 1.5rem 92px, 119 | 1.5rem 152px, 120 | 1.5rem 225px, 121 | 350% 0, 122 | 0 0; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /templates/quantity-input.php: -------------------------------------------------------------------------------- 1 | 44 |
45 | 46 | 47 | 50 | id="" 51 | class="" 52 | name="" 53 | value="" 54 | aria-label="" 55 | 56 | size="4" 57 | 58 | min="" 59 | max="" 60 | 61 | step="" 62 | placeholder="" 63 | inputmode="" 64 | autocomplete="" 65 | 66 | /> 67 | 68 |
69 | $this->caption_id(), 11 | 'title' => $this->caption_title(), 12 | 'sku' => $this->caption_sku(), 13 | 'price' => $this->caption_price(), 14 | 'attr' => $this->caption_attributes(), 15 | 'qty' => $this->caption_qty(), 16 | 'sum' => $this->caption_amount(), 17 | 'categories' => $this->formatted_category_list(), 18 | 'link' => $this->caption_link(), 19 | ]; 20 | 21 | if ( $this->main->get_mode()->is_mode_catalog() ) { 22 | unset( $data['qty'] ); 23 | } 24 | 25 | return $data; 26 | } 27 | 28 | 29 | protected function caption_title(): string { 30 | 31 | return sprintf( '%s%s', __( 'Title: ', 'art-woocommerce-order-one-click' ), $this->title() ); 32 | } 33 | 34 | 35 | protected function caption_id(): string { 36 | 37 | return sprintf( 38 | 'ID: %s', 39 | $this->id() 40 | ); 41 | } 42 | 43 | 44 | protected function caption_price(): string { 45 | 46 | return sprintf( 47 | '%s%s', 48 | __( 'Price: ', 'art-woocommerce-order-one-click' ), 49 | wp_filter_nohtml_kses( wc_price( $this->price() ) ) 50 | ); 51 | } 52 | 53 | 54 | protected function caption_sku(): string { 55 | 56 | return sprintf( 57 | '%s%s', 58 | __( 'SKU: ', 'art-woocommerce-order-one-click' ), 59 | $this->sku() 60 | ); 61 | } 62 | 63 | 64 | protected function caption_attributes(): string { 65 | 66 | return sprintf( 67 | '%s%s', 68 | __( 'Attributes: ', 'art-woocommerce-order-one-click' ), 69 | $this->attributes() 70 | ); 71 | } 72 | 73 | 74 | protected function caption_qty(): string { 75 | 76 | return sprintf( 77 | '%s%s', 78 | __( 'Quantity: ', 'art-woocommerce-order-one-click' ), 79 | $this->get_qty() 80 | ); 81 | } 82 | 83 | 84 | protected function caption_amount(): string { 85 | 86 | return sprintf( 87 | '%s%s', 88 | __( 'Amount: ', 'art-woocommerce-order-one-click' ), 89 | wp_filter_nohtml_kses( wc_price( $this->get_sum() ) ) 90 | ); 91 | } 92 | 93 | 94 | /** 95 | * Получаем ссылку на товар 96 | * 97 | * @return string 98 | * @since 3.0.0 99 | */ 100 | protected function caption_link(): string { 101 | 102 | return sprintf( 103 | '%s%s', 104 | __( 'Link to the product: ', 'art-woocommerce-order-one-click' ), 105 | $this->link() 106 | ); 107 | } 108 | 109 | 110 | /** 111 | * Форматированные категории товара 112 | * 113 | * @return string 114 | * @since 3.0.0 115 | */ 116 | protected function formatted_category_list(): string { 117 | 118 | $categories_list = $this->category_list(); 119 | 120 | return sprintf( 121 | '%s%s', 122 | _n( 123 | 'Category: ', 'Categories: ', 124 | count( $categories_list ), 125 | 'art-woocommerce-order-one-click' 126 | ), 127 | implode( ', ', $categories_list ) 128 | ); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /art-woo-order-one-click.php: -------------------------------------------------------------------------------- 1 | . 38 | */ 39 | 40 | if ( ! defined( 'ABSPATH' ) ) { 41 | exit; 42 | } 43 | 44 | $plugin_data = get_file_data( 45 | __FILE__, 46 | [ 47 | 'ver' => 'Version', 48 | 'name' => 'Plugin Name', 49 | ] 50 | ); 51 | 52 | const AWOOC_PLUGIN_DIR = __DIR__; 53 | define( 'AWOOC_PLUGIN_URI', plugin_dir_url( __FILE__ ) ); 54 | define( 'AWOOC_PLUGIN_FILE', plugin_basename( __FILE__ ) ); 55 | 56 | define( 'AWOOC_PLUGIN_VER', $plugin_data['ver'] ); 57 | define( 'AWOOC_PLUGIN_NAME', $plugin_data['name'] ); 58 | 59 | require AWOOC_PLUGIN_DIR . '/vendor/autoload.php'; 60 | require_once AWOOC_PLUGIN_DIR . '/includes/create-cf7-field.php'; 61 | require_once AWOOC_PLUGIN_DIR . '/includes/helpers.php'; 62 | require_once AWOOC_PLUGIN_DIR . '/includes/template-functions.php'; 63 | 64 | register_uninstall_hook( __FILE__, [ Art\AWOOC\Uninstall::class, 'uninstall' ] ); 65 | register_activation_hook( __FILE__, [ Art\AWOOC\Admin\Create_Form::class, 'install_form' ] ); 66 | 67 | if ( ! function_exists( 'awooc' ) ) { 68 | /** 69 | * The main function responsible for returning the AWOOC object. 70 | * 71 | * Use this function like you would a global variable, except without needing to declare the global. 72 | * 73 | * Example: method_name(); ?> 74 | * 75 | * @return object AWOOC class object. 76 | * @since 1.0.0 77 | */ 78 | function awooc(): object { 79 | 80 | return \Art\AWOOC\Main::instance(); 81 | } 82 | } 83 | 84 | awooc(); 85 | -------------------------------------------------------------------------------- /src/js/public/components/Request.js: -------------------------------------------------------------------------------- 1 | import { ajax, translate } from '../config'; 2 | import UpdateQuantity from './RequestProcessing/UpdateQuantity'; 3 | import DataCollector from './RequestProcessing/DataCollector'; 4 | 5 | export default class Request { 6 | constructor( app ) { 7 | this.app = app; 8 | this.$ = this.app.$; 9 | this.dataCollector = new DataCollector( this ); 10 | } 11 | 12 | sendRequest( e ) { 13 | const data = this.dataCollector.collectData( e ); 14 | 15 | this.app.xhr = this.$.ajax( { 16 | url: ajax.url, 17 | data, 18 | type: 'POST', 19 | dataType: 'json', 20 | success: ( response ) => this.handleSuccessResponse( response, e ), 21 | error: ( response ) => this.handleErrorResponse( response ), 22 | } ); 23 | } 24 | 25 | getProductID( e ) { 26 | const variationsForm = e.target.closest( '.variations_form' ); 27 | const variationId = variationsForm?.querySelector( 'input[name="variation_id"]' )?.value; 28 | 29 | return variationId ?? e.target.dataset.valueProductId; 30 | } 31 | 32 | handleSuccessResponse( response, e ) { 33 | this.removeSkeleton(); 34 | const { toMail, toPopup, toAnalytics } = response.data; 35 | 36 | this.fillDataToPopup( toPopup ); 37 | this.fillHiddenFormFields( toMail, e ); 38 | new UpdateQuantity( toMail, this, e ).bindEvent(); 39 | 40 | this.app.analyticData = toAnalytics; 41 | 42 | this.initUI(); 43 | 44 | this.closeMagnificPopup(); 45 | 46 | this.app.events.trigger( 'awooc_popup_ajax_trigger', response ); 47 | } 48 | 49 | handleErrorResponse( response ) { 50 | if ( response.responseJSON ) { 51 | // eslint-disable-next-line no-console 52 | console.error( response.responseJSON.data ); 53 | } 54 | } 55 | 56 | fillDataToMail( data ) { 57 | return `\n${ translate.product_data_title }\n———\n${ Object.values( data ).join( '\n' ) }`; 58 | } 59 | 60 | fillDataToPopup( data ) { 61 | Object.entries( data ).forEach( ( [ key, value ] ) => { 62 | const element = document.querySelector( `.awooc-popup-${ key }` ); 63 | if ( element ) { 64 | element.innerHTML = value; 65 | } 66 | } ); 67 | } 68 | 69 | fillHiddenFormFields( data, e ) { 70 | this.updateField( 'awooc_product_id', this.getProductID( e ) ); 71 | this.updateField( 'awooc_product_qty', this.getQty() ); 72 | this.updateField( 'awooc-hidden-data', this.fillDataToMail( data ) ); 73 | } 74 | 75 | updateField( name, value ) { 76 | const field = document.querySelector( `input[name="${ name }"]` ); 77 | if ( field ) { 78 | field.value = value; 79 | } 80 | } 81 | 82 | initUI() { 83 | this.app.form.initContactForm(); 84 | this.app.form.initMask(); 85 | } 86 | 87 | removeSkeleton() { 88 | document.querySelectorAll( '.awooc-popup-inner .awooc-popup-item' ).forEach( ( item ) => { 89 | item.classList.remove( 'skeleton-loader' ); 90 | } ); 91 | } 92 | 93 | closeMagnificPopup() { 94 | if ( this.$.magnificPopup?.instance ) { 95 | this.$.magnificPopup.close(); 96 | } 97 | } 98 | 99 | getQty() { 100 | const qty = document.querySelector( '.quantity input[name="quantity"]' ); 101 | if ( qty ) { 102 | return qty.value; 103 | } 104 | return 1; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require( 'path' ); 2 | const defaultConfig = require( "@wordpress/scripts/config/webpack.config" ); 3 | const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' ); 4 | const CssMinimizerPlugin = require( "css-minimizer-webpack-plugin" ); 5 | const { hasBabelConfig } = require( '@wordpress/scripts/utils' ); 6 | const TerserPlugin = require( 'terser-webpack-plugin' ); 7 | const UnminifiedWebpackPlugin = require( 'unminified-webpack-plugin' ); 8 | const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts'); 9 | 10 | const isProduction = process.env.NODE_ENV === 'production'; 11 | const mode = isProduction ? 'production' : 'development'; 12 | 13 | const BUILD_DIR = path.resolve( __dirname, 'assets' ); 14 | 15 | const filename = ext => isProduction ? ext + '/[name].min.' + ext : ext + '/[name].min.' + ext; 16 | 17 | module.exports = { 18 | ...defaultConfig, 19 | mode, 20 | //devtool: ! isProduction ? 'source-map' : false, 21 | devtool: ! isProduction ? false : false, 22 | entry: { 23 | "awooc-scripts": path.resolve( process.cwd(), 'src/js', 'awooc-scripts.js' ), 24 | "awooc-public-script": path.resolve( process.cwd(), 'src/js/public', 'main.js' ), 25 | "awooc-admin-script": path.resolve( process.cwd(), 'src/js/admin', 'script.js' ), 26 | "awooc-styles": path.resolve( process.cwd(), 'src/scss', 'awooc-styles.scss' ), 27 | "admin-style": path.resolve( process.cwd(), 'src/scss', 'admin-style.scss' ), 28 | }, 29 | output: { 30 | filename: filename( 'js' ), 31 | path: BUILD_DIR, 32 | clean: true 33 | }, 34 | optimization: { 35 | minimize: true, 36 | minimizer: [ 37 | new CssMinimizerPlugin( { 38 | minimizerOptions: { 39 | preset: [ 40 | "default", 41 | { "discardComments": { "removeAll": true } } 42 | ] 43 | }, 44 | } ), 45 | new TerserPlugin( { 46 | extractComments: false, 47 | } ), 48 | ] 49 | }, 50 | module: { 51 | rules: [ 52 | { 53 | test: /\.js$/, 54 | exclude: /node_modules/, 55 | use: [ 56 | require.resolve('babel-loader'), 57 | { 58 | loader: require.resolve('babel-loader'), 59 | options: { 60 | cacheDirectory: process.env.BABEL_CACHE_DIRECTORY || true, 61 | presets: [ 62 | require.resolve('@wordpress/babel-preset-default'), 63 | require.resolve('@babel/preset-env'), 64 | ], 65 | }, 66 | }, 67 | ], 68 | }, 69 | { 70 | test: /\.css$/i, 71 | use: [ "style-loader", "css-loader" ], 72 | }, 73 | { 74 | test: /\.s[ac]ss$/i, 75 | exclude: /node_modules/, 76 | use: [ 77 | { 78 | loader: MiniCssExtractPlugin.loader, 79 | }, 80 | { 81 | loader: 'css-loader', 82 | options: { 83 | sourceMap: ! isProduction, 84 | }, 85 | }, 86 | { 87 | loader: 'postcss-loader', 88 | options: { 89 | sourceMap: ! isProduction, 90 | }, 91 | }, 92 | { 93 | loader: 'sass-loader', 94 | options: { 95 | sourceMap: ! isProduction, 96 | }, 97 | }, 98 | ], 99 | }, 100 | ], 101 | }, 102 | plugins: [ 103 | new RemoveEmptyScriptsPlugin(), 104 | new MiniCssExtractPlugin( { 105 | filename: filename( 'css' ), 106 | } ), 107 | new UnminifiedWebpackPlugin( ) 108 | ], 109 | 110 | externals: { 111 | jquery: 'jQuery' 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/js/public/config.js: -------------------------------------------------------------------------------- 1 | /* global awooc_scripts_settings, awooc_scripts_ajax, awooc_scripts_translate */ 2 | 3 | /** 4 | * @typedef {Object} awooc_scripts_ajax - Общий объект с ссылкой на обработчик. 5 | * @property {string} url - Ссылка на AJAX обработчик. 6 | * @property {string} nonce - Одноразовое число. 7 | */ 8 | 9 | /** 10 | * @typedef {Object} awooc_scripts_translate - Объект с переводами строк. 11 | * @property {string} product_qty - Количество продукта. 12 | * @property {string} title - Название продукта. 13 | * @property {string} price - Цена продукта. 14 | * @property {string} sku - Артикул продукта. 15 | * @property {string} formatted_sum - Сумма заказа. 16 | * @property {string} attributes_list - Список атрибутов продукта. 17 | * @property {string} product_data_title - Заголовок информации о выбранном продукте. 18 | * @property {string} product_link - Ссылка на продукт. 19 | * @property {string} title_close - Текст для закрытия окна. 20 | */ 21 | 22 | /** 23 | * @typedef {Object} PopupCSS - Стили для всплывающего окна. 24 | * @property {string} width - Ширина окна. 25 | * @property {string} maxWidth - Максимальная ширина. 26 | * @property {string} maxHeight - Максимальная высота. 27 | * @property {string} top - Отступ сверху. 28 | * @property {string} left - Отступ слева. 29 | * @property {string} border - Ширина границы. 30 | * @property {string} borderRadius - Радиус скругления углов. 31 | * @property {string} cursor - Курсор. 32 | * @property {string} overflowY - Переполнение по вертикали. 33 | * @property {string} boxShadow - Тень окна. 34 | * @property {number} zIndex - Z-индекс окна. 35 | * @property {string} transform - Трансформация окна. 36 | * @property {string} overscroll-behavior - Поведение при скролле. 37 | */ 38 | 39 | /** 40 | * @typedef {Object} PopupOverlay - Стили оверлея. 41 | * @property {number} zIndex - Z-индекс оверлея. 42 | * @property {string} backgroundColor - Цвет фона оверлея. 43 | * @property {number} opacity - Прозрачность оверлея. 44 | * @property {string} cursor - Курсор при наведении. 45 | */ 46 | 47 | /** 48 | * @typedef {Object} PopupSettings - Настройки всплывающего окна. 49 | * @property {number} mailsent_timeout - Время ожидания после успешной отправки письма (в мс). 50 | * @property {number} invalid_timeout - Время ожидания при ошибке валидации (в мс). 51 | * @property {number} cf7_form_id - ID выбранной формы Contact Form 7. 52 | * @property {string} price_decimal_sep - Десятичный разделитель в цене. 53 | * @property {number} price_num_decimals - Количество знаков после запятой в цене. 54 | * @property {string} price_thousand_sep - Разделитель тысяч в цене. 55 | * @property {PopupCSS} css - Стили для всплывающего окна. 56 | * @property {PopupOverlay} overlay - Стили оверлея. 57 | * @property {number} fadeIn - Время анимации появления (в мс). 58 | * @property {number} fadeOut - Время анимации исчезновения (в мс). 59 | * @property {boolean} focusInput - Автоматический фокус на поле ввода. 60 | */ 61 | 62 | /** 63 | * @typedef {Object} awooc_scripts_settings - Основной объект настроек. 64 | * @property {string} mode - Режим работы плагина. 65 | * @property {string} template - Шаблон всплывающего окна. 66 | * @property {string} custom_label - Произвольная надпись на кнопке. 67 | * @property {PopupSettings} popup - Настройки всплывающего окна. 68 | */ 69 | 70 | /** 71 | * @module awooc_scripts 72 | * @exports settings 73 | * @exports ajax 74 | * @exports translate 75 | */ 76 | 77 | export const settings = awooc_scripts_settings; 78 | export const ajax = awooc_scripts_ajax; 79 | export const translate = awooc_scripts_translate; 80 | -------------------------------------------------------------------------------- /src/js/public/components/Form.js: -------------------------------------------------------------------------------- 1 | import { settings } from '../config'; 2 | 3 | /*global wpcf7 */ 4 | export default class Form { 5 | constructor( app ) { 6 | this.app = app; 7 | /* eslint-disable camelcase */ 8 | const { cf7_form_id, mailsent_timeout, invalid_timeout } = settings.popup; 9 | this.formId = Number( cf7_form_id ); 10 | this.mailsentTimeout = mailsent_timeout; 11 | this.invalidTimeout = invalid_timeout; 12 | /* eslint-disable camelcase */ 13 | } 14 | 15 | init() { 16 | this.bindEvents(); 17 | } 18 | 19 | bindEvents() { 20 | document.addEventListener( 'wpcf7mailsent', ( event ) => this.handleMailSent( event ) ); 21 | document.addEventListener( 'wpcf7invalid', ( event ) => this.handleInvalid( event ) ); 22 | 23 | this.app.events.on( 'awooc_popup_ajax_trigger', () => this.setupFormSubmitListener() ); 24 | } 25 | 26 | setupFormSubmitListener() { 27 | const form = document.querySelector( 'form.wpcf7-form' ); 28 | if ( form ) { 29 | form.addEventListener( 'submit', ( event ) => this.handleFormSubmit( event ) ); 30 | } 31 | } 32 | 33 | handleMailSent( event ) { 34 | const { detail } = event; 35 | 36 | setTimeout( () => jQuery.unblockUI(), this.mailsentTimeout ); 37 | 38 | if ( this.formId === detail.contactFormId ) { 39 | this.app.events.trigger( 'awooc_mail_sent_trigger', { 40 | selectedProduct: this.app.analyticData, 41 | mailDetail: detail, 42 | } ); 43 | } 44 | } 45 | 46 | handleInvalid( event ) { 47 | const { detail } = event; 48 | 49 | if ( this.formId === detail.contactFormId ) { 50 | this.app.events.trigger( 'awooc_mail_invalid_trigger' ); 51 | } 52 | 53 | setTimeout( () => this.clearFormErrors(), this.invalidTimeout ); 54 | } 55 | 56 | clearFormErrors() { 57 | const formOutput = document.querySelector( '.awooc-form-custom-order .wpcf7-response-output' ); 58 | const notValidTips = document.querySelectorAll( '.awooc-form-custom-order .wpcf7-not-valid-tip' ); 59 | const submitButton = document.querySelector( '.awooc-form-custom-order input[type="submit"]' ); 60 | 61 | if ( formOutput ) { 62 | formOutput.innerHTML = ''; 63 | } 64 | 65 | if ( submitButton ) { 66 | submitButton.disabled = false; 67 | } 68 | notValidTips.forEach( ( tip ) => tip.remove() ); 69 | } 70 | 71 | handleFormSubmit( event ) { 72 | const submitButton = event.currentTarget.querySelector( 'input[type="submit"]' ); 73 | if ( submitButton ) { 74 | submitButton.disabled = true; 75 | } 76 | } 77 | 78 | initContactForm() { 79 | document.querySelectorAll( '.awooc-form-custom-order div.wpcf7 > form' ).forEach( ( form ) => { 80 | const versionInput = form.querySelector( 'input[name="_wpcf7_version"]' ); 81 | if ( ! versionInput ) { 82 | return; 83 | } 84 | 85 | const isOldVersion = versionInput.value && versionInput.value <= '5.4'; 86 | if ( isOldVersion ) { 87 | this.initOldWpcf7( form ); 88 | } else { 89 | wpcf7.init( form ); 90 | } 91 | } ); 92 | } 93 | 94 | initOldWpcf7( form ) { 95 | wpcf7.initForm( form ); 96 | if ( wpcf7.cached ) { 97 | wpcf7.refill( form ); 98 | } 99 | } 100 | 101 | initMask() { 102 | document.querySelectorAll( '.awooc-form-custom-order .wpcf7-mask' ).forEach( ( field ) => { 103 | const dataMask = jQuery( field ).data( 'mask' ); 104 | if ( ! dataMask ) { 105 | return; 106 | } 107 | 108 | try { 109 | jQuery( field ).mask( dataMask ); 110 | if ( ! /[a*]/.test( dataMask ) ) { 111 | field.setAttribute( 'inputmode', 'numeric' ); 112 | } 113 | } catch ( e ) { 114 | // eslint-disable-next-line no-console 115 | console.error( `Error ${ e.name }: ${ e.message }\n${ e.stack }` ); 116 | } 117 | } ); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/js/public/components/Buttons.js: -------------------------------------------------------------------------------- 1 | import { settings } from '../config.js'; 2 | 3 | /*global wc_add_to_cart_variation_params */ 4 | export default class Buttons { 5 | constructor( app ) { 6 | this.app = app; 7 | this.button = document.querySelectorAll( 'form.cart .awooc-button-js' ); 8 | this.addToCartElements = document.querySelectorAll( '.woocommerce-variation-add-to-cart .quantity, .woocommerce-variation-add-to-cart .single_add_to_cart_button' ); 9 | this.disableClasses = [ 'disabled', 'wc-variation-selection-needed' ]; 10 | this.disableClassesOutStock = [ 'disabled', 'wc-variation-is-unavailable' ]; 11 | } 12 | 13 | init() { 14 | document.addEventListener( 'click', ( e ) => this.handleShowPopup( e ) ); 15 | 16 | jQuery( document.body ) 17 | .on( 'hide_variation', () => this.toggleButtonClasses( this.button, 'add', this.disableClasses ) ) 18 | .on( 'show_variation', ( event, variation, purchasable ) => this.updateButtonState( variation, purchasable ) ); 19 | } 20 | 21 | handleShowPopup( e ) { 22 | const button = e.target.closest( '.awooc-button-js' ); 23 | if ( ! button ) { 24 | return; 25 | } 26 | 27 | if ( button.classList.contains( 'disabled' ) ) { 28 | e.preventDefault(); 29 | const message = button.classList.contains( 'wc-variation-is-unavailable' ) 30 | ? wc_add_to_cart_variation_params.i18n_unavailable_text 31 | : wc_add_to_cart_variation_params.i18n_make_a_selection_text; 32 | // eslint-disable-next-line no-alert 33 | window.alert( message ); 34 | return false; 35 | } 36 | 37 | this.app.popup.showPopup( e ); 38 | } 39 | 40 | updateButtonState( variation, purchasable, button = null ) { 41 | const targetButton = button || this.button; 42 | 43 | switch ( settings.mode ) { 44 | case 'in_stock_add_to_card': // preload 45 | this.handlePreloadMode( variation, targetButton ); 46 | break; 47 | 48 | case 'no_stock_no_price': // special 49 | this.handleSpecialMode( variation, targetButton ); 50 | break; 51 | 52 | default: 53 | this.handleDefaultMode( purchasable, targetButton ); 54 | break; 55 | } 56 | } 57 | 58 | handlePreloadMode( variation, button = null ) { 59 | const targetButton = button || this.button; 60 | 61 | if ( variation.backorders_allowed || ! variation.is_in_stock ) { 62 | this.hideAddToCartModule(); 63 | this.toggleButtonClasses( targetButton, 'remove', this.disableClassesOutStock ); 64 | } else { 65 | this.showAddToCartModule(); 66 | this.toggleButtonClasses( targetButton, 'add', this.disableClasses ); 67 | } 68 | } 69 | 70 | handleSpecialMode( variation, button = null ) { 71 | const targetButton = button || this.button; 72 | 73 | if ( variation.is_purchasable && ! variation.is_in_stock ) { 74 | this.hideAddToCartModule(); 75 | this.toggleButtonClasses( targetButton, 'remove', this.disableClasses ); 76 | } else if ( variation.is_purchasable && variation.is_in_stock ) { 77 | this.showAddToCartModule(); 78 | this.toggleButtonClasses( targetButton, 'remove', this.disableClasses ); 79 | } else { 80 | this.showAddToCartModule(); 81 | this.toggleButtonClasses( targetButton, 'add', this.disableClasses ); 82 | } 83 | } 84 | 85 | handleDefaultMode( purchasable, button = null ) { 86 | const targetButton = button || this.button; 87 | 88 | this.toggleButtonClasses( targetButton, purchasable ? 'remove' : 'add', this.disableClassesOutStock ); 89 | } 90 | 91 | toggleButtonClasses( buttons, action, classes ) { 92 | if ( ! buttons ) { 93 | buttons = this.button; 94 | } 95 | 96 | if ( buttons instanceof NodeList ) { 97 | buttons = Array.from( buttons ); 98 | } 99 | 100 | if ( ! Array.isArray( buttons ) ) { 101 | buttons = [ buttons ]; 102 | } 103 | 104 | buttons.forEach( ( btn ) => { 105 | if ( btn && btn.classList ) { 106 | classes.forEach( ( cls ) => { 107 | if ( typeof cls === 'string' ) { 108 | btn.classList[ action ]( cls ); 109 | } 110 | } ); 111 | } 112 | } ); 113 | } 114 | 115 | toggleModuleVisibility( action ) { 116 | this.addToCartElements.forEach( ( el ) => el.classList[ action ]( 'awooc-hide' ) ); 117 | this.button.forEach( ( btn ) => btn.classList[ action ]( 'no-margin' ) ); 118 | } 119 | 120 | hideAddToCartModule() { 121 | this.toggleModuleVisibility( 'add' ); 122 | } 123 | 124 | showAddToCartModule() { 125 | this.toggleModuleVisibility( 'remove' ); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/js/public/components/Integrations/VariationSwatchesByCartFlows.js: -------------------------------------------------------------------------------- 1 | export default class VariationSwatchesByCartFlows { 2 | constructor( app ) { 3 | this.app = app; 4 | this.variationForms = document.querySelectorAll( '.cfvsw_variations_form, form.variations_form' ); 5 | this.buttons = this.getButtons(); 6 | } 7 | 8 | getButtons() { 9 | return Array.from( this.variationForms ) 10 | .map( ( form ) => form.closest( 'li' ) ) 11 | .filter( ( li ) => li !== null ) 12 | .flatMap( ( li ) => Array.from( li.querySelectorAll( '.awooc-button-js' ) ) ); 13 | } 14 | 15 | init() { 16 | if ( this.variationForms.length < 0 ) { 17 | return; 18 | } 19 | 20 | this.addedToButtonAttributes(); 21 | this.disableButtons(); 22 | 23 | this.bindEvents(); 24 | } 25 | 26 | bindEvents() { 27 | document.addEventListener( 'cfvswVariationLoad', () => this.addedToButtonAttributes() ); 28 | document.addEventListener( 'astraInfinitePaginationLoaded', () => this.addedToButtonAttributes() ); 29 | 30 | const swatchesOptions = document.querySelectorAll( '.cfvsw-swatches-option' ); 31 | 32 | swatchesOptions.forEach( ( swatch ) => { 33 | swatch.addEventListener( 'click', ( e ) => this.onClickSwatchesOption( e ) ); 34 | } ); 35 | } 36 | 37 | addedToButtonAttributes() { 38 | this.variationForms.forEach( ( form ) => { 39 | jQuery( form ).wc_variation_form(); 40 | 41 | if ( form.dataset.cfvswCatalog ) { 42 | return; 43 | } 44 | 45 | jQuery( form ).on( 'found_variation', () => this.updateButtonData( form ) ); 46 | } ); 47 | } 48 | 49 | updateButtonData( variant ) { 50 | const selectElements = variant.querySelectorAll( '.variations select' ); 51 | const data = {}; 52 | const button = variant.closest( 'li' )?.querySelector( '.awooc-button-js' ); 53 | 54 | selectElements.forEach( ( selectElement ) => { 55 | const attributeName = selectElement.dataset.attributeName || selectElement.name; 56 | data[ attributeName ] = selectElement.value || ''; 57 | } ); 58 | 59 | if ( button ) { 60 | button.disabled = false; 61 | button.classList.add( 'cfvsw_variation_found' ); 62 | button.dataset.selectedVariant = JSON.stringify( data ); 63 | } 64 | } 65 | 66 | onClickSwatchesOption( e ) { 67 | const swatch = e.target; 68 | 69 | if ( this.isSwatchSelected( swatch ) ) { 70 | this.resetButtonData( swatch ); 71 | } else { 72 | this.deselectAllSwatches( swatch ); 73 | this.selectSwatch( swatch ); 74 | } 75 | 76 | this.updateSelectOption( swatch ); 77 | } 78 | 79 | resetButtonData( swatch ) { 80 | const button = swatch.closest( 'li' )?.querySelector( '.awooc-button-js' ); 81 | 82 | if ( ! button ) { 83 | return; 84 | } 85 | 86 | const select = this.getSelectElement( swatch ); 87 | const hasDefaultValue = select?.value && select.value !== ''; 88 | 89 | if ( hasDefaultValue ) { 90 | this.updateButtonData( swatch.closest( 'form' ) ); 91 | } else { 92 | button.disabled = true; 93 | button.classList.remove( 'cfvsw_variation_found' ); 94 | button.dataset.selectedVariant = ''; 95 | } 96 | } 97 | 98 | updateSelectOption( swatch ) { 99 | const value = this.getSwatchValue( swatch ); 100 | const select = this.getSelectElement( swatch ); 101 | 102 | if ( select ) { 103 | select.value = value; 104 | setTimeout( () => { 105 | select.dispatchEvent( new CustomEvent( 'change', { bubbles: true } ) ); 106 | }, 50 ); 107 | } 108 | } 109 | 110 | isSwatchSelected( swatch ) { 111 | return ( 112 | ( ! swatch.classList.contains( 'cfvsw-swatches-disabled' ) || ! swatch.classList.contains( 'cfvsw-swatches-out-of-stock' ) ) && 113 | swatch.classList.contains( 'cfvsw-selected-swatch' ) 114 | ); 115 | } 116 | 117 | deselectAllSwatches( swatch ) { 118 | swatch.parentElement 119 | .querySelectorAll( '.cfvsw-swatches-option' ) 120 | .forEach( ( option ) => option.classList.remove( 'cfvsw-selected-swatch' ) ); 121 | } 122 | 123 | selectSwatch( swatch ) { 124 | swatch.classList.add( 'cfvsw-selected-swatch' ); 125 | } 126 | 127 | getSwatchValue( swatch ) { 128 | return this.isSwatchSelected( swatch ) ? swatch.dataset.slug : ''; 129 | } 130 | 131 | getSelectElement( swatch ) { 132 | return swatch.closest( '.cfvsw-swatches-container' )?.previousElementSibling?.querySelector( 'select' ); 133 | } 134 | 135 | disableButtons() { 136 | this.buttons.forEach( ( button ) => ( button.disabled = true ) ); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /classes/RequestProcessing/OrderCreator.php: -------------------------------------------------------------------------------- 1 | has_create_order = get_option( 'woocommerce_awooc_created_order' ); 41 | 42 | /** 43 | * Хук wpcf7_before_send_mail используется для создания заказов, а не wpcf7_mail_sent, потому что только на этом хуке возможно изменять данные письма до его отправки. На хуке wpcf7_mail_sent ничего изменить не получиться, письмо уже ушло 44 | */ 45 | add_action( 'wpcf7_before_send_mail', [ $this, 'created_order_mail_send' ], 10, 3 ); 46 | 47 | add_filter( 'woocommerce_admin_order_buyer_name', [ $this, 'add_name' ], 10, 2 ); 48 | } 49 | 50 | 51 | /** 52 | * @param string $buyer 53 | * @param \WC_Order $order 54 | * 55 | * @return string 56 | */ 57 | public function add_name( string $buyer, \WC_Order $order ): string { 58 | 59 | if ( $order->get_meta( '_awooc_order' ) && get_option( 'woocommerce_awooc_change_name' ) ) { 60 | $buyer = __( 'Order One Click: ', 'art-woocommerce-order-fast' ) . $order->get_billing_phone(); 61 | } 62 | 63 | return $buyer; 64 | } 65 | 66 | 67 | /** 68 | * Создание заказа при отправке письма 69 | * 70 | * @param \WPCF7_ContactForm $contact_form 71 | * @param bool $abort 72 | * @param \WPCF7_Submission $submission 73 | * 74 | * @return void 75 | * @since 2.2.6 76 | * @since 1.5.0 77 | */ 78 | public function created_order_mail_send( WPCF7_ContactForm $contact_form, $abort, WPCF7_Submission $submission ): void { 79 | 80 | if ( 'yes' !== $this->has_create_order ) { 81 | return; 82 | } 83 | 84 | if ( $contact_form->id() !== $this->main->get_selected_form_id() ) { 85 | return; 86 | } 87 | 88 | $posted_data = $submission->get_posted_data(); 89 | 90 | [ $posted_text, $posted_email, $posted_tel, $product_id, $product_qty, $customer_id ] = $this->prepare_posted_data( $posted_data ); 91 | 92 | $address = apply_filters( 93 | 'awooc_order_address_arg', 94 | [ 95 | 'first_name' => $posted_text, 96 | 'email' => $posted_email, 97 | 'phone' => $posted_tel, 98 | ], 99 | $posted_data 100 | ); 101 | 102 | $this->order = wc_create_order(); 103 | 104 | do_action( 'awooc_after_created_order', $product_id, $this->order, $address, $product_qty ); 105 | 106 | $this->add_order( (int) $product_id, $product_qty, $address, (int) $customer_id ); 107 | 108 | do_action( 'awooc_create_order', $this->order, $contact_form, $posted_data ); 109 | 110 | do_action( 'awooc_after_mail_send', $product_id, $this->order->get_id() ); 111 | } 112 | 113 | 114 | /** 115 | * Добавление в заказ данных товара 116 | * 117 | * @param int $product_id ID продкта. 118 | * @param float $product_qty количество продукта. 119 | * @param array $address адрес для заказа. 120 | * 121 | * @param int $customer_id 122 | * 123 | * @since 2.2.6 124 | */ 125 | public function add_order( int $product_id, float $product_qty, array $address, int $customer_id ): void { 126 | 127 | $this->order->add_product( wc_get_product( $product_id ), $product_qty ); 128 | $this->order->set_address( $address, 'billing' ); 129 | $this->order->set_address( $address, 'shipping' ); 130 | 131 | if ( 0 !== $customer_id ) { 132 | try { 133 | $this->order->set_customer_id( $customer_id ); 134 | } catch ( WC_Data_Exception $exception ) { 135 | wc_get_logger()->error( 136 | $exception->getMessage(), 137 | [ 138 | 'source' => 'awooc', 139 | 'backtrace' => true, 140 | ] 141 | ); 142 | } 143 | } 144 | 145 | $this->order->update_meta_data( '_awooc_order', true ); 146 | $this->order->add_order_note( __( 'The order was created by using the One-click Order button', 'art-woocommerce-order-one-click' ) ); 147 | $this->order->calculate_totals(); 148 | $this->order->update_status( 'pending', __( 'One click order', 'art-woocommerce-order-one-click' ), true ); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /classes/Admin/Create_Form.php: -------------------------------------------------------------------------------- 1 | [ 61 | 'timestamp' => time(), 62 | 'version' => AWOOC_PLUGIN_VER, 63 | 'count_valid' => 1, 64 | 'count_invalid' => 0, 65 | ], 66 | ]; 67 | 68 | if ( empty( get_option( 'woocommerce_awooc_active' ) ) ) { 69 | update_option( 'woocommerce_awooc_active', $option ); 70 | } 71 | } 72 | 73 | 74 | /** 75 | * @param string $mail_form 76 | * @param string $mail_body 77 | */ 78 | public static function created_form( string $mail_form, string $mail_body ): void { 79 | 80 | $contact_form = WPCF7_ContactForm::get_template(); 81 | 82 | $contact_form->set_properties( 83 | [ 84 | 'form' => $mail_form, 85 | 'mail' => [ 86 | 'subject' => /* translators: 1: blog name, 2: blog URL */ sprintf( 87 | __( 'Order from the site %1$s (%2$s)', 'art-woocommerce-order-one-click' ), 88 | get_bloginfo( 'name' ), 89 | get_bloginfo( 'url' ) 90 | ), 91 | 'sender' => sprintf( '%s <%s>', get_bloginfo( 'name' ), WPCF7_ContactFormTemplate::from_email() ), 92 | 'body' => $mail_body, 93 | 'recipient' => get_option( 'admin_email' ), 94 | 'additional_headers' => 'Reply-To:[awooc-email]', 95 | 'attachments' => '', 96 | 'use_html' => 1, 97 | 'exclude_blank' => 0, 98 | ], 99 | 'mail_2' => [ 100 | 'subject' => /* translators: 1: blog name, 2: blog URL */ sprintf( 101 | __( 'Order from the site %1$s (%2$s)', 'art-woocommerce-order-one-click' ), 102 | get_bloginfo( 'name' ), 103 | get_bloginfo( 'url' ) 104 | ), 105 | 'sender' => sprintf( '%s <%s>', get_bloginfo( 'name' ), WPCF7_ContactFormTemplate::from_email() ), 106 | 'body' => $mail_body, 107 | 'recipient' => get_option( 'admin_email' ), 108 | 'additional_headers' => 'Reply-To:[awooc-email]', 109 | 'attachments' => '', 110 | 'use_html' => 1, 111 | 'exclude_blank' => 0, 112 | ], 113 | ] 114 | ); 115 | 116 | $props = $contact_form->get_properties(); 117 | 118 | $post_content = implode( "\n", wpcf7_array_flatten( $props ) ); 119 | 120 | $post_id = wp_insert_post( 121 | [ 122 | 'post_type' => 'wpcf7_contact_form', 123 | 'post_status' => 'publish', 124 | 'post_title' => __( 'Order One Click', 'art-woocommerce-order-one-click' ), 125 | 'post_content' => trim( $post_content ), 126 | ] 127 | ); 128 | 129 | if ( $post_id ) { 130 | foreach ( $props as $prop => $value ) { 131 | update_post_meta( 132 | $post_id, 133 | '_' . $prop, 134 | wpcf7_normalize_newline_deep( $value ) 135 | ); 136 | } 137 | 138 | add_post_meta( $post_id, '_hash', 139 | wpcf7_generate_contact_form_hash( $post_id ), 140 | true 141 | ); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/scss/public/_popup.scss: -------------------------------------------------------------------------------- 1 | .awooc-close { 2 | font-size: 35px; 3 | position: absolute; 4 | top: 0; 5 | right: 0; 6 | font-family: initial; 7 | line-height: 30px; 8 | width: 30px; 9 | height: 30px; 10 | z-index: 100; 11 | color: #161616; 12 | opacity: 0.5; 13 | background: rgba(0, 0, 0, 0.00); 14 | -o-transition: all 0.5s ease-in-out; 15 | -moz-transition: all 0.5s ease-in-out; 16 | -webkit-transition: all 0.5s ease-in-out; 17 | 18 | &:hover { 19 | opacity: 1; 20 | cursor: pointer; 21 | } 22 | } 23 | 24 | .awooc-hide { 25 | display: none; 26 | } 27 | 28 | .blockUI:before, 29 | .awooc-hidden-data { 30 | display: none !important; 31 | } 32 | 33 | .awooc-custom-order-button { 34 | position: relative; 35 | vertical-align: middle; 36 | } 37 | 38 | .awooc-custom-order-button.button.alt.show-add-to-card, 39 | .awooc-custom-order-button.button.alt.no-stock-no-price:not(.no-margin) { 40 | margin-left: 0.6125rem; 41 | } 42 | 43 | .theme-woodmart .awooc-custom-order-button.button.alt.show-add-to-card { 44 | flex: 0 0 auto; 45 | margin-left: 0; 46 | } 47 | 48 | .theme-twentytwentyfour .awooc-custom-order-button.button.alt.show-add-to-card { 49 | float: none 50 | } 51 | 52 | .awooc-popup-wrapper, 53 | .awooc-popup-inner { 54 | display: flex; 55 | flex-direction: column; 56 | } 57 | 58 | .awooc-popup-inner { 59 | padding: 2.125rem; 60 | } 61 | 62 | .awooc-row { 63 | display: flex; 64 | flex-direction: row; 65 | flex-wrap: wrap; 66 | justify-content: space-between; 67 | align-content: flex-start; 68 | align-items: center; 69 | } 70 | 71 | .awooc-col { 72 | flex: 0 1 100%; 73 | max-width: 100%; 74 | align-self: flex-start; 75 | 76 | @include minWidth568px() { 77 | max-width: calc(50% - .875rem); 78 | } 79 | 80 | &.columns-left { 81 | text-align: left; 82 | } 83 | 84 | &.columns-right { 85 | text-align: left; 86 | 87 | @include maxWidth569px() { 88 | &:not(.awooc-col-full) { 89 | padding-top: 0.875rem; 90 | } 91 | } 92 | 93 | input { 94 | width: 100%; 95 | } 96 | } 97 | 98 | &.awooc-col-full { 99 | flex: 0 1 100%; 100 | max-width: 100%; 101 | } 102 | } 103 | 104 | .awooc-popup-image { 105 | text-align: center; 106 | float: left; 107 | 108 | & img { 109 | width: 100%; 110 | max-width: 100px; 111 | height: auto; 112 | margin-right: 1rem; 113 | 114 | @include minWidth568px() { 115 | width: 100%; 116 | max-width: 300px; 117 | height: auto; 118 | margin-bottom: 1rem; 119 | } 120 | } 121 | } 122 | 123 | .awooc-popup-price, 124 | .awooc-popup-sku, 125 | .awooc-popup-qty, 126 | .awooc-popup-attr { 127 | font-size: 0.875rem; 128 | 129 | @include minWidth568px() { 130 | font-size: inherit; 131 | } 132 | } 133 | 134 | .awooc-popup-qty { 135 | display: flex; 136 | align-items: center; 137 | gap: 0.5rem; 138 | 139 | @include minWidth568px() { 140 | width: 100%; // без этого стиля этот элемент, идущий сразу после изображения, ужимается до 0px 141 | } 142 | 143 | .quantity { 144 | display: grid; 145 | grid-template-columns: 32px 1fr 32px; 146 | flex-wrap: nowrap; 147 | align-items: center; 148 | white-space: nowrap; 149 | border: 1px solid rgba(0, 0, 0, 0.1); 150 | background: transparent; 151 | border-radius: 3px; 152 | transition: all 0.2s ease; 153 | 154 | input[type="number"] { 155 | width: 32px; 156 | height: auto; 157 | border-radius: 0; 158 | border-right: none; 159 | border-left: none; 160 | -moz-appearance: textfield; 161 | border: 0; 162 | box-shadow: none; 163 | background: #fff; 164 | max-height: 32px; 165 | padding: 0; 166 | font-size: small; 167 | 168 | &:hover, 169 | &:focus { 170 | -moz-appearance: number-input; 171 | } 172 | 173 | &::-webkit-outer-spin-button, 174 | &::-webkit-inner-spin-button { 175 | -webkit-appearance: none; 176 | margin: 0; 177 | } 178 | } 179 | 180 | .awooc-popup-input-qty--minus, .awooc-popup-input-qty--plus { 181 | background: transparent; 182 | box-shadow: none; 183 | border: 0; 184 | box-sizing: border-box; 185 | display: flex; 186 | align-items: center; 187 | justify-content: center; 188 | font-size: medium; 189 | color: rgba(0, 0, 0, 0.5); 190 | /* border: 1px solid rgba(0, 0, 0, 0.1); */ 191 | padding: 0; 192 | } 193 | } 194 | 195 | 196 | } 197 | 198 | .awooc-custom-order-wrap { 199 | .wpcf7-form { 200 | margin-bottom: 0; 201 | 202 | & > p { 203 | &:nth-child(2) { 204 | margin-top: 0 205 | } 206 | 207 | &:nth-last-child(2) { 208 | margin-bottom: 0 209 | } 210 | } 211 | } 212 | 213 | .wpcf7-form-control-wrap { 214 | display: inline-block; 215 | margin-bottom: 0.875rem; 216 | width: 100%; 217 | } 218 | 219 | .wpcf7-form-control { 220 | outline: 0; 221 | box-sizing: border-box; 222 | } 223 | } 224 | 225 | .awooc-attr-value, .awooc-option-value { 226 | span { 227 | padding-left: 1rem; 228 | display: block; 229 | line-height: 1.3; 230 | } 231 | } -------------------------------------------------------------------------------- /classes/Requirements.php: -------------------------------------------------------------------------------- 1 | required_plugins = [ 22 | [ 23 | 'plugin' => 'woocommerce/woocommerce.php', 24 | 'name' => 'WooCommerce', 25 | 'slug' => 'woocommerce', 26 | 'class' => 'WooCommerce', 27 | 'version' => '5.5', 28 | 'active' => false, 29 | ], 30 | [ 31 | 'plugin' => 'contact-form-7/wp-contact-form-7.php', 32 | 'name' => 'Contact Form 7', 33 | 'slug' => 'contact-form-7', 34 | 'class' => 'WPCF7', 35 | 'version' => '5.0', 36 | 'active' => false, 37 | ], 38 | ]; 39 | } 40 | 41 | 42 | public function init_hooks(): void { 43 | 44 | add_action( 'before_woocommerce_init', static function () { 45 | 46 | if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) { 47 | \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 48 | 'custom_order_tables', 49 | AWOOC_PLUGIN_FILE, 50 | true 51 | ); 52 | } 53 | } ); 54 | 55 | add_action( 'admin_init', [ $this, 'check_requirements' ] ); 56 | 57 | foreach ( $this->required_plugins as $required_plugin ) { 58 | if ( ! class_exists( $required_plugin['class'] ) ) { 59 | return; 60 | } 61 | } 62 | } 63 | 64 | 65 | /** 66 | * Check plugin requirements. If not met, show message and deactivate plugin. 67 | * 68 | * @since 1.0.0 69 | */ 70 | public function check_requirements(): void { 71 | 72 | if ( false === $this->requirements() ) { 73 | $this->deactivation_plugin(); 74 | } 75 | } 76 | 77 | 78 | /** 79 | * Сообщение при деактивации плагина 80 | */ 81 | public function deactivation_plugin(): void { 82 | 83 | add_action( 'admin_notices', [ $this, 'show_plugin_not_found_notice' ] ); 84 | 85 | if ( is_plugin_active( AWOOC_PLUGIN_FILE ) ) { 86 | 87 | deactivate_plugins( AWOOC_PLUGIN_FILE ); 88 | // @codingStandardsIgnoreStart 89 | if ( isset( $_GET['activate'] ) ) { 90 | unset( $_GET['activate'] ); 91 | } 92 | // @codingStandardsIgnoreEnd 93 | add_action( 'admin_notices', [ $this, 'show_deactivate_notice' ] ); 94 | } 95 | } 96 | 97 | 98 | public function show_plugin_not_found_notice(): void { 99 | 100 | $message = sprintf( 101 | /* translators: 1: Name author plugin */ 102 | __( 'The %s requires installed and activated plugins: ', 'art-woocommerce-order-fast' ), 103 | esc_attr( AWOOC_PLUGIN_NAME ) 104 | ); 105 | 106 | $message_parts = []; 107 | 108 | foreach ( $this->required_plugins as $key => $required_plugin ) { 109 | if ( ! $required_plugin['active'] ) { 110 | $href = '/wp-admin/plugin-install.php?tab=plugin-information&plugin='; 111 | 112 | $href .= sprintf( '%s&TB_iframe=true&width=640&height=500', $required_plugin['slug'] ); 113 | 114 | $message_parts[] = sprintf( 115 | '%s%s%s%s', 116 | $href, 117 | $required_plugin['name'], 118 | __( ' version ', 'art-woocommerce-order-one-click' ), 119 | $required_plugin['version'], 120 | __( ' or higher', 'art-woocommerce-order-one-click' ) 121 | ); 122 | } 123 | } 124 | 125 | $count = count( $message_parts ); 126 | 127 | foreach ( $message_parts as $key => $message_part ) { 128 | if ( 0 !== $key ) { 129 | if ( ( ( $count - 1 ) === $key ) ) { 130 | $message .= __( ' and ', 'art-woocommerce-order-one-click' ); 131 | } else { 132 | $message .= ', '; 133 | } 134 | } 135 | 136 | $message .= $message_part; 137 | } 138 | 139 | $message .= '.'; 140 | 141 | $this->admin_notice( $message, 'notice notice-error is-dismissible' ); 142 | } 143 | 144 | 145 | /** 146 | * Show a notice to inform the user that the plugin has been deactivated. 147 | * 148 | * @since 2.0.0 149 | */ 150 | public function show_deactivate_notice(): void { 151 | 152 | $message = sprintf( 153 | /* translators: 1: Name author plugin */ 154 | __( '%s plugin has been deactivated.', 'art-woocommerce-order-one-click' ), 155 | esc_attr( AWOOC_PLUGIN_NAME ) 156 | ); 157 | 158 | $this->admin_notice( $message, 'notice notice-warning is-dismissible' ); 159 | } 160 | 161 | 162 | private function admin_notice( $message, $classes ): void { 163 | 164 | printf( 165 | '

%2$s

', 166 | esc_attr( $classes ), 167 | wp_kses_post( $message ) 168 | ); 169 | } 170 | 171 | 172 | /** 173 | * Check if plugin requirements. 174 | * 175 | * @return bool 176 | * @since 1.0.0 177 | */ 178 | public function requirements(): bool { 179 | 180 | $all_active = true; 181 | 182 | include_once ABSPATH . 'wp-admin/includes/plugin.php'; 183 | 184 | foreach ( $this->required_plugins as $key => $required_plugin ) { 185 | 186 | if ( is_plugin_active( $required_plugin['plugin'] ) ) { 187 | $this->required_plugins[ $key ]['active'] = true; 188 | } else { 189 | $all_active = false; 190 | } 191 | } 192 | 193 | return $all_active; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /classes/RequestProcessing/EmailModifier.php: -------------------------------------------------------------------------------- 1 | '', 48 | 'awooc_product_id' => '', 49 | 'awooc_product_qty' => '', 50 | 'awooc_customer_id' => get_current_user_id(), 51 | ] 52 | ); 53 | 54 | if ( class_exists( 'Polylang' ) && ! defined( 'WP_CLI' ) ) { 55 | $addon_fields['lang'] = pll_current_language(); 56 | } 57 | 58 | if ( defined( 'ICL_LANGUAGE_CODE' ) && ! defined( 'WP_CLI' ) ) { 59 | $addon_fields['lang'] = ICL_LANGUAGE_CODE; 60 | } 61 | 62 | return array_merge( 63 | $fields, 64 | $addon_fields 65 | ); 66 | } 67 | 68 | 69 | /** 70 | * Изменение темы письма 71 | * 72 | * @param \WC_Order $order объект заказа. 73 | * @param \WPCF7_ContactForm $contact_form объект формы. 74 | * @param array $posted_data пересылаемые данные. 75 | * 76 | * @since 2.2.6 77 | */ 78 | public function change_email_subject( WC_Order $order, WPCF7_ContactForm $contact_form, $posted_data ): void { 79 | 80 | if ( 'yes' !== get_option( 'woocommerce_awooc_change_subject' ) ) { 81 | return; 82 | } 83 | 84 | $mail = $contact_form->prop( 'mail' ); 85 | 86 | $mail['subject'] = $mail['subject'] . ' №' . $order->get_order_number(); 87 | 88 | $contact_form->set_properties( [ 'mail' => $mail ] ); 89 | } 90 | 91 | 92 | /** 93 | * @param \WPCF7_ContactForm $contact_form 94 | * @param bool $abort 95 | * @param \WPCF7_Submission $submission 96 | * 97 | * @return void 98 | */ 99 | public function change_email_template( WPCF7_ContactForm $contact_form, bool $abort, WPCF7_Submission $submission ): void { 100 | 101 | if ( 'yes' !== get_option( 'woocommerce_awooc_enable_letter_template' ) ) { 102 | return; 103 | } 104 | 105 | if ( $contact_form->id() !== $this->main->get_selected_form_id() ) { 106 | return; 107 | } 108 | 109 | // TODO: костыль, почему-то get_locale() всегда дефолтное значение возвращает 110 | switch_to_locale( apply_filters( 'awooc_letter_locale', get_option( 'WPLANG' ) ) ); 111 | 112 | $mail_body = $submission->get_posted_data(); 113 | 114 | [ $posted_text, $posted_email, $posted_tel ] = $this->prepare_posted_data( $mail_body ); 115 | 116 | $mail = $contact_form->prop( 'mail' ); 117 | 118 | ob_start(); 119 | 120 | load_template( 121 | $this->main->get_template( 'email.php' ), 122 | true, 123 | apply_filters( 124 | 'awooc_letter_args', 125 | [ 126 | 'letter_data' => [ 127 | 'name' => $posted_text, 128 | 'email' => $posted_email, 129 | 'phone' => $posted_tel, 130 | ], 131 | 'letter_meta' => [ 132 | 'ip' => [ 133 | 'label' => esc_html__( 'IP', 'art-woocommerce-order-one-click' ), 134 | 'value' => $submission->get_meta( 'remote_ip' ), 135 | ], 136 | 'time' => [ 137 | 'label' => esc_html__( 'Date', 'art-woocommerce-order-one-click' ), 138 | 'value' => $submission->get_meta( 'timestamp' ), 139 | ], 140 | 'url' => [ 141 | 'label' => esc_html__( 'Domain', 'art-woocommerce-order-one-click' ), 142 | 'value' => $submission->get_meta( 'url' ), 143 | ], 144 | ], 145 | 'product_data' => $this->get_parse_mail_body( $mail_body['awooc-hidden-data'] ), 146 | ], 147 | $this, 148 | $submission 149 | ) 150 | ); 151 | 152 | $mail['body'] = ob_get_clean(); 153 | 154 | $contact_form->set_properties( [ 'mail' => $mail ] ); 155 | } 156 | 157 | 158 | public function get_parse_mail_body( $text ): array { 159 | 160 | $result = []; 161 | 162 | $lines = array_filter( array_map( 'trim', explode( "\n", $text ) ) ); 163 | 164 | unset( $lines[0], $lines[1] ); 165 | 166 | foreach ( $lines as $line ) { 167 | 168 | $parts = explode( ':', $line, 2 ); 169 | 170 | $key = trim( $parts[0] ); 171 | $value = trim( $parts[1] ); 172 | 173 | if ( empty( $value ) ) { 174 | continue; 175 | } 176 | 177 | $value = html_entity_decode( preg_replace( '/\s+/', ' ', $value ) ); 178 | 179 | $result[ $key ] = $value; 180 | } 181 | 182 | return $result; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /includes/create-cf7-field.php: -------------------------------------------------------------------------------- 1 | name ) ) { 41 | return ''; 42 | } 43 | 44 | $atts = []; 45 | $class = wpcf7_form_controls_class( $tag->type ) . ' awooc-hidden-data'; 46 | $atts['class'] = apply_filters( 'awooc_class_hidden_field', $tag->get_class_option( $class ) ); 47 | $atts['id'] = $tag->get_id_option(); 48 | $value = (string) reset( $tag->values ); 49 | $value = $tag->get_default_option( $value ); 50 | $atts['value'] = $value; 51 | $atts['type'] = 'hidden'; 52 | $atts['name'] = $tag->name; 53 | $atts = wpcf7_format_atts( $atts ); 54 | 55 | return sprintf( '', sanitize_html_class( $tag->name ), $atts ); 56 | } 57 | 58 | 59 | /** 60 | * Проверка формы 61 | * 62 | * @param WPCF7_Validation $result результат валидации. 63 | * @param WPCF7_FormTag $tag объект кнопкок. 64 | * 65 | * @return mixed 66 | */ 67 | function awooc_fields_validation_filter( $result, $tag ) { 68 | 69 | $name = $tag->name; 70 | 71 | // @codingStandardsIgnoreLine 72 | $value = isset( $_POST[ $name ] ) ? sanitize_text_field( $_POST[ $name ] ) : ''; 73 | 74 | if ( $tag->is_required() && '' === $value ) { 75 | $result->invalidate( $tag, wpcf7_get_message( 'invalid_required' ) ); 76 | } 77 | 78 | return $result; 79 | } 80 | 81 | 82 | /** 83 | * Подключаем калбек скрытого поля для сбора данных 84 | */ 85 | function awooc_fields_add_tag_generator_address() { 86 | 87 | $tag_generator = WPCF7_TagGenerator::get_instance(); 88 | $tag_generator->add( 89 | 'awooc_hidden', 90 | __( 'AWOOC hide field', 'art-woocommerce-order-one-click' ), 91 | 'awooc_tag_generator_hidden', 92 | [ 'version' => '2' ] 93 | ); 94 | } 95 | 96 | 97 | /** 98 | * Форма окна для заполнения поля 99 | */ 100 | function awooc_tag_generator_hidden() { 101 | 102 | $type = 'awooc_hidden'; 103 | 104 | /* translators: %s: window description */ 105 | $description = __( 'Generate a special hidden multi-line field. See %s.', 'art-woocommerce-order-one-click' ); 106 | 107 | $desc_link = wpcf7_link( 108 | __( 'https://wpruse.ru/my-plugins/order-one-click/', 'art-woocommerce-order-one-click' ), 109 | __( 'the description for details', 'art-woocommerce-order-one-click' ) 110 | ); 111 | 112 | ?> 113 |
114 |
115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 124 | 130 | 131 | 132 |
123 |
133 |
134 |
135 | 136 |
137 | 147 | 148 |
149 | 154 |
155 | 156 |
157 | 158 |

178 |
179 | ' . $value['title'] . ''; 41 | } else { 42 | $title = ''; 43 | } 44 | 45 | ?> 46 |
> 47 | 48 |
49 | 50 |
51 |
52 | '', 84 | 'type' => '', 85 | 'label' => '', 86 | ]; 87 | } 88 | 89 | 90 | /** 91 | * Произвольное поле для сообщений 92 | * 93 | * @param array $value массив аргументов поля. 94 | * 95 | * @since 2.0.0 96 | */ 97 | public static function text_notice( array $value ): void { 98 | 99 | $style = ''; 100 | 101 | if ( $value['style'] ) { 102 | $style = 'style="' . $value['style'] . '"'; 103 | } 104 | 105 | ?> 106 |
> 107 | 108 |
109 | 110 | 125 |
126 |
127 | 143 |
144 |
145 | 161 |
162 |
163 |
164 | 180 |
181 |
182 |
183 |
184 | '; 200 | } 201 | } 202 | 203 | 204 | /** 205 | * Закрывающий тег основного контента 206 | * 207 | * @param array $value массив аргументов поля. 208 | * 209 | * @since 2.2.6 210 | */ 211 | public static function main_close( array $value ): void { 212 | 213 | if ( ! empty( $value['id'] ) ) { 214 | echo ''; 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /classes/RequestHandler.php: -------------------------------------------------------------------------------- 1 | main = $main; 50 | } 51 | 52 | 53 | public function init_hooks(): void { 54 | 55 | add_action( 'wp_ajax_nopriv_awooc_ajax_product_form', [ $this, 'ajax_callback' ] ); 56 | add_action( 'wp_ajax_awooc_ajax_product_form', [ $this, 'ajax_callback' ] ); 57 | } 58 | 59 | 60 | protected function init_post_data(): void { 61 | 62 | // phpcs:disable WordPress.Security.NonceVerification.Missing 63 | $this->product_id = sanitize_text_field( wp_unslash( $_POST['id'] ?? 0 ) ); 64 | $this->product_qty = sanitize_text_field( wp_unslash( $_POST['quantity'] ?? 1 ) ); 65 | $this->attributes = $this->sanitize_attributes( map_deep( wp_unslash( $_POST['attributes'] ?? [] ), 'sanitize_text_field' ) ); 66 | // phpcs:enable WordPress.Security.NonceVerification.Missing 67 | } 68 | 69 | 70 | /** 71 | * Возвратна функция дл загрузки данных во всплывающем окне 72 | */ 73 | public function ajax_callback(): void { 74 | 75 | /** 76 | * Если включено кеширование, то нонсу не проверяем. 77 | */ 78 | if ( ! defined( 'WP_CACHE' ) ) { 79 | check_ajax_referer( 'awooc-nonce', 'nonce' ); 80 | } 81 | 82 | $this->init_post_data(); 83 | 84 | if ( empty( $this->product_id ) ) { 85 | wp_send_json_error( 86 | esc_html__( 87 | 'Something is wrong with sending data. Unable to get product ID. Disable the output in the popup window or contact the developers of the plugin', 88 | 'art-woocommerce-order-one-click' 89 | ), 90 | 403 91 | ); 92 | } 93 | 94 | $product = $this->get_product(); 95 | 96 | $send_data = $this->prepare_send_data( $product ); 97 | 98 | $response_data = $this->prepare_response_data( $send_data, $product ); 99 | 100 | wp_send_json_success( $response_data, 200 ); 101 | } 102 | 103 | 104 | protected function prepare_send_data( WC_Product $product ): array { 105 | 106 | $send_data = [ 107 | 'main' => $this->main, 108 | 'product' => $product, 109 | 'product_qty' => $this->product_qty, 110 | ]; 111 | 112 | if ( ! empty( $this->attributes ) ) { 113 | $send_data['attributes'] = $this->attributes; 114 | } 115 | 116 | return $send_data; 117 | } 118 | 119 | 120 | protected function prepare_response_data( array $send_data, WC_Product $product ): array { 121 | 122 | $data = apply_filters( 123 | 'awooc_data_ajax', 124 | [ 125 | 'elements' => 'full', 126 | 'productId' => $product->get_id(), 127 | 'productQty' => $send_data['product_qty'], 128 | 'toPopup' => ( new Popup( $send_data ) )->get_response(), 129 | 'toMail' => ( new Mail( $send_data ) )->get_response(), 130 | 'toAnalytics' => ( new Analytics( $send_data ) )->get_response(), 131 | ], 132 | $product 133 | ); 134 | 135 | if ( ! empty( $this->elements ) ) { 136 | $data['elements'] = 'empty'; 137 | } 138 | 139 | return $data; 140 | } 141 | 142 | 143 | /** 144 | * 145 | * @param int $id 146 | * 147 | * @return WC_Product 148 | */ 149 | public function get_product( int $id = 0 ): WC_Product { 150 | 151 | if ( ! empty( $this->product_id ) ) { 152 | $id = $this->product_id; 153 | } 154 | 155 | return wc_get_product( $id ); 156 | } 157 | 158 | 159 | /** 160 | * @param int $qty 161 | * 162 | * @return int 163 | */ 164 | protected function get_qty( int $qty = 1 ): int { 165 | 166 | if ( ! empty( $this->product_qty ) ) { 167 | $qty = $this->product_qty; 168 | } 169 | 170 | return $qty; 171 | } 172 | 173 | 174 | /** 175 | * @param array $posted_data 176 | * 177 | * @return array 178 | */ 179 | protected function prepare_posted_data( array $posted_data ): array { 180 | 181 | $posted_data = map_deep( wp_unslash( $posted_data ), 'sanitize_text_field' ); 182 | 183 | return [ 184 | $posted_data['awooc-text'] ?? '', 185 | $posted_data['awooc-email'] ?? '', 186 | $posted_data['awooc-tel'] ?? '', 187 | (int) ( $posted_data['awooc_product_id'] ?? 0 ), 188 | (float) ( $posted_data['awooc_product_qty'] ?? 1 ), 189 | (int) ( $posted_data['awooc_customer_id'] ?? 0 ), 190 | ]; 191 | } 192 | 193 | 194 | /** 195 | * @param string $field 196 | * 197 | * @return string 198 | */ 199 | protected function sanitize_field( string $field ): string { 200 | 201 | return sanitize_text_field( wp_unslash( $field ) ); 202 | } 203 | 204 | 205 | protected function sanitize_attributes( $attributes ): array { 206 | 207 | if ( empty( $attributes ) ) { 208 | return []; 209 | } 210 | 211 | $attr = []; 212 | 213 | foreach ( $attributes as $key => $val ) { 214 | $attr[ str_replace( 'attribute_', '', $key ) ] = sanitize_text_field( wp_unslash( $val ) ); 215 | } 216 | 217 | return $attr; 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | A custom set of code standard rules to check for WordPress code. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | . 25 | */\.github/* 26 | */examples/* 27 | */vendor/* 28 | 29 | 30 | */\.idea/* 31 | */\.github/* 32 | */\.idea/* 33 | */\.make/* 34 | */\.wordpress-org/* 35 | */assets/* 36 | */languages/* 37 | */lib/* 38 | */node_modules/* 39 | */src/js/* 40 | */vendor/* 41 | *\.js 42 | *\.mo 43 | *\.po 44 | *\.twig 45 | *\.css 46 | *\.scss 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 0 126 | 127 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | 5 | ## [3.1.8] - 2025-05-03 6 | 7 | ### Added 8 | - Добавлено: поддержка WPML для мультиязычности 9 | ### Changed 10 | 11 | ### Fixed 12 | - Исправлено: поведение при дефолтных атрибутах и использовании плагина Variation Swatches for WooCommerce 13 | - Исправлено: поддержка WP 6.8 14 | 15 | ## [3.1.7] - 2025-03-11 16 | 17 | ### Added 18 | 19 | ### Changed 20 | 21 | ### Fixed 22 | - Исправлено: ошибка версионности 23 | 24 | ## [3.1.5] - 2025-02-06 25 | 26 | ### Added 27 | 28 | ### Changed 29 | 30 | ### Fixed 31 | - Исправлено: поддержка дробного количества при создании заказа 32 | 33 | ## [3.1.4] - 2025-02-05 34 | 35 | ### Added 36 | 37 | ### Changed 38 | 39 | ### Fixed 40 | - Исправлено: поведение скрытого поля если оно указано в форме 41 | 42 | ## [3.1.3] - 2025-02-05 43 | 44 | ### Added 45 | - Добавлено: фильтр `awooc_quantity_input_args` изменения аттрибутов поля количества в окне 46 | 47 | ### Changed 48 | 49 | ### Fixed 50 | - Исправлено: пересчет дробных значений количества в окне 51 | 52 | ## [3.1.2] - 2025-02-04 53 | 54 | ### Added 55 | - Добавлено: настройка для включения изменения названия заказа в литинге заказов 56 | 57 | ### Changed 58 | 59 | ### Fixed 60 | - Исправлено: переводы 61 | 62 | ## [3.1.1] - 2025-02-01 63 | 64 | ### Added 65 | 66 | ### Changed 67 | 68 | ### Fixed 69 | - Исправлено: приведение итого товаров к числу и обратно в окне 70 | 71 | ## [3.1.0] - 2025-01-30 72 | 73 | ### Added 74 | - Добавлено: поддержка темы Woodmart 75 | - Добавлено: кнопки +\- на поле количества в окне 76 | - Добавлено: блокировка кнопки Отправить при отправке формы 77 | 78 | ### Changed 79 | - Изменено: рефакторинг js - переписан на ванильный (по возможности), приведен к WPCS 80 | - Изменено: обновление переводов 81 | - Изменено: поле количество вынесено в отдельный файл, независимый от WC 82 | - Изменено: отлажено поведение поля количество 83 | - Изменено: рефакторинг основных классов обработки запросов 84 | 85 | ### Fixed 86 | - Исправлено: подключение переводов на хук init 87 | - Исправлено: передача в аналитику верного id товара 88 | - Исправлено: переводы в кастовном теплейте письма 89 | - Исправлено: исправлено подключени скриптов-стилей в админке только на странице настроек 90 | - Исправлено: вывод данных в кастомном темплейте письма и внешний вид 91 | 92 | ## [3.0.1] - 2025-01-12 93 | 94 | ### Added 95 | - Добавлено: дополнительный фильтр awooc_added_hidden_fields для подстановки скрытых полей 96 | 97 | ### Changed 98 | 99 | ### Fixed 100 | - Исправлено: 101 | 102 | ## [3.0.0] - 2024-12-04 [rc9] 103 | 104 | ### Added 105 | - Добавлено: вывод названия заказа в списке заказа админки 106 | 107 | ### Changed 108 | 109 | ### Fixed 110 | - Исправлено: в фильтре awooc_order_address_arg добавлен аргумент передачи данных с формы 111 | 112 | ## [3.0.0] - 2024-11-18 [rc8] 113 | 114 | ### Added 115 | 116 | 117 | ### Changed 118 | 119 | 120 | ### Fixed 121 | - Исправлено: пересчет стоимости при изменении количества во всплывашке 122 | 123 | ## [3.0.0] - 2024-08-12 [rc7] 124 | 125 | ### Added 126 | - Добавлено: поддержка WP Rocket (принудительное подключение скриптов CF7) 127 | - Добавлено: вывод кнопки на странице каталога по настройке 128 | 129 | ### Changed 130 | - Изменено: рефакторинг, приведение к PSR4 131 | - Изменено: приведение к WPCS 132 | 133 | ### Fixed 134 | - Исправлено: пересчет стоимости при изменении количества во всплывашке 135 | 136 | ## [3.0.0] - 2023-11-07 [rc6] 137 | 138 | ### Added 139 | - Добавлено: фильтр опций `awooc_select_elements_item` 140 | 141 | ### Changed 142 | - Изменено: обработка сбора данных при открытии окна 143 | - Изменено: правки стилей всплывающего окна 144 | 145 | ### Fixed 146 | - Исправлено: 147 | 148 | ## [3.0.0] - 2023-11-07 [rc5] 149 | 150 | ### Added 151 | - Добавлено: поддержка плагина Polylang 152 | 153 | ### Changed 154 | - Изменено: рефакторинг 155 | - Изменено: обновлены переводы 156 | 157 | ### Fixed 158 | - Исправлено: 159 | 160 | ## [3.0.0] - 2023-03-12 [rc4] 161 | 162 | ### Added 163 | - Добавлено: поддержка плагина Variation Swatches for WooCommerce by CartFlows передача атрибутов на архивах 164 | - Добавлено: привязка заказа к залогиненому пользователю 165 | 166 | ### Changed 167 | - Изменено: рефакторинг 168 | 169 | ### Fixed 170 | - Исправлено: 171 | 172 | ## [3.0.0] - 2023-02-18 [rc3] 173 | 174 | ### Added 175 | - Добавлено: вывод управления количеством во всплывающем окне 176 | - Добавлено: хук `awooc_create_order` при создании заказа, передает объект формы, объект заказа и данные из формы 177 | 178 | ### Changed 179 | - Изменено: удален спецшорткод `awooc-hidden-data`, добавлены отдельные скрытые поля 180 | - Изменено: рефакторинг создание формы при первой активации плагина 181 | 182 | ### Fixed 183 | - Исправлено: ошибка при потери объекта товара при выводе шорткодов и блоков товаров 184 | - Исправлено: подключени скрипта только на странице настроек в админке 185 | 186 | ## [3.0.0] - 2023-01-28 [rc2] 187 | 188 | ### Added 189 | 190 | ### Changed 191 | 192 | ### Fixed 193 | - исправлена логика вывода произвольной надписи на кнопке в специальном режиме 194 | - ошибка при потери объекта товара при выводе шорткодов и блоков товара 195 | 196 | ## [3.0.0] - 2023-01-28 [rc1] 197 | 198 | ### Added 199 | 200 | ### Changed 201 | - удаление лишней настройки глобального подключения стилей, стили и скрипты включаются при подключении кнопки 202 | - рефакторинг класса создания заказов 203 | - рефакторинг класса настроек 204 | 205 | ### Fixed 206 | - ошибка предпреждения если не объекта поста 207 | - исправлено создание формы CF7 при первой активации плагина 208 | - исправлено срабатывание триггреа `awooc_mail_sent_trigger` отправки письма с любой формы 209 | 210 | ## [3.0.0] - 2023-01-22 [Beta] 211 | 212 | ### Added 213 | - вывод кнопки Быстрый заказ через подмену файлов теплейтов 214 | - автолоад классов и файлов 215 | - шаблон письма 216 | - возможность изменения файлов через дочернюю тему по аналогии как в Вукомерсе 217 | 218 | ### Changed 219 | - рефакторинг, изменение архитектуры кода 220 | - создание опций настроек без автозагрузки 221 | 222 | ### Fixed 223 | 224 | 225 | -------------------------------------------------------------------------------- /classes/Enqueue.php: -------------------------------------------------------------------------------- 1 | main = $main; 31 | $this->suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; 32 | } 33 | 34 | 35 | public function init_hooks(): void { 36 | 37 | add_action( 'wp_enqueue_scripts', [ $this, 'enqueue' ], 100 ); 38 | add_action( 'wp_enqueue_scripts', [ $this, 'localize' ], 110 ); 39 | add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue' ] ); 40 | } 41 | 42 | 43 | /** 44 | * Подключаем нужные стили и скрипты 45 | */ 46 | public function enqueue(): void { 47 | 48 | wp_register_script( 49 | 'awooc-scripts', 50 | AWOOC_PLUGIN_URI . 'assets/js/awooc-public-script' . $this->suffix . '.js', 51 | [ 'jquery', 'jquery-blockui', 'woocommerce', 'wc-add-to-cart-variation' ], 52 | AWOOC_PLUGIN_VER, 53 | false 54 | ); 55 | 56 | wp_register_style( 57 | 'awooc-styles', 58 | AWOOC_PLUGIN_URI . 'assets/css/awooc-styles' . $this->suffix . '.css', 59 | [], 60 | AWOOC_PLUGIN_VER 61 | ); 62 | } 63 | 64 | 65 | /** 66 | * Подключаем нужные стили и скрипты 67 | * 68 | * @since 2.0.0 69 | */ 70 | public function admin_enqueue(): void { 71 | 72 | if ( 'woocommerce_page_wc-settings' !== get_current_screen()->id ) { 73 | return; 74 | } 75 | 76 | //phpcs:ignore WordPress.Security.NonceVerification.Recommended 77 | if ( empty( $_GET['tab'] ) || 'awooc_settings' !== $_GET['tab'] ) { 78 | return; 79 | } 80 | 81 | wp_enqueue_style( 82 | 'admin-awooc-styles', 83 | AWOOC_PLUGIN_URI . 'assets/css/admin-style' . $this->suffix . '.css', 84 | [], 85 | AWOOC_PLUGIN_VER 86 | ); 87 | 88 | wp_enqueue_script( 89 | 'admin-awooc-script', 90 | AWOOC_PLUGIN_URI . 'assets/js/awooc-admin-script.js', 91 | [ 'jquery' ], 92 | AWOOC_PLUGIN_VER, 93 | false 94 | ); 95 | 96 | wp_localize_script( 97 | 'admin-awooc-script', 98 | 'awooc_admin', 99 | [ 100 | 'mode_catalog' => __( 101 | 'On the pages of the categories and the store itself, the Add to Cart buttons are disabled. On the product page, the "Add to cart" button is hidden and the "Order" button appears.', 102 | 'art-woocommerce-order-one-click' 103 | ), 104 | 'mode_normal' => __( 105 | 'The button "Add to cart" works in the normal mode, that is, goods can be added to the cart and at the same time ordered in one click', 106 | 'art-woocommerce-order-one-click' 107 | ), 108 | 'mode_in_stock' => __( 109 | 'The Order button will appear automatically if: Price not available; stock status "In Unfulfilled Order"; stock status "Out of stock"; inventory management is enabled at item level and preorders allowed', 110 | 'art-woocommerce-order-one-click' 111 | ), 112 | 'mode_special' => __( 113 | 'When turned on, it works the same way as normal mode. But if the goods have no price or the product out of stock, then only the Order button will appear.', 114 | 'art-woocommerce-order-one-click' 115 | ), 116 | ] 117 | ); 118 | } 119 | 120 | 121 | /** 122 | * @since 3.0.0 123 | */ 124 | public function localize(): void { 125 | 126 | if ( ! class_exists( 'Woocommerce' ) ) { 127 | return; 128 | } 129 | 130 | if ( is_admin() ) { 131 | return; 132 | } 133 | 134 | wp_localize_script( 135 | 'awooc-scripts', 136 | 'awooc_scripts_ajax', 137 | [ 138 | 'url' => admin_url( $this->main->get_ajax_url() ), 139 | 'nonce' => wp_create_nonce( 'awooc-nonce' ), 140 | ] 141 | ); 142 | 143 | wp_localize_script( 144 | 'awooc-scripts', 145 | 'awooc_scripts_translate', 146 | [ 147 | 'product_qty' => __( 'Quantity: ', 'art-woocommerce-order-one-click' ), 148 | 'title' => __( 'Title: ', 'art-woocommerce-order-one-click' ), 149 | 'price' => __( 'Price: ', 'art-woocommerce-order-one-click' ), 150 | 'sku' => __( 'SKU: ', 'art-woocommerce-order-one-click' ), 151 | 'formatted_sum' => __( 'Amount: ', 'art-woocommerce-order-one-click' ), 152 | 'attributes_list' => __( 'Attributes: ', 'art-woocommerce-order-one-click' ), 153 | 'product_data_title' => __( 'Information about the selected product', 'art-woocommerce-order-one-click' ), 154 | 'product_link' => __( 'Link to the product: ', 'art-woocommerce-order-one-click' ), 155 | 'title_close' => __( 'Click to close', 'art-woocommerce-order-one-click' ), 156 | ] 157 | ); 158 | 159 | wp_localize_script( 160 | 'awooc-scripts', 161 | 'awooc_scripts_settings', 162 | [ 163 | 'mode' => $this->main->get_mode()->get_mode_value(), 164 | 'template' => awooc_popup(), 165 | 'custom_label' => awooc_custom_button_label(), 166 | 'popup' => apply_filters( 167 | 'awooc_popup_setting', 168 | [ 169 | 'mailsent_timeout' => 3000, 170 | 'invalid_timeout' => 5000, 171 | 'cf7_form_id' => $this->main->get_selected_form_id(), 172 | 'price_decimal_sep' => get_option( 'woocommerce_price_decimal_sep' ), // Десятичный разделитель 173 | 'price_num_decimals' => get_option( 'woocommerce_price_num_decimals' ), // Число дробных знаков 174 | 'price_thousand_sep' => get_option( 'woocommerce_price_thousand_sep' ), // Разделитель тысяч 175 | 'css' => [ 176 | 'width' => 'calc(100vw - 1rem)', 177 | 'maxWidth' => '600px', 178 | 'maxHeight' => 'calc(100vh - 1rem)', 179 | 'top' => '50%', 180 | 'left' => '50%', 181 | 'border' => '4px', 182 | 'borderRadius' => '4px', 183 | 'cursor' => 'default', 184 | 'overflowY' => 'auto', 185 | 'boxShadow' => '0px 0px 3px 0px rgba(0, 0, 0, 0.2)', 186 | 'zIndex' => '1000000', 187 | 'transform' => 'translate(-50%, -50%)', 188 | 'overscroll-behavior' => 'contain', 189 | ], 190 | 'overlay' => [ 191 | 'zIndex' => '100000', 192 | 'backgroundColor' => '#000', 193 | 'opacity' => 0.6, 194 | 'cursor' => 'wait', 195 | ], 196 | 'fadeIn' => '400', 197 | 'fadeOut' => '400', 198 | 'focusInput' => false, 199 | ] 200 | ), 201 | ] 202 | ); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /classes/Main.php: -------------------------------------------------------------------------------- 1 | init_hooks(); 89 | ( new Enqueue( $this ) )->init_hooks(); 90 | ( new RequestHandler( $this ) )->init_hooks(); 91 | ( new OrderCreator( $this ) )->init_hooks(); 92 | ( new EmailModifier( $this ) )->init_hooks(); 93 | 94 | $this->front = new Front( $this ); 95 | $this->front->init_hooks(); 96 | 97 | Meta::init_hooks(); 98 | 99 | $this->templater = new Templater(); 100 | $this->mode = new Mode(); 101 | 102 | $this->init_hooks(); 103 | } 104 | 105 | 106 | /** 107 | * Init. 108 | * Initialize plugin parts. 109 | * 110 | * @since 1.8.0 111 | */ 112 | public function init_hooks(): void { 113 | 114 | add_action( 'init', [ $this, 'load_textdomain' ] ); 115 | 116 | add_action( 'wp_ajax_awooc_rated', [ $this, 'add_rated' ] ); 117 | 118 | add_filter( 'plugin_action_links_' . AWOOC_PLUGIN_FILE, [ $this, 'add_plugin_action_links' ], 10, 1 ); 119 | 120 | add_filter( 'woocommerce_get_settings_pages', [ $this, 'add_awooc_admin_settings' ], 15 ); 121 | } 122 | 123 | 124 | /** 125 | * Plugin action links. 126 | * Add links to the plugins.php page below the plugin name 127 | * and besides the 'activate', 'edit', 'delete' action links. 128 | * 129 | * @param array $links List of existing links. 130 | * 131 | * @return array List of modified links. 132 | * @since 1.8.0 133 | */ 134 | public function add_plugin_action_links( array $links ): array { 135 | 136 | $plugin_links = [ 137 | 'settings' => sprintf( 138 | '%s', 139 | esc_url( admin_url( 'admin.php?page=wc-settings&tab=awooc_settings' ) ), 140 | esc_html__( 'Settings', 'art-woocommerce-order-one-click' ) 141 | ), 142 | ]; 143 | 144 | return array_merge( $plugin_links, $links ); 145 | } 146 | 147 | 148 | /** 149 | * Settings. 150 | * Include the WooCommerce settings class. 151 | * 152 | * @param array $settings приходит массив настроек. 153 | * 154 | * @return array 155 | * @since 1.8.0 156 | * @since 1.8.5 157 | */ 158 | public function add_awooc_admin_settings( array $settings ): array { 159 | 160 | $settings[] = include __DIR__ . '/Admin/Settings.php'; 161 | 162 | new Settings(); 163 | ( new Settings_Fields() )->init_hooks(); 164 | 165 | return $settings; 166 | } 167 | 168 | 169 | /** 170 | * Textdomain. 171 | * Load the textdomain based on WP language. 172 | * 173 | * @since 2.0.0 174 | */ 175 | public function load_textdomain(): void { 176 | 177 | load_plugin_textdomain( 178 | 'art-woocommerce-order-one-click', 179 | false, 180 | dirname( AWOOC_PLUGIN_FILE ) . '/languages/' 181 | ); 182 | } 183 | 184 | 185 | /** 186 | * Instance. 187 | * A global instance of the class. Used to retrieve the instance 188 | * to use on other files/plugins/themes. 189 | * 190 | * @return object Instance of the class. 191 | * @since 1.8.0 192 | */ 193 | public static function instance() { 194 | 195 | if ( is_null( self::$instance ) ) : 196 | self::$instance = new self(); 197 | endif; 198 | 199 | return self::$instance; 200 | } 201 | 202 | 203 | /** 204 | * Приглашение поставить оценку 205 | */ 206 | public function add_rated(): void { 207 | 208 | if ( ! current_user_can( 'manage_woocommerce' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown 209 | wp_die( - 1 ); 210 | } 211 | 212 | update_option( 'woocommerce_awooc_text_rated', 1 ); 213 | 214 | wp_die(); 215 | } 216 | 217 | 218 | /** 219 | * Проверка на простой товар 220 | * 221 | * @return bool 222 | */ 223 | public function is_simple(): bool { 224 | 225 | $product = wc_get_product(); 226 | 227 | if ( is_null( $product ) ) { 228 | $product = $GLOBALS['product']; 229 | } 230 | 231 | return $product->is_type( 'simple' ); 232 | } 233 | 234 | 235 | /** 236 | * @return \Art\AWOOC\Front 237 | */ 238 | public function get_front(): Front { 239 | 240 | return $this->front; 241 | } 242 | 243 | 244 | /** 245 | * @return \Art\AWOOC\Templater 246 | */ 247 | public function get_templater(): Templater { 248 | 249 | return $this->templater; 250 | } 251 | 252 | 253 | /** 254 | * @param string $template_name 255 | * 256 | * @return string 257 | */ 258 | public function get_template( string $template_name ): string { 259 | 260 | return $this->get_templater()->get_template( $template_name ); 261 | } 262 | 263 | 264 | /** 265 | * @return \Art\AWOOC\Mode 266 | */ 267 | public function get_mode(): Mode { 268 | 269 | return $this->mode; 270 | } 271 | 272 | 273 | /** 274 | * @return string[] 275 | */ 276 | public function get_modes(): array { 277 | 278 | return $this->mode->get_modes(); 279 | } 280 | 281 | 282 | /** 283 | * @return int 284 | * @since 3.0.0 285 | */ 286 | public function get_selected_form_id(): int { 287 | 288 | return (int) apply_filters( 'awooc_selected_form_id', get_option( 'woocommerce_awooc_select_form' ) ); 289 | } 290 | 291 | 292 | /** 293 | * @return string 294 | * @since 3.0.0 295 | */ 296 | public function get_ajax_url(): string { 297 | 298 | $url = 'admin-ajax.php'; 299 | 300 | if ( class_exists( 'Polylang' ) && ! defined( 'WP_CLI' ) ) { 301 | $url = add_query_arg( [ 'lang' => pll_current_language() ], $url ); 302 | } 303 | 304 | if ( defined( 'ICL_LANGUAGE_CODE' ) && ! defined( 'WP_CLI' ) ) { 305 | $url = add_query_arg( [ 'lang' => ICL_LANGUAGE_CODE ], $url ); 306 | } 307 | 308 | return $url; 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /classes/Prepare/Popup.php: -------------------------------------------------------------------------------- 1 | $this->title(), 11 | 'image' => $this->image(), 12 | 'price' => $this->formatted_price(), 13 | 'sku' => $this->formatted_sku(), 14 | 'attr' => $this->formatted_attr(), 15 | 'qty' => $this->formatted_qty(), 16 | 'sum' => $this->formatted_sum(), 17 | 'form' => $this->select_form(), 18 | ]; 19 | 20 | if ( $this->main->get_mode()->is_mode_catalog() || $this->main->get_mode()->is_mode_preorder() ) { 21 | unset( $data['qty'] ); 22 | } 23 | 24 | return $data; 25 | } 26 | 27 | 28 | /** 29 | * Получаем цену товара 30 | * 31 | * @since 3.0.0 32 | */ 33 | protected function formatted_price() { 34 | 35 | if ( ! $this->price() ) { 36 | return ''; 37 | } 38 | 39 | add_filter( 'wc_price', [ $this, 'formatted_wc_price' ], 10, 5 ); 40 | 41 | return apply_filters( 42 | 'awooc_popup_price_html', 43 | sprintf( 44 | '%s%s', 45 | apply_filters( 'awooc_popup_price_label', __( 'Price: ', 'art-woocommerce-order-one-click' ) ), 46 | wc_price( $this->price() ) 47 | ), 48 | $this->get_product() 49 | ); 50 | } 51 | 52 | 53 | /** 54 | * Форматирование артикула 55 | * 56 | * @return string 57 | * @since 2.3.2 58 | */ 59 | protected function formatted_sku(): string { 60 | 61 | return wp_kses_post( 62 | apply_filters( 63 | 'awooc_popup_sku_html', 64 | sprintf( 65 | '%s%s', 66 | apply_filters( 'awooc_popup_sku_label', __( 'SKU: ', 'art-woocommerce-order-one-click' ) ), 67 | $this->sku() 68 | ), 69 | $this->get_product() 70 | ) 71 | ); 72 | } 73 | 74 | 75 | /** 76 | * Форматирование атрибутов вариативного товара 77 | * 78 | * @return string 79 | * @since 2.3.2 80 | */ 81 | protected function formatted_attr(): string { 82 | 83 | if ( $this->is_simple() ) { 84 | return ''; 85 | } 86 | 87 | $attributes_html = ''; 88 | 89 | if ( $this->get_attributes_alt_method() ) { 90 | $attributes_html = sprintf( 91 | '%s
%s', 92 | apply_filters( 'awooc_popup_attr_label', esc_html__( 'Attributes: ', 'art-woocommerce-order-one-click' ) ), 93 | implode( '; ', $this->get_attributes_alt_method() ) 94 | ); 95 | } 96 | 97 | return $attributes_html; 98 | } 99 | 100 | 101 | /** 102 | * Форматирование количества 103 | * 104 | * @return string 105 | * @since 3.0.0 106 | */ 107 | protected function formatted_qty(): string { 108 | 109 | $allowed_html = [ 110 | 'div' => [ 111 | 'class' => 'quantity', 112 | ], 113 | 'span' => [ 114 | 'class' => [], 115 | 116 | ], 117 | 'label' => [ 'class' => [] ], 118 | 'input' => [ 119 | 'type' => [], 120 | 'id' => [], 121 | 'class' => [], 122 | 'name' => [], 123 | 'value' => [], 124 | 'title' => [], 125 | 'size' => [], 126 | 'min' => [], 127 | 'max' => [], 128 | 'step' => [], 129 | 'placeholder' => [], 130 | 'inputmode' => [], 131 | 'autocomplete' => [], 132 | ], 133 | 'button' => [ 134 | 'type' => [], 135 | 'id' => [], 136 | 'class' => [], 137 | 'name' => [], 138 | 'value' => [], 139 | 'title' => [], 140 | 'size' => [], 141 | 'min' => [], 142 | 'max' => [], 143 | 'step' => [], 144 | 'placeholder' => [], 145 | 'inputmode' => [], 146 | 'autocomplete' => [], 147 | ], 148 | ]; 149 | 150 | return wp_kses( 151 | apply_filters( 152 | 'awooc_popup_qty_html', 153 | sprintf( 154 | '%s%s', 155 | apply_filters( 'awooc_popup_qty_label', __( 'Quantity: ', 'art-woocommerce-order-one-click' ) ), 156 | $this->get_quantity_input() 157 | ), 158 | $this->get_product() 159 | ), $allowed_html 160 | ); 161 | } 162 | 163 | 164 | /** 165 | * Получаем сумму товара 166 | * 167 | * @since 3.3.0 168 | */ 169 | protected function formatted_sum() { 170 | 171 | if ( ! $this->price() ) { 172 | return ''; 173 | } 174 | 175 | add_filter( 'wc_price', [ $this, 'formatted_wc_price' ], 10, 5 ); 176 | 177 | return apply_filters( 178 | 'awooc_popup_price_html', 179 | sprintf( 180 | '%s%s', 181 | apply_filters( 'awooc_popup_sum_label', __( 'Amount: ', 'art-woocommerce-order-one-click' ) ), 182 | wc_price( $this->get_sum() ) 183 | ), 184 | $this->get_product() 185 | ); 186 | } 187 | 188 | 189 | public function formatted_wc_price( $html_price, $price, $args, $unformatted_price, $original_price ): string { 190 | 191 | $negative = $price < 0; 192 | $formatted_price = ( $negative ? '-' : '' ) . sprintf( 193 | $args['price_format'], 194 | '' . get_woocommerce_currency_symbol( $args['currency'] ) . '', 195 | '' . $price . '' 196 | ); 197 | 198 | return '' . $formatted_price . ''; 199 | } 200 | 201 | 202 | /** 203 | * @return string 204 | */ 205 | protected function get_quantity_input(): string { 206 | 207 | $args = [ 208 | 'input_id' => uniqid( 'quantity_' ), 209 | 'input_name' => 'quantity', 210 | 'input_value' => $this->get_qty() !== null ? wc_stock_amount( wp_unslash( $this->get_qty() ) ) : $this->get_product()->get_min_purchase_quantity(), 211 | 'classes' => [ 'input-text', 'qty', 'text', 'awooc-popup-input-qty' ], 212 | 'min_value' => apply_filters( 'woocommerce_quantity_input_min', $this->get_product()->get_min_purchase_quantity(), $this->get_product() ), 213 | 'max_value' => apply_filters( 'woocommerce_quantity_input_max', $this->get_product()->get_max_purchase_quantity(), $this->get_product() ), 214 | 'step' => 1, 215 | 'pattern' => apply_filters( 'woocommerce_quantity_input_pattern', has_filter( 'woocommerce_stock_amount', 'intval' ) ? '[0-9]*' : '' ), 216 | 'inputmode' => apply_filters( 'woocommerce_quantity_input_inputmode', has_filter( 'woocommerce_stock_amount', 'intval' ) ? 'numeric' : '' ), 217 | 'product_name' => $this->get_product() ? $this->get_product()->get_title() : '', 218 | 'placeholder' => '', 219 | 'autocomplete' => 'off', 220 | 'readonly' => false, 221 | ]; 222 | 223 | $args['min_value'] = max( $args['min_value'], 0 ); 224 | $args['max_value'] = 0 < $args['max_value'] ? $args['max_value'] : ''; 225 | 226 | if ( '' !== $args['max_value'] && $args['max_value'] < $args['min_value'] ) { 227 | $args['max_value'] = $args['min_value']; 228 | } 229 | 230 | $args['type'] = 'number'; 231 | 232 | ob_start(); 233 | 234 | load_template( 235 | $this->main->get_template( 'quantity-input.php' ), 236 | true, 237 | apply_filters( 'awooc_quantity_input_args', $args, $this ) 238 | ); 239 | 240 | return ob_get_clean(); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /classes/Prepare/Prepare.php: -------------------------------------------------------------------------------- 1 | main = $data['main']; 51 | $this->product = $data['product']; 52 | $this->qty = $data['product_qty']; 53 | $this->attr = empty( $data['attributes'] ) ? [] : $data['attributes']; 54 | } 55 | 56 | 57 | /** 58 | * Метод обработки ответа 59 | * 60 | * @return array 61 | * @since 3.0.0 62 | */ 63 | abstract public function get_response(): array; 64 | 65 | 66 | /** 67 | * Объект товара 68 | * 69 | * @return \WC_Product 70 | * @since 3.0.0 71 | */ 72 | public function get_product(): WC_Product { 73 | 74 | return $this->product; 75 | } 76 | 77 | 78 | /** 79 | * Количество товара 80 | * 81 | * @return int 82 | * @since 3.0.0 83 | */ 84 | public function get_qty(): int { 85 | 86 | return $this->qty; 87 | } 88 | 89 | 90 | /** 91 | * ID товара 92 | * 93 | * @return int 94 | * @since 3.0.0 95 | */ 96 | public function id(): int { 97 | 98 | return $this->product->get_id(); 99 | } 100 | 101 | 102 | /** 103 | * Получение родительского ID 104 | * 105 | * @return int 106 | * @since 3.0.0 107 | */ 108 | public function parent_id(): int { 109 | 110 | $product_id = $this->product->get_parent_id(); 111 | 112 | if ( $this->is_simple() ) { 113 | $product_id = $this->product->get_id(); 114 | } 115 | 116 | return $product_id; 117 | } 118 | 119 | 120 | /** 121 | * Цена товара 122 | * 123 | * @since 3.0.0 124 | */ 125 | public function price(): string { 126 | 127 | return $this->product->get_price(); 128 | } 129 | 130 | 131 | /** 132 | * Получаем артикул товара 133 | * 134 | * @return string 135 | * @since 3.0.0 136 | */ 137 | public function sku(): string { 138 | 139 | $sku = $this->product->get_sku(); 140 | 141 | if ( ! $sku ) { 142 | $sku = __( 'N/A', 'woocommerce' ); 143 | } 144 | 145 | return $sku; 146 | } 147 | 148 | 149 | /** 150 | * Получение заголовка товара 151 | * 152 | * @return string 153 | * @since 3.0.0 154 | */ 155 | public function title(): string { 156 | 157 | return html_entity_decode( $this->product->get_name() ); 158 | } 159 | 160 | 161 | /** 162 | * Получаем изображение товара 163 | * 164 | * @return string 165 | * @since 3.0.0 166 | */ 167 | public function image(): string { 168 | 169 | $image = ''; 170 | 171 | $post_thumbnail_id = get_post_thumbnail_id( $this->id() ); 172 | 173 | if ( ! $post_thumbnail_id ) { 174 | $post_thumbnail_id = get_post_thumbnail_id( $this->parent_id() ); 175 | } 176 | 177 | if ( ! $post_thumbnail_id ) { 178 | $post_thumbnail_id = get_option( 'woocommerce_placeholder_image', 0 ); 179 | } 180 | 181 | $full_size_image = wp_get_attachment_image_src( $post_thumbnail_id, apply_filters( 'awooc_thumbnail_name', 'shop_single' ) ); 182 | 183 | if ( $full_size_image ) { 184 | $image = apply_filters( 185 | 'awooc_popup_image_html', 186 | sprintf( 187 | '%s', 188 | esc_url( $full_size_image[0] ), 189 | apply_filters( 'awooc_popup_image_alt', '' ), 190 | apply_filters( 'awooc_popup_image_classes', esc_attr( 'awooc-form-custom-order-img' ) ), 191 | esc_attr( $full_size_image[1] ), 192 | esc_attr( $full_size_image[2] ) 193 | ), 194 | $this->product 195 | ); 196 | } 197 | 198 | return $image; 199 | } 200 | 201 | 202 | /** 203 | * Получение атрибутов вариативного товара 204 | * 205 | * @return string 206 | * @since 3.0.0 207 | */ 208 | public function attributes(): string { 209 | 210 | if ( $this->is_simple() ) { 211 | return ''; 212 | } 213 | 214 | return implode( '; ', $this->get_attributes_alt_method() ); 215 | } 216 | 217 | 218 | /** 219 | * Получение атрибутов, если не сработает получение из ядра WC 220 | * 221 | * @return array 222 | * @since 2.4.0 223 | * @since 3.0.0 224 | */ 225 | public function get_attributes_alt_method(): array { 226 | 227 | if ( empty( $this->attr ) ) { 228 | $attributes = $this->product->get_attributes(); 229 | $product_variable = new WC_Product_Variable( $this->parent_id() ); 230 | $variations = $product_variable->get_variation_attributes(); 231 | } else { 232 | $attributes = $this->attr; 233 | } 234 | 235 | $attr_name = []; 236 | 237 | foreach ( $attributes as $attr => $value ) { 238 | 239 | $attr_label = wc_attribute_label( $attr, $this->product ); 240 | $meta = is_object( $value ) ? get_post_meta( $this->id(), wc_variation_attribute_name( $attr ), true ) : $value; 241 | $term = get_term_by( 'slug', $meta, $attr ); 242 | 243 | if ( false !== $term ) { 244 | $attr_name[] = sprintf( '%s: %s', $attr_label, $term->name ); 245 | } elseif ( $value && ! is_object( $value ) ) { 246 | $attr_name[] = sprintf( '%s: %s', $attr_label, $value ); 247 | } 248 | } 249 | 250 | if ( empty( $attr_name ) && isset( $variations ) ) { 251 | foreach ( $variations as $key => $item ) { 252 | 253 | $attr_name[] = sprintf( '%s — %s', wc_attribute_label( $key ), implode( array_intersect( $item, $attributes ) ) ); 254 | } 255 | } 256 | 257 | return $attr_name; 258 | } 259 | 260 | 261 | /** 262 | * Получаем сумму товара 263 | * 264 | * @return float|int 265 | * @since 3.0.0 266 | */ 267 | public function get_sum() { 268 | 269 | if ( ! $this->price() ) { 270 | return 0; 271 | } 272 | 273 | return $this->price() * $this->qty; 274 | } 275 | 276 | 277 | /** 278 | * Получаем ссылку 279 | * 280 | * @return string 281 | * @since 3.0.0 282 | */ 283 | public function link(): string { 284 | 285 | return esc_url( get_permalink( $this->parent_id() ) ); 286 | } 287 | 288 | 289 | /** 290 | * Массив категорий товара 291 | * 292 | * @return array 293 | * @since 3.0.0 294 | */ 295 | public function category_list(): array { 296 | 297 | $current_terms = get_the_terms( $this->parent_id(), 'product_cat' ); 298 | 299 | $terms = []; 300 | 301 | if ( is_array( $current_terms ) ) { 302 | foreach ( $current_terms as $term ) { 303 | $terms[] = $term->name; 304 | } 305 | } 306 | 307 | return $terms; 308 | } 309 | 310 | 311 | /** 312 | * Output form in a popup window 313 | * 314 | * @return string 315 | * @since 3.0.0 316 | */ 317 | public function select_form(): string { 318 | 319 | $select_form = $this->main->get_selected_form_id(); 320 | 321 | if ( ! $select_form ) { 322 | return ''; 323 | } 324 | 325 | if ( defined( 'ICL_LANGUAGE_CODE' ) && ! defined( 'WP_CLI' ) ) { 326 | $select_form = apply_filters( 327 | 'wpml_object_id', 328 | $select_form, 329 | 'wpcf7_contact_form', 330 | true, 331 | ICL_LANGUAGE_CODE 332 | ); 333 | } 334 | 335 | return wpcf7_contact_form_tag_func( [ 'id' => esc_attr( $select_form ) ], null, 'contact-form-7' ); 336 | } 337 | 338 | 339 | /** 340 | * Проверка на простой товар 341 | * 342 | * @return bool 343 | */ 344 | public function is_simple(): bool { 345 | 346 | return $this->product->is_type( 'simple' ); 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /src/js/public/components/RequestProcessing/UpdateQuantity.js: -------------------------------------------------------------------------------- 1 | import { settings, translate } from '../../config'; 2 | 3 | export default class UpdateQuantity { 4 | constructor( toMail, request, event ) { 5 | this.toMail = toMail; 6 | this.request = request; 7 | this.qtyVal = ''; 8 | 9 | this.cache = request.cache; 10 | this.event = event; 11 | } 12 | 13 | bindEvent() { 14 | const popupQtyContainer = document.querySelector( '.awooc-popup-qty' ); 15 | if ( ! popupQtyContainer ) { 16 | return; 17 | } 18 | 19 | const quantityInput = popupQtyContainer.querySelector( '.awooc-popup-input-qty' ); 20 | if ( quantityInput ) { 21 | this.initializeQuantityInput( quantityInput ); 22 | } 23 | } 24 | 25 | initializeQuantityInput( quantityInput ) { 26 | this.setMaxValueInput( quantityInput ); 27 | this.updateAll(); 28 | 29 | quantityInput.addEventListener( 'input', ( e ) => this.handleInputEvent( e ) ); 30 | 31 | this.handlerPlusMinusButtonsEvent( quantityInput ); 32 | } 33 | 34 | handlerPlusMinusButtonsEvent( quantityInput ) { 35 | const minusButton = document.querySelector( '.awooc-popup-input-qty--minus' ); 36 | const plusButton = document.querySelector( '.awooc-popup-input-qty--plus' ); 37 | if ( minusButton && plusButton ) { 38 | minusButton.addEventListener( 'click', () => this.updateInputQuantity( quantityInput, 'decrease' ) ); 39 | plusButton.addEventListener( 'click', () => this.updateInputQuantity( quantityInput, 'increase' ) ); 40 | } 41 | } 42 | 43 | updateInputQuantity( inputElement, action ) { 44 | const currentValue = parseFloat( inputElement.value ); 45 | const step = this.getSafeValue( inputElement.step, 1 ); 46 | const minValue = this.getSafeValue( inputElement.min, -Infinity ); 47 | const maxValue = this.getSafeValue( inputElement.max, Infinity ); 48 | 49 | let newValue = currentValue + ( action === 'decrease' ? -step : step ); 50 | 51 | const decimalPlaces = Math.max( 0, -Math.floor( Math.log10( step ) ) ); // Определяем количество знаков после запятой для step 52 | newValue = parseFloat( newValue.toFixed( decimalPlaces ) ); // Округляем до нужного количества знаков 53 | 54 | if ( newValue >= minValue && newValue <= maxValue ) { 55 | inputElement.value = newValue; 56 | inputElement.dispatchEvent( new Event( 'input', { bubbles: true } ) ); 57 | } 58 | } 59 | 60 | handleInputEvent( e, input = null ) { 61 | if ( ! input ) { 62 | input = e.target.closest( '.awooc-popup-input-qty' ); 63 | } 64 | 65 | if ( ! input ) { 66 | return; 67 | } 68 | 69 | this.setMaxValueInput( input ); 70 | 71 | this.qtyVal = input.value; 72 | 73 | this.updateAll(); 74 | } 75 | 76 | setMaxValueInput( input ) { 77 | const minValue = this.getSafeValue( input.min, input.step ); 78 | const maxValue = this.getSafeValue( input.max, input.value ); 79 | 80 | input.value = Math.min( Math.max( parseFloat( String( input.value ) ) || minValue, minValue ), maxValue ); 81 | 82 | this.qtyVal = input.value; 83 | } 84 | 85 | updateAll() { 86 | this.updateMailQuantity(); 87 | this.updateAmount(); 88 | this.updateMailData(); 89 | this.updateProductQuantity(); 90 | this.updateAnalytics(); 91 | } 92 | 93 | updateMailQuantity() { 94 | this.toMail.qty = `${ translate.product_qty }${ this.qtyVal }`; 95 | } 96 | 97 | updateMailData() { 98 | const hiddenDataField = document.querySelector( 'input[name="awooc-hidden-data"]' ); 99 | if ( hiddenDataField ) { 100 | hiddenDataField.value = this.request.fillDataToMail( this.toMail ); 101 | } 102 | } 103 | 104 | updateAnalytics() { 105 | this.request.app.analyticData.qty = this.qtyVal; 106 | } 107 | 108 | updateProductQuantity() { 109 | const productQtyField = document.querySelector( 'input[name="awooc_product_qty"]' ); 110 | if ( productQtyField ) { 111 | productQtyField.value = this.qtyVal; 112 | } 113 | } 114 | 115 | updateAmount() { 116 | const priceValue = this.getPrice(); 117 | if ( ! priceValue ) { 118 | return; 119 | } 120 | 121 | const amount = this.displayPrice( this.parsePrice( priceValue ) * this.qtyVal ); 122 | 123 | this.updateDOMAmount( amount ); 124 | this.updateMailAmount(); 125 | } 126 | 127 | getPrice() { 128 | const priceElement = document.querySelector( '.awooc-popup-price .woocommerce-Price-currencyValue' ); 129 | return priceElement?.textContent?.replace( /\s+/g, '' ) || null; 130 | } 131 | 132 | getSafeValue( value, defaultValue ) { 133 | return value !== '' && ! Number.isNaN( parseFloat( value ) ) ? parseFloat( value ) : defaultValue; 134 | } 135 | 136 | getPriceSettings() { 137 | const { 138 | price_num_decimals: rawDecimalPlaces = 0, 139 | price_decimal_sep: rawDecimalSeparator = '.', 140 | price_thousand_sep: rawThousandSeparator = '', 141 | } = settings.popup; 142 | 143 | const decimalSeparator = rawDecimalSeparator || '.'; 144 | const thousandSeparator = rawThousandSeparator || ''; 145 | const decimalPlaces = rawDecimalPlaces || 0; 146 | 147 | return { decimalPlaces, decimalSeparator, thousandSeparator }; 148 | } 149 | 150 | displayPrice( input ) { 151 | const sanitizedInput = String( input ).replace( /[^0-9.]/g, '' ); 152 | const number = parseFloat( sanitizedInput ); 153 | 154 | if ( isNaN( number ) ) { 155 | return 'Invalid number'; 156 | } 157 | 158 | const { decimalPlaces, decimalSeparator, thousandSeparator } = this.getPriceSettings(); 159 | 160 | // Разделяем на целую и дробную части 161 | const [ integerPart, decimalPart ] = String( number.toFixed( decimalPlaces ) ).split( '.' ); 162 | 163 | // Форматируем целую часть с разделителями тысяч 164 | const formattedIntegerPart = integerPart.replace( /\B(?=(\d{3})+(?!\d))/g, thousandSeparator ); 165 | 166 | // Форматируем дробную часть, если требуется 167 | const formattedDecimalPart = decimalPlaces > 0 168 | ? decimalPart.padEnd( decimalPlaces, '0' ).slice( 0, decimalPlaces ) 169 | : ''; 170 | 171 | return decimalPlaces > 0 && formattedDecimalPart 172 | ? `${ formattedIntegerPart }${ decimalSeparator }${ formattedDecimalPart }` 173 | : formattedIntegerPart; 174 | } 175 | 176 | parsePrice( number ) { 177 | number = number ?? 0; 178 | 179 | const { decimalPlaces, decimalSeparator, thousandSeparator } = this.getPriceSettings(); 180 | 181 | // Функция для экранирования символов в регулярных выражениях 182 | const escapeRegExp = ( string ) => string.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ); 183 | 184 | if ( typeof number !== 'number' ) { 185 | number = String( number ); 186 | 187 | [ thousandSeparator, ' ', '.', ',' ] 188 | .filter( Boolean ) // Игнорируем пустые значения 189 | .forEach( ( sep ) => { 190 | if ( sep !== decimalSeparator ) { // Исключаем десятичный разделитель 191 | number = number.replace( new RegExp( escapeRegExp( sep ), 'g' ), '' ); 192 | } 193 | } ); 194 | 195 | // Нормализуем десятичный разделитель к точке (.) 196 | if ( decimalSeparator !== '.' ) { 197 | number = number.replace( new RegExp( escapeRegExp( decimalSeparator ), 'g' ), '.' ); 198 | } 199 | 200 | // Удаляем лишние точки и все символы, кроме цифр, точки и минуса 201 | number = number.replace( /\.+(?![^.]+$)|[^0-9.-]/g, '' ); 202 | } 203 | 204 | number = parseFloat( number ) || 0; 205 | 206 | if ( decimalPlaces !== false ) { 207 | const decimalsCount = String( decimalPlaces ) === '' ? 2 : parseInt( decimalPlaces, 10 ); // По умолчанию 2 знака 208 | return number.toFixed( decimalsCount ); 209 | } 210 | 211 | return number.toFixed( 20 ); 212 | } 213 | 214 | updateDOMAmount( amount ) { 215 | const sumElement = document.querySelector( '.awooc-popup-sum .woocommerce-Price-currencyValue' ); 216 | if ( sumElement ) { 217 | sumElement.textContent = amount; 218 | } 219 | } 220 | 221 | updateMailAmount() { 222 | const currentAmountElement = document.querySelector( '.awooc-popup-sum bdi' ); 223 | if ( currentAmountElement ) { 224 | this.toMail.sum = `${ translate.formatted_sum }${ currentAmountElement.textContent }`; 225 | } else { 226 | delete this.toMail.sum; 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /includes/template-functions.php: -------------------------------------------------------------------------------- 1 | get_stock_status() || 'outofstock' === $product->get_stock_status() ) { 75 | $mode_classes[] = 'no-margin'; 76 | } 77 | 78 | break; 79 | } 80 | 81 | return apply_filters( 'awooc_mode_classes_button', $mode_classes ); 82 | } 83 | } 84 | 85 | if ( ! function_exists( 'awooc_html_custom_add_to_cart' ) ) { 86 | 87 | /** 88 | * Displaying the button add to card in product page 89 | * 90 | * @param array|string $args массив параметров. 91 | * @param WC_Product|null $product объект продукта. 92 | * 93 | * @since 1.5.0 94 | * @since 3.0.0 95 | */ 96 | function awooc_html_custom_add_to_cart( $args = [], WC_Product $product = null ) { 97 | 98 | if ( is_null( $product ) ) { 99 | $product = $GLOBALS['product']; 100 | } 101 | 102 | if ( 'yes' === $product->get_meta( '_awooc_button' ) ) { 103 | return; 104 | } 105 | 106 | if ( function_exists( 'rocket_init' ) && function_exists( 'wpcf7_enqueue_scripts' ) ) { 107 | wpcf7_enqueue_scripts(); 108 | } 109 | 110 | if ( function_exists( 'rocket_init' ) && function_exists( 'wpcf7_enqueue_styles' ) ) { 111 | wpcf7_enqueue_styles(); 112 | } 113 | 114 | wp_enqueue_script( 'awooc-scripts' ); 115 | wp_enqueue_style( 'awooc-styles' ); 116 | 117 | $defaults = [ 118 | 'product_id' => $product->get_id(), 119 | 'class' => apply_filters( 'awooc_classes_button', implode( ' ', awooc_mode_classes( $product ) ) ), 120 | 'id' => apply_filters( 'awooc_id_button', 'awooc-custom-order-button-' . $product->get_id() ), 121 | 'label' => apply_filters( 'awooc_button_label', awooc_custom_button_label( $product ) ), 122 | 'loop' => false, 123 | ]; 124 | 125 | $args = apply_filters( 126 | 'awooc_button_args', 127 | wp_parse_args( $args, $defaults ), 128 | $product 129 | ); 130 | 131 | load_template( 132 | awooc()->get_template( 'button.php' ), 133 | $args['loop'], 134 | [ 135 | 'product_id' => $args['product_id'], 136 | 'class' => $args['class'], 137 | 'id' => $args['id'], 138 | 'label' => $args['label'], 139 | ] 140 | ); 141 | } 142 | } 143 | 144 | if ( ! function_exists( 'awooc_popup' ) ) { 145 | /** 146 | * Вывод всплывающего окна 147 | * 148 | * @since 3.0.0 149 | */ 150 | function awooc_popup() { 151 | 152 | $elements = get_option( 'woocommerce_awooc_select_item' ); 153 | $product = wc_get_product(); 154 | 155 | if ( ! is_array( $elements ) ) { 156 | return null; 157 | } 158 | 159 | ob_start(); 160 | 161 | load_template( 162 | awooc()->get_template( 'popup.php' ), 163 | true, 164 | [ 165 | 'elements' => $elements, 166 | 'product' => $product, 167 | ] 168 | ); 169 | 170 | return ob_get_clean(); 171 | } 172 | } 173 | 174 | if ( ! function_exists( 'awooc_loop_add_to_cart_link' ) ) { 175 | /** 176 | * Вывод кнопки Купить на архивах 177 | * 178 | * @since 3.0.0 179 | */ 180 | function awooc_loop_add_to_cart_link( $product, $args ): void { 181 | 182 | $loop_add_to_cart_link = apply_filters( 183 | 'woocommerce_loop_add_to_cart_link', // WPCS: XSS ok. 184 | sprintf( 185 | '%s', 186 | esc_url( $product->add_to_cart_url() ), 187 | esc_attr( $product->get_id() ), 188 | esc_attr( isset( $args['quantity'] ) ? $args['quantity'] : 1 ), 189 | esc_attr( isset( $args['class'] ) ? $args['class'] : 'button' ), 190 | isset( $args['attributes'] ) ? wc_implode_html_attributes( $args['attributes'] ) : '', 191 | esc_html( $product->add_to_cart_text() ) 192 | ), 193 | $product, 194 | $args 195 | ); 196 | 197 | $loop_add_to_cart_link_describedby = sprintf( 198 | '%s', 199 | esc_attr( $product->get_id() ), 200 | esc_html( $args['aria-describedby_text'] ) 201 | ); 202 | 203 | //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 204 | echo $loop_add_to_cart_link . $loop_add_to_cart_link_describedby; 205 | } 206 | } 207 | 208 | if ( ! function_exists( 'awooc_custom_button_label' ) ) { 209 | /** 210 | * @param \WC_Product|null $product 211 | * 212 | * @return string 213 | */ 214 | function awooc_custom_button_label( WC_Product $product = null ): string { 215 | 216 | if ( is_null( $product ) ) { 217 | $product = wc_get_product(); 218 | } 219 | 220 | $label_button = esc_html( get_option( 'woocommerce_awooc_title_button' ) ); 221 | $custom_label_button = esc_html( get_option( 'woocommerce_awooc_title_custom' ) ); 222 | 223 | if ( empty( $product ) ) { 224 | return $label_button; 225 | } 226 | 227 | if ( awooc()->get_mode()->is_mode_special() && ( ( $product->get_price() <= 0 || '' === $product->get_price() ) || ! $product->is_in_stock() ) ) { 228 | 229 | return $custom_label_button ? : $label_button; 230 | } 231 | 232 | return $label_button; 233 | } 234 | } 235 | 236 | if ( ! function_exists( 'awooc_popup_window_title' ) ) { 237 | /** 238 | * Displaying the product header in a popup window 239 | * 240 | * @param array $elements массив настроек элементов окна. 241 | * @param \WC_Product $product объект продукта. 242 | * 243 | * @todo проблема с объектов товара всех страницах кроме страниц товара и архивов, если ставить тип, то выбивает фатал 244 | * 245 | * @since 1.5.0 246 | * @since 1.8.10 247 | */ 248 | function awooc_popup_window_title( array $elements, $product ) { 249 | 250 | if ( in_array( 'title', $elements, true ) ) { 251 | echo wp_kses_post( 252 | apply_filters( 253 | 'awooc_popup_title_html', 254 | sprintf( 255 | '

', 256 | esc_attr( 'awooc-form-custom-order-title awooc-popup-item awooc-popup-title skeleton-loader' ) 257 | ), 258 | $product 259 | ) 260 | ); 261 | } 262 | } 263 | } 264 | 265 | if ( ! function_exists( 'awooc_popup_window_image' ) ) { 266 | /** 267 | * Output of a product thumbnail in a popup window 268 | * 269 | * @param array $elements массив настроек элементов окна. 270 | * 271 | * @since 1.5.0 272 | * @since 1.8.0 273 | */ 274 | function awooc_popup_window_image( array $elements ) { 275 | 276 | if ( in_array( 'image', $elements, true ) ) { 277 | 278 | echo '
'; 279 | } 280 | } 281 | } 282 | 283 | if ( ! function_exists( 'awooc_popup_window_price' ) ) { 284 | /** 285 | * Output of a product price in a popup window 286 | * 287 | * @param array $elements массив настроек элементов окна. 288 | * @param \WC_Product $product объект продукта. 289 | * 290 | * @since 1.5.0 291 | * @since 1.8.10 292 | */ 293 | function awooc_popup_window_price( array $elements, $product ) { 294 | 295 | if ( in_array( 'price', $elements, true ) ) { 296 | 297 | echo wp_kses_post( 298 | apply_filters( 299 | 'awooc_popup_price_html', 300 | '
', 301 | $product 302 | ) 303 | ); 304 | } 305 | } 306 | } 307 | 308 | if ( ! function_exists( 'awooc_popup_window_sum' ) ) { 309 | /** 310 | * Output of a product price in a popup window 311 | * 312 | * @param array $elements массив настроек элементов окна. 313 | * @param WC_Product $product объект продукта. 314 | * 315 | * @since 1.5.0 316 | * @since 2.4.0 317 | */ 318 | function awooc_popup_window_sum( array $elements, $product ) { 319 | 320 | if ( in_array( 'sum', $elements, true ) ) { 321 | 322 | echo wp_kses_post( 323 | apply_filters( 324 | 'awooc_popup_price_html', 325 | '
', 326 | $product 327 | ) 328 | ); 329 | } 330 | } 331 | } 332 | 333 | if ( ! function_exists( 'awooc_popup_window_sku' ) ) { 334 | /** 335 | * Output of a product sku in a popup window 336 | * 337 | * @param array $elements массив настроек элементов окна. 338 | * 339 | * @since 1.5.0 340 | * @since 1.5.0 341 | */ 342 | function awooc_popup_window_sku( array $elements ) { 343 | 344 | if ( in_array( 'sku', $elements, true ) ) { 345 | 346 | echo '
'; 347 | } 348 | } 349 | } 350 | 351 | if ( ! function_exists( 'awooc_popup_window_qty' ) ) { 352 | /** 353 | * Output of a product quantity in a popup window 354 | * 355 | * @param array $elements массив настроек элементов окна. 356 | * 357 | * @since 2.1.0 358 | */ 359 | function awooc_popup_window_qty( array $elements ) { 360 | 361 | if ( in_array( 'qty', $elements, true ) ) { 362 | 363 | echo '
'; 364 | } 365 | } 366 | } 367 | 368 | if ( ! function_exists( 'awooc_popup_window_attr' ) ) { 369 | /** 370 | * Output of a product attributes in a popup window 371 | * 372 | * @param array $elements массив настроек элементов окна. 373 | * 374 | * @since 1.5.0 375 | * @since 1.8.9 376 | */ 377 | function awooc_popup_window_attr( array $elements ) { 378 | 379 | if ( in_array( 'attr', $elements, true ) ) { 380 | echo '
'; 381 | } 382 | } 383 | } 384 | 385 | if ( ! function_exists( 'awooc_popup_window_form' ) ) { 386 | /** 387 | * Output of a product attributes in a popup window 388 | * 389 | * @since 1.5.0 390 | * @since 1.8.9 391 | */ 392 | function awooc_popup_window_form() { 393 | 394 | echo '
'; 395 | } 396 | } 397 | 398 | if ( ! function_exists( 'awooc_popup_window_select_form' ) ) { 399 | /** 400 | * Output form in a popup window 401 | * 402 | * @since 1.5.0 403 | * @since 1.8.0 404 | */ 405 | function awooc_popup_window_select_form() { 406 | 407 | $select_form = awooc()->get_selected_form_id(); 408 | 409 | if ( $select_form ) { 410 | do_action( 'awooc_popup_before_form' ); 411 | 412 | if ( apply_filters( 'awooc_using_cf7', true ) ) { 413 | echo do_shortcode( '[contact-form-7 id="' . esc_attr( $select_form ) . '"]' ); 414 | } 415 | 416 | do_action( 'awooc_popup_after_form' ); 417 | } 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /templates/email.php: -------------------------------------------------------------------------------- 1 | 19 | 20 | > 21 | 22 | 23 | 24 | <?php echo esc_html( get_bloginfo( 'name', 'display' ) ); ?> 25 | 395 | 396 | 397 | 398 | 399 | 400 | 500 | 501 | 502 | 503 | 504 | --------------------------------------------------------------------------------