Skip to content

Commit

Permalink
MBS-8974: Add repeating of cards
Browse files Browse the repository at this point in the history
  • Loading branch information
sh-csg committed Dec 16, 2024
1 parent 52fd70a commit 29d3bbb
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 17 deletions.
15 changes: 14 additions & 1 deletion backup/moodle2/backup_kanban_stepslib.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,20 @@ protected function define_structure(): backup_nested_element {
$kanban = new backup_nested_element(
'kanban',
['id'],
['course', 'name', 'intro', 'introformat', 'userboards', 'history', 'completioncreate', 'completioncomplete']
[
'course',
'name',
'intro',
'introformat',
'userboards',
'history',
'completioncreate',
'completioncomplete',
'repeat_enable',
'repeat_interval',
'repeat_interval_type',
'repeat_newduedate',
]
);
$kanban->set_source_table('kanban', ['id' => backup::VAR_ACTIVITYID]);
$kanban->annotate_files('mod_kanban', 'intro', null);
Expand Down
63 changes: 54 additions & 9 deletions classes/boardmanager.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* Class to handle updating the board
*
* @package mod_kanban
* @copyright 2023-2024 ISB Bayern
* @copyright 2023-2024 ISB Bayern
* @author Stefan Hanauska
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
Expand All @@ -34,7 +34,7 @@
* Class to handle updating the board. It also sends notifications, but does not check permissions.
*
* @package mod_kanban
* @copyright 2023-2024 ISB Bayern
* @copyright 2023-2024 ISB Bayern
* @author Stefan Hanauska
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
Expand Down Expand Up @@ -477,7 +477,6 @@ public function add_card(int $columnid, int $aftercard = 0, array $data = []): i

// Users can always edit cards they created.
$data['canedit'] = $this->can_user_manage_specific_card($data['id']);
;
$data['columnname'] = clean_param($column->title, PARAM_TEXT);

$this->formatter->put('cards', $data);
Expand Down Expand Up @@ -547,7 +546,9 @@ public function move_card(int $cardid, int $aftercard, int $columnid = 0): void
// If target column has autoclose option set, update card to be completed.
$options = json_decode($targetcolumn->options);
if (!empty($options->autoclose)) {
$updatecard['completed'] = 1;
if ($card['completed']) {
self::set_card_complete($cardid, 1);
}
}
$DB->update_record('kanban_card', $updatecard);
// When inplace editing the title and moving the card happens quite fast in a row,
Expand Down Expand Up @@ -581,11 +582,7 @@ public function move_card(int $cardid, int $aftercard, int $columnid = 0): void
$assignees = $this->get_card_assignees($cardid);
helper::send_notification($this->cminfo, 'moved', $assignees, (object) $data);
if (!empty($options->autoclose) && $card->completed == 0) {
$data['title'] = clean_param($card->title, PARAM_TEXT);
helper::send_notification($this->cminfo, 'closed', $assignees, (object) $data);
helper::remove_calendar_event($this->kanban, $card);
$this->write_history('completed', constants::MOD_KANBAN_CARD, [], $columnid, $cardid);
$this->update_completion($assignees);
self::set_card_complete($cardid, 1);
}
$this->write_history(
'moved',
Expand Down Expand Up @@ -688,6 +685,30 @@ public function set_card_complete(int $cardid, int $state): void {
$assignees = $this->get_card_assignees($cardid);
if ($state) {
helper::remove_calendar_event($this->kanban, $card, $assignees);
if (!empty($card->repeat_enable)) {
$newcard = clone $card;
if ($card->repeat_newduedate == constants::MOD_KANBAN_REPEAT_NONEWDUEDATE) {
$newcard->duedate = 0;
$newcard->reminder = 0;
} else {
$timedifference = $newcard->duedate - $newcard->reminder;
$timebase = (
$card->repeat_newduedate == constants::MOD_KANBAN_REPEAT_NEWDUEDATE_AFTERDUE && !empty($newcard->duedate) ?
$newcard->duedate :
time()
);
$newcard->duedate = strtotime(
'+' .
$card->repeat_interval .
' ' .
constants::MOD_KANBAN_REPEAT_INTERVAL_TYPE[$card->repeat_interval_type],
$timebase
);
$newcard->reminder = $newcard->duedate - $timedifference;
}
$newcard->isrepeated = 1;
$this->add_card($this->get_leftmost_column($card->kanban_board), 0, (array)$newcard);
}
} else {
helper::add_or_update_calendar_event($this->kanban, $card, $assignees);
}
Expand Down Expand Up @@ -813,6 +834,10 @@ public function update_card(int $cardid, array $data): void {
'kanban_column',
'kanban_board',
'completed',
'repeat_enable',
'repeat_interval',
'repeat_interval_type',
'repeat_newduedate',
];
// Do some extra sanitizing.
if (isset($data['title'])) {
Expand Down Expand Up @@ -1230,4 +1255,24 @@ public function can_user_manage_specific_card(int $cardid, int $userid = 0): boo

return false;
}

/**
* Returns the leftmost column of a board, 0 if none is found.
*
* @param int $boardid Id of the board, defaults to 0 (current board)
* @return int
*/
public function get_leftmost_column(int $boardid = 0): int {
global $DB;
if (empty($boardid) || $this->board->id == $boardid) {
$sequence = $this->board->sequence;
} else {
$sequence = $DB->get_field('kanban_board', 'sequence', ['id' => $boardid]);
}
if (empty($sequence)) {
return 0;
}
$columnids = explode(',', $sequence, 2);
return $columnids[0];
}
}
42 changes: 42 additions & 0 deletions classes/constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,46 @@ class constants {
self::MOD_KANBAN_DISCUSSION => 'discussion',
self::MOD_KANBAN_HISTORY => 'history',
];
/**
* Repeat interval type: hours
*/
public const MOD_KANBAN_REPEAT_HOURS = 2;
/**
* Repeat interval type: days
*/
public const MOD_KANBAN_REPEAT_DAYS = 3;
/**
* Repeat interval type: weeks
*/
public const MOD_KANBAN_REPEAT_WEEKS = 4;
/**
* Repeat interval type: months
*/
public const MOD_KANBAN_REPEAT_MONTHS = 5;
/**
* Repeat interval type: years
*/
public const MOD_KANBAN_REPEAT_YEARS = 6;
/**
* Mapping of repeat interval types to strings
*/
public const MOD_KANBAN_REPEAT_INTERVAL_TYPE = [
self::MOD_KANBAN_REPEAT_HOURS => 'hour',
self::MOD_KANBAN_REPEAT_DAYS => 'day',
self::MOD_KANBAN_REPEAT_WEEKS => 'week',
self::MOD_KANBAN_REPEAT_MONTHS => 'month',
self::MOD_KANBAN_REPEAT_YEARS => 'year',
];
/**
* Repeat new due date: no new due date
*/
public const MOD_KANBAN_REPEAT_NONEWDUEDATE = 0;
/**
* Repeat new due date: after due date
*/
public const MOD_KANBAN_REPEAT_NEWDUEDATE_AFTERDUE = 1;
/**
* Repeat new due date: after completion
*/
public const MOD_KANBAN_REPEAT_NEWDUEDATE_AFTERCOMPLETION = 2;
}
1 change: 0 additions & 1 deletion classes/external/change_kanban_content.php
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,6 @@ public static function move_card_returns(): external_single_structure {
* @throws moodle_exception
*/
public static function move_card(int $cmid, int $boardid, array $data): array {
global $USER;
$params = self::validate_parameters(self::move_card_parameters(), [
'cmid' => $cmid,
'boardid' => $boardid,
Expand Down
29 changes: 28 additions & 1 deletion classes/form/edit_card_form.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@
use core_form\dynamic_form;
use mod_kanban\boardmanager;
use mod_kanban\helper;
use mod_kanban\constants;
use moodle_url;

/**
* From for editing a card.
*
* @package mod_kanban
* @copyright 2023-2024 ISB Bayern
* @copyright 2023-2024 ISB Bayern
* @author Stefan Hanauska
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
Expand Down Expand Up @@ -80,6 +81,32 @@ public function definition() {

$mform->addElement('date_time_selector', 'reminderdate', get_string('reminderdate', 'kanban'), ['optional' => true]);

$repeatgroup = [];
$repeatgroup[] = $mform->createElement('advcheckbox', 'repeat_enable', get_string('enable'));
$repeatgroup[] = $mform->createElement('text', 'repeat_interval', get_string('repeat_interval', 'kanban'), ['size' => 3]);
$repeatgroup[] = $mform->createElement('select', 'repeat_interval_type', get_string('repeat_interval_type', 'kanban'), [
constants::MOD_KANBAN_REPEAT_HOURS => get_string('hours'),
constants::MOD_KANBAN_REPEAT_DAYS => get_string('days'),
constants::MOD_KANBAN_REPEAT_WEEKS => get_string('weeks'),
constants::MOD_KANBAN_REPEAT_MONTHS => get_string('months'),
constants::MOD_KANBAN_REPEAT_YEARS => get_string('years'),
]);
$repeatgroup[] = $mform->createElement('select', 'repeat_newduedate', get_string('repeat_newduedate', 'kanban'), [
constants::MOD_KANBAN_REPEAT_NONEWDUEDATE => get_string('nonewduedate', 'kanban'),
constants::MOD_KANBAN_REPEAT_NEWDUEDATE_AFTERDUE => get_string('afterdue', 'kanban'),
constants::MOD_KANBAN_REPEAT_NEWDUEDATE_AFTERCOMPLETION => get_string('aftercompletion', 'kanban'),
]);

$mform->addElement('group', 'repeatgroup', get_string('repeat', 'kanban'), $repeatgroup, ' ', false);

$mform->setType('repeat_interval', PARAM_INT);
$mform->setType('repeat_interval_type', PARAM_INT);
$mform->setDefault('repeat_interval', 1);
$mform->disabledIf('repeatgroup', 'repeat_enable', 'notchecked');
$mform->disabledIf('repeat_interval', 'repeat_newduedate', 'eq', constants::MOD_KANBAN_REPEAT_NONEWDUEDATE);
$mform->disabledIf('repeat_interval_type', 'repeat_newduedate', 'eq', constants::MOD_KANBAN_REPEAT_NONEWDUEDATE);
$mform->addHelpButton('repeatgroup', 'repeat', 'kanban');

$mform->addElement('filemanager', 'attachments', get_string('attachments', 'kanban'));

$mform->addElement('color', 'color', get_string('color', 'mod_kanban'), ['size' => 5]);
Expand Down
4 changes: 4 additions & 0 deletions db/install.xml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@
<FIELD NAME="createdby" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="repeat_enable" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="repeat_interval" TYPE="int" LENGTH="11" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="repeat_interval_type" TYPE="int" LENGTH="11" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="repeat_newduedate" TYPE="int" LENGTH="5" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
Expand Down
58 changes: 56 additions & 2 deletions db/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* mod_kanban db upgrades.
*
* @package mod_kanban
* @copyright 2023-2024 ISB Bayern
* @copyright 2023-2024 ISB Bayern
* @author Stefan Hanauska
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
Expand All @@ -29,6 +29,60 @@
* @param int $oldversion Version number the plugin is being upgraded from.
*/
function xmldb_kanban_upgrade($oldversion) {
// No upgrade steps until now.
global $DB;
$dbman = $DB->get_manager();

if ($oldversion < 2024121602) {
// Define field repeat_enable to be added to kanban_card.
$table = new xmldb_table('kanban_card');
$field = new xmldb_field('repeat_enable', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'timemodified');

// Conditionally launch add field repeat_enable.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}

$field = new xmldb_field('repeat_interval', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, '0', 'repeat_enable');

// Conditionally launch add field repeat_interval.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}

$field = new xmldb_field(
'repeat_interval_type',
XMLDB_TYPE_INTEGER,
'11',
null,
XMLDB_NOTNULL,
null,
'0',
'repeat_interval'
);

// Conditionally launch add field repeat_interval_type.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}

$field = new xmldb_field(
'repeat_newduedate',
XMLDB_TYPE_INTEGER,
'5',
null,
XMLDB_NOTNULL,
null,
'0',
'repeat_interval_type'
);

// Conditionally launch add field repeat_newduedate.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}

// Kanban savepoint reached.
upgrade_mod_savepoint(true, 2024121602, 'kanban');
}
return true;
}
9 changes: 9 additions & 0 deletions lang/en/kanban.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

$string['addcard'] = 'Add a card to this column';
$string['addcolumn'] = 'Add a column to this board';
$string['aftercompletion'] = 'after card is closed';
$string['afterdue'] = 'after card is due';
$string['assignee'] = 'Assignee';
$string['assignees'] = 'Assignees';
$string['assignme'] = 'Assign me';
Expand Down Expand Up @@ -143,6 +145,7 @@
$string['newcolumn'] = 'New column';
$string['nogroupavailable'] = 'No group available';
$string['nokanbaninstances'] = 'There are no kanban boards in this course or you are not allowed to access them';
$string['nonewduedate'] = 'No new due date';
$string['nouser'] = 'No user';
$string['nouserboards'] = 'No personal boards';
$string['pluginadministration'] = 'Kanban administration';
Expand All @@ -167,6 +170,12 @@
$string['pushcardconfirm'] = 'This will send a copy of this card to all boards inside this kanban activity including templates. Existing copies will be replaced.';
$string['reminderdate'] = 'Reminder date';
$string['remindertask'] = 'Send reminder notifications';
$string['repeat'] = 'Repeat card';
$string['repeat_help'] = "If selected, a new copy of this card will be created in the leftmost column as soon as this instance is completed. Discussion, history and assignees are not copied.
You can choose how to calculate the new due date, if needed. This will also be applied to the new reminder date.";
$string['repeat_interval'] = 'Interval';
$string['repeat_interval_type'] = 'Frequency';
$string['repeat_newduedate'] = 'New due date';
$string['reset_group'] = 'Reset group boards';
$string['reset_kanban'] = 'Reset shared boards';
$string['reset_personal'] = 'Reset personal boards';
Expand Down
8 changes: 6 additions & 2 deletions tests/change_kanban_content_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,12 @@ public function test_move_card(): void {

$update = json_decode($returnvalue['update'], true);

$this->assertCount(3, $update);
// As the target column has autoclose enabled by default, we get two updates for cards.
$this->assertCount(4, $update);
$this->assertEquals('cards', $update[0]['name']);
$this->assertEquals('columns', $update[1]['name']);
$this->assertEquals('columns', $update[2]['name']);
$this->assertEquals('cards', $update[3]['name']);

$this->assertEquals(join(',', [$cards[0]->id, $cards[2]->id]), $update[2]['fields']['sequence']);
$this->assertEquals('', $update[1]['fields']['sequence']);
Expand Down Expand Up @@ -301,10 +303,12 @@ public function test_move_card(): void {

$update = json_decode($returnvalue['update'], true);

$this->assertCount(3, $update);
// As the target column has autoclose enabled by default, we get two updates for cards.
$this->assertCount(4, $update);
$this->assertEquals('cards', $update[0]['name']);
$this->assertEquals('columns', $update[1]['name']);
$this->assertEquals('columns', $update[2]['name']);
$this->assertEquals('cards', $update[3]['name']);

$this->assertEquals(join(',', [$cards[2]->id, $cards[1]->id, $cards[0]->id]), $update[2]['fields']['sequence']);
$this->assertEquals('', $update[1]['fields']['sequence']);
Expand Down
2 changes: 1 addition & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

$plugin->component = 'mod_kanban';
$plugin->release = '0.2.6';
$plugin->version = 2024121601;
$plugin->version = 2024121602;
$plugin->requires = 2022112800;
$plugin->supported = [401, 405];
$plugin->maturity = MATURITY_STABLE;

0 comments on commit 29d3bbb

Please sign in to comment.