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 ecb2ea84f..4905cb1d6 100644 --- a/src/Repository/Timeframe.php +++ b/src/Repository/Timeframe.php @@ -720,6 +720,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 804ef91ee..59bc9f58e 100644 --- a/src/View/Calendar.php +++ b/src/View/Calendar.php @@ -406,22 +406,45 @@ public static function getCalendarDataArray( $item, $location, string $startDate * * @return \CommonsBooking\Model\Timeframe|null */ - private 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; - } - ); + public static function getClosestBookableTimeFrameForToday( $bookableTimeframes ): ?\CommonsBooking\Model\Timeframe { + $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 $bStartDate <=> $aStartDate; + } ); + 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 8279c3135..2b6d8e24d 100644 --- a/tests/php/Repository/TimeframeTest.php +++ b/tests/php/Repository/TimeframeTest.php @@ -178,6 +178,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 e95f139ab..a966eb9f1 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; @@ -26,6 +25,8 @@ class CalendarTest extends CustomPostTypeTest { protected $secondClosestTimeframe; + private $now; + public function testKeepDateRangeParam() { $startDate = date( 'Y-m-d', strtotime( self::CURRENT_DATE ) ); $jsonresponse = Calendar::getCalendarDataArray( @@ -70,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, @@ -84,7 +85,7 @@ public function testClosestBookableTimeFrameFuntion() { $endDate ); - $this->assertTrue($jsonresponse['minDate'] == date('Y-m-d')); + $this->assertTrue( $jsonresponse['minDate'] == date( 'Y-m-d' ) ); } /* @@ -96,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', @@ -146,7 +147,7 @@ public function testBookingOffset() { 30, 2 ); - $jsonresponse = Calendar::getCalendarDataArray( + $jsonresponse = Calendar::getCalendarDataArray( $otherItemId, $otherLocationId, $startDate, @@ -154,37 +155,199 @@ 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('