Add Transformer and Integration tests for Modern Events Calendar Lite (#66)
https://webnus.net/modern-events-calendar/ Reviewed-on: Event-Federation/wordpress-activitypub-event-bridge#66 Co-authored-by: André Menrath <andre.menrath@posteo.de> Co-committed-by: André Menrath <andre.menrath@posteo.de>
This commit is contained in:
parent
29536e7a4d
commit
7e8346cf7b
11 changed files with 416 additions and 4 deletions
|
@ -37,7 +37,7 @@ jobs:
|
|||
path: |
|
||||
${{ env.WP_CORE_DIR }}
|
||||
${{ env.WP_TESTS_DIR }}
|
||||
key: cache-wordpress-8
|
||||
key: cache-wordpress-9
|
||||
|
||||
- name: Cache Composer
|
||||
id: cache-composer-phpunit
|
||||
|
@ -103,3 +103,8 @@ jobs:
|
|||
run: cd /workspace/Event-Federation/wordpress-activitypub-event-bridge/ && ./vendor/bin/phpunit --filter=eventin
|
||||
env:
|
||||
PHP_VERSION: ${{ matrix.php-version }}
|
||||
|
||||
- name: Run Integration tests for Modern Events Calendar Lite
|
||||
run: cd /workspace/Event-Federation/wordpress-activitypub-event-bridge/ && ./vendor/bin/phpunit --filter=modern_events_calendar_lite
|
||||
env:
|
||||
PHP_VERSION: ${{ matrix.php-version }}
|
|
@ -44,7 +44,7 @@ The Event Federation plugin ensures that users from those platforms are provided
|
|||
|
||||
### Features for Your WordPress Events and the Fediverse
|
||||
|
||||
**ActivityPub-Enabled Event Sharing:** Your WordPress events are now compatible with the Fediverse, using the ActivityStreams format. This means your events can be easily discovered and followed by users on platforms like Mastodon and other ActivityPub-compatible services.
|
||||
**ActivityPub-Enabled Event Sharing:** Your WordPress events are not part of the Fediverse and
|
||||
|
||||
**Automatic Event Summaries:** When your event is shared on the Fediverse, platforms like Mastodon that don't fully support events will display a brief HTML summary of key details — such as the event's title, start time, and location. This ensures that even if someone can't view the full event on their platform, they still get the important info at a glance, with a link to your WordPress event page.
|
||||
|
||||
|
@ -63,6 +63,8 @@ This plugin depends on the [ActivityPub plugin](https://wordpress.org/plugins/ac
|
|||
* [Events Manager](https://de.wordpress.org/plugins/events-manager/)
|
||||
* [WP Event Manager](https://de.wordpress.org/plugins/wp-event-manager/)
|
||||
* [Eventin](https://de.wordpress.org/plugins/wp-event-solution/)
|
||||
* [Modern Events Calendar Lite](https://webnus.net/modern-events-calendar/)
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
|
|
|
@ -226,6 +226,20 @@ install_wp_plugin() {
|
|||
unzip -q -o "$TMPDIR/$PLUGIN_FILE" -d "$WP_CORE_DIR/wp-content/plugins/"
|
||||
}
|
||||
|
||||
install_wp_plugin_mec() {
|
||||
mkdir -p "$WP_CORE_DIR/wp-content/plugins/"
|
||||
|
||||
if [ -d "$WP_CORE_DIR/wp-content/plugins/modern-events-calendar-lite" ]; then
|
||||
return;
|
||||
fi
|
||||
|
||||
PLUGIN_VERSION="v7.15.0"
|
||||
|
||||
URL="https://code.event-federation.eu/Event-Federation/modern-events-calendar-lite"
|
||||
|
||||
git clone $URL "$WP_CORE_DIR/wp-content/plugins/modern-events-calendar-lite"
|
||||
}
|
||||
|
||||
install_wp_plugins() {
|
||||
if [ "$SKIP_PLUGINS_INSTALL" = "true" ]; then
|
||||
echo "Skipping WordPress plugin installation."
|
||||
|
@ -240,6 +254,8 @@ install_wp_plugins() {
|
|||
install_wp_plugin events-manager
|
||||
install_wp_plugin wp-event-manager
|
||||
install_wp_plugin wp-event-solution
|
||||
# Mec is not installable via wordpress.org, we use our own mirror.
|
||||
install_wp_plugin_mec
|
||||
}
|
||||
|
||||
install_wp
|
||||
|
|
|
@ -53,11 +53,12 @@
|
|||
"@test-gatherpress",
|
||||
"@test-events-manager",
|
||||
"@test-wp-event-manager",
|
||||
"@test-eventin"
|
||||
"@test-eventin",
|
||||
"@test-modern-events-calendar-lite"
|
||||
],
|
||||
"test-debug": [
|
||||
"@prepare-test",
|
||||
"@test-eventin"
|
||||
"@test-modern-events-calendar-lite"
|
||||
],
|
||||
"test-vs-event-list": "phpunit --filter=vs_event_list",
|
||||
"test-the-events-calendar": "phpunit --filter=the_events_calendar",
|
||||
|
@ -65,6 +66,7 @@
|
|||
"test-events-manager": "phpunit --filter=events_manager",
|
||||
"test-wp-event-manager": "phpunit --filter=wp_event_manager",
|
||||
"test-eventin": "phpunit --filter=eventin",
|
||||
"test-modern-events-calendar-lite": "phpunit --filter=modern_events_calendar_lite",
|
||||
"test-all": "phpunit"
|
||||
}
|
||||
}
|
||||
|
|
16
improvements.md
Normal file
16
improvements.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
Make use of:
|
||||
|
||||
https://docs.theeventscalendar.com/reference/classes/tribe__events__api/get_event_terms/
|
||||
|
||||
for getting all tags/terms for an event!
|
||||
|
||||
```
|
||||
public static function get_event_terms( $event_id, array $args = array() ) {
|
||||
$terms = array();
|
||||
foreach ( get_post_taxonomies( $event_id ) as $taxonomy ) {
|
||||
$tax_terms = wp_get_object_terms( $event_id, $taxonomy, $args );
|
||||
$terms[ $taxonomy ] = $tax_terms;
|
||||
}
|
||||
return $terms;
|
||||
}
|
||||
```
|
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
/**
|
||||
* ActivityPub Tribe Transformer
|
||||
*
|
||||
* @package ActivityPub_Event_Bridge
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace ActivityPub_Event_Bridge\Activitypub\Transformer;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use ActivityPub_Event_Bridge\Activitypub\Transformer\Event;
|
||||
|
||||
use MEC;
|
||||
use MEC\Events\Event as MEC_Event;
|
||||
use MEC_main;
|
||||
|
||||
/**
|
||||
* ActivityPub Tribe Transformer
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Modern_Events_Calendar_Lite extends Event {
|
||||
/**
|
||||
* The MEC Event object.
|
||||
*
|
||||
* @var MEC_Event|null
|
||||
*/
|
||||
protected $mec_event;
|
||||
|
||||
/**
|
||||
* The MEC main instance.
|
||||
*
|
||||
* @var MEC_main|null
|
||||
*/
|
||||
protected $mec_main;
|
||||
|
||||
|
||||
/**
|
||||
* Extend the constructor, to also set the tribe object.
|
||||
*
|
||||
* This is a special class object form The Events Calendar which
|
||||
* has a lot of useful functions, we make use of our getter functions.
|
||||
*
|
||||
* @param WP_Post $wp_object The WordPress object.
|
||||
* @param string $wp_taxonomy The taxonomy slug of the event post type.
|
||||
*/
|
||||
public function __construct( $wp_object, $wp_taxonomy ) {
|
||||
parent::__construct( $wp_object, $wp_taxonomy );
|
||||
$this->mec_main = MEC::getInstance( 'app.libraries.main' );
|
||||
$this->mec_event = new MEC_Event( $wp_object );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the event object.
|
||||
*/
|
||||
public function get_start_time(): string {
|
||||
return \gmdate( 'Y-m-d\TH:i:s\Z', $this->mec_event->get_datetime()['start']['timestamp'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the event object.
|
||||
*/
|
||||
public function get_end_time(): ?string {
|
||||
return \gmdate( 'Y-m-d\TH:i:s\Z', $this->mec_event->get_datetime()['end']['timestamp'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location.
|
||||
*/
|
||||
public function get_location(): ?Place {
|
||||
$location_id = $this->mec_main->get_master_location_id( $this->mec_event->ID );
|
||||
|
||||
if ( ! $location_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = $this->mec_main->get_location_data( $location_id );
|
||||
|
||||
$location = new Place();
|
||||
$location->set_sensitive( null );
|
||||
|
||||
if ( ! empty( $data['address'] ) ) {
|
||||
$location->set_address( $data['address'] );
|
||||
}
|
||||
if ( ! empty( $data['name'] ) ) {
|
||||
$location->set_name( $data['name'] );
|
||||
}
|
||||
if ( ! empty( $data['longitude'] ) ) {
|
||||
$location->set_longitude( $data['longitude'] );
|
||||
}
|
||||
if ( ! empty( $data['latitude'] ) ) {
|
||||
$location->set_latitude( $data['latitude'] );
|
||||
}
|
||||
|
||||
return $location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location.
|
||||
*/
|
||||
public function get_timezone(): string {
|
||||
$timezone = get_post_meta( $this->wp_object->ID, 'mec_timezone', true );
|
||||
|
||||
if ( 'global' === $timezone ) {
|
||||
return parent::get_timezone();
|
||||
}
|
||||
|
||||
return $timezone;
|
||||
}
|
||||
}
|
|
@ -130,6 +130,7 @@ class Setup {
|
|||
'\ActivityPub_Event_Bridge\Plugins\VS_Event_List',
|
||||
'\ActivityPub_Event_Bridge\Plugins\WP_Event_Manager',
|
||||
'\ActivityPub_Event_Bridge\Plugins\Eventin',
|
||||
'\ActivityPub_Event_Bridge\Plugins\Modern_Events_Calendar_Lite',
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
61
includes/plugins/class-modern-events-calendar-lite.php
Normal file
61
includes/plugins/class-modern-events-calendar-lite.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
/**
|
||||
* Modern Events Calendar (Lite)
|
||||
*
|
||||
* Defines all the necessary meta information for the Modern Events Calendar (Lite).
|
||||
*
|
||||
* @link https://webnus.net/modern-events-calendar/
|
||||
* @package ActivityPub_Event_Bridge
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
namespace ActivityPub_Event_Bridge\Plugins;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Interface for a supported event plugin.
|
||||
*
|
||||
* This interface defines which information is necessary for a supported event plugin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Modern_Events_Calendar_Lite extends Event_plugin {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_plugin_file(): string {
|
||||
return 'modern-events-calendar-lite/modern-events-calendar-lite.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event post type of the plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_post_type(): string {
|
||||
// See MEC_feature_events->get_main_post_type().
|
||||
return apply_filters( 'mec_post_type_name', 'mec-events' ); // phpcs:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the main settings page of the plugin.
|
||||
*
|
||||
* @return string The settings page url.
|
||||
*/
|
||||
public static function get_settings_page(): string {
|
||||
return 'mec-event';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_event_category_taxonomy(): string {
|
||||
return 'mec_category';
|
||||
}
|
||||
}
|
|
@ -53,6 +53,7 @@ This plugin depends on the [ActivityPub plugin](https://wordpress.org/plugins/ac
|
|||
* [Events Manager](https://de.wordpress.org/plugins/events-manager/)
|
||||
* [WP Event Manager](https://de.wordpress.org/plugins/wp-event-manager/)
|
||||
* [Eventin](https://de.wordpress.org/plugins/wp-event-solution/)
|
||||
* [Modern Events Calendar Lite](https://webnus.net/modern-events-calendar/)
|
||||
|
||||
== Configuration ==
|
||||
|
||||
|
|
|
@ -62,6 +62,9 @@ function _manually_load_plugin() {
|
|||
case 'eventin':
|
||||
$plugin_file = 'wp-event-solution/eventin.php';
|
||||
break;
|
||||
case 'modern_events_calendar_lite':
|
||||
$plugin_file = 'modern-events-calendar-lite/modern-events-calendar-lite.php';
|
||||
break;
|
||||
case 'gatherpress':
|
||||
$plugin_file = 'gatherpress/gatherpress.php';
|
||||
break;
|
||||
|
@ -88,6 +91,12 @@ function _manually_load_plugin() {
|
|||
em_create_locations_table();
|
||||
}
|
||||
|
||||
if ( 'modern_events_calendar_lite' === $activitypub_event_extension_integration_filter ) {
|
||||
require_once $plugin_dir . 'modern-events-calendar-lite/app/libraries/factory.php';
|
||||
$mec_factory = new MEC_factory();
|
||||
$mec_factory->install();
|
||||
}
|
||||
|
||||
// At last manually load our WordPress plugin.
|
||||
require dirname( __DIR__ ) . '/activitypub-event-bridge.php';
|
||||
}
|
||||
|
|
185
tests/test-class-plugin-modern-events-calendar-lite.php
Normal file
185
tests/test-class-plugin-modern-events-calendar-lite.php
Normal file
|
@ -0,0 +1,185 @@
|
|||
<?php
|
||||
/**
|
||||
* Tests or Modern Events Calendar Lite
|
||||
*
|
||||
* @package ActivityPub_Event_Bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sample test case.
|
||||
*/
|
||||
class Test_Modern_Events_Calendar_Lite extends WP_UnitTestCase {
|
||||
/**
|
||||
* The MEC main instance.
|
||||
*
|
||||
* @var \MEC_main|null
|
||||
*/
|
||||
protected $mec_main;
|
||||
|
||||
/**
|
||||
* Override the setup function, so that tests don't run if the Events Calendar is not active.
|
||||
*/
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
|
||||
if ( ! class_exists( '\MEC' ) ) {
|
||||
self::markTestSkipped( 'Modern Events Calendar Lite is not active.' );
|
||||
}
|
||||
|
||||
// Make sure that ActivityPub support is enabled for The Events Calendar.
|
||||
$aec = \ActivityPub_Event_Bridge\Setup::get_instance();
|
||||
$aec->activate_activitypub_support_for_active_event_plugins();
|
||||
|
||||
$this->mec_main = \MEC::getInstance( 'app.libraries.main' );
|
||||
|
||||
// Delete all posts afterwards.
|
||||
_delete_all_posts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the right transformer gets applied.
|
||||
*/
|
||||
public function test_modern_events_calendar_lite_transformer_class() {
|
||||
// We only test for one event plugin being active at the same time,
|
||||
// even though we support multiple onces in theory.
|
||||
// But testing all combinations is beyond scope.
|
||||
$active_event_plugins = \ActivityPub_Event_Bridge\Setup::get_instance()->get_active_event_plugins();
|
||||
$this->assertEquals( 1, count( $active_event_plugins ) );
|
||||
|
||||
// Enable ActivityPub support for the event plugin.
|
||||
$this->assertContains( 'mec-events', get_option( 'activitypub_support_post_types' ) );
|
||||
|
||||
// Insert a new Event.
|
||||
$event = array(
|
||||
'title' => 'MEC Test Event',
|
||||
'status' => 'publish',
|
||||
'start_time_hour' => '3',
|
||||
'start_time_minutes' => '00',
|
||||
'start_time_ampm' => 'PM',
|
||||
'start' => '2025-01-01',
|
||||
'end' => '2025-01-01',
|
||||
'end_time_hour' => '4',
|
||||
'end_time_minutes' => '00',
|
||||
'end_time_ampm' => 'PM',
|
||||
'repeat_status' => 0,
|
||||
'repeat_type' => 'daily',
|
||||
'interval' => 1,
|
||||
);
|
||||
|
||||
$post_id = $this->mec_main->save_event( $event );
|
||||
|
||||
$wp_object = get_post( $post_id );
|
||||
|
||||
// Call the transformer Factory.
|
||||
$transformer = \Activitypub\Transformer\Factory::get_transformer( $wp_object );
|
||||
|
||||
// Check that we got the right transformer.
|
||||
$this->assertInstanceOf( \ActivityPub_Event_Bridge\Activitypub\Transformer\Modern_Events_Calendar_Lite::class, $transformer );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the transformation of minimal event.
|
||||
*/
|
||||
public function test_modern_events_calendar_lite_minimal_event() {
|
||||
$start_timestamp = strtotime( '+10 days 15:00:00' );
|
||||
$end_timestamp = strtotime( '+10 days 16:00:00' );
|
||||
|
||||
// Insert a new Event.
|
||||
$event = array(
|
||||
'title' => 'MEC Test Event',
|
||||
'status' => 'publish',
|
||||
'content' => 'This is the content of the MEC!',
|
||||
'start_time_hour' => gmdate( 'h', $start_timestamp ),
|
||||
'start_time_minutes' => gmdate( 'i', $start_timestamp ),
|
||||
'start_time_ampm' => gmdate( 'A', $start_timestamp ),
|
||||
'start' => gmdate( 'Y-m-d', $start_timestamp ),
|
||||
'end' => gmdate( 'Y-m-d', $end_timestamp ),
|
||||
'end_time_hour' => gmdate( 'h', $end_timestamp ),
|
||||
'end_time_minutes' => gmdate( 'i', $end_timestamp ),
|
||||
'end_time_ampm' => gmdate( 'A', $end_timestamp ),
|
||||
'repeat_status' => 0,
|
||||
'repeat_type' => 'daily',
|
||||
'interval' => 1,
|
||||
);
|
||||
|
||||
$post_id = $this->mec_main->save_event( $event );
|
||||
|
||||
$wp_object = get_post( $post_id );
|
||||
|
||||
// Call the transformer to make the ActivityStreams representation of the event.
|
||||
$event_array = \Activitypub\Transformer\Factory::get_transformer( $wp_object )->to_object()->to_array();
|
||||
|
||||
$this->assertEquals( 'Event', $event_array['type'] );
|
||||
$this->assertEquals( 'MEC Test Event', $event_array['name'] );
|
||||
$this->assertEquals( 'This is the content of the MEC!', wp_strip_all_tags( $event_array['content'] ) );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 16:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] );
|
||||
$this->assertTrue( $event_array['commentsEnabled'] );
|
||||
$this->assertEquals( 'allow_all', $event_array['repliesModerationOption'] );
|
||||
$this->assertEquals( 'external', $event_array['joinMode'] );
|
||||
$this->assertEquals( get_permalink( $wp_object ), $event_array['externalParticipationUrl'] );
|
||||
$this->assertArrayNotHasKey( 'location', $event_array );
|
||||
$this->assertEquals( 'MEETING', $event_array['category'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the transformation of minimal event.
|
||||
*/
|
||||
public function test_modern_events_calendar_lite_event_with_location() {
|
||||
$start_timestamp = strtotime( '+10 days 15:00:00' );
|
||||
$end_timestamp = strtotime( '+10 days 16:00:00' );
|
||||
|
||||
// Add new location.
|
||||
$location = array(
|
||||
'name' => 'MEC Location',
|
||||
'latitude' => '52.356370',
|
||||
'longitude' => '4.955760',
|
||||
'address' => 'Stichting NLnet, Science Park 400, 1098 XH Amsterdam',
|
||||
'url' => 'https://nlnet.nl/',
|
||||
);
|
||||
|
||||
$location_id = $this->mec_main->save_location( $location );
|
||||
|
||||
// Insert a new Event.
|
||||
$event = array(
|
||||
'title' => 'MEC Test Event',
|
||||
'status' => 'publish',
|
||||
'content' => 'This is the content of the MEC!',
|
||||
'start_time_hour' => gmdate( 'h', $start_timestamp ),
|
||||
'start_time_minutes' => gmdate( 'i', $start_timestamp ),
|
||||
'start_time_ampm' => gmdate( 'A', $start_timestamp ),
|
||||
'start' => gmdate( 'Y-m-d', $start_timestamp ),
|
||||
'end' => gmdate( 'Y-m-d', $end_timestamp ),
|
||||
'end_time_hour' => gmdate( 'h', $end_timestamp ),
|
||||
'end_time_minutes' => gmdate( 'i', $end_timestamp ),
|
||||
'end_time_ampm' => gmdate( 'A', $end_timestamp ),
|
||||
'repeat_status' => 0,
|
||||
'repeat_type' => 'daily',
|
||||
'interval' => 1,
|
||||
'location_id' => $location_id,
|
||||
);
|
||||
|
||||
$post_id = $this->mec_main->save_event( $event );
|
||||
|
||||
$wp_object = get_post( $post_id );
|
||||
|
||||
// Call the transformer to make the ActivityStreams representation of the event.
|
||||
$event_array = \Activitypub\Transformer\Factory::get_transformer( $wp_object )->to_object()->to_array();
|
||||
|
||||
$this->assertEquals( 'Event', $event_array['type'] );
|
||||
$this->assertEquals( 'MEC Test Event', $event_array['name'] );
|
||||
$this->assertEquals( 'This is the content of the MEC!', wp_strip_all_tags( $event_array['content'] ) );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 15:00:00' ) ) . 'T15:00:00Z', $event_array['startTime'] );
|
||||
$this->assertEquals( gmdate( 'Y-m-d', strtotime( '+10 days 16:00:00' ) ) . 'T16:00:00Z', $event_array['endTime'] );
|
||||
$this->assertTrue( $event_array['commentsEnabled'] );
|
||||
$this->assertEquals( 'allow_all', $event_array['repliesModerationOption'] );
|
||||
$this->assertEquals( 'external', $event_array['joinMode'] );
|
||||
$this->assertEquals( get_permalink( $wp_object ), $event_array['externalParticipationUrl'] );
|
||||
$this->assertArrayHasKey( 'location', $event_array );
|
||||
$this->assertEquals( 'MEETING', $event_array['category'] );
|
||||
$this->assertEquals( $location['address'], $event_array['location']['address'] );
|
||||
$this->assertEquals( $location['name'], $event_array['location']['name'] );
|
||||
$this->assertEquals( $location['latitude'], $event_array['location']['latitude'] );
|
||||
$this->assertEquals( $location['longitude'], $event_array['location']['longitude'] );
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue