Skip to content
Merged
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
1 change: 0 additions & 1 deletion .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ jobs:

- name: Run E2E tests
env:
WP_BASE_URL: http://localhost:8888
WP_USERNAME: admin
WP_PASSWORD: password
run: npm run test:e2e
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
build/*
!build/index.php

# wp-env local overrides (per-machine ports etc. — never commit)
.wp-env.override.json

# Playwright
test-results/
playwright-report/
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,12 @@ Simple plugin to add a focal point control to the featured post image.
**If you want to use this plugin extension**, you can find it at https://github.com/a8cteam51/bamberg-ua/tree/trunk/mu-plugins/team51-focal-point

Copy the `team51-focal-point` folder to your `mu-plugins` directory.

## Changelog

### 2.1.1

- **Loop Event Info block:** added a configurable HTML wrapper element (`div`/`p`/`h1`–`h6`), per-block date and time format overrides, and a query offset control.
- **Query Loop Events:** the block-editor preview now matches the published front-end for every feed order. Previously, with "Newest to Oldest" the editor preview showed the oldest events while the front-end showed the newest.
- Removed a redundant query cache-buster that wrote a timestamp into post content on every change.
- Added Playwright end-to-end tests (run in CI) covering the event-dates save flow, the Query Loop editor/front-end parity, and the Loop Event Info rendering options.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "wpcomsp-simple-events",
"version": "2.1.0",
"version": "2.1.1",
"description": "A simple Gutenberg-first event management plugin that integrates with WooCommerce Box Office.",
"author": {
"name": "WordPress.com Special Projects Team",
Expand Down Expand Up @@ -48,6 +48,7 @@
"lint:pkg-json": "wp-scripts lint-pkg-json",
"packages-update": "wp-scripts packages-update",
"test:e2e": "playwright test --config=tests/e2e/playwright.config.js",
"test:e2e:local": "wp-env start && playwright test --config=tests/e2e/playwright.config.js",
"test:e2e:ui": "playwright test --config=tests/e2e/playwright.config.js --ui",
"test:e2e:headed": "playwright test --config=tests/e2e/playwright.config.js --headed",
"test:e2e:debug": "playwright test --config=tests/e2e/playwright.config.js --debug",
Expand Down
6 changes: 3 additions & 3 deletions plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Simple Events Plugin bootstrap file.
*
* @since 1.0.0
* @version 2.1.0
* @version 2.1.1
* @author WordPress.com Special Projects
* @license GPL-3.0-or-later
*
Expand All @@ -14,7 +14,7 @@
* Description: Event management frontend for WooCommerce Box Office.
* Requires at least: 6.5
* Tested up to: 6.9
* Version: 2.1.0
* Version: 2.1.1
* Requires PHP: 8.0
* Author: WordPress.com Special Projects
* Author URI: https://wpspecialprojects.wordpress.com
Expand All @@ -32,7 +32,7 @@
function_exists( 'get_plugin_data' ) || require_once ABSPATH . 'wp-admin/includes/plugin.php';
define( 'SE_METADATA', get_plugin_data( __FILE__, false, false ) );

define( 'SE_VERSION', '2.1.0' );
define( 'SE_VERSION', '2.1.1' );
define( 'SE_BASENAME', plugin_basename( __FILE__ ) );
define( 'SE_PLUGIN_DIR', untrailingslashit( plugin_dir_path( __FILE__ ) ) );
define( 'SE_PLUGIN_URL', untrailingslashit( plugin_dir_url( __FILE__ ) ) );
Expand Down
13 changes: 13 additions & 0 deletions src/blocks/loop-event-info/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@
"order": {
"type": "string",
"default": "asc"
},
"dateFormat": {
"type": "string",
"default": ""
},
"timeFormat": {
"type": "string",
"default": ""
},
"tagName": {
"enum": ["div", "p", "h1", "h2", "h3", "h4", "h5", "h6"],
"type": "string",
"default": "div"
}
},
"supports": {
Expand Down
91 changes: 89 additions & 2 deletions src/blocks/loop-event-info/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import './index.scss';
import './editor.scss';
import metadata from './block.json';

import { __ } from '@wordpress/i18n';
import { __, sprintf } from '@wordpress/i18n';
import { registerBlockType } from '@wordpress/blocks';
import {
PanelBody,
Expand All @@ -19,9 +19,27 @@ import {
} from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';
import { useEffect } from '@wordpress/element';
import { dateI18n, getSettings as getDateSettings } from '@wordpress/date';

registerBlockType(metadata, {
edit: ({ attributes: { metaName, metaPrefix, thePostId, textAlign, addCalendarLinks, feedType, order }, setAttributes, context: { postId }, clientId }) => {
edit: ({ attributes: { metaName, metaPrefix, thePostId, textAlign, addCalendarLinks, feedType, order, dateFormat, timeFormat, tagName }, setAttributes, context: { postId }, clientId }) => {

const siteFormats = getDateSettings().formats;
const siteDateFormat = siteFormats.date;
const siteTimeFormat = siteFormats.time;
const showDateFormat = metaName === 'dates' || metaName === 'date';
const showTimeFormat = metaName === 'dates' || metaName === 'time';

const formatPreview = (format) => {
if (!format) {
return '';
}
try {
return dateI18n(format, new Date());
} catch (e) {
return '';
}
};

// Get query loop data from our custom store
const queryData = useSelect((select) => {
Expand Down Expand Up @@ -57,6 +75,24 @@ registerBlockType(metadata, {
<PanelBody
title={__('Display Options', 'simple-events')}
>
<SelectControl
label={__('HTML element', 'simple-events')}
value={tagName}
options={[
{ label: __( 'Default (div)', 'simple-events' ), value: 'div' },
{ label: __( 'Paragraph (p)', 'simple-events' ), value: 'p' },
{ label: __( 'Heading 1 (h1)', 'simple-events' ), value: 'h1' },
{ label: __( 'Heading 2 (h2)', 'simple-events' ), value: 'h2' },
{ label: __( 'Heading 3 (h3)', 'simple-events' ), value: 'h3' },
{ label: __( 'Heading 4 (h4)', 'simple-events' ), value: 'h4' },
{ label: __( 'Heading 5 (h5)', 'simple-events' ), value: 'h5' },
{ label: __( 'Heading 6 (h6)', 'simple-events' ), value: 'h6' },
]}
onChange={(value) =>
setAttributes({ tagName: value })
}
__nextHasNoMarginBottom
/>
<SelectControl
label={__('Show what event info?', 'simple-events')}
value={metaName}
Expand Down Expand Up @@ -87,6 +123,54 @@ registerBlockType(metadata, {
setAttributes({ addCalendarLinks: value } )
}
/>
{ showDateFormat && (
<TextControl
label={__('Date format override', 'simple-events')}
help={
dateFormat
? sprintf(
/* translators: %s: rendered date example. */
__('Preview: %s', 'simple-events'),
formatPreview(dateFormat)
)
: sprintf(
/* translators: %s: site default date format. */
__('Leave empty to use the site default (%s).', 'simple-events'),
siteDateFormat
)
}
placeholder={siteDateFormat}
value={dateFormat}
onChange={(value) =>
setAttributes({ dateFormat: value })
}
__nextHasNoMarginBottom
/>
) }
{ showTimeFormat && (
<TextControl
label={__('Time format override', 'simple-events')}
help={
timeFormat
? sprintf(
/* translators: %s: rendered time example. */
__('Preview: %s', 'simple-events'),
formatPreview(timeFormat)
)
: sprintf(
/* translators: %s: site default time format. */
__('Leave empty to use the site default (%s).', 'simple-events'),
siteTimeFormat
)
}
placeholder={siteTimeFormat}
value={timeFormat}
onChange={(value) =>
setAttributes({ timeFormat: value })
}
__nextHasNoMarginBottom
/>
) }
</PanelBody>
</InspectorControls>
<BlockControls group="block">
Expand All @@ -108,6 +192,9 @@ registerBlockType(metadata, {
addCalendarLinks,
feedType, // Use block attribute values
order, // Use block attribute values
dateFormat,
timeFormat,
tagName,
}}
/>
</div>
Expand Down
42 changes: 40 additions & 2 deletions src/classes/class-date-display-formatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,20 @@ class SE_Date_Display_Formatter {
*/
private $use_html_in_date_output = false;

/**
* Override for the date format. Empty string means use the site option.
*
* @var string
*/
private $date_format = '';

/**
* Override for the time format. Empty string means use the site option.
*
* @var string
*/
private $time_format = '';

/**
* Create a new instance of the date display formatter.
*
Expand Down Expand Up @@ -169,6 +183,28 @@ public function set_time_only( bool $time_only = true ) {
$this->time_only = $time_only;
}

/**
* Override the date format used by format_date(). Empty string restores the site default.
*
* @param string $format A PHP date format string.
*
* @return void
*/
public function set_date_format( string $format ): void {
$this->date_format = $format;
}

/**
* Override the time format used by format_time(). Empty string restores the site default.
*
* @param string $format A PHP date format string.
*
* @return void
*/
public function set_time_format( string $format ): void {
$this->time_format = $format;
}
Comment on lines +193 to +206
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Sanitize format overrides before storing to prevent HTML injection in rendered date output.

Line 193 and Line 204 accept raw strings that later flow into frontend-rendered date/time text. A format like <script>... is treated as literal output by date formatting and can be rendered unescaped.

Suggested fix
 public function set_date_format( string $format ): void {
-	$this->date_format = $format;
+	$this->date_format = trim( wp_strip_all_tags( $format ) );
 }
@@
 public function set_time_format( string $format ): void {
-	$this->time_format = $format;
+	$this->time_format = trim( wp_strip_all_tags( $format ) );
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/classes/class-date-display-formatter.php` around lines 193 - 206, The
setters set_date_format and set_time_format currently store raw user-provided
strings which can include HTML/JS and later be rendered; update both methods
(set_date_format and set_time_format) to sanitize the incoming $format before
assigning to $this->date_format and $this->time_format (e.g., use a
WordPress-safe sanitizer like sanitize_text_field() or wp_strip_all_tags() to
strip tags/unsafe characters and optionally validate against a whitelist/regex
of allowed date format characters), so only cleaned format strings are stored
and later rendered.


/**
* Modify Timezone.
*
Expand Down Expand Up @@ -630,7 +666,8 @@ private function get_timezone_abbreviation() {
* @return string
*/
public function format_date( $date_timestamp ) {
return wp_date( get_option( 'date_format' ), $date_timestamp, $this->get_timezone_instance() );
$format = '' !== $this->date_format ? $this->date_format : get_option( 'date_format' );
return wp_date( $format, $date_timestamp, $this->get_timezone_instance() );
}

/**
Expand All @@ -641,7 +678,8 @@ public function format_date( $date_timestamp ) {
* @return string
*/
public function format_time( $time_timestamp ) {
return wp_date( get_option( 'time_format' ), $time_timestamp, $this->get_timezone_instance() );
$format = '' !== $this->time_format ? $this->time_format : get_option( 'time_format' );
return wp_date( $format, $time_timestamp, $this->get_timezone_instance() );
}

/**
Expand Down
22 changes: 21 additions & 1 deletion src/classes/class-se-block-variations.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,27 @@ public function modify_event_posts( $posts, $query ) {
public function set_admin_query( $args, $request ) {

$feed_type = $request->get_param( 'feedType' );
$feed_order = $request->get_param( 'order' );#
$feed_order = $request->get_param( 'order' );

// This filter runs for EVERY rest_se-event_query request. `feedType`
// is a custom param only the events Query Loop variation sends; a
// plain /wp/v2/se-event request never has it. Bail untouched for
// those so generic REST consumers (post lists, integrations) aren't
// switched to the child post type. (`order` is a standard REST
// collection param present on every request, so it can't be the
// discriminator.)
if ( null === $feed_type ) {
return $args;
}

// Mirror build_query: run the events query against the child
// se-event-date posts (which carry se_event_date_start/end);
// modify_event_posts remaps them back to parent events. Without
// this the editor REST preview queries se-event parents that lack
// the date meta, so the meta-order SQL never gets its `+0 ASC`
// form and fix_sort_order can't flip it — the editor preview was
// stuck oldest-first regardless of feed order.
$args['post_type'] = SE_Event_Post_Type::$event_date_post_type;

return $this->set_event_query_args( $args, $feed_type, $feed_order );
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Expand Down
17 changes: 13 additions & 4 deletions src/classes/class-se-blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,9 @@ public static function loop_event_info_render( $attributes, $content, $block ):
break;
}

$date_format = isset( $attributes['dateFormat'] ) ? (string) $attributes['dateFormat'] : '';
$time_format = isset( $attributes['timeFormat'] ) ? (string) $attributes['timeFormat'] : '';

// Generate output based on meta name.
if ( ! empty( $post_ID ) ) {
switch ( $attributes['metaName'] ) {
Expand All @@ -852,13 +855,13 @@ public static function loop_event_info_render( $attributes, $content, $block ):
$output = se_event_get_venue( $post_ID );
break;
case 'dates':
$output = $get_date_function( $post_ID, $event_date_id );
$output = $get_date_function( $post_ID, $event_date_id, false, false, null, $date_format, $time_format );
break;
case 'date':
$output = $get_date_function( $post_ID, $event_date_id, true, false );
$output = $get_date_function( $post_ID, $event_date_id, true, false, null, $date_format, $time_format );
break;
case 'time':
$output = $get_date_function( $post_ID, $event_date_id, false, true );
$output = $get_date_function( $post_ID, $event_date_id, false, true, null, $date_format, $time_format );
break;
}
}
Expand Down Expand Up @@ -890,9 +893,15 @@ public static function loop_event_info_render( $attributes, $content, $block ):
$output .= se_template_calendar_links( false );
}

$allowed_tags = array( 'div', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' );
$tag_name = isset( $attributes['tagName'] ) && in_array( $attributes['tagName'], $allowed_tags, true )
? $attributes['tagName']
: 'div';

// Add gutenberg generated wrapper atts.
$output = sprintf(
'<div %s>%s%s</div>',
'<%1$s %2$s>%3$s%4$s</%1$s>',
$tag_name,
get_block_wrapper_attributes(
array(
'class' => 'has-text-align-' . esc_attr( $attributes['textAlign'] ),
Expand Down
Loading
Loading