Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions assets/src/js/front/tutor-cart-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Tutor Cart Button - Global JavaScript for cart count updates
*
* This file handles dynamic cart count updates for the Tutor LMS cart button.
* It listens for custom events dispatched by the cart operations and updates
* the cart count display across all cart button instances on the page.
*
* @package Tutor
* @since 4.1.0
*/

document.addEventListener('DOMContentLoaded', function () {
// Listen for add to cart event
document.addEventListener('tutorAddToCartEvent', function (event) {
const cartCounters = document.querySelectorAll('.tutor-cart-count');
cartCounters.forEach(function (counter) {
if (event.detail.cart_count > 0) {
counter.textContent = event.detail.cart_count;
counter.style.display = 'inline-block';
}
});
});

// Listen for remove from cart event
document.addEventListener('tutorRemoveCartEvent', function (event) {
const cartCounters = document.querySelectorAll('.tutor-cart-count');
cartCounters.forEach(function (counter) {
const mode = counter.getAttribute('data-show-count') || 'if_has_items';

if (event.detail.cart_count > 0) {
counter.textContent = event.detail.cart_count;
counter.style.display = 'inline-block';
} else {
if (mode === 'always') {
counter.textContent = '0';
counter.style.display = 'inline-block';
} else {
counter.textContent = '';
counter.style.display = 'none';
}
}
});
});
});
1 change: 1 addition & 0 deletions assets/src/js/front/tutor-front.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import './pages/cart';
import './pages/checkout';
import './pages/course-landing';
import './pages/instructor-list-filter';
import './tutor-cart-button';

/**
* Codes from this file should be decentralized according to relavent file/folder structure.
Expand Down
40 changes: 40 additions & 0 deletions assets/src/js/gutenberg/cart-button/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "tutor-gutenberg/cart-button",
"title": "Tutor Cart Button",
"category": "tutor",
"icon": "cart",
"description": "Display a cart button with item count for Tutor LMS ecommerce",
"attributes": {
"showCount": {
"type": "string",
"default": "if_has_items"
},
"customClass": {
"type": "string",
"default": "cart-contents"
},
"iconColor": {
"type": "string",
"default": ""
},
"badgeBgColor": {
"type": "string",
"default": "#e74c3c"
},
"badgeTextColor": {
"type": "string",
"default": "#ffffff"
}
},
"supports": {
"spacing": {
"margin": true,
"padding": true
}
},
"editorScript": "tutor-gutenberg-cart-button",
"style": "tutor-cart-button",
"render": "file:./render.php"
}
112 changes: 112 additions & 0 deletions assets/src/js/gutenberg/cart-button/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import { PanelBody, RadioControl, TextControl, BaseControl } from '@wordpress/components';
import { ColorPalette } from '@wordpress/components';
import { __ } from '@wordpress/i18n';

export default function Edit( { attributes, setAttributes } ) {
const { showCount, customClass, iconColor, badgeBgColor, badgeTextColor } = attributes;

// Use a sample count for editor preview
const cartCount = 3;

return (
<>
<InspectorControls group="settings">
<PanelBody title={ __( 'Settings', 'tutor' ) }>
<RadioControl
label={ __( 'Cart Item Count', 'tutor' ) }
selected={ showCount }
options={ [
{ label: __( 'Always (even if empty)', 'tutor' ), value: 'always' },
{ label: __( 'Only if has items', 'tutor' ), value: 'if_has_items' },
{ label: __( 'Never', 'tutor' ), value: 'never' },
] }
onChange={ ( value ) => setAttributes( { showCount: value } ) }
help={ __( 'The editor does not display the real count value, but a placeholder to indicate how it will look on the front-end.', 'tutor' ) }
/>
<TextControl
label={ __( 'Custom CSS Class', 'tutor' ) }
value={ customClass }
onChange={ ( value ) => setAttributes( { customClass: value } ) }
placeholder="cart-contents"
/>
</PanelBody>
</InspectorControls>

<InspectorControls group="styles">
<PanelBody title={ __( 'Colors', 'tutor' ) }>
<BaseControl label={ __( 'Icon Color', 'tutor' ) }>
<ColorPalette
colors={ [] }
value={ iconColor }
onChange={ ( value ) => setAttributes( { iconColor: value } ) }
disableCustomColors={ false }
clearable={ true }
/>
</BaseControl>
<BaseControl label={ __( 'Badge Background Color', 'tutor' ) }>
<ColorPalette
colors={ [] }
value={ badgeBgColor }
onChange={ ( value ) => setAttributes( { badgeBgColor: value } ) }
disableCustomColors={ false }
clearable={ true }
/>
</BaseControl>
</PanelBody>
</InspectorControls>

<div { ...useBlockProps() }>
<div className="tutor-cart-button">
<span
className="tutor-btn-cart"
style={ {
...(iconColor && { '--tutor-cart-icon-color': iconColor }),
} }
>
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.75055 17.964C7.1915 17.964 7.54895 17.6065 7.54895 17.1656C7.54895 16.7246 7.1915 16.3672 6.75055 16.3672C6.30961 16.3672 5.95215 16.7246 5.95215 17.1656C5.95215 17.6065 6.30961 17.964 6.75055 17.964Z"
stroke="currentColor"
strokeWidth="1.3"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M15.5328 17.964C15.9737 17.964 16.3312 17.6065 16.3312 17.1656C16.3312 16.7246 15.9737 16.3672 15.5328 16.3672C15.0918 16.3672 14.7344 16.7246 14.7344 17.1656C14.7344 17.6065 15.0918 17.964 15.5328 17.964Z"
stroke="currentColor"
strokeWidth="1.3"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M2 2.03516H3.59681L5.72056 11.9513C5.79847 12.3145 6.00053 12.6391 6.29198 12.8694C6.58343 13.0996 6.94603 13.2211 7.31736 13.2128H15.1257C15.4892 13.2122 15.8415 13.0877 16.1246 12.8598C16.4076 12.6319 16.6045 12.3142 16.6826 11.9593L18 6.02717H4.4511"
stroke="currentColor"
strokeWidth="1.3"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
{ ( showCount === 'always' || showCount === 'if_has_items' ) && (
<span
className="tutor-cart-count"
style={ {
...(badgeBgColor && { '--tutor-cart-badge-bg': badgeBgColor }),
...(badgeTextColor && { '--tutor-cart-badge-color': badgeTextColor }),
} }
>
{ cartCount }
</span>
) }
</span>
</div>
</div>
</>
);
}
10 changes: 10 additions & 0 deletions assets/src/js/gutenberg/cart-button/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { registerBlockType } from '@wordpress/blocks';
import metadata from './block.json';
import Edit from './edit';
import save from './save';

registerBlockType( metadata.name, {
...metadata,
edit: Edit,
save,
} );
48 changes: 48 additions & 0 deletions assets/src/js/gutenberg/cart-button/render.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
/**
* Server-side rendering for the Tutor Cart Button block
*
* @package Tutor
* @since 4.1.0
*/

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

$attributes = $attributes ?? array();

$atts = array(
'show_icon' => 'true',
'show_count' => isset( $attributes['showCount'] ) ? $attributes['showCount'] : 'if_has_items',
'class' => isset( $attributes['customClass'] ) && ! empty( $attributes['customClass'] ) ? $attributes['customClass'] : 'cart-contents',
);

$shortcode = '[tutor_cart_button';
foreach ( $atts as $key => $value ) {
$shortcode .= ' ' . $key . '="' . esc_attr( $value ) . '"';
}
$shortcode .= ']';

// Build inline CSS variables for custom colors set in the block editor.
// The stylesheet consumes these via var(--tutor-cart-*) with hardcoded fallbacks,
// so no <style> injection is needed — just a single style attribute on the wrapper.
$css_vars = '';
if ( ! empty( $attributes['iconColor'] ) ) {
$css_vars .= '--tutor-cart-icon-color:' . esc_attr( $attributes['iconColor'] ) . ';';
}
if ( ! empty( $attributes['badgeBgColor'] ) ) {
$css_vars .= '--tutor-cart-badge-bg:' . esc_attr( $attributes['badgeBgColor'] ) . ';';
}
if ( ! empty( $attributes['badgeTextColor'] ) ) {
$css_vars .= '--tutor-cart-badge-color:' . esc_attr( $attributes['badgeTextColor'] ) . ';';
}

$wrapper_attributes = get_block_wrapper_attributes(
array_filter( array(

Check failure on line 42 in assets/src/js/gutenberg/cart-button/render.php

View workflow job for this annotation

GitHub Actions / WPCS

Opening parenthesis of a multi-line function call must be the last content on the line
'class' => 'tutor-cart-button',
'style' => $css_vars ?: null,

Check failure on line 44 in assets/src/js/gutenberg/cart-button/render.php

View workflow job for this annotation

GitHub Actions / WPCS

Using short ternaries is not allowed as they are rarely used correctly
) )

Check failure on line 45 in assets/src/js/gutenberg/cart-button/render.php

View workflow job for this annotation

GitHub Actions / WPCS

Closing parenthesis of a multi-line function call must be on a line by itself
);

echo '<div ' . $wrapper_attributes . '>' . do_shortcode( $shortcode ) . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
3 changes: 3 additions & 0 deletions assets/src/js/gutenberg/cart-button/save.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function save() {
return null;
}
30 changes: 30 additions & 0 deletions assets/src/scss/blocks/cart-button.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.tutor-cart-button {
display: inline-block;
line-height: normal;

.tutor-btn-cart {
position: relative;
display: flex;

svg {
color: var(--tutor-cart-icon-color, currentColor);
}
}

.tutor-cart-count {
position: absolute;
top: -8px;
right: -8px;
border-radius: 50%;
padding: 0 6px;
text-align: center;
font-weight: bold;
box-sizing: border-box;
font-size: 12px;
min-width: 18px;
height: 18px;
line-height: 18px;
color: var(--tutor-cart-badge-color, #ffffff);
background-color: var(--tutor-cart-badge-bg, #e74c3c);
}
}
1 change: 1 addition & 0 deletions classes/Assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@
function ( $file ) {
return basename( $file, '.svg' );
},
glob( tutor()->path . 'assets/icons/kids/*.svg' ) ?: array()

Check failure on line 184 in classes/Assets.php

View workflow job for this annotation

GitHub Actions / WPCS

Using short ternaries is not allowed as they are rarely used correctly
);
}

Expand Down Expand Up @@ -345,6 +345,7 @@
* @since 1.9.0
*/
wp_enqueue_style( 'tutor-frontend', tutor()->url . 'assets/css/tutor-front.min.css', array(), TUTOR_VERSION );
wp_enqueue_style( 'tutor-cart-button', tutor()->url . 'assets/css/tutor-cart-button.min.css', array(), TUTOR_VERSION );
wp_enqueue_script( 'tutor-frontend', tutor()->url . 'assets/js/tutor-front.js', array( 'jquery', 'wp-i18n', 'wp-date' ), TUTOR_VERSION, true );
}

Expand Down
18 changes: 17 additions & 1 deletion classes/Gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,22 @@ public function register_blocks() {
)
);

// Register cart button block scripts and styles.
wp_register_script(
'tutor-gutenberg-cart-button',
tutor()->url . 'assets/js/tutor-gutenberg-cart-button.js',
array( 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-components', 'wp-editor' ),
TUTOR_VERSION
);
wp_register_style(
'tutor-cart-button',
tutor()->url . 'assets/css/tutor-cart-button.min.css',
array(),
TUTOR_VERSION
);

register_block_type( tutor()->path . 'assets/src/js/gutenberg/cart-button' );

// Check if WP version is equal to or greater than 5.9.
global $wp_version;
if ( version_compare( $wp_version, '5.9', '>=' ) && function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) {
Expand Down Expand Up @@ -198,6 +214,7 @@ public function render_block_tutor() {
$allowed_shortcode = array(
'tutor_instructor_registration_form',
'tutor_student_registration_form',
'tutor_cart_button',
);

if ( ! in_array( $shortcode, $allowed_shortcode ) ) {
Expand All @@ -206,5 +223,4 @@ public function render_block_tutor() {

wp_send_json_success( do_shortcode( "[{$shortcode}]" ) );
}

}
40 changes: 40 additions & 0 deletions classes/Shortcode.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public function __construct( $register_hooks = true ) {

add_shortcode( 'tutor_cart', array( $this, 'tutor_cart_page' ) );
add_shortcode( 'tutor_checkout', array( $this, 'tutor_checkout_page' ) );
add_shortcode( 'tutor_cart_button', array( $this, 'tutor_cart_button' ) );

/**
* Load more categories
Expand Down Expand Up @@ -505,4 +506,43 @@ public function tutor_checkout_page() {
tutor_load_template( 'ecommerce.checkout' );
return apply_filters( 'tutor_ecommerce/checkout', ob_get_clean() );
}

/**
* Tutor Cart Button Shortcode
*
* @since 4.1.0
*
* @param array $atts shortcode attributes.
*
* @return mixed
*/
public function tutor_cart_button( $atts ) {
$atts = shortcode_atts(
array(
'class' => 'cart-contents',
'title' => __( 'View your shopping cart', 'tutor' ),
'show_icon' => 'true',
'show_count' => 'if_has_items',
'before_count' => '(',
'after_count' => ')',
),
$atts
);

$atts['show_icon'] = 'true' === $atts['show_icon'];

if ( 'true' === $atts['show_count'] ) {
$atts['show_count'] = 'if_has_items';
} elseif ( 'false' === $atts['show_count'] ) {
$atts['show_count'] = 'never';
}

if ( ! in_array( $atts['show_count'], array( 'always', 'if_has_items', 'never' ), true ) ) {
$atts['show_count'] = 'if_has_items';
}

ob_start();
tutor_ecommerce_cart_button( $atts );
return ob_get_clean();
}
}
Loading
Loading