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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion app/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class Event extends Model
'latitude', 'longitude', 'timezone', 'status',
'website', 'tickets_url', 'code_of_conduct_url', 'meeting_url', 'video_url', 'notes_url',
'summary', 'description', 'cover_image', 'unlisted', 'parent_id', 'hide_from_main_feed',
'recurrence_interval',
'recurrence_interval', 'recurrence_interval_count',
];

public static function slug_from_name($name) {
Expand Down Expand Up @@ -424,6 +424,8 @@ public function recurrence_description() {
return 'Every week on '.$start->format('l').'s';
case 'biweekly_dow':
return 'Every other week on '.$start->format('l').'s';
case 'weekly_n':
return 'Every '.((int)$this->recurrence_interval_count ?: 1).' weeks on '.$start->format('l').'s';
case 'monthly_date':
return 'Every month on the '.$start->format('dS');
case 'yearly':
Expand All @@ -440,6 +442,8 @@ public function recurrence_date_interval() {
return new DateInterval('P1W');
case 'biweekly_dow':
return new DateInterval('P2W');
case 'weekly_n':
return new DateInterval('P'.((int)$this->recurrence_interval_count ?: 1).'W');
case 'monthly_date':
return new DateInterval('P1M');
case 'yearly':
Expand All @@ -458,6 +462,8 @@ public function recurrence_end_datetime() {
return $now->add(new DateInterval('P5W'));
case 'biweekly_dow':
return $now->add(new DateInterval('P9W'));
case 'weekly_n':
return $now->add(new DateInterval('P'.(((int)$this->recurrence_interval_count ?: 1) * 5).'W'));
case 'monthly_date':
return $now->add(new DateInterval('P4M'));
case 'yearly':
Expand Down Expand Up @@ -496,6 +502,7 @@ public function create_upcoming_recurrences() {
$copy->start_date = $date->format('Y-m-d');
$copy->is_template = false;
$copy->recurrence_interval = null;
$copy->recurrence_interval_count = null;
$copy->sort_date = $copy->sort_date();
$copy->reset_live_event_stats();

Expand Down
8 changes: 8 additions & 0 deletions app/Http/Controllers/EventController.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public function create_event(Request $request) {
'name' => 'required',
'start_date' => 'required|date_format:Y-m-d',
'status' => 'in:'.implode(',', array_keys(Event::$STATUSES)),
'recurrence_interval_count' => 'required_if:recurrence_interval,weekly_n|nullable|integer|min:1|max:52',
]);

$event = new Event();
Expand All @@ -75,6 +76,9 @@ public function create_event(Request $request) {
if(request('is_template')) {
$event->is_template = true;
$event->recurrence_interval = request('recurrence_interval');
$event->recurrence_interval_count = request('recurrence_interval') === 'weekly_n'
? (request('recurrence_interval_count') ?: null)
: null;
$event->key = '';
}

Expand Down Expand Up @@ -244,6 +248,7 @@ public function save_event(Request $request, Event $event) {
'name' => 'required',
'start_date' => 'required|date_format:Y-m-d',
'status' => 'in:'.implode(',', array_keys(Event::$STATUSES)),
'recurrence_interval_count' => 'required_if:recurrence_interval,weekly_n|nullable|integer|min:1|max:52',
]);

if($event->fields_from_ics) {
Expand Down Expand Up @@ -312,6 +317,9 @@ public function save_event(Request $request, Event $event) {
// Allow event templates to change the recurrence property
if($event->is_template) {
$event->recurrence_interval = request('recurrence_interval');
$event->recurrence_interval_count = request('recurrence_interval') === 'weekly_n'
? (request('recurrence_interval_count') ?: null)
: null;
}

$event->last_modified_by = Auth::user()->id;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('events', function (Blueprint $table) {
$table->unsignedInteger('recurrence_interval_count')->nullable();
});
Schema::table('event_revisions', function (Blueprint $table) {
$table->unsignedInteger('recurrence_interval_count')->nullable();
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('events', function (Blueprint $table) {
$table->dropColumn('recurrence_interval_count');
});
Schema::table('event_revisions', function (Blueprint $table) {
$table->dropColumn('recurrence_interval_count');
});
}
};
17 changes: 17 additions & 0 deletions resources/views/edit-event.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,13 @@
<script>
$(function(){
var selected_recurrence_interval;
var selected_recurrence_interval_count;
function toggle_count() {
if($("select[name=recurrence_interval]").val() == 'weekly_n')
$("#recurrence_count_field").show();
else
$("#recurrence_count_field").hide();
}
function reload_recurrence_menu() {
$.post("/event/{{ $event->id }}/recurring/details", {
_token: csrf_token(),
Expand All @@ -259,8 +266,18 @@ function reload_recurrence_menu() {
$("select[name=recurrence_interval]").val("{{ $event->recurrence_interval ?: 'weekly_dow' }}");
}

if(selected_recurrence_interval_count) {
$("input[name=recurrence_interval_count]").val(selected_recurrence_interval_count);
}
toggle_count();

$("select[name=recurrence_interval]").on('change', function(){
selected_recurrence_interval = $(this).val();
toggle_count();
});

$("input[name=recurrence_interval_count]").on('change', function(){
selected_recurrence_interval_count = $(this).val();
});
});
}
Expand Down
8 changes: 8 additions & 0 deletions resources/views/recurring-event-details.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,17 @@
<select name="recurrence_interval">
<option value="weekly_dow">Every Week on {{ $recur_dow }}</option>
<option value="biweekly_dow">Every Other Week on {{ $recur_dow }}</option>
<option value="weekly_n">Every N Weeks on {{ $recur_dow }}</option>
<option value="monthly_date">Every Month on the {{ $recur_date }}</option>
<option value="yearly">Every Year on {{ $recur_month_date }}</option>
</select>
</div>
</div>
</div>
<div class="field" id="recurrence_count_field" style="display:none;">
<label class="label">Repeat every how many weeks?</label>
<div class="control">
<input type="number" class="input" name="recurrence_interval_count"
min="1" max="52" value="{{ $event->recurrence_interval_count ?: 3 }}">
</div>
</div>
31 changes: 31 additions & 0 deletions tests/Unit/EventTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,35 @@ public function testIsPastNoEndTime() {
$now = new DateTime('2024-12-01T17:31:00');
$this->assertTrue($event->is_past($now));
}

public function testRecurrenceEveryNWeeksInterval() {
$event = new Event;
$event->start_date = '2024-01-01'; // a Monday
$event->recurrence_interval = 'weekly_n';
$event->recurrence_interval_count = 3;

// 'P3W' is normalized to 21 days internally
$this->assertEquals(21, $event->recurrence_date_interval()->d);
$this->assertEquals('Every 3 weeks on Mondays', $event->recurrence_description());
}

public function testRecurrenceEveryNWeeksEndWindow() {
$event = new Event;
$event->start_date = '2024-01-01';
$event->recurrence_interval = 'weekly_n';
$event->recurrence_interval_count = 3;

// Lookahead is count * 5 weeks = 15 weeks = 105 days ahead of "now"
$now = new DateTime();
$end = $event->recurrence_end_datetime();
$this->assertEquals(105, $now->diff($end)->days);
}

public function testRecurrenceEveryNWeeksFallsBackToWeekly() {
$event = new Event;
$event->start_date = '2024-01-01';
$event->recurrence_interval = 'weekly_n';
// No count set: should behave as every 1 week (7 days), not crash
$this->assertEquals(7, $event->recurrence_date_interval()->d);
}
}