From 5fd13decd82ec47e0be207f4b03597636096e840 Mon Sep 17 00:00:00 2001 From: mbentz Date: Mon, 28 Jun 2021 15:31:12 -0400 Subject: [PATCH 1/4] Add support for event instances --- ExternalModule.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/ExternalModule.php b/ExternalModule.php index 8738770..5e6f95c 100644 --- a/ExternalModule.php +++ b/ExternalModule.php @@ -164,7 +164,10 @@ function setDefaultValues() { // Getting previous event ID. foreach ($events as $event) { - if ($event == $_GET['event_id']) { + // Event instances share the same event id for every instance. + // To support event instances we only break on the first instance, and allow execution for all future instances i.e., $_GET['instance'] greater than 1. + // Event instances are enabled under: Project Setup > Enable optional modules and customizations > Repeatable instruments and events > 'Repeat instruments (repeat independently of each other)` + if ($event == $_GET['event_id'] && $_GET['instance'] == 1) { break; } @@ -178,11 +181,14 @@ function setDefaultValues() { } // Getting previous event value. - if (isset($data[$prev_event][$source_field])) { - $default_value = $data[$prev_event][$source_field]; - } elseif (isset($data['repeat_instances'][$prev_event][""])) { + // isset returns true for event instances when $prev_event_field_value is equal to an empty string (""). + // An additional check to verify the value is not empty is required. + $prev_event_field_value = $data[$prev_event][$source_field]; + if (isset($prev_event_field_value) && !empty($prev_event_field_value)) { + $default_value = $prev_event_field_value; + } elseif ($data['repeat_instances'][$prev_event][$source_form]) { // Handling repeat events by using the most recent instance of the previous event to source values - $most_recent_instance = array_slice($data['repeat_instances'][$prev_event][""], -1)[0]; + $most_recent_instance = array_slice($data['repeat_instances'][$prev_event][$source_form], -1)[0]; $default_value = $most_recent_instance[$source_field]; } From 5a1933e48d96f3019a385a4d2505acb62fc9f65c Mon Sep 17 00:00:00 2001 From: mbentz Date: Tue, 29 Jun 2021 11:35:25 -0400 Subject: [PATCH 2/4] Fix bug breaking 'Repeat Entire Event' --- ExternalModule.php | 55 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/ExternalModule.php b/ExternalModule.php index 5e6f95c..6ec485a 100644 --- a/ExternalModule.php +++ b/ExternalModule.php @@ -19,6 +19,34 @@ class ExternalModule extends AbstractExternalModule { private $survey_APF_fields = []; + /** + * Returns true if the event is either a "Repeat Entire Event" or "Repeat Instrument" repeating event. + */ + function isRepeatEvent() { + $val = ($this->isRepeatEntireEvent() || $this->isRepeatInstrument()); + return $val; + } + + /** + * Returns true if the event is a "Repeat Entire Event (repeat all instruments together)". + */ + function isRepeatEntireEvent() { + if ($_GET['instance'] > 1 && isset($_GET['oldinstance'])) { + return true; + } + return false; + } + + /** + * Returns true if the event is a "Repeat Instrument (repeat independently of each other)". + */ + function isRepeatInstrument() { + if ($_GET['instance'] > 1 && !isset($_GET['oldinstance'])) { + return true; + } + return false; + } + /** * @inheritdoc */ @@ -145,6 +173,7 @@ function setDefaultValues() { $default_value = ''; + $form_name = ""; // Looping over @DEFAULT_ and @DEFAULT-FROM-PREVIOUS-EVENT_ // action tags. foreach ($action_tags as $action_tag) { @@ -163,16 +192,28 @@ function setDefaultValues() { $source_form = $Proj->metadata[$source_field]['form_name']; // Getting previous event ID. + // For non repeating events the previous event is the closest event prior to the current event + // For repeating events, (Repeat Entire Event & Repeat Instrument) the previous event is the current event foreach ($events as $event) { - // Event instances share the same event id for every instance. - // To support event instances we only break on the first instance, and allow execution for all future instances i.e., $_GET['instance'] greater than 1. - // Event instances are enabled under: Project Setup > Enable optional modules and customizations > Repeatable instruments and events > 'Repeat instruments (repeat independently of each other)` - if ($event == $_GET['event_id'] && $_GET['instance'] == 1) { + // Only break for non repeating events. + // Non repeating events should not get data from current event, whereas, repeating events with instances depend on data from the current event i.e., $_GET['event_id'] + if ($event == $_GET['event_id'] && !$this->isRepeatEvent()) { break; } - if (in_array($source_form, $Proj->eventsForms[$event])) { + if (in_array($source_form, $Proj->eventsForms[$event]) && !$this->isRepeatEvent()) { $prev_event = $event; + $form_name = ""; + } elseif (in_array($source_form, $Proj->eventsForms[$event]) && $this->isRepeatEntireEvent()) { + $prev_event = $_GET['event_id']; + $form_name = ""; + break; + } elseif (in_array($source_form, $Proj->eventsForms[$event]) && $this->isRepeatInstrument()) { + // Repeat instruments behave differently from 'Repeat Entire Event' and non repeating events. + // Repeat instruments require the $Proj->metadata[$field_name]['form_name'] when looking up the data from the event + $prev_event = $_GET['event_id']; + $form_name = $source_form; + break; } } @@ -186,9 +227,9 @@ function setDefaultValues() { $prev_event_field_value = $data[$prev_event][$source_field]; if (isset($prev_event_field_value) && !empty($prev_event_field_value)) { $default_value = $prev_event_field_value; - } elseif ($data['repeat_instances'][$prev_event][$source_form]) { + } elseif (isset($data['repeat_instances'][$prev_event][$form_name])) { // Handling repeat events by using the most recent instance of the previous event to source values - $most_recent_instance = array_slice($data['repeat_instances'][$prev_event][$source_form], -1)[0]; + $most_recent_instance = array_slice($data['repeat_instances'][$prev_event][$form_name], -1)[0]; $default_value = $most_recent_instance[$source_field]; } From ba01745b22c9c5bf19272f81f051ae2c2c792bf9 Mon Sep 17 00:00:00 2001 From: mbentz Date: Mon, 26 Jul 2021 16:22:13 -0400 Subject: [PATCH 3/4] Fix edge case for the first instance of repeating events --- ExternalModule.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ExternalModule.php b/ExternalModule.php index 6ec485a..3064574 100644 --- a/ExternalModule.php +++ b/ExternalModule.php @@ -28,10 +28,13 @@ function isRepeatEvent() { } /** - * Returns true if the event is a "Repeat Entire Event (repeat all instruments together)". + * Returns true if the event is a "Repeat Entire Event (repeat all instruments together)" and is not first instance. */ function isRepeatEntireEvent() { - if ($_GET['instance'] > 1 && isset($_GET['oldinstance'])) { + // The first instance of Repeating events are treated differently from events where $_GET['instance'] > 1. + // For the first instance, we look at previous event. For $_GET['instance'] > 1, we look at the current event. + // Because $GLOBALS['isRepeatingEvent'] is true regardless of the instance of the event, we also check for the current instance. + if ($_GET['instance'] > 1 && $GLOBALS['isRepeatingEvent']) { return true; } return false; @@ -41,10 +44,7 @@ function isRepeatEntireEvent() { * Returns true if the event is a "Repeat Instrument (repeat independently of each other)". */ function isRepeatInstrument() { - if ($_GET['instance'] > 1 && !isset($_GET['oldinstance'])) { - return true; - } - return false; + return $GLOBALS['isRepeatingForm']; } /** From 066188514fc74ea6a57f0336637d008fc6a96b57 Mon Sep 17 00:00:00 2001 From: mbentz Date: Wed, 11 Aug 2021 12:27:35 -0400 Subject: [PATCH 4/4] Simplify logic and fix auto populate bug on form deletion --- ExternalModule.php | 84 +++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/ExternalModule.php b/ExternalModule.php index 3064574..6aeb7bf 100644 --- a/ExternalModule.php +++ b/ExternalModule.php @@ -19,21 +19,30 @@ class ExternalModule extends AbstractExternalModule { private $survey_APF_fields = []; + function isFirstRepeatEventOrForm() { + $val = ($this->isFirstRepeatEvent() || $this->isFirstRepeatForm()); + return $val; + } + + function isNthRepeatEventOrForm() { + $val = ($this->isNthRepeatEvent() || $this->isNthRepeatForm()); + return $val; + } + /** - * Returns true if the event is either a "Repeat Entire Event" or "Repeat Instrument" repeating event. + * Returns true if the event is a "Repeat Entire Event (repeat all instruments together)" and is the first instance. */ - function isRepeatEvent() { - $val = ($this->isRepeatEntireEvent() || $this->isRepeatInstrument()); - return $val; + function isFirstRepeatEvent() { + if ($_GET['instance'] == 1 && $GLOBALS['isRepeatingEvent']) { + return true; + } + return false; } /** - * Returns true if the event is a "Repeat Entire Event (repeat all instruments together)" and is not first instance. + * Returns true if the event is a "Repeat Entire Event (repeat all instruments together)" and is NOT first instance. */ - function isRepeatEntireEvent() { - // The first instance of Repeating events are treated differently from events where $_GET['instance'] > 1. - // For the first instance, we look at previous event. For $_GET['instance'] > 1, we look at the current event. - // Because $GLOBALS['isRepeatingEvent'] is true regardless of the instance of the event, we also check for the current instance. + function isNthRepeatEvent() { if ($_GET['instance'] > 1 && $GLOBALS['isRepeatingEvent']) { return true; } @@ -41,10 +50,17 @@ function isRepeatEntireEvent() { } /** - * Returns true if the event is a "Repeat Instrument (repeat independently of each other)". + * Returns true if the event is a "Repeat Instrument (repeat independently of each other)" and is the first instance. */ - function isRepeatInstrument() { - return $GLOBALS['isRepeatingForm']; + function isFirstRepeatForm() { + return ($_GET['instance'] == 1 && $GLOBALS['isRepeatingForm']); + } + + /** + * Returns true if the event is a "Repeat Instrument (repeat independently of each other)" and is NOT the first instance. + */ + function isNthRepeatForm() { + return ($_GET['instance'] > 1 && $GLOBALS['isRepeatingForm']); } /** @@ -192,29 +208,27 @@ function setDefaultValues() { $source_form = $Proj->metadata[$source_field]['form_name']; // Getting previous event ID. - // For non repeating events the previous event is the closest event prior to the current event - // For repeating events, (Repeat Entire Event & Repeat Instrument) the previous event is the current event + // For the first instance of a repeating event/form, the previous event is the closest saved event/form prior to the current event/form. + // For the nth instance of a repeating event/form, the previous event is the current event. foreach ($events as $event) { - // Only break for non repeating events. - // Non repeating events should not get data from current event, whereas, repeating events with instances depend on data from the current event i.e., $_GET['event_id'] - if ($event == $_GET['event_id'] && !$this->isRepeatEvent()) { + if (!in_array($source_form, $Proj->eventsForms[$event])) { break; } - if (in_array($source_form, $Proj->eventsForms[$event]) && !$this->isRepeatEvent()) { - $prev_event = $event; - $form_name = ""; - } elseif (in_array($source_form, $Proj->eventsForms[$event]) && $this->isRepeatEntireEvent()) { - $prev_event = $_GET['event_id']; - $form_name = ""; + // Only break for the first instance of an event/form. + // First instance events or forms need data from previous events/forms, whereas, nth repeating events/forms depend on data from the current event i.e., $_GET['event_id'] + if ($event == $_GET['event_id'] && $this->isFirstRepeatEventOrForm()) { break; - } elseif (in_array($source_form, $Proj->eventsForms[$event]) && $this->isRepeatInstrument()) { - // Repeat instruments behave differently from 'Repeat Entire Event' and non repeating events. - // Repeat instruments require the $Proj->metadata[$field_name]['form_name'] when looking up the data from the event + } + + // Set the previous event for first instance of a repeat event/form to the previous event. + // Set the previous event for the nth instance of a repeat event/form to the current event. + if ($this->isFirstRepeatEventOrForm()) { + $prev_event = $event; + } elseif ($this->isNthRepeatEventOrForm()) { $prev_event = $_GET['event_id']; - $form_name = $source_form; break; - } + } } if (!$prev_event) { @@ -225,12 +239,20 @@ function setDefaultValues() { // isset returns true for event instances when $prev_event_field_value is equal to an empty string (""). // An additional check to verify the value is not empty is required. $prev_event_field_value = $data[$prev_event][$source_field]; + + // The object returned by $instances = $data['repeat_instances'][$event] changes dependening on if the data is from an event or a form. + // For repeating events, the key for $instances is an empty string ("") i.e., ["": ...] + // For repeating forms, the key for $instances is the form name i.e., ["baseline_data": ...] + $instances = ($data['repeat_instances'][$prev_event][$source_form]) ?? ($data['repeat_instances'][$prev_event][""]); + if (isset($prev_event_field_value) && !empty($prev_event_field_value)) { $default_value = $prev_event_field_value; - } elseif (isset($data['repeat_instances'][$prev_event][$form_name])) { + } elseif (isset($instances)) { // Handling repeat events by using the most recent instance of the previous event to source values - $most_recent_instance = array_slice($data['repeat_instances'][$prev_event][$form_name], -1)[0]; - $default_value = $most_recent_instance[$source_field]; + // $instances are out of order when form data is deleted. + // In that case, using array_slice($instances, -1)[0] is unreliable and can return the wrong instance. + $max = max(array_keys($instances)); + $default_value = $instances[$max][$source_field]; } // Handling checkboxes case.