Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MBS-8974: Add repeating of cards #47

Merged
merged 3 commits into from
Jan 14, 2025
Merged
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
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
64 changes: 54 additions & 10 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 @@ -682,12 +679,35 @@ public function unassign_user(int $cardid, int $userid): void {
public function set_card_complete(int $cardid, int $state): void {
global $DB, $USER;
$card = $this->get_card($cardid);
$update = ['id' => $cardid, 'completed' => $state, 'timemodified' => time()];
$update = ['id' => $cardid, 'completed' => $state, 'timemodified' => time(), 'repeat_enable' => 0];
$this->formatter->put('cards', $update);
$DB->update_record('kanban_card', $update);
$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;
}
$this->add_card($this->get_leftmost_column($card->kanban_board), 0, (array)$newcard);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(array) $newcard fehlendes Leerzeichen

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHPCS moniert das nicht

}
} else {
helper::add_or_update_calendar_event($this->kanban, $card, $assignees);
}
Expand Down Expand Up @@ -813,6 +833,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 +1254,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
30 changes: 29 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,33 @@ 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_enable', 0);
$mform->setDefault('repeat_interval', 1);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bei mir ist der default leider 0 (also 0 Stunden).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Das kommt wohl daher, dass der Standard-Wert in der DB 0 ist. Ich habe das angepasst.

$mform->disabledIf('repeatgroup', 'repeat_enable');
$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="1" 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, '1', 'repeat_enable');

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

$field = new xmldb_field(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warum wird hier eine unterschiedliche Formatierung genutzt? Oben inline, hier über mehrere Zeilen.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wegen der maximalen Zeilenlänge von 132 Zeichen.

'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;
Loading