From 989844f92279634d462ef9659d0d06f4f3a5886a Mon Sep 17 00:00:00 2001 From: Hans Morbach <6433480+hansmorb@users.noreply.github.com> Date: Thu, 8 Feb 2024 18:46:06 +0100 Subject: [PATCH 01/25] added failing unit tests for #1505 --- src/View/Calendar.php | 2 +- tests/php/View/CalendarTest.php | 51 +++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/View/Calendar.php b/src/View/Calendar.php index a128ba9b4..af42a3ebd 100644 --- a/src/View/Calendar.php +++ b/src/View/Calendar.php @@ -401,7 +401,7 @@ public static function getCalendarDataArray( $item, $location, string $startDate * * @return \CommonsBooking\Model\Timeframe|null */ - private static function getClosestBookableTimeFrameForToday( $bookableTimeframes ): ?\CommonsBooking\Model\Timeframe { + public static function getClosestBookableTimeFrameForToday( $bookableTimeframes ): ?\CommonsBooking\Model\Timeframe { // Sort timeframes by startdate usort( $bookableTimeframes, diff --git a/tests/php/View/CalendarTest.php b/tests/php/View/CalendarTest.php index ae7387fee..28097f92a 100644 --- a/tests/php/View/CalendarTest.php +++ b/tests/php/View/CalendarTest.php @@ -26,6 +26,8 @@ class CalendarTest extends CustomPostTypeTest { protected $secondClosestTimeframe; + private $now; + public function testKeepDateRangeParam() { $startDate = date( 'Y-m-d', strtotime( self::CURRENT_DATE ) ); $jsonresponse = Calendar::getCalendarDataArray( @@ -160,15 +162,58 @@ public function testBookingOffset() { $this->assertTrue($days[date('Y-m-d', strtotime('+1 day', strtotime($today)))]['locked']); } + public function testGetClosestBookableTimeFrameForToday() { + //Case 1: Timeframes do not overlap + $closestTimeframeModel = new Timeframe( $this->closestTimeframe ); + $secondClosestTimeframeModel = new Timeframe( $this->secondClosestTimeframe ); + $timeframes = [ $closestTimeframeModel, $secondClosestTimeframeModel ]; + $this->assertEquals($closestTimeframeModel->ID, Calendar::getClosestBookableTimeFrameForToday($timeframes)->ID ); + + //Case 2: Timeframes overlap + $otherLocation = $this->createLocation("OtherLocation"); + $otherItem = $this->createItem("OtherItem"); + $continuedTimeframe = new Timeframe( $this->createTimeframe( + $otherLocation, + $otherItem, + strtotime( '-50 days midnight', $this->now ), + strtotime( '+365 days midnight', $this->now ), + \CommonsBooking\Wordpress\CustomPostType\Timeframe::BOOKABLE_ID, + "on", + 'w', + 0, + '8:00 AM', + '12:00 PM', + 'publish', + [ "1", "2", "3", "4" ], + ) ); + $inBetweenTimeframe = new Timeframe( $this->createTimeframe( + $otherLocation, + $otherItem, + strtotime( '+20 days midnight', $this->now ), + strtotime( '+40 days midnight', $this->now ), + \CommonsBooking\Wordpress\CustomPostType\Timeframe::BOOKABLE_ID, + "on", + 'w', + 0, + '8:00 AM', + '12:00 PM', + 'publish', + [ "5", "6" ], + ) ); + $timeframes = [ $inBetweenTimeframe, $continuedTimeframe ]; + $this->assertEquals($continuedTimeframe->ID, Calendar::getClosestBookableTimeFrameForToday($timeframes)->ID); + + } + protected function setUp() : void { parent::setUp(); - $now = time(); + $this->now = time(); $this->timeframeId = $this->createTimeframe( $this->locationId, $this->itemId, - strtotime( '+' . self::timeframeStart . ' days midnight', $now ), - strtotime( '+' . self::timeframeEnd . ' days midnight', $now ) + strtotime( '+' . self::timeframeStart . ' days midnight', $this->now ), + strtotime( '+' . self::timeframeEnd . ' days midnight', $this->now ) ); // set booking days in advance update_post_meta( $this->timeframeId, Timeframe::META_TIMEFRAME_ADVANCE_BOOKING_DAYS, self::bookingDaysInAdvance ); From dd141f367bbeb058fa4f0442a7358402a5df5132 Mon Sep 17 00:00:00 2001 From: Hans Morbach <6433480+hansmorb@users.noreply.github.com> Date: Wed, 29 May 2024 14:13:16 +0200 Subject: [PATCH 02/25] fix #1505 --- src/Model/Day.php | 15 ++++++ src/Repository/Timeframe.php | 29 +++++++++++ src/View/Calendar.php | 53 +++++++++++++------ tests/php/Model/DayTest.php | 13 +++++ tests/php/Repository/TimeframeTest.php | 16 ++++++ tests/php/View/CalendarTest.php | 72 +++++++++++++++++++++++++- 6 files changed, 182 insertions(+), 16 deletions(-) diff --git a/src/Model/Day.php b/src/Model/Day.php index c5e9ab41c..846cdf5de 100644 --- a/src/Model/Day.php +++ b/src/Model/Day.php @@ -440,6 +440,21 @@ protected function mapRestrictions( array &$slots ) { } } + public function getStartTimestamp(): int { + $dt = new DateTime( $this->getDate() ); + $dt->modify( 'midnight' ); + + return $dt->getTimestamp(); + } + + public function getEndTimestamp(): int { + $dt = new DateTime( $this->getDate() ); + $dt->modify( '23:59:59' ); + + return $dt->getTimestamp(); + } + + /** * Remove empty and merge connected slots. * diff --git a/src/Repository/Timeframe.php b/src/Repository/Timeframe.php index 35d719d6d..c65a18e6a 100644 --- a/src/Repository/Timeframe.php +++ b/src/Repository/Timeframe.php @@ -564,6 +564,35 @@ private static function filterTimeframesForCurrentUser( $posts ): array { } ); } + /** + * Will filter out all timeframes that are not in the given timerange. + * + * @param \CommonsBooking\Model\Timeframe[] $timeframes + * @param int $startTimestamp + * @param int $endTimestamp + * + * @return \CommonsBooking\Model\Timeframe[] + * @throws Exception + */ + public static function filterTimeframesForTimerange( array $timeframes, int $startTimestamp, int $endTimestamp ): array { + return array_filter( $timeframes, function ( $timeframe ) use ( $startTimestamp, $endTimestamp ) { + //filter out anything in the future + if ( $timeframe->getStartDate() > $endTimestamp ) { + return false; + } + //always include infinite timeframes + if ( ! $timeframe->getEndDate() ) { + return true; + } + //filter out anything in the past + if ( $timeframe->getEndDate() < $startTimestamp ) { + return false; + } + + return true; + } ); + } + /** * Instantiate models for posts. * Why? In some cases we need more than WP_Post methods and for this case we have Models, that enrich WP_Post diff --git a/src/View/Calendar.php b/src/View/Calendar.php index 16ce20b11..c9b8b4ac1 100644 --- a/src/View/Calendar.php +++ b/src/View/Calendar.php @@ -407,21 +407,44 @@ public static function getCalendarDataArray( $item, $location, string $startDate * @return \CommonsBooking\Model\Timeframe|null */ public static function getClosestBookableTimeFrameForToday( $bookableTimeframes ): ?\CommonsBooking\Model\Timeframe { - // Sort timeframes by startdate - usort( - $bookableTimeframes, - function ( \CommonsBooking\Model\Timeframe $item1, \CommonsBooking\Model\Timeframe $item2 ) { - $item1StartDateDistance = abs( time() - $item1->getStartDate() ); - $item1EndDateDistance = abs( time() - $item1->getEndDate() ); - $item1SmallestDistance = min( $item1StartDateDistance, $item1EndDateDistance ); - - $item2StartDateDistance = abs( time() - $item2->getStartDate() ); - $item2EndDateDistance = abs( time() - $item2->getEndDate() ); - $item2SmallestDistance = min( $item2StartDateDistance, $item2EndDateDistance ); - - return $item2SmallestDistance <=> $item1SmallestDistance; - } - ); + $today = new Day( date( 'Y-m-d' ) ); + $todayTimeframes = \CommonsBooking\Repository\Timeframe::filterTimeframesForTimerange( $bookableTimeframes, $today->getStartTimestamp(), $today->getEndTimestamp() ); + $todayTimeframes = array_filter( $todayTimeframes, function ( $timeframe ) use ( $today ) { //also consider repetition + return $today->isInTimeframe( $timeframe ); + } ); + switch ( count( $todayTimeframes ) ) { + case 1: + $bookableTimeframes = $todayTimeframes; + break; + case 0: + usort( $bookableTimeframes, function ( $a, $b ) { + $aStartDate = $a->getStartDate(); + $bStartDate = $b->getStartDate(); + + if ( $aStartDate == $bStartDate ) { + $aStartTimeDT = $a->getStartTimeDateTime(); + $bStartTimeDT = $b->getStartTimeDateTime(); + + return $bStartTimeDT <=> $aStartTimeDT; + } + + return $aStartDate <=> $bStartDate; + } ); + break; + default: //More than one timeframe for current day + // consider starttime and endtime + $now = new DateTime(); + /** @var \CommonsBooking\Model\Timeframe $todayTimeframes */ + $bookableTimeframes = array_filter( $todayTimeframes, function ( $timeframe ) use ( $now ) { + $startTime = $timeframe->getStartTime(); + $startTimeDT = new DateTime( $startTime ); + $endTime = $timeframe->getEndTime(); + $endTimeDT = new DateTime( $endTime ); + + return $startTimeDT <= $now && $now <= $endTimeDT; + } ); + break; + } return array_pop( $bookableTimeframes ); } diff --git a/tests/php/Model/DayTest.php b/tests/php/Model/DayTest.php index c51a882e9..15ef2b9bb 100644 --- a/tests/php/Model/DayTest.php +++ b/tests/php/Model/DayTest.php @@ -206,4 +206,17 @@ public function testGetRestrictions() { $this->assertTrue(count($this->instance->getRestrictions()) == 1); } + + public function testGetStartTimestamp() { + $start = strtotime( self::CURRENT_DATE . ' midnight' ); + $this->assertEquals( $start, $this->instance->getStartTimestamp() ); + } + + public function testGetEndTimestamp() { + $end = strtotime( self::CURRENT_DATE . ' 23:59:59' ); + $this->assertEquals( $end, $this->instance->getEndTimestamp() ); + } + + + } diff --git a/tests/php/Repository/TimeframeTest.php b/tests/php/Repository/TimeframeTest.php index 69fe55970..b5df9c481 100644 --- a/tests/php/Repository/TimeframeTest.php +++ b/tests/php/Repository/TimeframeTest.php @@ -164,6 +164,22 @@ public function testGetHoliday() { ); } + public function testFilterTimeframesForTimerange() { + $allTimeframeModels = array_map( function ( $timeframeId ) { + return new \CommonsBooking\Model\Timeframe( $timeframeId ); + }, $this->allTimeframes ); + //should return everything in the repetition + $filteredTimeframes = Timeframe::filterTimeframesForTimerange( + $allTimeframeModels, + $this->repetition_start, + $this->repetition_end + ); + $this->assertEqualsCanonicalizing( $this->allTimeframes, array_map( function ( $timeframe ) { + return $timeframe->ID; + }, $filteredTimeframes ) ); + + } + protected function setUp() : void { parent::setUp(); $this->repetition_start = strtotime(self::CURRENT_DATE); diff --git a/tests/php/View/CalendarTest.php b/tests/php/View/CalendarTest.php index fcf039d8b..9a7032b81 100644 --- a/tests/php/View/CalendarTest.php +++ b/tests/php/View/CalendarTest.php @@ -185,7 +185,7 @@ public function testGetClosestBookableTimeFrameForToday() { $timeframes = [ $closestTimeframeModel, $secondClosestTimeframeModel ]; $this->assertEquals($closestTimeframeModel->ID, Calendar::getClosestBookableTimeFrameForToday($timeframes)->ID ); - //Case 2: Timeframes overlap + //Case 2: Timeframes overlap but repetition is different $otherLocation = $this->createLocation("OtherLocation"); $otherItem = $this->createItem("OtherItem"); $continuedTimeframe = new Timeframe( $this->createTimeframe( @@ -219,6 +219,76 @@ public function testGetClosestBookableTimeFrameForToday() { $timeframes = [ $inBetweenTimeframe, $continuedTimeframe ]; $this->assertEquals($continuedTimeframe->ID, Calendar::getClosestBookableTimeFrameForToday($timeframes)->ID); + //case 3: Timeframes only exist in the future + $timeframeModel = new Timeframe( $this->timeframeId ); + $timeframes = [ $timeframeModel, $secondClosestTimeframeModel ]; + $this->assertEquals($timeframeModel->ID, Calendar::getClosestBookableTimeFrameForToday($timeframes)->ID); + + //case 4: timeframes overlap on the day but have different times (for today) + $otherItem = $this->createItem("OtherItem"); + $otherLocation = $this->createLocation("OtherLocation"); + $noonTimeframe = new Timeframe( $this->createTimeframe( + $otherLocation, + $otherItem, + strtotime( '-1 days midnight', $this->now ), + strtotime( '+1 days midnight', $this->now ), + \CommonsBooking\Wordpress\CustomPostType\Timeframe::BOOKABLE_ID, + "off", + 'd', + 0, + '8:00 AM', + '11:00 AM', + ) ); + $afternoonTimeframe = new Timeframe( $this->createTimeframe( + $otherLocation, + $otherItem, + strtotime( '-1 days midnight', $this->now ), + strtotime( '+1 days midnight', $this->now ), + \CommonsBooking\Wordpress\CustomPostType\Timeframe::BOOKABLE_ID, + "on", + 'd', + 0, + '12:00 PM', + '3:00 PM', + ) ); + $timeframes = [ $noonTimeframe, $afternoonTimeframe ]; + $morning = new \DateTime(); + $morning->setTime(9,0); + ClockMock::freeze($morning); + $this->assertEquals($noonTimeframe->ID, Calendar::getClosestBookableTimeFrameForToday($timeframes)->ID); + + $afternoon = new \DateTime(); + $afternoon->setTime(13,0); + ClockMock::freeze($afternoon); + $this->assertEquals($afternoonTimeframe->ID, Calendar::getClosestBookableTimeFrameForToday($timeframes)->ID); + + //case 5 timeframes overlap on the day but have different times (for future) + $noonFutureTimeframe = new Timeframe( $this->createTimeframe( + $otherLocation, + $otherItem, + strtotime( '+5 days midnight', $this->now ), + strtotime( '+7 days midnight', $this->now ), + \CommonsBooking\Wordpress\CustomPostType\Timeframe::BOOKABLE_ID, + "off", + 'd', + 0, + '8:00 AM', + '11:00 AM', + ) ); + $afternoonFutureTimeframe = new Timeframe( $this->createTimeframe( + $otherLocation, + $otherItem, + strtotime( '+5 days midnight', $this->now ), + strtotime( '+7 days midnight', $this->now ), + \CommonsBooking\Wordpress\CustomPostType\Timeframe::BOOKABLE_ID, + "on", + 'd', + 0, + '12:00 PM', + '3:00 PM', + ) ); + $timeframes = [ $noonFutureTimeframe, $afternoonFutureTimeframe ]; + $this->assertEquals($noonFutureTimeframe->ID, Calendar::getClosestBookableTimeFrameForToday($timeframes)->ID); } protected function setUp() : void { From c86ee47cf92eea911a62eb3f186161260abf948c Mon Sep 17 00:00:00 2001 From: Nils Larsen Date: Sat, 20 Jan 2024 18:03:07 +0100 Subject: [PATCH 03/25] Create new function BookingCodes::lookupCode() to factorize code (should not change behavior). The new function is similar to getCode() but does not generate codes. --- src/Repository/BookingCodes.php | 71 +++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/src/Repository/BookingCodes.php b/src/Repository/BookingCodes.php index 68e848b51..6f050ae6c 100644 --- a/src/Repository/BookingCodes.php +++ b/src/Repository/BookingCodes.php @@ -127,7 +127,45 @@ public static function getCodes( int $timeframeId, int $startDate = null, int $e } /** - * Returns booking code by timeframe, location, item and date. + * Gets a specific booking code by item ID and date. + * + * @param int $itemId - ID of item attached to timeframe + * @param string $date - Date in format Y-m-d + * + * @return BookingCode|null + */ + private static function lookupCode(int $itemId, string $date): ?BookingCode { + global $wpdb; + $table_name = $wpdb->prefix . self::$tablename; + + $sql = $wpdb->prepare( + "SELECT * FROM $table_name + WHERE + item = %s AND + date = %s + ORDER BY item ASC, date ASC + LIMIT 1", + $itemId, + $date + ); + + $bookingCodes = $wpdb->get_results($sql); + + if (count($bookingCodes)) { + return new BookingCode( + $bookingCodes[0]->date, + $bookingCodes[0]->item, + $bookingCodes[0]->location, + $bookingCodes[0]->timeframe, + $bookingCodes[0]->code + ); + } + + return null; + } + + /** + * Returns booking code by timeframe, location, item and date. If no code exist yet and timeframe does not have end-date, generate it. * * @param Timeframe $timeframe - Timeframe object to get code for * @param int $itemId - ID of item attached to timeframe @@ -143,25 +181,10 @@ public static function getCode( Timeframe $timeframe, int $itemId, int $location if ( $cacheItem ) { return $cacheItem; } else { - global $wpdb; - $table_name = $wpdb->prefix . self::$tablename; - $sql = $wpdb->prepare( - "SELECT * FROM $table_name - WHERE - timeframe = %s AND - item = %s AND - location = %s AND - date = %s - ORDER BY item ASC ,date ASC", - $timeframe->ID, - $itemId, - $locationId, - $date - ); - $bookingCodes = $wpdb->get_results($sql); + $bookingCodeObject = static::lookupCode($itemId, $date); - if ( empty( $bookingCodes ) ) { + if ( ! $bookingCodeObject ) { //when we have a timeframe without end-date we generate as many codes as we need if (! $timeframe->getRawEndDate() && $timeframe->bookingCodesApplicable() ) { $begin = $timeframe->getUTCStartDateDateTime(); @@ -173,20 +196,10 @@ public static function getCode( Timeframe $timeframe, int $itemId, int $location $interval = DateInterval::createFromDateString( '1 day' ); $period = new DatePeriod( $begin, $interval, $endDate ); static::generatePeriod($timeframe,$period); - $bookingCodes = $wpdb->get_results($sql); + $bookingCodeObject = static::lookupCode($itemId, $date); } } - $bookingCodeObject = null; - if ( count( $bookingCodes ) ) { - $bookingCodeObject = new BookingCode( - $bookingCodes[0]->date, - $bookingCodes[0]->item, - $bookingCodes[0]->location, - $bookingCodes[0]->timeframe, - $bookingCodes[0]->code - ); - } Plugin::setCacheItem( $bookingCodeObject, [$timeframe->ID] ); return $bookingCodeObject; From acca7d0af94b6abff1bc6a766ec5de3a2284fba0 Mon Sep 17 00:00:00 2001 From: Nils Larsen Date: Sat, 20 Jan 2024 18:41:50 +0100 Subject: [PATCH 04/25] do not delete codes before generation new and do not generate when a code already exists for the given day, item, timeframe and location. do not test for timeframeId and LocationId of bookingcodes because they are now deprecated --- src/Repository/BookingCodes.php | 27 +++++------------------ tests/php/Repository/BookingCodesTest.php | 10 --------- 2 files changed, 6 insertions(+), 31 deletions(-) diff --git a/src/Repository/BookingCodes.php b/src/Repository/BookingCodes.php index 6f050ae6c..03e39701b 100644 --- a/src/Repository/BookingCodes.php +++ b/src/Repository/BookingCodes.php @@ -330,8 +330,6 @@ private static function generatePeriod( Timeframe $timeframe, DatePeriod $period throw new BookingCodeException( __( "No booking codes could be created because the item of the timeframe could not be found.", 'commonsbooking' ) ); } - self::deleteOldCodes( $timeframe->ID, $location->ID, $item->ID ); - $bookingCodesRandomizer = intval( $timeframe->ID ); $bookingCodesRandomizer += $item->ID; $bookingCodesRandomizer += $location->ID; @@ -339,6 +337,12 @@ private static function generatePeriod( Timeframe $timeframe, DatePeriod $period foreach ( $period as $dt ) { $day = new Day( $dt->format( 'Y-m-d' ) ); if ( $day->isInTimeframe( $timeframe ) ) { + + // Check if a code already exists, if so DO NOT generate new + if ( static::lookupCode($item->ID, $dt->format( 'Y-m-d' )) ) { + continue; + } + $bookingCode = new BookingCode( $dt->format( 'Y-m-d' ), $item->ID, @@ -353,25 +357,6 @@ private static function generatePeriod( Timeframe $timeframe, DatePeriod $period return true; } - /** - * Removes all codes for the post, that don't have the current location-id or item-id. - * - * @param int $postId - * @param int $locationId - * @param int $itemId - */ - public static function deleteOldCodes( int $postId, int $locationId, int $itemId ) : void { - global $wpdb; - $table_name = $wpdb->prefix . self::$tablename; - - $query = $wpdb->prepare( 'DELETE FROM ' . $table_name . ' WHERE timeframe = %d AND (location != %d OR item != %d)', - $postId, - $locationId, - $itemId - ); - $wpdb->query( $query ); - } - /** * @param BookingCode $bookingCode * diff --git a/tests/php/Repository/BookingCodesTest.php b/tests/php/Repository/BookingCodesTest.php index 607e6c07e..494c3ca60 100644 --- a/tests/php/Repository/BookingCodesTest.php +++ b/tests/php/Repository/BookingCodesTest.php @@ -39,8 +39,6 @@ public function testGenerate() $this->assertNotNull($code); $this->assertEquals($todayDate,$code->getDate()); $this->assertEquals($this->itemId,$code->getItem()); - $this->assertEquals($this->locationId,$code->getLocation()); - $this->assertEquals($this->timeframeWithEndDate->ID,$code->getTimeframe()); //and now without end date (the fabled "infinite" timeframe) BookingCodes::generate($this->timeframeWithoutEndDate,self::ADVANCE_GENERATION_DAYS); @@ -48,8 +46,6 @@ public function testGenerate() $this->assertNotNull($code); $this->assertEquals($todayDate,$code->getDate()); $this->assertEquals($this->itemId,$code->getItem()); - $this->assertEquals($this->locationId,$code->getLocation()); - $this->assertEquals($this->timeframeWithoutEndDate->ID,$code->getTimeframe()); //make sure, that the last infinite code is also generated $advanceDays = self::ADVANCE_GENERATION_DAYS - 1; @@ -58,8 +54,6 @@ public function testGenerate() $this->assertNotNull($code); $this->assertEquals($lastCodeDay,$code->getDate()); $this->assertEquals($this->itemId,$code->getItem()); - $this->assertEquals($this->locationId,$code->getLocation()); - $this->assertEquals($this->timeframeWithoutEndDate->ID,$code->getTimeframe()); } public function testGetCode() { @@ -90,9 +84,7 @@ public function testGetCode() { self::ADVANCE_GENERATION_DAYS ); $this->assertNotNull( $code ); - $this->assertEquals( $this->timeframeWithoutEndDate->ID, $code->getTimeframe() ); $this->assertEquals( $this->itemId, $code->getItem() ); - $this->assertEquals( $this->locationId, $code->getLocation() ); $this->assertEquals( $dayInFuture, $code->getDate() ); //test that the code is persisted (i.e. it's not generated again) @@ -116,9 +108,7 @@ public function testGetCode() { $dayInFutureTwo, self::ADVANCE_GENERATION_DAYS); $this->assertNotNull( $futureTwoCode ); - $this->assertEquals( $this->timeframeWithoutEndDate->ID, $futureTwoCode->getTimeframe() ); $this->assertEquals( $this->itemId, $futureTwoCode->getItem() ); - $this->assertEquals( $this->locationId, $futureTwoCode->getLocation() ); $this->assertEquals( $dayInFutureTwo, $futureTwoCode->getDate() ); //now check, that the old code is still persisted $stillSameCode = BookingCodes::getCode( From f52b112815384b4fc221e9e914a5e9d42a535bbf Mon Sep 17 00:00:00 2001 From: Nils Larsen Date: Sat, 20 Jan 2024 18:48:49 +0100 Subject: [PATCH 05/25] deprecate timeframeId, use itemId to select code --- src/Repository/BookingCodes.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Repository/BookingCodes.php b/src/Repository/BookingCodes.php index 03e39701b..882c6c3e2 100644 --- a/src/Repository/BookingCodes.php +++ b/src/Repository/BookingCodes.php @@ -98,11 +98,11 @@ public static function getCodes( int $timeframeId, int $startDate = null, int $e $sql = $wpdb->prepare( "SELECT * FROM $table_name - WHERE timeframe = %d + WHERE item = %d AND date BETWEEN %s AND %s ORDER BY item ASC ,date ASC ", - $timeframeId, + $timeframe->getItem()->ID, $startDate, $endDate ); From baf2388694b2c6db4a95b0a7e5772f6e66b57e98 Mon Sep 17 00:00:00 2001 From: Nils Larsen Date: Sat, 20 Jan 2024 18:52:22 +0100 Subject: [PATCH 06/25] remove function getLastCode() which did a quick and dirty check to test if new codes should be generated. Instead, do a thorough test with lookupCode for each day. --- src/Repository/BookingCodes.php | 48 +++-------------------- tests/php/Repository/BookingCodesTest.php | 21 ---------- 2 files changed, 6 insertions(+), 63 deletions(-) diff --git a/src/Repository/BookingCodes.php b/src/Repository/BookingCodes.php index 882c6c3e2..f97e65336 100644 --- a/src/Repository/BookingCodes.php +++ b/src/Repository/BookingCodes.php @@ -75,11 +75,12 @@ public static function getCodes( int $timeframeId, int $startDate = null, int $e //check, if we have enough codes for the timeframe or if we need to generate more //we only need to check, if we have an open-ended timeframe - //we check, if the end date of the last generated code is before the end date of the requested time period - if ( ! $timeframe->getRawEndDate() && self::getLastCode( $timeframe ) - && strtotime( self::getLastCode( $timeframe )->getDate() ) < strtotime( $endDate ) - ) { - $startGenerationPeriod = new \DateTime( self::getLastCode($timeframe)->getDate() ); + // NOTE: there used to be a check if generation is necessary by checking the date of the last code. However, + // as the codes are never deleted anymore, it is possible that they get fragmented with date gaps and the + // check is not trivial anymore. Is is easier and safer to always try to generate codes. It is no serious + // performance issue as getCodes() is only used in admin pages on particular admin actions. + if ( ! $timeframe->getRawEndDate() ) { + $startGenerationPeriod = new \DateTime( $startDate ); $endGenerationPeriod = new \DateTime( $endDate ); // set $endGenerationPeriod's time > 00:00:00 such that $endDate is included in DatePeriod iteration // and code is generated for $endDate @@ -206,43 +207,6 @@ public static function getCode( Timeframe $timeframe, int $itemId, int $location } } - /** - * Will get the last booking code that was generated for a given timeframe, item and location. - * This can be used to determine if we need to generate new codes. - * - * @param Timeframe $timeframe - * @param int $itemId - * @param int $locationId - * - * @return BookingCode|null - */ - public static function getLastCode(Timeframe $timeframe) : ?BookingCode { - global $wpdb; - $table_name = $wpdb->prefix . self::$tablename; - - $sql = $wpdb->prepare( - "SELECT * FROM $table_name - WHERE - timeframe = %s - ORDER BY date DESC", - $timeframe->ID - ); - $bookingCodes = $wpdb->get_results($sql); - - $bookingCodeObject = null; - if ( count( $bookingCodes ) ) { - $bookingCodeObject = new BookingCode( - $bookingCodes[0]->date, - $bookingCodes[0]->item, - $bookingCodes[0]->location, - $bookingCodes[0]->timeframe, - $bookingCodes[0]->code - ); - } - - return $bookingCodeObject; - } - /** * Creates booking-codes table; */ diff --git a/tests/php/Repository/BookingCodesTest.php b/tests/php/Repository/BookingCodesTest.php index 494c3ca60..7d17d620d 100644 --- a/tests/php/Repository/BookingCodesTest.php +++ b/tests/php/Repository/BookingCodesTest.php @@ -227,27 +227,6 @@ public function testGetCodesFuture() { } } - public function testGetLastCode() { - ClockMock::freeze( new \DateTime( self::CURRENT_DATE ) ); - BookingCodes::generate( $this->timeframeWithEndDate, self::ADVANCE_GENERATION_DAYS ); - $lastCode = BookingCodes::getLastCode( $this->timeframeWithEndDate ); - $this->assertNotNull( $lastCode ); - $this->assertEquals( $this->timeframeWithEndDate->ID, $lastCode->getTimeframe() ); - $this->assertEquals( $this->itemId, $lastCode->getItem() ); - $this->assertEquals( $this->locationId, $lastCode->getLocation() ); - $this->assertEquals( strtotime( '+29 day', strtotime( self::CURRENT_DATE ) ), strtotime($lastCode->getDate() ) ); - $advanceGenerationDays = self::ADVANCE_GENERATION_DAYS; - BookingCodes::generate( $this->timeframeWithoutEndDate, self::ADVANCE_GENERATION_DAYS ); - $lastCode = BookingCodes::getLastCode( $this->timeframeWithoutEndDate ); - $this->assertNotNull( $lastCode ); - $this->assertEquals( $this->timeframeWithoutEndDate->ID, $lastCode->getTimeframe() ); - $this->assertEquals( $this->itemId, $lastCode->getItem() ); - $this->assertEquals( $this->locationId, $lastCode->getLocation() ); - //The DatePeriod does not include the endDay, so we have to subtract one day - $advanceGenerationDays -= 1; - $this->assertEquals( strtotime( '+' . $advanceGenerationDays . ' day', strtotime( self::CURRENT_DATE ) ), strtotime($lastCode->getDate() ) ); - } - protected function setUp(): void { parent::setUp(); $this->timeframeWithEndDate = new Timeframe($this->createTimeframe( From f376aa8cf3e3cedf4d120ead2b57b208822f91f8 Mon Sep 17 00:00:00 2001 From: Nils Larsen Date: Sat, 20 Jan 2024 18:58:53 +0100 Subject: [PATCH 07/25] also do not delete booking codes on timeframe deletion --- src/Plugin.php | 6 ++---- src/Repository/BookingCodes.php | 28 ---------------------------- 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/src/Plugin.php b/src/Plugin.php index 61b810433..6b1473f09 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -861,14 +861,12 @@ function () { /** * Adds bookingcode actions. * They: - * 1. Delete booking codes when a booking is deleted. - * 2. Hook appropriate function to button that downloads the booking codes in the backend. + * - Hook appropriate function to button that downloads the booking codes in the backend. * @see \CommonsBooking\View\BookingCodes::renderTable() - * 3. Hook appropriate function to button that sends out emails with booking codes to the station. + * - Hook appropriate function to button that sends out emails with booking codes to the station. * @see \CommonsBooking\View\BookingCodes::renderDirectEmailRow() */ public function initBookingcodes() { - add_action( 'before_delete_post', array( BookingCodes::class, 'deleteBookingCodes' ), 10 ); add_action( 'admin_action_cb_download-bookingscodes-csv', array( View\BookingCodes::class, 'renderCSV' ), 10, 0 ); add_action( 'admin_action_cb_email-bookingcodes', array(View\BookingCodes::class, 'emailCodes'), 10, 0); } diff --git a/src/Repository/BookingCodes.php b/src/Repository/BookingCodes.php index f97e65336..0df7f6edf 100644 --- a/src/Repository/BookingCodes.php +++ b/src/Repository/BookingCodes.php @@ -362,32 +362,4 @@ private static function getCodesArray(): array { }, $bookingCodesArray ); } - /** - * Deletes booking codes for current post or if posted for post with $postId. - * - * @param null $postId - */ - public static function deleteBookingCodes( $postId = null ) { - if ( $postId ) { - $post = get_post( $postId ); - } else { - global $post; - } - if ( - $post && - $post->post_type == \CommonsBooking\Wordpress\CustomPostType\Timeframe::$postType - ) { - global $wpdb; - $table_name = $wpdb->prefix . self::$tablename; - - - $query = $wpdb->prepare( 'SELECT timeframe FROM ' . $table_name . ' WHERE timeframe = %d', $post->ID ); - $var = $wpdb->get_var( $query ); - if ( $var ) { - $query2 = $wpdb->prepare( 'DELETE FROM ' . $table_name . ' WHERE timeframe = %d', $post->ID ); - $wpdb->query( $query2 ); - } - } - } - } From 394a6f6b53abd230c8e753e49b00a882fbd7bc53 Mon Sep 17 00:00:00 2001 From: Nils Larsen Date: Sat, 20 Jan 2024 19:00:19 +0100 Subject: [PATCH 08/25] add unit tests for eternal booking codes --- tests/php/Repository/BookingCodesTest.php | 83 +++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/tests/php/Repository/BookingCodesTest.php b/tests/php/Repository/BookingCodesTest.php index 7d17d620d..5936e583c 100644 --- a/tests/php/Repository/BookingCodesTest.php +++ b/tests/php/Repository/BookingCodesTest.php @@ -121,6 +121,89 @@ public function testGetCode() { $this->assertEquals( $code->getCode(), $stillSameCode->getCode() ); } + public function testIfCodesAreEternal() { + ClockMock::freeze( new \DateTime( self::CURRENT_DATE ) ); + + // create timeframe with parameters like $timeframeWithoutEndDate + $timeframe_1 = new Timeframe($this->createTimeframe( + $this->locationId, + $this->itemId, + strtotime( '-1 day', strtotime( self::CURRENT_DATE ) ), + null, + )); + + // generate some codes + BookingCodes::generate( $timeframe_1, self::ADVANCE_GENERATION_DAYS ); + + // get code for $this->itemId today + $todaysCode = BookingCodes::getCode( $timeframe_1, + $this->itemId, + $this->locationId, + date('Y-m-d', strtotime( self::CURRENT_DATE )), + self::ADVANCE_GENERATION_DAYS); + + $this->assertNotEmpty($todaysCode->getCode()); + + // Check if codes are persistant/eternal: + // check that codes are persistant, ie when a code is once generated for a certain item and date, it should never change again + $countBefore = $this->countBookingCodes(); + $this->assertGreaterThan(0, $countBefore); + + // add some booking codes (like a WP Admin would do on Commonbookings admin pages) + $oldBookingCodes = Settings::getOption( 'commonsbooking_options_bookingcodes', 'bookingcodes' ); + $updateSuccessful = Settings::updateOption('commonsbooking_options_bookingcodes','bookingcodes',"$oldBookingCodes,NewCode"); + $this->assertTrue($updateSuccessful); + + // repeat generate() with same parameters as above ... + BookingCodes::generate( $timeframe_1, self::ADVANCE_GENERATION_DAYS ); + + // ... it should NOT lead to any new codes, because they are already existing. Check by counting: + $countAfter = $this->countBookingCodes(); + $this->assertEquals($countBefore, $countAfter); + + // ... and check by comparing today's code + $todaysCodeAfter = BookingCodes::getCode( $timeframe_1, + $this->itemId, + $this->locationId, + date('Y-m-d', strtotime( self::CURRENT_DATE )), + self::ADVANCE_GENERATION_DAYS); + + $this->assertEquals($todaysCode->getCode(), $todaysCodeAfter->getCode()); + + // now delete timeframe and create another timeframe with same parameters (especially same item and overlapping in time) + wp_delete_post( $timeframe_1->ID, true ); + + $timeframe_2 = new Timeframe($this->createTimeframe( + $this->locationId, + $this->itemId, + strtotime( '-1 day', strtotime( self::CURRENT_DATE ) ), + null, + )); + + // repeat generate() with same parameters as above ... + BookingCodes::generate( $timeframe_2, self::ADVANCE_GENERATION_DAYS ); + + // ... and check by comparing today's code + $todaysCodeTimeframe2 = BookingCodes::getCode( $timeframe_2, + $this->itemId, + $this->locationId, + date('Y-m-d', strtotime( self::CURRENT_DATE )), + self::ADVANCE_GENERATION_DAYS); + + // even if it is another timeframe, the today's code for the same item must still be the same + $this->assertEquals($todaysCode->getCode(), $todaysCodeTimeframe2->getCode()); + + } + + private function countBookingCodes() { + global $wpdb; + $table_name = $wpdb->prefix . BookingCodes::$tablename; + + $sql = "SELECT COUNT(*) FROM $table_name"; + + return $wpdb->get_var($sql); + } + public function testGetCodes() { ClockMock::freeze( new \DateTime( self::CURRENT_DATE ) ); //make sure that we get no codes before generation From 0bc727aa4cbab83b3696292eeaa6532f86de813b Mon Sep 17 00:00:00 2001 From: Nils Larsen Date: Sat, 20 Jan 2024 21:06:34 +0100 Subject: [PATCH 09/25] remove deprecated locationId and timeframeId from BookingCode-object and remove unused getters/setters --- src/Migration/Migration.php | 4 ---- src/Model/BookingCode.php | 26 ++++---------------------- src/Repository/BookingCodes.php | 13 ++++--------- 3 files changed, 8 insertions(+), 35 deletions(-) diff --git a/src/Migration/Migration.php b/src/Migration/Migration.php index ad002a978..a4e2e7764 100644 --- a/src/Migration/Migration.php +++ b/src/Migration/Migration.php @@ -580,17 +580,13 @@ public static function migrateBooking( $booking ): bool { * @return mixed */ public static function migrateBookingCode( $bookingCode ) { - $cb2LocationId = CB1::getCB2LocationId( $bookingCode['location_id'] ); $cb2ItemId = CB1::getCB2ItemId( $bookingCode['item_id'] ); - $cb2TimeframeId = CB1::getCB2TimeframeId( $bookingCode['timeframe_id'] ); $date = $bookingCode['booking_date']; $code = $bookingCode['bookingcode']; $bookingCode = new BookingCode( $date, $cb2ItemId, - $cb2LocationId, - $cb2TimeframeId, $code ); diff --git a/src/Model/BookingCode.php b/src/Model/BookingCode.php index 8121bb4ff..86713d6e7 100644 --- a/src/Model/BookingCode.php +++ b/src/Model/BookingCode.php @@ -23,46 +23,30 @@ class BookingCode { * Datestring in the format Y-m-d * @var string */ - protected $date; + private $date; /** * Item ID * @var int */ - protected $item; - - /** - * Location ID - * @var int - */ - protected $location; - - /** - * Timeframe ID - * @var int - */ - protected $timeframe; + private $item; /** * Code string * @var string */ - protected $code; + private $code; /** * BookingCode constructor. * * @param $date * @param $item - * @param $location - * @param $timeframe * @param $code */ - public function __construct( $date, $item, $location, $timeframe, $code ) { + public function __construct( $date, $item, $code ) { $this->date = $date; $this->item = $item; - $this->location = $location; - $this->timeframe = $timeframe; $this->code = $code; } @@ -165,6 +149,4 @@ public function setCode( $code ): BookingCode { $this->code = $code; return $this; - } - } diff --git a/src/Repository/BookingCodes.php b/src/Repository/BookingCodes.php index 0df7f6edf..ca26a4bf5 100644 --- a/src/Repository/BookingCodes.php +++ b/src/Repository/BookingCodes.php @@ -114,8 +114,6 @@ public static function getCodes( int $timeframeId, int $startDate = null, int $e $bookingCodeObject = new BookingCode( $bookingCode->date, $bookingCode->item, - $bookingCode->location, - $bookingCode->timeframe, $bookingCode->code ); $codes[] = $bookingCodeObject; @@ -156,8 +154,6 @@ private static function lookupCode(int $itemId, string $date): ?BookingCode { return new BookingCode( $bookingCodes[0]->date, $bookingCodes[0]->item, - $bookingCodes[0]->location, - $bookingCodes[0]->timeframe, $bookingCodes[0]->code ); } @@ -170,7 +166,7 @@ private static function lookupCode(int $itemId, string $date): ?BookingCode { * * @param Timeframe $timeframe - Timeframe object to get code for * @param int $itemId - ID of item attached to timeframe - * @param int $locationId - ID of location attached to timeframe + * @param int $locationId - ID of location attached to timeframe (DEPRECATED, has no effect) * @param string $date - Date in format Y-m-d * @param int $advanceGenerationDays - Open-ended timeframes: If 0, generates code(s) until $date. If >0, generate additional codes after $date. * @@ -217,6 +213,7 @@ public static function initBookingCodesTable() :void { $table_name = $wpdb->prefix . self::$tablename; $charset_collate = $wpdb->get_charset_collate(); + // TODO To be removed later: timeframe, location (not used anymore) $sql = "CREATE TABLE $table_name ( date date DEFAULT '0000-00-00' NOT NULL, timeframe bigint(20) unsigned NOT NULL, @@ -310,8 +307,6 @@ private static function generatePeriod( Timeframe $timeframe, DatePeriod $period $bookingCode = new BookingCode( $dt->format( 'Y-m-d' ), $item->ID, - $location->ID, - $timeframe->ID, $bookingCodesArray[ ( (int) $dt->format( 'z' ) + $bookingCodesRandomizer ) % count( $bookingCodesArray ) ] ); self::persist( $bookingCode ); @@ -334,9 +329,9 @@ public static function persist( BookingCode $bookingCode ) { $result = $wpdb->replace( $table_name, array( - 'timeframe' => $bookingCode->getTimeframe(), + 'timeframe' => 0, 'date' => $bookingCode->getDate(), - 'location' => $bookingCode->getLocation(), + 'location' => 0, 'item' => $bookingCode->getItem(), 'code' => $bookingCode->getCode() ) From dc79704847d3c01a342448604e54864c0d521f6b Mon Sep 17 00:00:00 2001 From: Nils Larsen Date: Sat, 20 Jan 2024 22:33:00 +0100 Subject: [PATCH 10/25] updating inline documentation --- src/Repository/BookingCodes.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Repository/BookingCodes.php b/src/Repository/BookingCodes.php index ca26a4bf5..3fe450f3a 100644 --- a/src/Repository/BookingCodes.php +++ b/src/Repository/BookingCodes.php @@ -16,7 +16,7 @@ /** * This class generates booking codes for a timeframe. - * Currently, booking codes can only be created for timeframes with an end date and where one slot fills the whole day. + * Currently, booking codes can only be created where one slot fills the whole day. */ class BookingCodes { @@ -277,7 +277,7 @@ private static function generatePeriod( Timeframe $timeframe, DatePeriod $period if (! $bookingCodesArray ){ throw new BookingCodeException( __( "No booking codes could be created because there were no booking codes to choose from. Please set some booking codes in the CommonsBooking settings.", 'commonsbooking' ) ); } - // Before we add new codes, we remove old ones, that are not relevant anymore + try { //TODO #507 $location = $timeframe->getLocation(); From dc69ce4ba8de862a635bf13f9ebea632635e6719 Mon Sep 17 00:00:00 2001 From: Nils Larsen Date: Sat, 16 Mar 2024 17:53:11 +0100 Subject: [PATCH 11/25] remove getLocation and getTimeframe because a BookingCode does not have associated location and timeframe anymore --- src/Model/BookingCode.php | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/src/Model/BookingCode.php b/src/Model/BookingCode.php index 86713d6e7..166c14838 100644 --- a/src/Model/BookingCode.php +++ b/src/Model/BookingCode.php @@ -94,44 +94,6 @@ public function setItem( $item ): BookingCode { return $this; } - /** - * @return int - */ - public function getLocation(): int { - return $this->location; - } - - /** - * @deprecated will be deleted in the next version. This Type should be immutable, use constructor to create a new instance - * @param mixed $location - * - * @return BookingCode - */ - public function setLocation( $location ): BookingCode { - $this->location = $location; - - return $this; - } - - /** - * @return int - */ - public function getTimeframe(): int { - return $this->timeframe; - } - - /** - * @deprecated will be deleted in the next version. This Type should be immutable, use constructor to create a new instance - * @param mixed $timeframe - * - * @return BookingCode - */ - public function setTimeframe( $timeframe ): BookingCode { - $this->timeframe = $timeframe; - - return $this; - } - /** * @return string */ From a749fd2a7ddbd826ac8dd5fb66b3786677262140 Mon Sep 17 00:00:00 2001 From: Nils Larsen Date: Sat, 16 Mar 2024 18:39:35 +0100 Subject: [PATCH 12/25] fix syntax error (a closing bracket was lost) --- src/Model/BookingCode.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Model/BookingCode.php b/src/Model/BookingCode.php index 166c14838..93a8d6e75 100644 --- a/src/Model/BookingCode.php +++ b/src/Model/BookingCode.php @@ -111,4 +111,5 @@ public function setCode( $code ): BookingCode { $this->code = $code; return $this; + } } From 75d137e5d8cf24e4c3d5ae47cc43a5b96d4b3e5f Mon Sep 17 00:00:00 2001 From: Nils Larsen Date: Wed, 5 Jun 2024 21:58:40 +0200 Subject: [PATCH 13/25] bookingcodes: store deprecated locationId and timeframeId when generating new codes for backward compatibility. These data base fields are needed by former versions of cb. --- src/Repository/BookingCodes.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Repository/BookingCodes.php b/src/Repository/BookingCodes.php index 3fe450f3a..9b46f0cd2 100644 --- a/src/Repository/BookingCodes.php +++ b/src/Repository/BookingCodes.php @@ -309,7 +309,11 @@ private static function generatePeriod( Timeframe $timeframe, DatePeriod $period $item->ID, $bookingCodesArray[ ( (int) $dt->format( 'z' ) + $bookingCodesRandomizer ) % count( $bookingCodesArray ) ] ); - self::persist( $bookingCode ); + self::persist( + $bookingCode, + $timeframe->ID, // deprecated, only for backward compatibility + $location->ID, // deprecated, only for backward compatibility + ); } } @@ -317,26 +321,27 @@ private static function generatePeriod( Timeframe $timeframe, DatePeriod $period } /** + * Stores a booking code in database without checking if code already exists (please check before) * @param BookingCode $bookingCode + * @param timeframeId deprecated (only necessary to make database compatible to former versions of cb) + * @param locationId deprecated (only necessary to make database compatible to former versions of cb) * * @return mixed */ - public static function persist( BookingCode $bookingCode ) { + public static function persist( BookingCode $bookingCode, $timeframeId = 0, $locationId = 0 ) { global $wpdb; - $wpdb->show_errors( 0 ); $table_name = $wpdb->prefix . self::$tablename; - $result = $wpdb->replace( + $result = $wpdb->insert( $table_name, array( - 'timeframe' => 0, + 'timeframe' => $timeframeId, // deprecated field, only for backward compatibility 'date' => $bookingCode->getDate(), - 'location' => 0, + 'location' => $locationId, // deprecated field, only for backward compatibility 'item' => $bookingCode->getItem(), 'code' => $bookingCode->getCode() ) ); - $wpdb->show_errors( 1 ); return $result; } From 1bffb568487a3f6ac174358868a72da52502327a Mon Sep 17 00:00:00 2001 From: Nils Larsen Date: Sun, 9 Jun 2024 17:00:08 +0200 Subject: [PATCH 14/25] bookingcodes: add backward compatibility with codes created by old version of cb and add phpunit tests for backward compatibility --- src/Repository/BookingCodes.php | 67 ++++++++++++++++++++--- tests/php/Repository/BookingCodesTest.php | 56 +++++++++++++++++++ 2 files changed, 115 insertions(+), 8 deletions(-) diff --git a/src/Repository/BookingCodes.php b/src/Repository/BookingCodes.php index 9b46f0cd2..15aadfdfd 100644 --- a/src/Repository/BookingCodes.php +++ b/src/Repository/BookingCodes.php @@ -101,7 +101,7 @@ public static function getCodes( int $timeframeId, int $startDate = null, int $e "SELECT * FROM $table_name WHERE item = %d AND date BETWEEN %s AND %s - ORDER BY item ASC ,date ASC + ORDER BY item ASC, date ASC, timeframe ASC, location ASC ", $timeframe->getItem()->ID, $startDate, @@ -109,6 +109,8 @@ public static function getCodes( int $timeframeId, int $startDate = null, int $e ); $bookingCodes = $wpdb->get_results($sql); + self::backwardCompatibilityFilter( $bookingCodes, $timeframeId, $timeframe->getLocation()->ID ); // for backward compatibility: delete line in future cb + $codes = []; foreach ( $bookingCodes as $bookingCode ) { $bookingCodeObject = new BookingCode( @@ -125,15 +127,62 @@ public static function getCodes( int $timeframeId, int $startDate = null, int $e } } + /** + * Filter an array of BookingCode|s such that it contains only one BookingCode per date. + * Function is only needed when new cb version handles database entries created by old cb version. + * The filtering can be omitted in future cb versions when backward compatibility with old cb is dropped. + * + * @param $bookingCodes[] array of BookingCode|s. It is assumed that they all have the same itemId. + * @param int $preferredTimeframeId timeframeId to prefer when filtering + * @param int $preferredLocationId locationId to prefer when filtering + * + * @return $bookingCodes[] array of BookingCode|s (only one code per day) + */ + private static function backwardCompatibilityFilter(&$bookingCodes, $preferredTimeframeId, $preferredLocationId) { + $filteredCodes = []; + $codesByDate = []; + + // Group booking codes by date + foreach ( $bookingCodes as $code ) { + $date = $code->date; + if (! isset($codesByDate[$date]) ) { + $codesByDate[$date] = []; + } + $codesByDate[$date][] = $code; + } + + // For each date, filter out codes to ensure only one entry per date + foreach ( $codesByDate as $date => $codes ) { + if ( count($codes) > 1 ) { + // Keep entries matching $preferredTimeframeId and $preferredLocationId + $preferredCodes = array_filter( $codes, function($code) use ($preferredTimeframeId, $preferredLocationId ) { + return $code->timeframe == $preferredTimeframeId && $code->location == $preferredLocationId; + }); + + // If there are preferred codes, use them. Otherwise, use all codes. + $finalCodes = !empty($preferredCodes) ? $preferredCodes : $codes; + + // Pick the first code if there are still multiple entries + $filteredCodes[] = reset($finalCodes); + } else { + $filteredCodes[] = reset($codes); + } + } + + $bookingCodes = $filteredCodes; + } + /** * Gets a specific booking code by item ID and date. * * @param int $itemId - ID of item attached to timeframe * @param string $date - Date in format Y-m-d + * @param int $preferredTimeframeId only for compatibility with database entries created by old cb version. delete in future + * @param int $preferredLocationId see $preferredTimeframeId * * @return BookingCode|null */ - private static function lookupCode(int $itemId, string $date): ?BookingCode { + private static function lookupCode(int $itemId, string $date, int $preferredTimeframeId = 0, int $preferredLocationId = 0): ?BookingCode { global $wpdb; $table_name = $wpdb->prefix . self::$tablename; @@ -142,14 +191,15 @@ private static function lookupCode(int $itemId, string $date): ?BookingCode { WHERE item = %s AND date = %s - ORDER BY item ASC, date ASC - LIMIT 1", + ORDER BY item ASC, date ASC, timeframe ASC, location ASC", $itemId, $date ); $bookingCodes = $wpdb->get_results($sql); + self::backwardCompatibilityFilter( $bookingCodes, $preferredTimeframeId, $preferredLocationId ); // for backward compatibility: delete line in future cb + if (count($bookingCodes)) { return new BookingCode( $bookingCodes[0]->date, @@ -178,8 +228,8 @@ public static function getCode( Timeframe $timeframe, int $itemId, int $location if ( $cacheItem ) { return $cacheItem; } else { - - $bookingCodeObject = static::lookupCode($itemId, $date); + // timeframeid and locationid are only for backward compatibility with database entries from old cb + $bookingCodeObject = static::lookupCode( $itemId, $date, $timeframe->ID, $locationId ); if ( ! $bookingCodeObject ) { //when we have a timeframe without end-date we generate as many codes as we need @@ -193,7 +243,7 @@ public static function getCode( Timeframe $timeframe, int $itemId, int $location $interval = DateInterval::createFromDateString( '1 day' ); $period = new DatePeriod( $begin, $interval, $endDate ); static::generatePeriod($timeframe,$period); - $bookingCodeObject = static::lookupCode($itemId, $date); + $bookingCodeObject = static::lookupCode( $itemId, $date, $timeframe->ID, $locationId ); } } @@ -300,7 +350,8 @@ private static function generatePeriod( Timeframe $timeframe, DatePeriod $period if ( $day->isInTimeframe( $timeframe ) ) { // Check if a code already exists, if so DO NOT generate new - if ( static::lookupCode($item->ID, $dt->format( 'Y-m-d' )) ) { + // timeframeid and locationid are only for backward compatibility with database entries from old cb + if ( static::lookupCode($item->ID, $dt->format( 'Y-m-d' ), $timeframe->ID, $location->ID) ) { continue; } diff --git a/tests/php/Repository/BookingCodesTest.php b/tests/php/Repository/BookingCodesTest.php index 5936e583c..e3f2286da 100644 --- a/tests/php/Repository/BookingCodesTest.php +++ b/tests/php/Repository/BookingCodesTest.php @@ -310,6 +310,62 @@ public function testGetCodesFuture() { } } + public function testBackwardCompatibility() { + ClockMock::freeze( new \DateTime( self::CURRENT_DATE ) ); + $todayDate = date( 'Y-m-d', strtotime(self::CURRENT_DATE) ); + $thisTimeframeId = $this->timeframeWithEndDate->ID; + + // (at least) up to cb 2.9.3, booking codes were saved with timeframeId and locationId + // and under some circumstances, there could be several booking codes for a certain (date, itemId) pair. + // Now cb never generates new codes for a (data, itemId) pair if a code for that pair already exists. + // But old codes generated by old cb could be left in the database and should still work as expected, + // what is tested here + + // Insert codes as if generated by an old cb version: the "right" code and two "wrong" codes + // which do have matching date and itemId, but with unsuitable timeframe and location fields + // note: the "wrong" timeframe and location ids are chosen smaller for deterministic testing: + // the tested methods getCode() and getCodes() sort by ASC id so the "wrong" codes should be + // returned first such that test does not PASS accidentally + $codesToInsert = [ + ['code' => 'right_code', 'timeframe' => $thisTimeframeId, 'location' => $this->locationId], + ['code' => 'wrong_timeframe', 'timeframe' => $thisTimeframeId - 1, 'location' => $this->locationId], + ['code' => 'wrong_location', 'timeframe' => $thisTimeframeId, 'location' => $this->locationId - 1] + ]; + + // insert the codes (all with same date & itemId) + foreach ($codesToInsert as $codeData) { + self::insertBookingCode( $todayDate, $codeData['timeframe'], $codeData['location'], $this->itemId, $codeData['code'] ); + } + + BookingCodes::generate( $this->timeframeWithEndDate,self::ADVANCE_GENERATION_DAYS ); + + $code = BookingCodes::getCode( $this->timeframeWithEndDate, $this->itemId, $this->locationId, $todayDate, self::ADVANCE_GENERATION_DAYS ); + $this->assertEquals( 'right_code', $code->getCode() ); + + // now test getCodes() + $todayDateTime = new \DateTime( self::CURRENT_DATE ); + + $codes = BookingCodes::getCodes( $this->timeframeWithEndDate->ID, $todayDateTime->getTimestamp(), $todayDateTime->getTimestamp() ); + + $this->assertNotEmpty( $codes ); + $this->assertCount( 1, $codes ); + $this->assertEquals( 'right_code', $codes[0]->getCode() ); + + } + + private static function insertBookingCode($date, $timeframe, $location, $item, $code) { + global $wpdb; + $table_name = $wpdb->prefix . BookingCodes::$tablename; + + $wpdb->insert($table_name, array( + 'date' => $date, + 'timeframe' => $timeframe, + 'location' => $location, + 'item' => $item, + 'code' => $code + )); + } + protected function setUp(): void { parent::setUp(); $this->timeframeWithEndDate = new Timeframe($this->createTimeframe( From 9804f5b6300ff1f7c1f427d379237ceb744cb2c4 Mon Sep 17 00:00:00 2001 From: Hans Morbach <6433480+hansmorb@users.noreply.github.com> Date: Sat, 15 Jun 2024 12:15:47 +0200 Subject: [PATCH 15/25] update wp-env to latest version --- package-lock.json | 35 +++++++++++++++++++++++++++-------- package.json | 2 +- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 247f70469..e9224a107 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ }, "devDependencies": { "@babel/preset-env": "^7.23.2", - "@wordpress/env": "^5.0.0", + "@wordpress/env": "^10.0.0", "commons-api": "git+https://github.com/wielebenwir/commons-api.git", "cypress": "^13.9.0", "editorconfig": "^2.0.0", @@ -2205,14 +2205,14 @@ } }, "node_modules/@wordpress/env": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-5.16.0.tgz", - "integrity": "sha512-zx6UO8PuJBrQ34cfeedK1HlGHLFaj7oWzTo9tTt+noB79Ttqc4+a0lYwDqBLLJhlHU+cWgcyOP2lB6TboXH0xA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-10.0.0.tgz", + "integrity": "sha512-GpbYT7P61U6PWOqpyPmTBGLswBoyOCUtGF2/0qCsbN7vC8oekm+iNpYT2JS0ETSC1/8esxxKlYVvLxXwTF9MPg==", "dev": true, "dependencies": { "chalk": "^4.0.0", "copy-dir": "^1.3.0", - "docker-compose": "^0.22.2", + "docker-compose": "^0.24.3", "extract-zip": "^1.6.7", "got": "^11.8.5", "inquirer": "^7.1.0", @@ -2225,6 +2225,10 @@ }, "bin": { "wp-env": "bin/wp-env" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" } }, "node_modules/@wordpress/env/node_modules/ansi-styles": { @@ -3838,14 +3842,29 @@ } }, "node_modules/docker-compose": { - "version": "0.22.2", - "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.22.2.tgz", - "integrity": "sha512-iXWb5+LiYmylIMFXvGTYsjI1F+Xyx78Jm/uj1dxwwZLbWkUdH6yOXY5Nr3RjbYX15EgbGJCq78d29CmWQQQMPg==", + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.8.tgz", + "integrity": "sha512-plizRs/Vf15H+GCVxq2EUvyPK7ei9b/cVesHvjnX4xaXjM9spHe2Ytq0BitndFgvTJ3E3NljPNUEl7BAN43iZw==", "dev": true, + "dependencies": { + "yaml": "^2.2.2" + }, "engines": { "node": ">= 6.0.0" } }, + "node_modules/docker-compose/node_modules/yaml": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/dom-serializer": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", diff --git a/package.json b/package.json index a2d7f4ff7..4b2dc9c6f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ }, "devDependencies": { "@babel/preset-env": "^7.23.2", - "@wordpress/env": "^5.0.0", + "@wordpress/env": "^10.0.0", "commons-api": "git+https://github.com/wielebenwir/commons-api.git", "cypress": "^13.9.0", "editorconfig": "^2.0.0", From 262f1ef1db3abc28df10d60954e5cb567f3595df Mon Sep 17 00:00:00 2001 From: Hans Morbach <6433480+hansmorb@users.noreply.github.com> Date: Sat, 15 Jun 2024 12:21:12 +0200 Subject: [PATCH 16/25] use wp-env built in wp-cli for testing --- .wp-env.json | 61 ++++++++++--------- bin/install-wp-cli.sh | 15 ----- bin/setup-cypress-env.sh | 13 +--- bin/wp-env-cli | 52 ---------------- package.json | 3 +- .../cypress/e2e/litepicker-overbooking.cy.js | 2 +- 6 files changed, 38 insertions(+), 108 deletions(-) delete mode 100755 bin/install-wp-cli.sh delete mode 100755 bin/wp-env-cli diff --git a/.wp-env.json b/.wp-env.json index 52202242a..aeee73820 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,30 +1,35 @@ { - "phpVersion": "7.4", - "core": "Wordpress/Wordpress#6.5", - "plugins": [ - ".", - "https://downloads.wordpress.org/plugin/wp-crontrol.zip", - "https://downloads.wordpress.org/plugin/wordpress-importer.zip", - "https://downloads.wordpress.org/plugin/query-monitor.zip", - "https://downloads.wordpress.org/plugin/wp-mail-logging.zip", - "https://downloads.wordpress.org/plugin/jsm-show-post-meta.zip", - "https://downloads.wordpress.org/plugin/disable-welcome-messages-and-tips.zip" - ], - "port": 1000, - "testsPort": 1001, - "config": { - "WP_DEBUG": true, - "WP_DEBUG_LOG": true, - "WP_DEBUG_DISPLAY": false - }, - "themes": [ - "flegfleg/kasimir-theme" - ], - "env": { - "tests": { - "mappings": { - "wp-content/plugins/commonsbooking": "." - } - } - } + "phpVersion" : "7.4", + "core" : "Wordpress/Wordpress#6.5", + "plugins" : [ + ".", + "https://downloads.wordpress.org/plugin/wp-crontrol.zip", + "https://downloads.wordpress.org/plugin/wordpress-importer.zip", + "https://downloads.wordpress.org/plugin/query-monitor.zip", + "https://downloads.wordpress.org/plugin/wp-mail-logging.zip", + "https://downloads.wordpress.org/plugin/jsm-show-post-meta.zip", + "https://downloads.wordpress.org/plugin/disable-welcome-messages-and-tips.zip" + ], + "port" : 1000, + "testsPort" : 1001, + "config" : { + "WP_DEBUG" : true, + "WP_DEBUG_LOG" : true, + "WP_DEBUG_DISPLAY" : false + }, + "themes" : [ + "flegfleg/kasimir-theme" + ], + "env" : { + "tests" : { + "mappings" : { + "wp-content/plugins/commonsbooking" : "." + }, + "config" : { + "WP_DEBUG" : true, + "WP_DEBUG_LOG" : true, + "WP_DEBUG_DISPLAY" : false + } + } + } } diff --git a/bin/install-wp-cli.sh b/bin/install-wp-cli.sh deleted file mode 100755 index 799269577..000000000 --- a/bin/install-wp-cli.sh +++ /dev/null @@ -1,15 +0,0 @@ -# install-wp-cli.sh -# -# Felipe Elia and 10up contributors -# -# The following code is a derivative work of the code from the ElasticPress project, -# which is licensed GPLv2. This code therefore is also licensed under the terms -# of the GNU Public License, version 2.' - -#!/usr/bin/env bash - -echo "Installing WP-CLI in $1" - -./bin/wp-env-cli $1 curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar -./bin/wp-env-cli $1 chmod +x wp-cli.phar -./bin/wp-env-cli $1 mv wp-cli.phar /usr/local/bin/wp diff --git a/bin/setup-cypress-env.sh b/bin/setup-cypress-env.sh index 659139a8d..ef78b8005 100755 --- a/bin/setup-cypress-env.sh +++ b/bin/setup-cypress-env.sh @@ -1,15 +1,8 @@ -# setup-cypress-env.sh -# Felipe Elia and 10up contributors -# -# The following code is a derivative work of the code from the ElasticPress project, -# which is licensed GPLv2. This code therefore is also licensed under the terms -# of the GNU Public License, version 2.' - #!/bin/bash # Install our example posts from a WP export file -./bin/wp-env-cli tests-wordpress "wp --allow-root import /var/www/html/wp-content/plugins/commonsbooking/tests/cypress/wordpress-files/content-example.xml --authors=create" +wp-env run tests-cli wp import /var/www/html/wp-content/plugins/commonsbooking/tests/cypress/wordpress-files/content-example.xml --authors=create # Switch to Kasimir theme -./bin/wp-env-cli tests-wordpress "wp --allow-root theme activate kasimir-theme" +wp-env run tests-cli wp theme activate kasimir-theme # Create subscriber with username "subscriber" and password "password" -./bin/wp-env-cli tests-wordpress "wp --allow-root user create subscriber sub@sub.de --role=subscriber --user_pass=password" \ No newline at end of file +wp-env run tests-cli wp user create subscriber sub@sub.de --role=subscriber --user_pass=password \ No newline at end of file diff --git a/bin/wp-env-cli b/bin/wp-env-cli deleted file mode 100755 index f5ff7e12f..000000000 --- a/bin/wp-env-cli +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env node - -const path = require('path'); -const { spawn } = require('child_process'); -const { readConfig } = require('@wordpress/env/lib/config'); - -function spawnCommandDirectly({ container, command, dockerComposeConfigPath }) { - const composeCommand = [ - '-f', - dockerComposeConfigPath, - 'exec -T', - container, - ...command.split(' '), // The command will fail if passed as a complete string. - ]; - - return new Promise((resolve, reject) => { - const childProc = spawn('docker-compose', composeCommand, { - stdio: 'inherit', - shell: true, - }); - childProc.on('error', reject); - childProc.on('exit', (code) => { - // Code 130 is set if the user tries to exit with ctrl-c before using - // ctrl-d (so it is not an error which should fail the script.) - if (code === 0 || code === 130) { - resolve(); - } else { - reject(code); - } - }); - }); -} - -const run = async () => { - const configPath = path.resolve('.wp-env.json'); - const { dockerComposeConfigPath } = await readConfig(configPath); - - const container = process.argv[2]; - const command = process.argv.splice(3).join(' '); - - try { - await spawnCommandDirectly({ - container, - command, - dockerComposeConfigPath, - }); - } catch (errorCode) { - process.exit(errorCode); - } -}; - -run(); diff --git a/package.json b/package.json index 4b2dc9c6f..086bc374d 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,7 @@ "scripts": { "start": "composer install --ignore-platform-reqs && npm install && npm run dist", "env": "wp-env", - "env:install-tests-cli": "./bin/install-wp-cli.sh tests-wordpress", - "env:start": "wp-env start && npm run env:install-tests-cli", + "env:start": "wp-env start", "env:stop": "wp-env stop", "cypress:setup": "./bin/setup-cypress-env.sh", "cypress:open": "cypress open --config-file tests/cypress/cypress.config.js", diff --git a/tests/cypress/e2e/litepicker-overbooking.cy.js b/tests/cypress/e2e/litepicker-overbooking.cy.js index fca40a5c1..f1e6352d9 100644 --- a/tests/cypress/e2e/litepicker-overbooking.cy.js +++ b/tests/cypress/e2e/litepicker-overbooking.cy.js @@ -18,7 +18,7 @@ describe('test overbooking process', () => { } function updatePostMetaAndReload(postID, metaKey, metaValue){ - cy.exec('bin/wp-env-cli tests-wordpress "wp --allow-root post meta update "' + postID + '" ' + metaKey + ' ' + metaValue + '"') + cy.exec('wp-env run tests-cli tests-wordpress wp post meta update ' + postID + ' ' + metaKey + ' ' + metaValue) cy.reload() } From b17b54334499d3054c96c1a8317419a2c9c18422 Mon Sep 17 00:00:00 2001 From: Hans Morbach <6433480+hansmorb@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:36:11 +0200 Subject: [PATCH 17/25] fixed cy.exec command --- tests/cypress/e2e/litepicker-overbooking.cy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cypress/e2e/litepicker-overbooking.cy.js b/tests/cypress/e2e/litepicker-overbooking.cy.js index f1e6352d9..9588e1995 100644 --- a/tests/cypress/e2e/litepicker-overbooking.cy.js +++ b/tests/cypress/e2e/litepicker-overbooking.cy.js @@ -18,7 +18,7 @@ describe('test overbooking process', () => { } function updatePostMetaAndReload(postID, metaKey, metaValue){ - cy.exec('wp-env run tests-cli tests-wordpress wp post meta update ' + postID + ' ' + metaKey + ' ' + metaValue) + cy.exec('wp-env run tests-cli wp post meta update ' + postID + ' ' + metaKey + ' ' + metaValue) cy.reload() } From a0e1600f869fac5c1d3b29f7abb71eb4349774a6 Mon Sep 17 00:00:00 2001 From: Hans Morbach <6433480+hansmorb@users.noreply.github.com> Date: Mon, 17 Jun 2024 18:36:27 +0200 Subject: [PATCH 18/25] use identifying user-agent for nominatim: Prevents requests being blocked --- src/Helper/NominatimGeoCodeService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Helper/NominatimGeoCodeService.php b/src/Helper/NominatimGeoCodeService.php index 99edb77dc..6657ac103 100644 --- a/src/Helper/NominatimGeoCodeService.php +++ b/src/Helper/NominatimGeoCodeService.php @@ -28,7 +28,7 @@ public function getAddressData( $addressString ): ?Location { throw new \Exception("Could not get address data because of missing curl extension."); } - $defaultUserAgent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0'; + $defaultUserAgent = 'CommonsBooking v.' . COMMONSBOOKING_VERSION . " Contact: mail@commonsbooking.org"; $client = new Client( null, From f4a89a955eea35af8143cb5a805832c52d59e6b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 16:47:58 +0000 Subject: [PATCH 19/25] npm(deps): bump feiertagejs from 1.4.0 to 1.4.1 Bumps [feiertagejs](https://github.com/sfakir/feiertagejs) from 1.4.0 to 1.4.1. - [Changelog](https://github.com/sfakir/feiertagejs/blob/master/CHANGELOG.md) - [Commits](https://github.com/sfakir/feiertagejs/commits) --- updated-dependencies: - dependency-name: feiertagejs dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index e9224a107..8f90cb0cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@commonsbooking/frontend": "^0.1.0-beta.6", - "feiertagejs": "^1.4.0", + "feiertagejs": "^1.4.1", "leaflet": "^1.7.1", "leaflet-easybutton": "^2.4.0", "leaflet-spin": "^1.1.2", @@ -4448,9 +4448,9 @@ } }, "node_modules/feiertagejs": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/feiertagejs/-/feiertagejs-1.4.0.tgz", - "integrity": "sha512-04oxk2oksavDs1eYewhqrJlnocWvI8a2i3S2oBZybjwRPTZv1A08sFnUnqExbp1JiVgukVaPZ3aqyVqmCJX7mw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/feiertagejs/-/feiertagejs-1.4.1.tgz", + "integrity": "sha512-+tXhTi6KG9rEw9iDR0TZGA5/lChAjwXuVYXiuHbzAOlfdmCo3MUMPYeL7GW4qjxS5KwsWqfi877JmzXZyhiimA==", "engines": { "node": ">=8.0.0" } diff --git a/package.json b/package.json index 086bc374d..5daa9af1a 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ }, "dependencies": { "@commonsbooking/frontend": "^0.1.0-beta.6", - "feiertagejs": "^1.4.0", + "feiertagejs": "^1.4.1", "leaflet": "^1.7.1", "leaflet-easybutton": "^2.4.0", "leaflet-spin": "^1.1.2", From 56f375bd515a07847f49317cf5e41b26539727c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 16:48:08 +0000 Subject: [PATCH 20/25] npm(deps): bump vue from 3.4.27 to 3.4.29 Bumps [vue](https://github.com/vuejs/core) from 3.4.27 to 3.4.29. - [Release notes](https://github.com/vuejs/core/releases) - [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/vuejs/core/compare/v3.4.27...v3.4.29) --- updated-dependencies: - dependency-name: vue dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 121 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 62 insertions(+), 61 deletions(-) diff --git a/package-lock.json b/package-lock.json index e9224a107..1ccd0ec44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "leaflet.markercluster": "^1.5.0", "shufflejs": "^6.1.1", "spin.js": "^2.3.2", - "vue": "^3.4.27" + "vue": "^3.4.29" }, "devDependencies": { "@babel/preset-env": "^7.23.2", @@ -471,9 +471,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", - "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -2021,12 +2021,12 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz", - "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.29.tgz", + "integrity": "sha512-TFKiRkKKsRCKvg/jTSSKK7mYLJEQdUiUfykbG49rubC9SfDyvT2JrzTReopWlz2MxqeLyxh9UZhvxEIBgAhtrg==", "dependencies": { - "@babel/parser": "^7.24.4", - "@vue/shared": "3.4.27", + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.29", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" @@ -2044,24 +2044,24 @@ } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz", - "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.29.tgz", + "integrity": "sha512-A6+iZ2fKIEGnfPJejdB7b1FlJzgiD+Y/sxxKwJWg1EbJu6ZPgzaPQQ51ESGNv0CP6jm6Z7/pO6Ia8Ze6IKrX7w==", "dependencies": { - "@vue/compiler-core": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/compiler-core": "3.4.29", + "@vue/shared": "3.4.29" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz", - "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==", - "dependencies": { - "@babel/parser": "^7.24.4", - "@vue/compiler-core": "3.4.27", - "@vue/compiler-dom": "3.4.27", - "@vue/compiler-ssr": "3.4.27", - "@vue/shared": "3.4.27", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.29.tgz", + "integrity": "sha512-zygDcEtn8ZimDlrEQyLUovoWgKQic6aEQqRXce2WXBvSeHbEbcAsXyCk9oG33ZkyWH4sl9D3tkYc1idoOkdqZQ==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.29", + "@vue/compiler-dom": "3.4.29", + "@vue/compiler-ssr": "3.4.29", + "@vue/shared": "3.4.29", "estree-walker": "^2.0.2", "magic-string": "^0.30.10", "postcss": "^8.4.38", @@ -2069,57 +2069,58 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz", - "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.29.tgz", + "integrity": "sha512-rFbwCmxJ16tDp3N8XCx5xSQzjhidYjXllvEcqX/lopkoznlNPz3jyy0WGJCyhAaVQK677WWFt3YO/WUEkMMUFQ==", "dependencies": { - "@vue/compiler-dom": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/compiler-dom": "3.4.29", + "@vue/shared": "3.4.29" } }, "node_modules/@vue/reactivity": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz", - "integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.29.tgz", + "integrity": "sha512-w8+KV+mb1a8ornnGQitnMdLfE0kXmteaxLdccm2XwdFxXst4q/Z7SEboCV5SqJNpZbKFeaRBBJBhW24aJyGINg==", "dependencies": { - "@vue/shared": "3.4.27" + "@vue/shared": "3.4.29" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz", - "integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.29.tgz", + "integrity": "sha512-s8fmX3YVR/Rk5ig0ic0NuzTNjK2M7iLuVSZyMmCzN/+Mjuqqif1JasCtEtmtoJWF32pAtUjyuT2ljNKNLeOmnQ==", "dependencies": { - "@vue/reactivity": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/reactivity": "3.4.29", + "@vue/shared": "3.4.29" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz", - "integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.29.tgz", + "integrity": "sha512-gI10atCrtOLf/2MPPMM+dpz3NGulo9ZZR9d1dWo4fYvm+xkfvRrw1ZmJ7mkWtiJVXSsdmPbcK1p5dZzOCKDN0g==", "dependencies": { - "@vue/runtime-core": "3.4.27", - "@vue/shared": "3.4.27", + "@vue/reactivity": "3.4.29", + "@vue/runtime-core": "3.4.29", + "@vue/shared": "3.4.29", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz", - "integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.29.tgz", + "integrity": "sha512-HMLCmPI2j/k8PVkSBysrA2RxcxC5DgBiCdj7n7H2QtR8bQQPqKAe8qoaxLcInzouBmzwJ+J0x20ygN/B5mYBng==", "dependencies": { - "@vue/compiler-ssr": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/compiler-ssr": "3.4.29", + "@vue/shared": "3.4.29" }, "peerDependencies": { - "vue": "3.4.27" + "vue": "3.4.29" } }, "node_modules/@vue/shared": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", - "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==" + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", + "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==" }, "node_modules/@vueuse/core": { "version": "10.6.1", @@ -12129,15 +12130,15 @@ "dev": true }, "node_modules/vue": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz", - "integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==", - "dependencies": { - "@vue/compiler-dom": "3.4.27", - "@vue/compiler-sfc": "3.4.27", - "@vue/runtime-dom": "3.4.27", - "@vue/server-renderer": "3.4.27", - "@vue/shared": "3.4.27" + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.29.tgz", + "integrity": "sha512-8QUYfRcYzNlYuzKPfge1UWC6nF9ym0lx7mpGVPJYNhddxEf3DD0+kU07NTL0sXuiT2HuJuKr/iEO8WvXvT0RSQ==", + "dependencies": { + "@vue/compiler-dom": "3.4.29", + "@vue/compiler-sfc": "3.4.29", + "@vue/runtime-dom": "3.4.29", + "@vue/server-renderer": "3.4.29", + "@vue/shared": "3.4.29" }, "peerDependencies": { "typescript": "*" diff --git a/package.json b/package.json index 086bc374d..030872eb8 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,6 @@ "leaflet.markercluster": "^1.5.0", "shufflejs": "^6.1.1", "spin.js": "^2.3.2", - "vue": "^3.4.27" + "vue": "^3.4.29" } } From 28586f53394500326586a1cee3767a9561b71de7 Mon Sep 17 00:00:00 2001 From: Hans Morbach <6433480+hansmorb@users.noreply.github.com> Date: Mon, 17 Jun 2024 20:55:15 +0200 Subject: [PATCH 21/25] screenshot added holiday loading --- tests/cypress/e2e/cpt-creation.cy.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cypress/e2e/cpt-creation.cy.js b/tests/cypress/e2e/cpt-creation.cy.js index 2cfd0d76b..c09b024ac 100644 --- a/tests/cypress/e2e/cpt-creation.cy.js +++ b/tests/cypress/e2e/cpt-creation.cy.js @@ -62,6 +62,7 @@ describe('correctly render metaboxes for backend CPT creation', () => { cy.get('#timeframe-repetition').select(MANUAL_SELECTION); cy.get('#holiday_load_btn').click(); cy.get('#timeframe_manual_date').should('have.prop', 'value').should('not.be.empty') + cy.screenshot('cb-timeframe-holiday-loaded'); }) }) From 9652f6fd03ffc9f52e31ab13143712d77f6da005 Mon Sep 17 00:00:00 2001 From: hansmorb Date: Mon, 17 Jun 2024 20:26:32 +0000 Subject: [PATCH 22/25] Ran wp i18n make-pot --- languages/commonsbooking-de_DE.po | 6 +++--- languages/commonsbooking.pot | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/languages/commonsbooking-de_DE.po b/languages/commonsbooking-de_DE.po index d65e200ea..680bd4e89 100644 --- a/languages/commonsbooking-de_DE.po +++ b/languages/commonsbooking-de_DE.po @@ -1409,15 +1409,15 @@ msgstr "Artikel-Kategorie" msgid "Location Category" msgstr "Standort-Kategorie" -#: src/Repository/BookingCodes.php:304 +#: src/Repository/BookingCodes.php:328 msgid "No booking codes could be created because there were no booking codes to choose from. Please set some booking codes in the CommonsBooking settings." msgstr "Es konnten keine Buchungscodes erstellt werden, da keine Buchungscodes zur Auswahl standen. Bitte lege einige Buchungscodes in den CommonsBooking-Einstellungen fest." -#: src/Repository/BookingCodes.php:311 +#: src/Repository/BookingCodes.php:335 msgid "No booking codes could be created because the location of the timeframe could not be found." msgstr "Es konnten keine Buchungscodes erstellt werden, da der Standort nicht gefunden wurde." -#: src/Repository/BookingCodes.php:317 +#: src/Repository/BookingCodes.php:341 msgid "No booking codes could be created because the item of the timeframe could not be found." msgstr "Es konnten keine Buchungscodes erstellt werden, da der Artikel nicht gefunden wurde." diff --git a/languages/commonsbooking.pot b/languages/commonsbooking.pot index 832837217..9335a8c6a 100644 --- a/languages/commonsbooking.pot +++ b/languages/commonsbooking.pot @@ -9,7 +9,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2024-06-05T11:41:25+00:00\n" +"POT-Creation-Date: 2024-06-17T20:26:25+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.10.0\n" "X-Domain: commonsbooking\n" @@ -1599,15 +1599,15 @@ msgstr "" msgid "CommonsBooking Bookings" msgstr "" -#: src/Repository/BookingCodes.php:304 +#: src/Repository/BookingCodes.php:328 msgid "No booking codes could be created because there were no booking codes to choose from. Please set some booking codes in the CommonsBooking settings." msgstr "" -#: src/Repository/BookingCodes.php:311 +#: src/Repository/BookingCodes.php:335 msgid "No booking codes could be created because the location of the timeframe could not be found." msgstr "" -#: src/Repository/BookingCodes.php:317 +#: src/Repository/BookingCodes.php:341 msgid "No booking codes could be created because the item of the timeframe could not be found." msgstr "" From 5c3d6943e8f59d076bd7bbe3ac8e5a3978c1e44c Mon Sep 17 00:00:00 2001 From: Hans Morbach <6433480+hansmorb@users.noreply.github.com> Date: Thu, 20 Jun 2024 13:31:55 +0200 Subject: [PATCH 23/25] turned the spaceship the correct way --- src/View/Calendar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/View/Calendar.php b/src/View/Calendar.php index c9b8b4ac1..59bc9f58e 100644 --- a/src/View/Calendar.php +++ b/src/View/Calendar.php @@ -428,7 +428,7 @@ public static function getClosestBookableTimeFrameForToday( $bookableTimeframes return $bStartTimeDT <=> $aStartTimeDT; } - return $aStartDate <=> $bStartDate; + return $bStartDate <=> $aStartDate; } ); break; default: //More than one timeframe for current day From 9d18b3fbc7407852ea5a94972df827d88eea5a0e Mon Sep 17 00:00:00 2001 From: Hans Morbach <6433480+hansmorb@users.noreply.github.com> Date: Thu, 20 Jun 2024 13:32:55 +0200 Subject: [PATCH 24/25] refactored testGetClosestBookableTimeFrameForToday --- tests/php/View/CalendarTest.php | 345 ++++++++++++++++++-------------- 1 file changed, 190 insertions(+), 155 deletions(-) diff --git a/tests/php/View/CalendarTest.php b/tests/php/View/CalendarTest.php index 9a7032b81..3b51bbf02 100644 --- a/tests/php/View/CalendarTest.php +++ b/tests/php/View/CalendarTest.php @@ -11,7 +11,6 @@ /** * @TODO: Write test for restriction cache invalidation. */ - class CalendarTest extends CustomPostTypeTest { protected const bookingDaysInAdvance = 35; @@ -72,12 +71,12 @@ public function testAdvancedBookingDays() { // days between start date and latest possible booking date $maxBookableDays = date_diff( $latestPossibleBookingDate, $timeframeStart )->days; - $this->assertTrue( $maxBookableDays == (self::bookingDaysInAdvance - self::timeframeStart - 1) ); + $this->assertTrue( $maxBookableDays == ( self::bookingDaysInAdvance - self::timeframeStart - 1 ) ); } public function testClosestBookableTimeFrameFuntion() { - $startDate = date( 'Y-m-d', strtotime( 'midnight', strtotime(self::CURRENT_DATE) ) ); - $endDate = date( 'Y-m-d', strtotime( '+60 days midnight', strtotime(self::CURRENT_DATE) ) ); + $startDate = date( 'Y-m-d', strtotime( 'midnight', strtotime( self::CURRENT_DATE ) ) ); + $endDate = date( 'Y-m-d', strtotime( '+60 days midnight', strtotime( self::CURRENT_DATE ) ) ); $jsonresponse = Calendar::getCalendarDataArray( $this->itemId, @@ -86,7 +85,7 @@ public function testClosestBookableTimeFrameFuntion() { $endDate ); - $this->assertTrue($jsonresponse['minDate'] == date('Y-m-d')); + $this->assertTrue( $jsonresponse['minDate'] == date( 'Y-m-d' ) ); } /* @@ -98,42 +97,42 @@ public function testOverbookingDefaultValues() { $jsonresponse = Calendar::getCalendarDataArray( $this->itemId, $this->locationId, - date( 'Y-m-d', strtotime( 'midnight', strtotime(self::CURRENT_DATE) ) ), - date( 'Y-m-d', strtotime( '+60 days midnight', strtotime(self::CURRENT_DATE) ) ) + date( 'Y-m-d', strtotime( 'midnight', strtotime( self::CURRENT_DATE ) ) ), + date( 'Y-m-d', strtotime( '+60 days midnight', strtotime( self::CURRENT_DATE ) ) ) ); - $this->assertTrue($jsonresponse['disallowLockDaysInRange']); - $this->assertFalse($jsonresponse['countLockDaysInRange']); - $this->assertEquals(0, $jsonresponse['countLockDaysMaxDays']); + $this->assertTrue( $jsonresponse['disallowLockDaysInRange'] ); + $this->assertFalse( $jsonresponse['countLockDaysInRange'] ); + $this->assertEquals( 0, $jsonresponse['countLockDaysMaxDays'] ); //old locations which only have overbooking enabled should not have the countLockDaysInRange set and countLockDaysMaxDays should be 0 - $differentItemId = $this->createItem("Different Item",'publish'); - $oldLocationId = $this->createLocation("Old Location",'publish'); - $otherTimeframe = $this->createBookableTimeFrameIncludingCurrentDay($oldLocationId,$differentItemId); + $differentItemId = $this->createItem( "Different Item", 'publish' ); + $oldLocationId = $this->createLocation( "Old Location", 'publish' ); + $otherTimeframe = $this->createBookableTimeFrameIncludingCurrentDay( $oldLocationId, $differentItemId ); update_post_meta( $oldLocationId, COMMONSBOOKING_METABOX_PREFIX . 'allow_lockdays_in_range', 'on' ); - ClockMock::freeze( new \DateTime( self::CURRENT_DATE )); + ClockMock::freeze( new \DateTime( self::CURRENT_DATE ) ); $jsonresponse = Calendar::getCalendarDataArray( $differentItemId, $oldLocationId, - date( 'Y-m-d', strtotime( '-1 days', strtotime(self::CURRENT_DATE) ) ), - date( 'Y-m-d', strtotime( '+60 days midnight', strtotime(self::CURRENT_DATE) ) ) + date( 'Y-m-d', strtotime( '-1 days', strtotime( self::CURRENT_DATE ) ) ), + date( 'Y-m-d', strtotime( '+60 days midnight', strtotime( self::CURRENT_DATE ) ) ) ); - $this->assertFalse($jsonresponse['disallowLockDaysInRange']); - $this->assertFalse($jsonresponse['countLockDaysInRange']); - $this->assertEquals(0, $jsonresponse['countLockDaysMaxDays']); + $this->assertFalse( $jsonresponse['disallowLockDaysInRange'] ); + $this->assertFalse( $jsonresponse['countLockDaysInRange'] ); + $this->assertEquals( 0, $jsonresponse['countLockDaysMaxDays'] ); } public function testBookingOffset() { - ClockMock::freeze( new \DateTime( self::CURRENT_DATE )); - $startDate = date( 'Y-m-d', strtotime( '-1 day', strtotime(self::CURRENT_DATE) ) ); - $today = date( 'Y-m-d', strtotime( self::CURRENT_DATE ) ); - $endDate = date( 'Y-m-d', strtotime( '+60 days midnight', strtotime(self::CURRENT_DATE) ) ); - $otherItemId = $this->createItem("Other Item",'publish'); - $otherLocationId = $this->createLocation("Other Location",'publish'); - $offsetTF = $this->createTimeframe( + ClockMock::freeze( new \DateTime( self::CURRENT_DATE ) ); + $startDate = date( 'Y-m-d', strtotime( '-1 day', strtotime( self::CURRENT_DATE ) ) ); + $today = date( 'Y-m-d', strtotime( self::CURRENT_DATE ) ); + $endDate = date( 'Y-m-d', strtotime( '+60 days midnight', strtotime( self::CURRENT_DATE ) ) ); + $otherItemId = $this->createItem( "Other Item", 'publish' ); + $otherLocationId = $this->createLocation( "Other Location", 'publish' ); + $offsetTF = $this->createTimeframe( $otherLocationId, $otherItemId, - strtotime($startDate), - strtotime($endDate), + strtotime( $startDate ), + strtotime( $endDate ), \CommonsBooking\Wordpress\CustomPostType\Timeframe::BOOKABLE_ID, "on", 'd', @@ -148,7 +147,7 @@ public function testBookingOffset() { 30, 2 ); - $jsonresponse = Calendar::getCalendarDataArray( + $jsonresponse = Calendar::getCalendarDataArray( $otherItemId, $otherLocationId, $startDate, @@ -156,145 +155,181 @@ public function testBookingOffset() { ); //considering the advance booking days $days = $jsonresponse['days']; - $this->assertEquals(32, count($days)); + $this->assertEquals( 32, count( $days ) ); //considering the offset, today and tomorrow should be locked - $this->assertTrue($days[$today]['locked']); - $this->assertTrue($days[date('Y-m-d', strtotime('+1 day', strtotime($today)))]['locked']); + $this->assertTrue( $days[ $today ]['locked'] ); + $this->assertTrue( $days[ date( 'Y-m-d', strtotime( '+1 day', strtotime( $today ) ) ) ]['locked'] ); } public function testRenderTable() { - $calendar = Calendar::renderTable([]); - $item = new \CommonsBooking\Model\Item($this->itemId); - $location = new \CommonsBooking\Model\Location($this->locationId); - $this->assertStringContainsString('assertStringContainsString($item->post_title, $calendar); - $this->assertStringContainsString($location->post_title, $calendar); + $calendar = Calendar::renderTable( [] ); + $item = new \CommonsBooking\Model\Item( $this->itemId ); + $location = new \CommonsBooking\Model\Location( $this->locationId ); + $this->assertStringContainsString( 'assertStringContainsString( $item->post_title, $calendar ); + $this->assertStringContainsString( $location->post_title, $calendar ); //in a year, all timeframes will have expired -> calendar should be empty $inAYear = new \DateTime(); - $inAYear->modify('+1 year'); - ClockMock::freeze($inAYear); - $calendar = Calendar::renderTable([]); - $this->assertStringContainsString('No items found', $calendar); + $inAYear->modify( '+1 year' ); + ClockMock::freeze( $inAYear ); + $calendar = Calendar::renderTable( [] ); + $this->assertStringContainsString( 'No items found', $calendar ); } - public function testGetClosestBookableTimeFrameForToday() { - //Case 1: Timeframes do not overlap - $closestTimeframeModel = new Timeframe( $this->closestTimeframe ); - $secondClosestTimeframeModel = new Timeframe( $this->secondClosestTimeframe ); - $timeframes = [ $closestTimeframeModel, $secondClosestTimeframeModel ]; - $this->assertEquals($closestTimeframeModel->ID, Calendar::getClosestBookableTimeFrameForToday($timeframes)->ID ); - - //Case 2: Timeframes overlap but repetition is different - $otherLocation = $this->createLocation("OtherLocation"); - $otherItem = $this->createItem("OtherItem"); - $continuedTimeframe = new Timeframe( $this->createTimeframe( - $otherLocation, - $otherItem, - strtotime( '-50 days midnight', $this->now ), - strtotime( '+365 days midnight', $this->now ), - \CommonsBooking\Wordpress\CustomPostType\Timeframe::BOOKABLE_ID, - "on", - 'w', - 0, - '8:00 AM', - '12:00 PM', - 'publish', - [ "1", "2", "3", "4" ], - ) ); - $inBetweenTimeframe = new Timeframe( $this->createTimeframe( - $otherLocation, - $otherItem, - strtotime( '+20 days midnight', $this->now ), - strtotime( '+40 days midnight', $this->now ), - \CommonsBooking\Wordpress\CustomPostType\Timeframe::BOOKABLE_ID, - "on", - 'w', - 0, - '8:00 AM', - '12:00 PM', - 'publish', - [ "5", "6" ], - ) ); - $timeframes = [ $inBetweenTimeframe, $continuedTimeframe ]; - $this->assertEquals($continuedTimeframe->ID, Calendar::getClosestBookableTimeFrameForToday($timeframes)->ID); - - //case 3: Timeframes only exist in the future - $timeframeModel = new Timeframe( $this->timeframeId ); - $timeframes = [ $timeframeModel, $secondClosestTimeframeModel ]; - $this->assertEquals($timeframeModel->ID, Calendar::getClosestBookableTimeFrameForToday($timeframes)->ID); - - //case 4: timeframes overlap on the day but have different times (for today) - $otherItem = $this->createItem("OtherItem"); - $otherLocation = $this->createLocation("OtherLocation"); - $noonTimeframe = new Timeframe( $this->createTimeframe( - $otherLocation, - $otherItem, - strtotime( '-1 days midnight', $this->now ), - strtotime( '+1 days midnight', $this->now ), - \CommonsBooking\Wordpress\CustomPostType\Timeframe::BOOKABLE_ID, - "off", - 'd', - 0, - '8:00 AM', - '11:00 AM', - ) ); - $afternoonTimeframe = new Timeframe( $this->createTimeframe( - $otherLocation, - $otherItem, - strtotime( '-1 days midnight', $this->now ), - strtotime( '+1 days midnight', $this->now ), - \CommonsBooking\Wordpress\CustomPostType\Timeframe::BOOKABLE_ID, - "on", - 'd', - 0, - '12:00 PM', - '3:00 PM', - ) ); - $timeframes = [ $noonTimeframe, $afternoonTimeframe ]; - $morning = new \DateTime(); - $morning->setTime(9,0); - ClockMock::freeze($morning); - $this->assertEquals($noonTimeframe->ID, Calendar::getClosestBookableTimeFrameForToday($timeframes)->ID); - - $afternoon = new \DateTime(); - $afternoon->setTime(13,0); - ClockMock::freeze($afternoon); - $this->assertEquals($afternoonTimeframe->ID, Calendar::getClosestBookableTimeFrameForToday($timeframes)->ID); - - //case 5 timeframes overlap on the day but have different times (for future) - $noonFutureTimeframe = new Timeframe( $this->createTimeframe( - $otherLocation, - $otherItem, - strtotime( '+5 days midnight', $this->now ), - strtotime( '+7 days midnight', $this->now ), - \CommonsBooking\Wordpress\CustomPostType\Timeframe::BOOKABLE_ID, - "off", - 'd', - 0, - '8:00 AM', - '11:00 AM', - ) ); - $afternoonFutureTimeframe = new Timeframe( $this->createTimeframe( - $otherLocation, - $otherItem, - strtotime( '+5 days midnight', $this->now ), - strtotime( '+7 days midnight', $this->now ), - \CommonsBooking\Wordpress\CustomPostType\Timeframe::BOOKABLE_ID, - "on", - 'd', - 0, - '12:00 PM', - '3:00 PM', - ) ); - $timeframes = [ $noonFutureTimeframe, $afternoonFutureTimeframe ]; - $this->assertEquals($noonFutureTimeframe->ID, Calendar::getClosestBookableTimeFrameForToday($timeframes)->ID); + /** + * + * @return array + */ + public function provideGetClosestBookableTimeFrameForToday() { + $currentTimestamp = strtotime( self::CURRENT_DATE . ' 12:00' ); + //will define an array with settings for the timeframes + //that the getClosestBookableTimeFrameForToday function will be tested against + //you can provide the name of the test, the closest timeframe and another timeframe. + //supported arguments for timeframe, if not specified default values will be used + //repetition, repetition_start, repetition_end = null,weekdays = ["1","2","3","4","5","6","7"], start_time = '8:00 AM', end_time = '12:00 PM' + //if no start and endtime are provided, the timeframe will span the full day. + //If they are provided, fullday is turned off. + //Please note: The date that we test against is a thursday. + return [ + "daily not overlapping" => [ + "closest" => [ + "repetition" => "d", + "repetition_start" => strtotime( "-7 days", $currentTimestamp ), + "repetition_end" => strtotime( "+7 days", $currentTimestamp ), + ], + "other" => [ + "repetition" => "d", + "repetition_start" => strtotime( "+8 days", $currentTimestamp ), + "repetition_end" => strtotime( "+14 days", $currentTimestamp ), + ] + ], + "weekly (different weekdays)" => [ + "closest" => [ + "repetition" => "w", + "repetition_start" => strtotime( "-7 days", $currentTimestamp ), + "repetition_end" => strtotime( "+7 days", $currentTimestamp ), + "weekdays" => [ "4" ] //just thursday + ], + "other" => [ + "repetition" => "w", + "repetition_start" => strtotime( "-7 days", $currentTimestamp ), + "repetition_end" => strtotime( "+7 days", $currentTimestamp ), + "weekdays" => [ "1", "2", "3", "5", "6", "7" ] //all but thursday + ] + ], + "both timeframes in future (daily rep)" => [ + "closest" => [ + "repetition" => "d", + "repetition_start" => strtotime( "+7 days", $currentTimestamp ), + "repetition_end" => strtotime( "+14 days", $currentTimestamp ), + ], + "other" => [ + "repetition" => "d", + "repetition_start" => strtotime( "+15 days", $currentTimestamp ), + "repetition_end" => strtotime( "+21 days", $currentTimestamp ), + ] + ], + "daily overlap with different times (present)" => [ + "closest" => [ + "repetition" => "d", + "repetition_start" => strtotime( "-1 days", $currentTimestamp ), + "repetition_end" => strtotime( "+1 days", $currentTimestamp ), + "start_time" => "8:00 AM", + "end_time" => "01:00 PM" + ], + "other" => [ + "repetition" => "d", + "repetition_start" => strtotime( "-1 days", $currentTimestamp ), + "repetition_end" => strtotime( "+1 days", $currentTimestamp ), + "start_time" => "02:00 PM", + "end_time" => "06:00 PM" + ] + ], + "daily overlap with different times (future)" => [ + "closest" => [ + "repetition" => "d", + "repetition_start" => strtotime( "+5 days", $currentTimestamp ), + "repetition_end" => strtotime( "+7 days", $currentTimestamp ), + "start_time" => "8:00 AM", + "end_time" => "01:00 PM" + ], + "other" => [ + "repetition" => "d", + "repetition_start" => strtotime( "+5 days", $currentTimestamp ), + "repetition_end" => strtotime( "+7 days", $currentTimestamp ), + "start_time" => "02:00 PM", + "end_time" => "06:00 PM" + ], + ] + ]; + } + + /** + * These are the tests for timeframes with daily repetition + * @return void + * @throws \Exception + * @dataProvider provideGetClosestBookableTimeFrameForToday + */ + public function testGetClosestBookableTimeFrameForToday( array $closest, array $other ) { + $testItem = $this->createItem( "Item" ); + $testLocation = $this->createLocation( "Location" ); + $currentTime = new \DateTime( self::CURRENT_DATE ); + $currentTime->setTime( 12, 0 ); + //Time set to '01.07.2021 12:00' + ClockMock::freeze( $currentTime ); + $expectedClosestTimeframe = $this->createTimeframeFromConfig( "closest timeframe", $testItem, $testLocation, $closest ); + $otherTimeframe = $this->createTimeframeFromConfig( "other timeframe", $testItem, $testLocation, $other ); + $closestTimeframe = Calendar::getClosestBookableTimeFrameForToday( [ + $expectedClosestTimeframe, + $otherTimeframe + ] ); + $this->assertEquals( $expectedClosestTimeframe->ID, $closestTimeframe->ID ); + } + + /** + * Will create the timeframes from the configuration defined in the dataProvider of testGetClosestBookableTimeFrameForToday + * + * @param int $itemId + * @param int $locationID + * @param array $config + * + * @return void + */ + private function createTimeframeFromConfig( string $name, int $itemId, int $locationID, array $config ): Timeframe { + $fullDay = ! ( isset ( $config["start_time"] ) && isset( $config["end_time"] ) ); + $grid = $fullDay ? 1 : 0; //Currently, grid is becoming hourly when not full day (TODO: Also test slots) + + return new Timeframe( + $this->createTimeframe( + $locationID, + $itemId, + $config["repetition_start"], + $config["repetition_end"] ?? null, + \CommonsBooking\Wordpress\CustomPostType\Timeframe::BOOKABLE_ID, + $fullDay ? "on" : "off", + $config["repetition"], + $grid, + $config["start_time"] ?? '8:00 AM', + $config["end_time"] ?? '12:00 PM', + "publish", + $config["weekdays"] ?? [ "1", "2", "3", "4", "5", "6", "7" ], + "", + self::USER_ID, + 3, + 30, + 0, + "on", + "on", + $name + ) + ); } - protected function setUp() : void { + protected function setUp(): void { parent::setUp(); - $this->now = time(); + $this->now = time(); $this->timeframeId = $this->createTimeframe( $this->locationId, $this->itemId, From 98e16795cb6ca149dc671f8adce90509347c2919 Mon Sep 17 00:00:00 2001 From: Hans Morbach <6433480+hansmorb@users.noreply.github.com> Date: Fri, 21 Jun 2024 18:16:51 +0200 Subject: [PATCH 25/25] added weekly & daily testcase --- tests/php/View/CalendarTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/php/View/CalendarTest.php b/tests/php/View/CalendarTest.php index 3b51bbf02..a966eb9f1 100644 --- a/tests/php/View/CalendarTest.php +++ b/tests/php/View/CalendarTest.php @@ -230,6 +230,19 @@ public function provideGetClosestBookableTimeFrameForToday() { "repetition_end" => strtotime( "+21 days", $currentTimestamp ), ] ], + "weekly and daily" => [ + "closest" => [ + "repetition" => "w", + "repetition_start" => strtotime( "-64 days", $currentTimestamp ), + "repetition_end" => strtotime( "+199 days", $currentTimestamp ), + "weekdays" => [ "1", "2", "3", "4", "5" ] + ], + "other" => [ + "repetition" => "d", + "repetition_start" => strtotime( "+21 days", $currentTimestamp ), + "repetition_end" => strtotime( "+21 days", $currentTimestamp ) + ] + ], "daily overlap with different times (present)" => [ "closest" => [ "repetition" => "d",