diff --git a/class.jetpack.php b/class.jetpack.php index 08fd288676b5b..8566fd6700430 100644 --- a/class.jetpack.php +++ b/class.jetpack.php @@ -58,7 +58,8 @@ class Jetpack { 'wordads', 'eu-cookie-law-style', 'flickr-widget-style', - 'jetpack-search-widget' + 'jetpack-search-widget', + 'jetpack-simple-payments-widget-style', ); /** diff --git a/modules/widgets/simple-payments.php b/modules/widgets/simple-payments.php new file mode 100644 index 0000000000000..1a583c29b3607 --- /dev/null +++ b/modules/widgets/simple-payments.php @@ -0,0 +1,487 @@ +<?php +/** + * Disable direct access/execution to/of the widget code. + */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + +if ( ! class_exists( 'Jetpack_Simple_Payments_Widget' ) ) { + /** + * Simple Payments Button + * + * Display a Simple Payment Button as a Widget. + */ + class Jetpack_Simple_Payments_Widget extends WP_Widget { + // https://developer.paypal.com/docs/integration/direct/rest/currency-codes/ + private static $supported_currency_list = array( + 'USD' => '$', + 'GBP' => '£', + 'JPY' => '¥', + 'BRL' => 'R$', + 'EUR' => '€', + 'NZD' => 'NZ$', + 'AUD' => 'A$', + 'CAD' => 'C$', + 'INR' => '₹', + 'ILS' => '₪', + 'RUB' => '₽', + 'MXN' => 'MX$', + 'SEK' => 'Skr', + 'HUF' => 'Ft', + 'CHF' => 'CHF', + 'CZK' => 'Kč', + 'DKK' => 'Dkr', + 'HKD' => 'HK$', + 'NOK' => 'Kr', + 'PHP' => '₱', + 'PLN' => 'PLN', + 'SGD' => 'S$', + 'TWD' => 'NT$', + 'THB' => '฿', + ); + + /** + * Constructor. + */ + function __construct() { + parent::__construct( + 'jetpack_simple_payments_widget', + /** This filter is documented in modules/widgets/facebook-likebox.php */ + apply_filters( 'jetpack_widget_name', __( 'Simple Payments', 'jetpack' ) ), + array( + 'classname' => 'jetpack-simple-payments', + 'description' => __( 'Add a Simple Payment Button as a Widget.', 'jetpack' ), + 'customize_selective_refresh' => true, + ) + ); + + if ( is_customize_preview() ) { + add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_styles_and_scripts' ) ); + + add_filter( 'customize_refresh_nonces', array( $this, 'filter_nonces' ) ); + add_action( 'wp_ajax_customize-jetpack-simple-payments-buttons-get', array( $this, 'ajax_get_payment_buttons' ) ); + add_action( 'wp_ajax_customize-jetpack-simple-payments-button-save', array( $this, 'ajax_save_payment_button' ) ); + add_action( 'wp_ajax_customize-jetpack-simple-payments-button-delete', array( $this, 'ajax_delete_payment_button' ) ); + } + + if ( is_active_widget( false, false, $this->id_base ) || is_customize_preview() ) { + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_style' ) ); + } + } + + /** + * Return an associative array of default values. + * + * These values are used in new widgets. + * + * @return array Default values for the widget options. + */ + private function defaults() { + $current_user = wp_get_current_user(); + $default_product_id = $this->get_first_product_id(); + + return array( + 'title' => '', + 'product_post_id' => $default_product_id, + 'form_action' => '', + 'form_product_id' => 0, + 'form_product_title' => '', + 'form_product_description' => '', + 'form_product_image_id' => 0, + 'form_product_image_src' => '', + 'form_product_currency' => '', + 'form_product_price' => '', + 'form_product_multiple' => '', + 'form_product_email' => $current_user->user_email, + ); + } + + /** + * Adds a nonce for customizing menus. + * + * @param array $nonces Array of nonces. + * @return array $nonces Modified array of nonces. + */ + function filter_nonces( $nonces ) { + $nonces['customize-jetpack-simple-payments'] = wp_create_nonce( 'customize-jetpack-simple-payments' ); + return $nonces; + } + + function enqueue_style() { + wp_enqueue_style( 'jetpack-simple-payments-widget-style', plugins_url( 'simple-payments/style.css', __FILE__ ), array(), '20180518' ); + } + + function admin_enqueue_styles_and_scripts(){ + wp_enqueue_style( 'jetpack-simple-payments-widget-customizer', plugins_url( 'simple-payments/customizer.css', __FILE__ ) ); + + wp_enqueue_media(); + wp_enqueue_script( 'jetpack-simple-payments-widget-customizer', plugins_url( '/simple-payments/customizer.js', __FILE__ ), array( 'jquery' ), false, true ); + wp_localize_script( 'jetpack-simple-payments-widget-customizer', 'jpSimplePaymentsStrings', array( + 'deleteConfirmation' => __( 'Are you sure you want to delete this item? It will be disabled and removed from all locations where it currently appears.', 'jetpack' ) + ) ); + } + + public function ajax_get_payment_buttons() { + if ( ! check_ajax_referer( 'customize-jetpack-simple-payments', 'customize-jetpack-simple-payments-nonce', false ) ) { + wp_send_json_error( 'bad_nonce', 400 ); + } + + if ( ! current_user_can( 'customize' ) ) { + wp_send_json_error( 'customize_not_allowed', 403 ); + } + + $post_type_object = get_post_type_object( Jetpack_Simple_Payments::$post_type_product ); + if ( ! current_user_can( $post_type_object->cap->create_posts ) || ! current_user_can( $post_type_object->cap->publish_posts ) ) { + wp_send_json_error( 'insufficient_post_permissions', 403 ); + } + + $product_posts = get_posts( array( + 'numberposts' => 100, + 'orderby' => 'date', + 'post_type' => Jetpack_Simple_Payments::$post_type_product, + 'post_status' => 'publish', + ) ); + + $formatted_products = array_map( array( $this, 'format_product_post_for_ajax_reponse' ), $product_posts ); + + wp_send_json_success( $formatted_products ); + } + + public function format_product_post_for_ajax_reponse( $product_post ) { + return array( + 'ID' => $product_post->ID, + 'post_title' => $product_post->post_title, + ); + } + + public function ajax_save_payment_button() { + if ( ! check_ajax_referer( 'customize-jetpack-simple-payments', 'customize-jetpack-simple-payments-nonce', false ) ) { + wp_send_json_error( 'bad_nonce', 400 ); + } + + if ( ! current_user_can( 'customize' ) ) { + wp_send_json_error( 'customize_not_allowed', 403 ); + } + + $post_type_object = get_post_type_object( Jetpack_Simple_Payments::$post_type_product ); + if ( ! current_user_can( $post_type_object->cap->create_posts ) || ! current_user_can( $post_type_object->cap->publish_posts ) ) { + wp_send_json_error( 'insufficient_post_permissions', 403 ); + } + + if ( empty( $_POST['params'] ) || ! is_array( $_POST['params'] ) ) { + wp_send_json_error( 'missing_params', 400 ); + } + + $params = wp_unslash( $_POST['params'] ); + $errors = $this->validate_ajax_params( $params ); + if ( ! empty( $errors->errors ) ) { + wp_send_json_error( $errors ); + } + + $product_post_id = isset( $params['product_post_id'] ) ? intval( $params['product_post_id'] ) : 0; + + $product_post = array( + 'ID' => $product_post_id, + 'post_type' => Jetpack_Simple_Payments::$post_type_product, + 'post_status' => 'publish', + 'post_title' => $params['post_title'], + 'post_content' => $params['post_content'], + '_thumbnail_id' => ! empty( $params['image_id'] ) ? $params['image_id'] : -1, + 'meta_input' => array( + 'spay_currency' => $params['currency'], + 'spay_price' => $params['price'], + 'spay_multiple' => isset( $params['multiple'] ) ? intval( $params['multiple'] ) : 0, + 'spay_email' => is_email( $params['email'] ), + ), + ); + + if ( empty( $product_post_id ) ) { + $product_post_id = wp_insert_post( $product_post ); + } else { + $product_post_id = wp_update_post( $product_post ); + } + + if ( ! $product_post_id || is_wp_error( $product_post_id ) ) { + wp_send_json_error( $product_post_id ); + } + + $tracks_properties = array( + 'id' => $product_post_id, + 'currency' => $params['currency'], + 'price' => $params['price'] + ); + if ( 0 === $product_post['ID'] ) { + $this->record_event( 'created', 'create', $tracks_properties ); + } else { + $this->record_event( 'updated', 'update', $tracks_properties ); + } + + wp_send_json_success( [ + 'product_post_id' => $product_post_id, + 'product_post_title' => $params['post_title'], + ] ); + } + + public function ajax_delete_payment_button() { + if ( ! check_ajax_referer( 'customize-jetpack-simple-payments', 'customize-jetpack-simple-payments-nonce', false ) ) { + wp_send_json_error( 'bad_nonce', 400 ); + } + + if ( ! current_user_can( 'customize' ) ) { + wp_send_json_error( 'customize_not_allowed', 403 ); + } + + if ( empty( $_POST['params'] ) || ! is_array( $_POST['params'] ) ) { + wp_send_json_error( 'missing_params', 400 ); + } + + $params = wp_unslash( $_POST['params'] ); + $illegal_params = array_diff( array_keys( $params ), array( 'product_post_id' ) ); + if ( ! empty( $illegal_params ) ) { + wp_send_json_error( 'illegal_params', 400 ); + } + + $product_id = ( int ) $params['product_post_id']; + $product_post = get_post( $product_id ); + + $return = array( 'status' => $product_post->post_status ); + + wp_delete_post( $product_id, true ); + $status = get_post_status( $product_id ); + if ( false === $status ) { + $return['status'] = 'deleted'; + } + + $this->record_event( 'deleted', 'delete', array( 'id' => $product_id ) ); + + wp_send_json_success( $return ); + } + + public function validate_ajax_params( $params ) { + $errors = new WP_Error(); + + $illegal_params = array_diff( array_keys( $params ), array( 'product_post_id', 'post_title', 'post_content', 'image_id', 'currency', 'price', 'multiple', 'email' ) ); + if ( ! empty( $illegal_params ) ) { + $errors.add( 'illegal_params' ); + } + + if ( empty( $params['post_title'] ) ) { + $errors->add( 'post_title', __( 'People need to know what they\'re paying for! Please add a brief title.' ) ); + } + + if ( empty( $params['price'] ) || floatval( $params['price'] ) <= 0 ) { + $errors->add( 'price', __( 'Everything comes with a price tag these days. Please add a your product price.' ) ); + } + + if ( empty( $params['email'] ) || ! is_email( $params['email'] ) ) { + $errors->add( 'email', __( 'We want to make sure payments reach you, so please add an email address.' ) ); + } + + return $errors; + } + + function get_first_product_id() { + $product_posts = get_posts( array( + 'numberposts' => 1, + 'orderby' => 'date', + 'post_type' => Jetpack_Simple_Payments::$post_type_product, + 'post_status' => 'publish', + ) ); + + return ! empty( $product_posts ) ? $product_posts[0]->ID : null; + } + + /** + * Front-end display of widget. + * + * @see WP_Widget::widget() + * + * @param array $args Widget arguments. + * @param array $instance Saved values from database. + */ + function widget( $args, $instance ) { + $instance = wp_parse_args( $instance, $this->defaults() ); + + echo $args['before_widget']; + + /** This filter is documented in core/src/wp-includes/default-widgets.php */ + $title = apply_filters( 'widget_title', $instance['title'] ); + if ( ! empty( $title ) ) { + echo $args['before_title'] . $title . $args['after_title']; + } + + echo '<div class="jetpack-simple-payments-content">'; + + if ( ! empty( $instance['form_action'] ) && in_array( $instance['form_action'], array( 'add', 'edit' ) ) && is_customize_preview() ) { + require( dirname( __FILE__ ) . '/simple-payments/widget.php' ); + } else { + $jsp = Jetpack_Simple_Payments::getInstance(); + $simple_payments_button = $jsp->parse_shortcode( array( + 'id' => $instance['product_post_id'], + ) ); + + if ( ! is_null( $simple_payments_button ) || is_customize_preview() ) { + echo $simple_payments_button; + } + } + + echo '</div><!--simple-payments-->'; + + echo $args['after_widget']; + + /** This action is already documented in modules/widgets/gravatar-profile.php */ + do_action( 'jetpack_stats_extra', 'widget_view', 'simple_payments' ); + } + + /** + * Gets the latests field value from either the old instance or the new instance. + * + * @param array $mixed Array of values for the new form instance. + * @param array $mixed Array of values for the old form instance. + * @return mixed $mixed Field value. + */ + private function get_latest_field_value( $new_instance, $old_instance, $field) { + return ! empty( $new_instance[ $field ] ) + ? sanitize_text_field( $new_instance[ $field ] ) + : $old_instance[ $field ]; + } + + /** + * Gets the product fields from the product post. If no post found + * it returns the default values. + * + * @param int Product Post ID. + * @return array $fields Product Fields from the Product Post. + */ + private function get_product_from_post( $product_post_id ) { + $product_post = get_post( $product_post_id ); + $form_product_id = $product_post_id; + if( ! empty( $product_post ) ) { + $form_product_image_id = get_post_thumbnail_id( $product_post_id ); + + return array( + 'form_product_id' => $form_product_id, + 'form_product_title' => get_the_title( $product_post ), + 'form_product_description' => $product_post->post_content, + 'form_product_image_id' => $form_product_image_id, + 'form_product_image_src' => wp_get_attachment_image_url( $form_product_image_id, 'thumbnail' ), + 'form_product_currency' => get_post_meta( $product_post_id, 'spay_currency', true ), + 'form_product_price' => get_post_meta( $product_post_id, 'spay_price', true ), + 'form_product_multiple' => get_post_meta( $product_post_id, 'spay_multiple', true ) || '0', + 'form_product_email' => get_post_meta( $product_post_id, 'spay_email', true ), + ); + } + + return $this->defaults(); + } + + /** + * Record a Track event and bump a MC stat. + * + * @param string $stat_name + * @param string $event_action + * @param array $event_properties + */ + private function record_event( $stat_name, $event_action, $event_properties = array() ) { + $current_user = wp_get_current_user(); + + // `bumps_stats_extra` only exists on .com + if ( function_exists( 'bump_stats_extras' ) ) { + require_lib( 'tracks/client' ); + tracks_record_event( $current_user, 'simple_payments_button_' . $event_action, $event_properties ); + /** This action is documented in modules/widgets/social-media-icons.php */ + do_action( 'jetpack_bump_stats_extra', 'jetpack-simple_payments', $stat_name ); + return; + } + + jetpack_tracks_record_event( $current_user, 'jetpack_wpa_simple_payments_button_' . $event_action, $event_properties ); + $jetpack = Jetpack::init(); + // $jetpack->stat automatically prepends the stat group with 'jetpack-' + $jetpack->stat( 'simple_payments', $stat_name ) ; + $jetpack->do_stats( 'server_side' ); + } + + /** + * Sanitize widget form values as they are saved. + * + * @see WP_Widget::update() + * + * @param array $new_instance Values just sent to be saved. + * @param array $old_instance Previously saved values from database. + * + * @return array Updated safe values to be saved. + */ + function update( $new_instance, $old_instance ) { + $defaults = $this->defaults(); + //do not overrite `product_post_id` for `$new_instance` with the defaults + $new_instance = wp_parse_args( $new_instance, array_diff_key( $defaults, array( 'product_post_id' => 0 ) ) ); + $old_instance = wp_parse_args( $old_instance, $defaults ); + + $required_widget_props = array( + 'title' => $this->get_latest_field_value( $new_instance, $old_instance, 'title' ), + 'product_post_id' => $this->get_latest_field_value( $new_instance, $old_instance, 'product_post_id' ), + 'form_action' => $this->get_latest_field_value( $new_instance, $old_instance, 'form_action' ), + ); + + if ( strcmp( $new_instance['form_action'], $old_instance['form_action'] ) !== 0 ) { + if ( $new_instance['form_action'] == 'edit' ) { + return array_merge( $this->get_product_from_post( ( int ) $old_instance['product_post_id'] ), $required_widget_props ); + } + + if ( $new_instance['form_action'] == 'clear' ) { + return array_merge( $this->defaults(), $required_widget_props ); + } + } + + $form_product_image_id = (int) $new_instance['form_product_image_id']; + + $form_product_email = ! empty( $new_instance['form_product_email'] ) + ? sanitize_text_field( $new_instance['form_product_email'] ) + : $this->defaults()['form_product_email']; + + return array_merge( $required_widget_props, array( + 'form_product_id' => ( int ) $new_instance['form_product_id'], + 'form_product_title' => sanitize_text_field( $new_instance['form_product_title'] ), + 'form_product_description' => sanitize_text_field( $new_instance['form_product_description'] ), + 'form_product_image_id' => $form_product_image_id, + 'form_product_image_src' => wp_get_attachment_image_url( $form_product_image_id, 'thumbnail' ), + 'form_product_currency' => sanitize_text_field( $new_instance['form_product_currency'] ), + 'form_product_price' => sanitize_text_field( $new_instance['form_product_price'] ), + 'form_product_multiple' => sanitize_text_field( $new_instance['form_product_multiple'] ), + 'form_product_email' => $form_product_email, + ) ); + } + + /** + * Back-end widget form. + * + * @see WP_Widget::form() + * + * @param array $instance Previously saved values from database. + */ + function form( $instance ) { + $instance = wp_parse_args( $instance, $this->defaults() ); + + $product_posts = get_posts( array( + 'numberposts' => 100, + 'orderby' => 'date', + 'post_type' => Jetpack_Simple_Payments::$post_type_product, + 'post_status' => 'publish', + ) ); + + require( dirname( __FILE__ ) . '/simple-payments/form.php' ); + } + } + + // Register Jetpack_Simple_Payments_Widget widget. + function register_widget_jetpack_simple_payments() { + $jetpack_simple_payments = Jetpack_Simple_Payments::getInstance(); + if ( ! $jetpack_simple_payments->is_enabled_jetpack_simple_payments() ) { + return; + } + + register_widget( 'Jetpack_Simple_Payments_Widget' ); + } + add_action( 'widgets_init', 'register_widget_jetpack_simple_payments' ); +} diff --git a/modules/widgets/simple-payments/customizer.css b/modules/widgets/simple-payments/customizer.css new file mode 100644 index 0000000000000..48733d51f63b9 --- /dev/null +++ b/modules/widgets/simple-payments/customizer.css @@ -0,0 +1,72 @@ +.widget-content .jetpack-simple-payments, +.widget-content .jetpack-simple-payments-form { + clear: both; +} + +.widget-content .jetpack-simple-payments-form .invalid { + border: 1px solid #dc3232; +} + +.widget-content .jetpack-simple-payments-form .cost label { + display: block; +} + +.widget-content .jetpack-simple-payments-image-fieldset { + position: relative; + width: 100%; +} + +.widget-content .jetpack-simple-payments-image-fieldset .placeholder { + border: 1px dashed #b4b9be; + box-sizing: border-box; + cursor: pointer; + line-height: 20px; + padding: 9px 0; + position: relative; + text-align: center; + width: 100%; + margin: 4px 0 1em; +} + +.widget-content .jetpack-simple-payments-image { + max-width: 100%; + margin-top: 4px; + position: relative; + text-align: center; +} + +.widget-content .jetpack-simple-payments-image img { + max-width: 100%; + box-sizing: border-box; + border: 1px dashed #b4b9be; + padding: 4px; + height: auto; + cursor: pointer; +} + +.widget-content .jetpack-simple-payments-image img:hover { + border-style: solid; +} + +.widget-content .jetpack-simple-payments-form .field-currency { + display: inline-block; + vertical-align: top; + width: 40%; +} + +.widget-content .jetpack-simple-payments-form .field-price { + display: inline-block; + line-height: 20px; + width: 58%; +} + +.widget-content .jetpack-simple-payments-form .alignleft button, +.widget-content .jetpack-simple-payments-form .alignright span { + display: inline-block; + margin-top: 5px; +} + +.widget-content .button-link:disabled, +.widget-content .button-link:hover[disabled] { + color: #a0a5aa; +} diff --git a/modules/widgets/simple-payments/customizer.js b/modules/widgets/simple-payments/customizer.js new file mode 100644 index 0000000000000..8930ebbab6060 --- /dev/null +++ b/modules/widgets/simple-payments/customizer.js @@ -0,0 +1,373 @@ +/* global jQuery, jpSimplePaymentsStrings, confirm, _ */ +/* eslint no-var: 0, quote-props: 0 */ + +( function( api, wp, $ ) { + var $document = $( document ); + + $document.ready( function() { + $document.on( 'widget-added', function( event, widgetContainer ) { + if ( widgetContainer.is( '[id*="jetpack_simple_payments_widget"]' ) ) { + initWidget( widgetContainer ); + } + } ); + + $document.on( 'widget-synced widget-updated', function( event, widgetContainer ) { + //this fires for all widgets, this prevent errors for non SP widgets + if ( ! widgetContainer.is( '[id*="jetpack_simple_payments_widget"]' ) ) { + return; + } + + event.preventDefault(); + + syncProductLists(); + + var widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); + + enableFormActions( widgetForm ); + + updateProductImage( widgetForm ); + } ); + } ); + + function initWidget( widgetContainer ) { + var widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); + + //Add New Button + widgetForm.find( '.jetpack-simple-payments-add-product' ).on( 'click', showAddNewForm( widgetForm ) ); + //Edit Button + widgetForm.find( '.jetpack-simple-payments-edit-product' ).on( 'click', showEditForm( widgetForm ) ); + //Select an Image + widgetForm.find( '.jetpack-simple-payments-image-fieldset .placeholder, .jetpack-simple-payments-image > img' ).on( 'click', selectImage( widgetForm ) ); + //Remove Image Button + widgetForm.find( '.jetpack-simple-payments-remove-image' ).on( 'click', removeImage( widgetForm ) ); + //Save Product button + widgetForm.find( '.jetpack-simple-payments-save-product' ).on( 'click', saveChanges( widgetForm ) ); + //Cancel Button + widgetForm.find( '.jetpack-simple-payments-cancel-form' ).on( 'click', clearForm( widgetForm ) ); + //Delete Selected Product + widgetForm.find( '.jetpack-simple-payments-delete-product' ).on( 'click', deleteProduct( widgetForm ) ); + //Input, Select and Checkbox change + widgetForm.find( 'select, input, textarea, checkbox' ).on( 'change input propertychange', _.debounce( function() { + disableFormActions( widgetForm ); + }, 250 ) ); + } + + function syncProductLists() { + var request = wp.ajax.post( 'customize-jetpack-simple-payments-buttons-get', { + 'customize-jetpack-simple-payments-nonce': api.settings.nonce[ 'customize-jetpack-simple-payments' ], + 'customize_changeset_uuid': api.settings.changeset.uuid + } ); + + request.done( function( data ) { + var selectedProduct = 0; + + $( document ).find( 'select.jetpack-simple-payments-products' ).each( function( index, select ) { + var $select = $( select ); + selectedProduct = $select.val(); + + $select.find( 'option' ).remove(); + $select.append( $.map( data, function( product ) { + return $( '<option>', { value: product.ID, text: product.post_title } ); + } ) ); + $select.val( selectedProduct ); + } ); + } ); + } + + function showForm( widgetForm ) { + //reset validations + widgetForm.find( '.invalid' ).removeClass( 'invalid' ); + //disable widget title and product selector + widgetForm.find( '.jetpack-simple-payments-widget-title' ) + .add( '.jetpack-simple-payments-products' ) + //disable add and edit buttons + .add( '.jetpack-simple-payments-add-product' ) + .add( '.jetpack-simple-payments-edit-product' ) + //disable save, delete and cancel until the widget update event is fired + .add( '.jetpack-simple-payments-save-product' ) + .add( '.jetpack-simple-payments-cancel-form' ) + .add( '.jetpack-simple-payments-delete-product' ) + .attr( 'disabled', 'disabled' ); + //show form + widgetForm.find( '.jetpack-simple-payments-form' ).show(); + } + + function hideForm( widgetForm ) { + //enable widget title and product selector + widgetForm.find( '.jetpack-simple-payments-widget-title' ) + .add( '.jetpack-simple-payments-products' ) + .removeAttr( 'disabled' ); + //hide the form + widgetForm.find( '.jetpack-simple-payments-form' ).hide(); + } + + function changeFormAction( widgetForm, action ) { + widgetForm.find( '.jetpack-simple-payments-form-action' ).val( action ).change(); + } + + function showAddNewForm( widgetForm ) { + return function( event ) { + event.preventDefault(); + + showForm( widgetForm ); + changeFormAction( widgetForm, 'add' ); + }; + } + + function showEditForm( widgetForm ) { + return function( event ) { + event.preventDefault(); + + showForm( widgetForm ); + changeFormAction( widgetForm, 'edit' ); + }; + } + + function clearForm( widgetForm ) { + return function( event ) { + event.preventDefault(); + + hideForm( widgetForm ); + widgetForm.find( '.jetpack-simple-payments-add-product, .jetpack-simple-payments-edit-product' ).attr( 'disabled', 'disabled' ); + changeFormAction( widgetForm, 'clear' ); + }; + } + + function enableFormActions( widgetForm ) { + var isFormVisible = widgetForm.find( '.jetpack-simple-payments-form' ).is( ':visible' ); + var isProductSelectVisible = widgetForm.find( '.jetpack-simple-payments-products' ).is( ':visible' ); //areProductsVisible ? + var isEdit = widgetForm.find( '.jetpack-simple-payments-form-action' ).val() === 'edit'; + + if ( isFormVisible ) { + widgetForm.find( '.jetpack-simple-payments-save-product' ) + .add( '.jetpack-simple-payments-cancel-form' ) + .removeAttr( 'disabled' ); + } else { + widgetForm.find( '.jetpack-simple-payments-add-product' ).removeAttr( 'disabled' ); + } + + if ( isFormVisible && isEdit ) { + widgetForm.find( '.jetpack-simple-payments-delete-product' ).removeAttr( 'disabled' ); + } + + if ( isProductSelectVisible && ! isFormVisible ) { + widgetForm.find( '.jetpack-simple-payments-edit-product' ).removeAttr( 'disabled' ); + } + } + + function disableFormActions( widgetForm ) { + widgetForm.find( '.jetpack-simple-payments-add-product' ) + .add( '.jetpack-simple-payments-edit-product' ) + .add( '.jetpack-simple-payments-save-product' ) + .add( '.jetpack-simple-payments-cancel-form' ) + .add( '.jetpack-simple-payments-delete-product' ) + .attr( 'disabled', 'disabled' ); + } + + function selectImage( widgetForm ) { + return function( event ) { + event.preventDefault(); + + var imageContainer = widgetForm.find( '.jetpack-simple-payments-image' ); + + var mediaFrame = new wp.media.view.MediaFrame.Select( { + title: 'Choose Product Image', + multiple: false, + library: { type: 'image' }, + button: { text: 'Choose Image' } + } ); + + mediaFrame.on( 'select', function() { + var selection = mediaFrame.state().get( 'selection' ).first().toJSON(); + //hide placeholder + widgetForm.find( '.jetpack-simple-payments-image-fieldset .placeholder' ).hide(); + + //load image from media library + imageContainer.find( 'img' ) + .attr( 'src', selection.url ) + .show(); + + //show image and remove button + widgetForm.find( '.jetpack-simple-payments-image' ).show(); + + //set hidden field for the selective refresh + widgetForm.find( '.jetpack-simple-payments-form-image-id' ).val( selection.id ).change(); + } ); + + mediaFrame.open(); + }; + } + + function removeImage( widgetForm ) { + return function( event ) { + event.preventDefault(); + + //show placeholder + widgetForm.find( '.jetpack-simple-payments-image-fieldset .placeholder' ).show(); + + //hide image and remove button + widgetForm.find( '.jetpack-simple-payments-image' ).hide(); + + //set hidden field for the selective refresh + widgetForm.find( '.jetpack-simple-payments-form-image-id' ).val( '' ).change(); + }; + } + + function updateProductImage( widgetForm ) { + var newImageId = parseInt( widgetForm.find( '.jetpack-simple-payments-form-image-id' ).val(), 10 ); + var newImageSrc = widgetForm.find( '.jetpack-simple-payments-form-image-src' ).val(); + + var placeholder = widgetForm.find( '.jetpack-simple-payments-image-fieldset .placeholder' ); + var image = widgetForm.find( '.jetpack-simple-payments-image > img' ); + var imageControls = widgetForm.find( '.jetpack-simple-payments-image' ); + + if ( newImageId && newImageSrc ) { + image.attr( 'src', newImageSrc ); + placeholder.hide(); + imageControls.show(); + } else { + placeholder.show(); + image.removeAttr( 'src' ); + imageControls.hide(); + } + } + + function isFormValid( widgetForm ) { + widgetForm.find( '.invalid' ).removeClass( 'invalid' ); + + var errors = false; + + var postTitle = widgetForm.find( '.jetpack-simple-payments-form-product-title' ).val(); + if ( ! postTitle ) { + widgetForm.find( '.jetpack-simple-payments-form-product-title' ).addClass( 'invalid' ); + errors = true; + } + + var productPrice = widgetForm.find( '.jetpack-simple-payments-form-product-price' ).val(); + if ( ! productPrice || isNaN( parseFloat( productPrice ) ) || parseFloat( productPrice ) <= 0 ) { + widgetForm.find( '.jetpack-simple-payments-form-product-price' ).addClass( 'invalid' ); + errors = true; + } + + var productEmail = widgetForm.find( '.jetpack-simple-payments-form-product-email' ).val(); + var isProductEmailValid = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test( productEmail ); + if ( ! productEmail || ! isProductEmailValid ) { + widgetForm.find( '.jetpack-simple-payments-form-product-email' ).addClass( 'invalid' ); + errors = true; + } + + return ! errors; + } + + function saveChanges( widgetForm ) { + return function( event ) { + event.preventDefault(); + var productPostId = widgetForm.find( '.jetpack-simple-payments-form-product-id' ).val(); + + if ( ! isFormValid( widgetForm ) ) { + return; + } + + disableFormActions( widgetForm ); + + widgetForm.find( '.spinner' ).show(); + + var request = wp.ajax.post( 'customize-jetpack-simple-payments-button-save', { + 'customize-jetpack-simple-payments-nonce': api.settings.nonce[ 'customize-jetpack-simple-payments' ], + 'customize_changeset_uuid': api.settings.changeset.uuid, + 'params': { + 'product_post_id': productPostId, + 'post_title': widgetForm.find( '.jetpack-simple-payments-form-product-title' ).val(), + 'post_content': widgetForm.find( '.jetpack-simple-payments-form-product-description' ).val(), + 'image_id': widgetForm.find( '.jetpack-simple-payments-form-image-id' ).val(), + 'currency': widgetForm.find( '.jetpack-simple-payments-form-product-currency' ).val(), + 'price': widgetForm.find( '.jetpack-simple-payments-form-product-price' ).val(), + 'multiple': widgetForm.find( '.jetpack-simple-payments-form-product-multiple' ).is( ':checked' ) ? 1 : 0, + 'email': widgetForm.find( '.jetpack-simple-payments-form-product-email' ).val() + } + } ); + + request.done( function( data ) { + var select = widgetForm.find( 'select.jetpack-simple-payments-products' ); + var productOption = select.find( 'option[value="' + productPostId + '"]' ); + + if ( productOption.length > 0 ) { + productOption.text( data.product_post_title ); + } else { + select.append( + $( '<option>', { + value: data.product_post_id, + text: data.product_post_title + } ) + ); + select.val( data.product_post_id ).change(); + } + + widgetForm.find( '.jetpack-simple-payments-products-fieldset' ).show(); + widgetForm.find( '.jetpack-simple-payments-products-warning' ).hide(); + + changeFormAction( widgetForm, 'clear' ); + hideForm( widgetForm ); + } ); + + request.fail( function( data ) { + var validCodes = { + 'post_title': 'product-title', + 'price': 'product-price', + 'email': 'product-email' + }; + + data.forEach( function( item ) { + if ( validCodes.hasOwnProperty( item.code ) ) { + widgetForm.find( '.jetpack-simple-payments-form-' + validCodes[ item.code ] ).addClass( 'invalid' ); + } + } ); + + enableFormActions( widgetForm ); + } ); + }; + } + + function deleteProduct( widgetForm ) { + return function( event ) { + event.preventDefault(); + + if ( ! confirm( jpSimplePaymentsStrings.deleteConfirmation ) ) { + return; + } + + var formProductId = parseInt( widgetForm.find( '.jetpack-simple-payments-form-product-id' ).val(), 10 ); + if ( ! formProductId ) { + return; + } + + disableFormActions( widgetForm ); + + widgetForm.find( '.spinner' ).show(); + + var request = wp.ajax.post( 'customize-jetpack-simple-payments-button-delete', { + 'customize-jetpack-simple-payments-nonce': api.settings.nonce[ 'customize-jetpack-simple-payments' ], + 'customize_changeset_uuid': api.settings.changeset.uuid, + 'params': { + 'product_post_id': formProductId + } + } ); + + request.done( function() { + var productList = widgetForm.find( 'select.jetpack-simple-payments-products' )[ 0 ]; + productList.remove( productList.selectedIndex ); + productList.dispatchEvent( new Event( 'change' ) ); + + if ( $( productList ).has( 'option' ).length === 0 ) { + //hide products select and label + widgetForm.find( '.jetpack-simple-payments-products-fieldset' ).hide(); + //show empty products list warning + widgetForm.find( '.jetpack-simple-payments-products-warning' ).show(); + } + + changeFormAction( widgetForm, 'clear' ); + hideForm( widgetForm ); + } ); + }; + } +}( wp.customize, wp, jQuery ) ); diff --git a/modules/widgets/simple-payments/form.php b/modules/widgets/simple-payments/form.php new file mode 100644 index 0000000000000..483c46610a967 --- /dev/null +++ b/modules/widgets/simple-payments/form.php @@ -0,0 +1,155 @@ +<p> + <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Widget Title', 'jetpack' ); ?></label> + <input + type="text" + class="widefat jetpack-simple-payments-widget-title" + id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" + name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" + value="<?php echo esc_attr( $instance['title'] ); ?>" /> +</p> +<p class="jetpack-simple-payments-products-fieldset" <?php if ( empty( $product_posts ) ) { echo 'style="display:none;"'; } ?>> + <label for="<?php echo $this->get_field_id('product_post_id'); ?>"><?php _e( 'Select a Simple Payment Button:', 'jetpack' ); ?></label> + <select + class="widefat jetpack-simple-payments-products" + id="<?php echo $this->get_field_id('product_post_id'); ?>" + name="<?php echo $this->get_field_name('product_post_id'); ?>"> + <?php foreach ( $product_posts as $product_post ) { ?> + <option value="<?php echo esc_attr( $product_post->ID ) ?>" <?php selected( (int) $instance['product_post_id'], $product_post->ID ); ?>> + <?php echo esc_attr( get_the_title( $product_post ) ) ?> + </option> + <?php } ?> + </select> +</p> +<?php if ( is_customize_preview() ) { ?> +<p class="jetpack-simple-payments-products-warning" <?php if ( ! empty( $product_posts ) ) { echo 'style="display:none;"'; } ?>> + <?php echo __( 'Looks like you don\'t have any products. You can create one using the Add New button below.' ) ?> +</p> +<p> + <div class="alignleft"> + <button class="button jetpack-simple-payments-edit-product" <?php disabled( empty( $product_posts ), true ); ?>> + <?php esc_html_e( 'Edit Selected' ); ?> + </button> + </div> + <div class="alignright"> + <button class="button jetpack-simple-payments-add-product"><?php esc_html_e( 'Add New' ); ?></button> + </div> + <br class="clear"> +</p> +<hr /> +<div class="jetpack-simple-payments-form" style="display: none;"> + <input + type="hidden" + id="<?php echo $this->get_field_id('form_action'); ?>" + name="<?php echo $this->get_field_name('form_action'); ?>" + value="<?php echo esc_attr( $instance['form_action'] ); ?>" + class="jetpack-simple-payments-form-action" /> + <input + type="hidden" + id="<?php echo $this->get_field_id('form_product_id'); ?>" + name="<?php echo $this->get_field_name('form_product_id'); ?>" + value="<?php echo esc_attr( $instance['form_product_id'] ); ?>" + class="jetpack-simple-payments-form-product-id" /> + <input + type="hidden" + id="<?php echo esc_attr( $this->get_field_id( 'form_product_image_id' ) ); ?>" + name="<?php echo esc_attr( $this->get_field_name( 'form_product_image_id' ) ); ?>" + value="<?php echo esc_attr( $instance['form_product_image_id'] ); ?>" + class="jetpack-simple-payments-form-image-id" /> + <input + type="hidden" + id="<?php echo esc_attr( $this->get_field_id( 'form_product_image_src' ) ); ?>" + name="<?php echo esc_attr( $this->get_field_name( 'form_product_image_src' ) ); ?>" + value="<?php echo esc_attr( $instance['form_product_image_src'] ); ?>" + class="jetpack-simple-payments-form-image-src" /> + <p> + <label for="<?php echo esc_attr( $this->get_field_id( 'form_product_title' ) ); ?>"><?php esc_html_e( 'What is this payment for?' ); ?></label> + <input + type="text" + class="widefat field-title jetpack-simple-payments-form-product-title" + id="<?php echo esc_attr( $this->get_field_id( 'form_product_title' ) ); ?>" + name="<?php echo esc_attr( $this->get_field_name( 'form_product_title' ) ); ?>" + value="<?php echo esc_attr( $instance['form_product_title'] ); ?>" /> + <br /> + <small><?php _e( 'For example: event tickets, charitable donations, training courses, coaching fees, etc.' ); ?></small> + </p> + <div class="jetpack-simple-payments-image-fieldset"> + <label><?php esc_html_e( 'Product image' ); ?></label> + <div class="placeholder" <?php if ( ! empty( $instance['form_product_image_id'] ) ) echo 'style="display:none;"'; ?>><?php esc_html_e( 'Select an image' ); ?></div> + <div class="jetpack-simple-payments-image" <?php if ( empty( $instance['form_product_image_id'] ) ) echo 'style="display:none;"'; ?>> + <img src="<?php echo esc_url( $instance['form_product_image_src'] ); ?>" /> + <button class="button jetpack-simple-payments-remove-image"><?php esc_html_e( 'Remove image' ); ?></button> + </div> + </div> + <p> + <label for="<?php echo esc_attr( $this->get_field_id( 'form_product_description' ) ); ?>"><?php esc_html_e( 'Description' ); ?></label> + <textarea + class="field-description widefat jetpack-simple-payments-form-product-description" + rows=5 + id="<?php echo esc_attr( $this->get_field_id( 'form_product_description' ) ); ?>" + name="<?php echo esc_attr( $this->get_field_name( 'form_product_description' ) ); ?>"><?php esc_html_e( $instance['form_product_description'] ); ?></textarea> + </p> + <p class="cost"> + <label for="<?php echo esc_attr( $this->get_field_id( 'form_product_price' ) ); ?>"><?php esc_html_e( 'Price' ); ?></label> + <select + class="field-currency widefat jetpack-simple-payments-form-product-currency" + id="<?php echo esc_attr( $this->get_field_id( 'form_product_currency' ) ); ?>" + name="<?php echo esc_attr( $this->get_field_name( 'form_product_currency' ) ); ?>"> + <?php foreach( Jetpack_Simple_Payments_Widget::$supported_currency_list as $code => $currency ) {?> + <option value="<?php echo esc_attr( $code ) ?>"<?php selected( $instance['form_product_currency'], $code ); ?>> + <?php esc_html_e( $code . ' ' . $currency ) ?> + </option> + <?php } ?> + </select> + <input + type="text" + class="field-price widefat jetpack-simple-payments-form-product-price" + id="<?php echo esc_attr( $this->get_field_id( 'form_product_price' ) ); ?>" + name="<?php echo esc_attr( $this->get_field_name( 'form_product_price' ) ); ?>" + value="<?php echo esc_attr( $instance['form_product_price'] ); ?>" + placeholder="1.00" /> + </p> + <p> + <input + class="field-multiple jetpack-simple-payments-form-product-multiple" + id="<?php echo esc_attr( $this->get_field_id( 'form_product_multiple' ) ); ?>" + name="<?php echo esc_attr( $this->get_field_name( 'form_product_multiple' ) ); ?>" + type="checkbox" + value="1" + <?php checked( $instance['form_product_multiple'], '1' ); ?> /> + <label for="<?php echo esc_attr( $this->get_field_id( 'form_product_multiple' ) ); ?>"><?php esc_html_e( 'Allow people to buy more than one item at a time.' ); ?></label> + </p> + <p> + <label for="<?php echo esc_attr( $this->get_field_id( 'form_product_email' ) ); ?>"><?php esc_html_e( 'Email' ); ?></label> + <input + class="field-email widefat jetpack-simple-payments-form-product-email" + id="<?php echo esc_attr( $this->get_field_id( 'form_product_email' ) ); ?>" + name="<?php echo esc_attr( $this->get_field_name( 'form_product_email' ) ); ?>" + type="email" + value="<?php echo esc_attr( $instance['form_product_email'] ); ?>" /> + <small><?php printf( esc_html__( 'This is where PayPal will send your money. To claim a payment, you\'ll need a %1$sPayPal account%2$s connected to a bank account.' ), '<a href="https://paypal.com" target="_blank">', '</a>' ) ?></small> + </p> + <p> + <div class="alignleft"> + <button type="button" class="button-link button-link-delete jetpack-simple-payments-delete-product"><?php _e( 'Delete Product' ); ?></button> + </div> + <div class="alignright"> + <button name="<?php echo $this->get_field_name('save'); ?>" class="button jetpack-simple-payments-save-product"><?php _e( 'Save' ); ?></button> + <span> | <button type="button" class="button-link jetpack-simple-payments-cancel-form"><?php _e( 'Cancel' ); ?></button></span> + </div> + <br class="clear"> + </p> + <hr /> +</div> +<?php } else { ?> +<p class="jetpack-simple-payments-products-warning"> + <?php + echo sprintf( + wp_kses( + __( 'This widget adds a payment button of your choice to your sidebar. To create or edit the payment buttons themselves, <a href="%s">use the Customizer</a>.' ), + array( 'a' => array( 'href' => array() ) ) + ), + esc_url( add_query_arg( array( 'autofocus[panel]' => 'widgets' ), admin_url( 'customize.php' ) ) ) + ); + ?> +</p> +<?php } ?> diff --git a/modules/widgets/simple-payments/style.css b/modules/widgets/simple-payments/style.css new file mode 100644 index 0000000000000..3a701e01eb7f2 --- /dev/null +++ b/modules/widgets/simple-payments/style.css @@ -0,0 +1,8 @@ +@media screen and (min-width: 400px) { + .widget.jetpack-simple-payments .jetpack-simple-payments-product { + flex-direction: column; + } + .widget.jetpack-simple-payments .jetpack-simple-payments-details { + padding-left: 0; + } +} diff --git a/modules/widgets/simple-payments/widget.php b/modules/widgets/simple-payments/widget.php new file mode 100644 index 0000000000000..740cbd7491784 --- /dev/null +++ b/modules/widgets/simple-payments/widget.php @@ -0,0 +1,25 @@ +<div class='jetpack-simple-payments-wrapper'> + <div class='jetpack-simple-payments-product'> + <div class='jetpack-simple-payments-product-image' <?php if ( empty( $instance['form_product_image_id'] ) ) echo 'style="display:none;"'; ?>> + <div class='jetpack-simple-payments-image'> + <?php echo wp_get_attachment_image( $instance['form_product_image_id'], 'full' ) ?> + </div> + </div> + <div class='jetpack-simple-payments-details'> + <div class='jetpack-simple-payments-title'><p><?php esc_attr_e( $instance['form_product_title'] ); ?></p></div> + <div class='jetpack-simple-payments-description'><p><?php esc_html_e( $instance['form_product_description'] ); ?></p></div> + <div class='jetpack-simple-payments-price'><p><?php esc_attr_e( $instance['form_product_price'] ); ?> <?php esc_attr_e( $instance['form_product_currency'] ); ?></p></div> + <div class='jetpack-simple-payments-purchase-box'> + <?php if ( $instance['form_product_multiple'] ) { ?> + <div class='jetpack-simple-payments-items'> + <input + type='number' + class='jetpack-simple-payments-items-number' + value='1' + min='1' /> + </div> + <?php } ?> + </div> + </div> + </div> +</div> diff --git a/tools/builder/frontend-css.js b/tools/builder/frontend-css.js index 57d41745e82b8..42abd008f5e23 100644 --- a/tools/builder/frontend-css.js +++ b/tools/builder/frontend-css.js @@ -44,7 +44,8 @@ const concat_list = [ 'modules/wordads/css/style.css', 'modules/widgets/eu-cookie-law/style.css', 'modules/widgets/flickr/style.css', - 'modules/search/css/search-widget-frontend.css' + 'modules/search/css/search-widget-frontend.css', + 'modules/widgets/simple-payments/style.css', ]; /**