From 4814b0be6b7ed32bc81043b38206579822c9008b Mon Sep 17 00:00:00 2001 From: "Scott J. Pearson" Date: Fri, 10 Jul 2020 15:17:49 -0500 Subject: [PATCH] 2.15.0 --- Application.php | 4 + CareerDev.php | 15 +- FlightTrackerExternalModule.php | 384 +++++++++++++++++++++++++++++--- add.php | 26 ++- addNewScholar.php | 3 +- config.php | 24 +- cronLoad.php | 12 +- drivers/2n_updateReporters.php | 4 +- hooks/saveHook.php | 97 +------- index.php | 2 +- install.php | 2 + metadata.json | 5 +- newFaculty.php | 2 +- wrangler/index.php | 9 +- wrangler/pubs.php | 5 +- 15 files changed, 446 insertions(+), 148 deletions(-) diff --git a/Application.php b/Application.php index f4f58c73..eaae4930 100644 --- a/Application.php +++ b/Application.php @@ -15,6 +15,10 @@ public static function getGrantClasses() { return CareerDev::getGrantClasses(); } + public static function refreshRecordSummary($token, $server, $pid, $recordId) { + return CareerDev::refreshRecordSummary($token, $server, $pid, $recordId); + } + public static function getProgramName() { return CareerDev::getProgramName(); } diff --git a/CareerDev.php b/CareerDev.php index 3f56bcbd..e6fe9bb8 100644 --- a/CareerDev.php +++ b/CareerDev.php @@ -8,14 +8,25 @@ class CareerDev { public static $passedModule = NULL; public static function getVersion() { - return "2.14.8"; + return "2.15.0"; } public static function getLockFile($pid) { return APP_PATH_TEMP.date("Ymdhis")."_6_makeSummary.$pid.lock"; } - public static function getUnknown() { + public static function refreshRecordSummary($token, $server, $pid, $recordId) { + if (self::getSetting("auto_recalculate", $pid)) { + require_once(dirname(__FILE__) . "/drivers/6d_makeSummary.php"); + try { + makeSummary($token, $server, $pid, $recordId); + } catch (\Exception $e) { + echo "
" . $e->getMessage() . "
\n"; + } + } + } + + public static function getUnknown() { return "Unknown"; } diff --git a/FlightTrackerExternalModule.php b/FlightTrackerExternalModule.php index 1e4f5e06..e61b9e91 100755 --- a/FlightTrackerExternalModule.php +++ b/FlightTrackerExternalModule.php @@ -4,16 +4,22 @@ use ExternalModules\AbstractExternalModule; use ExternalModules\ExternalModules; +use Vanderbilt\CareerDevLibrary\Application; use Vanderbilt\CareerDevLibrary\CronManager; use Vanderbilt\CareerDevLibrary\EmailManager; use Vanderbilt\CareerDevLibrary\NavigationBar; use Vanderbilt\CareerDevLibrary\REDCapManagement; +use Vanderbilt\CareerDevLibrary\Download; +use Vanderbilt\CareerDevLibrary\NameMatcher; +use Vanderbilt\CareerDevLibrary\Upload; require_once(dirname(__FILE__)."/CareerDev.php"); require_once(dirname(__FILE__)."/classes/Crons.php"); require_once(dirname(__FILE__)."/classes/EmailManager.php"); require_once(dirname(__FILE__)."/classes/NavigationBar.php"); require_once(dirname(__FILE__)."/classes/REDCapManagement.php"); +require_once(dirname(__FILE__)."/classes/Download.php"); +require_once(dirname(__FILE__)."/classes/NameMatcher.php"); require_once(dirname(__FILE__)."/cronLoad.php"); class FlightTrackerExternalModule extends AbstractExternalModule @@ -54,49 +60,367 @@ function emails() { } } + # returns a boolean; modifies $normativeRow + private static function copyDataFromRowToNormative($sourceRow, $completeValue, $prefix, $metadataFields, $choices, &$normativeRow, $instrument) { + $hasChanged = self::copyDataFromRowToRow($sourceRow, $prefix, $metadataFields, $choices, "", $normativeRow); + if ($hasChanged) { + $normativeRow[$instrument."_complete"] = $completeValue; + } + return $hasChanged; + } + + # returns a boolean; modifies $destRow + private static function copyDataFromRowToRow($sourceRow, $prefix, $metadataFields, $choices, $instrument, &$destRow) { + $hasChanged = FALSE; + if ($sourceRow["redcap_repeat_instrument"] == $instrument) { + foreach ($sourceRow as $sourceField => $sourceValue) { + if (preg_match("/^$prefix/", $sourceField) + && in_array($sourceField, $metadataFields) + && REDCapManagement::isValidChoice($sourceValue, $choices[$sourceField])) { + $destRow[$sourceField] = $sourceValue; + $hasChanged = TRUE; + } + } + } + return $hasChanged; + } + + # returns a row if data can be copied; otherwise returns FALSE + private static function copyDataFromRowToNewRow($sourceRow, $completeValue, $prefix, $metadataFields, $choices, $recordId, $instrument, $newInstance) { + $newRow = [ + "record_id" => $recordId, + "redcap_repeat_instrument" => $instrument, + "redcap_repeat_instance" => $newInstance, + $instrument."_complete" => $completeValue, + ]; + $hasChanged = self::copyDataFromRowToRow($sourceRow, $prefix, $metadataFields, $choices, $instrument, $newRow); + if ($hasChanged) { + return $newRow; + } + return $hasChanged; + } + + private static function allFieldsMatch($fields, $row1, $row2, $choices1, $choices2) { + $allMatch = TRUE; + foreach ($fields as $testField) { + if ($choices1[$testField] && $choices2[$testField]) { + $value1 = $choices1[$testField][$row1[$testField]]; + $value2 = $choices2[$testField][$row2[$testField]]; + } else { + $value1 = $row1[$testField]; + $value2 = $row2[$testField]; + } + if ($value1 != $value2) { + $allMatch = FALSE; + } + } + return $allMatch; + } + + private static function fieldBlank($field, $row) { + if (!isset($row[$field])) { + return TRUE; + } + if ($row[$field] === "") { + return TRUE; + } + return FALSE; + } + public function cleanupLogs($pid) { CareerDev::log("Cleaning up logs for $pid"); $daysPrior = 28; - $this->cleanupExtModLogs($pid, $daysPrior); } public function cleanupExtModLogs($pid, $daysPrior) { - $ts = time() - $daysPrior * 24 * 3600; - $thresholdTs = date("Y-m-d", $ts); - $this->removeLogs("timestamp <= '$thresholdTs' AND project_id = '$pid'"); + $ts = time() - $daysPrior * 24 * 3600; + $thresholdTs = date("Y-m-d", $ts); + $this->removeLogs("timestamp <= '$thresholdTs' AND project_id = '$pid'"); + } + + private static function isValidToCopy($fields, $sourceRow, $destRow, $sourceChoices, $destChoices) { + if ((count($fields) == 1) && self::fieldBlank($fields[0], $sourceRow)) { + # one blank field => not valid enough to copy + // Application::log("isValidToCopy Rejecting because one field blank: ".json_encode($fields)); + return FALSE; + } else if (self::allFieldsMatch($fields, $sourceRow, $destRow, $sourceChoices, $destChoices)) { + # already copied => skip + // Application::log("isValidToCopy Rejecting because all fields match"); + return FALSE;; + } + // Application::log("isValidToCopy returning TRUE"); + return TRUE; + } + + private static function getSharingInformation() { + return [ + "initial_survey" => ["prefix" => "check", "formType" => "single", "test_fields" => ["check_date"], "always_copy" => TRUE, ], + "followup" => ["prefix" => "followup", "formType" => "repeating", "test_fields" => ["followup_date"], "always_copy" => TRUE, ], + "position_change" => [ "prefix" => "promotion", "formType" => "repeating", "test_fields" => ["promotion_job_title", "promotion_date"], "always_copy" => FALSE, ], + "resources" => [ "prefix" => "resources", "formType" => "repeating", "test_fields" => ["resources_date", "resources_resource"], "always_copy" => FALSE, ], + "honors_and_awards" => [ "prefix" => "honor", "formType" => "repeating", "test_fields" => ["honor_name", "honor_date"], "always_copy" => FALSE, ], + ]; } - public function cron() { + public static function getConfigurableForms() { + $forms = self::getSharingInformation(); + $formsForCopy = []; + foreach ($forms as $instrument => $config) { + if ($config["always_copy"]) { + $formsForCopy[] = $instrument; + } + } + $instrumentsWithLabels = []; + foreach ($formsForCopy as $instrument) { + $label = preg_replace("/_/", " ", $instrument); + $label = ucwords($label); + $instrumentsWithLabels[$instrument] = $label; + } + return $instrumentsWithLabels; + } + + private function shareDataInternally($pids) { + $markedAsComplete = [2]; + # all test_fields must be equal and, if by self (i.e., list of one), non-blank + $forms = self::getSharingInformation(); + $firstNames = []; + $lastNames = []; + $completes = []; + $servers = []; + $tokens = []; + $metadataFields = []; + $choices = []; + $pidsUpdated = []; + + foreach ($pids as $pid) { + $token = $this->getProjectSetting("token", $pid); + $server = $this->getProjectSetting("server", $pid); + $tokens[$pid] = $token; + $servers[$pid] = $server; + } + $credentialsFile = "/app001/credentials/career_dev/credentials.php"; + if (preg_match("/redcap.vanderbilt.edu/", SERVER_NAME) && file_exists($credentialsFile)) { + require_once($credentialsFile); + if (isset($info)) { + $prodPid = $info["prod"]["pid"]; + $prodToken = $info["prod"]["token"]; + $prodServer = $info["prod"]["server"]; + $pids[] = $prodPid; + $tokens[$prodPid] = $prodToken; + $servers[$prodServer] = $prodServer; + Application::log("Searching through Vanderbilt Master Project ($prodPid)"); + } + } + + foreach ($pids as $pid) { + $token = $tokens[$pid]; + $server = $servers[$pid]; + $firstNames[$pid] = Download::firstnames($token, $server); + $lastNames[$pid] = Download::lastnames($token, $server); + $metadata = Download::metadata($token, $server); + $choices[$pid] = REDCapManagement::getChoices($metadata); + $metadataFields[$pid] = REDCapManagement::getFieldsFromMetadata($metadata); + foreach (array_keys($forms) as $instrument) { + $field = $instrument."_complete"; + if (!isset($completes[$instrument])) { + $completes[$instrument] = []; + } + $completes[$instrument][$pid] = Download::oneField($token, $server, $field); + } + } + + # push + foreach ($pids as $sourcePid) { + $sourceToken = $tokens[$sourcePid]; + $sourceServer = $servers[$sourcePid]; + $sharedFormsForSource = $this->getProjectSetting("shared_forms", $sourcePid); + if (!$sharedFormsForSource) { + $sharedFormsForSource = []; + } + foreach ($pids as $destPid) { + if ($destPid != $sourcePid) { + $destToken = $tokens[$destPid]; + $destServer = $servers[$destPid]; + $sharedFormsForDest = $this->getProjectSetting("shared_forms", $destPid); + if (!$sharedFormsForDest) { + $sharedFormsForDest = []; + } + foreach (array_keys($firstNames[$destPid]) as $destRecordId) { + $combos = []; + foreach (NameMatcher::splitName($firstNames[$destPid][$destRecordId]) as $firstName) { + foreach (NameMatcher::splitName($lastNames[$destPid][$destRecordId]) as $lastName) { + if ($firstName && $lastName) { + $combos[] = ["first" => $firstName, "last" => $lastName]; + } + } + } + foreach ($combos as $nameAry) { + $firstName = $nameAry["first"]; + $lastName = $nameAry["last"]; + CareerDev::log("Searching for $firstName $lastName from $destPid in $sourcePid"); + if ($sourceRecordId = NameMatcher::matchName($firstName, $lastName, $sourceToken, $sourceServer)) { + CareerDev::log("Match in above"); + $normativeRow = [ + "record_id" => $destRecordId, + "redcap_repeat_instrument" => "", + "redcap_repeat_instance" => "", + ]; + foreach (array_keys($forms) as $instrument) { + $field = $instrument . "_complete"; + $completes[$instrument][$destPid] = Download::oneField($destToken, $destServer, $field); + $completes[$instrument][$sourcePid] = Download::oneField($sourceToken, $sourceServer, $field); + } + $uploadNormativeRow = FALSE; + $repeatingRows = []; + $sourceData = []; + $destData = []; + foreach ($completes as $instrument => $completeData) { + if (!in_array($completeData[$destPid][$destRecordId], $markedAsComplete) + && in_array($completeData[$sourcePid][$sourceRecordId], $markedAsComplete)) { + # copy over from source to dest and mark as same as $projectData[$sourceRecordId] + // CareerDev::log("Matched complete for $instrument in dest $destPid $destRecordId ".$completeData[$destPid][$destRecordId]." and source $sourcePid $sourceRecordId ".$completeData[$sourcePid][$sourceRecordId]); + if (empty($sourceData) || empty($destData)) { + $sourceData = Download::records($sourceToken, $sourceServer, array($sourceRecordId)); + $destData = Download::records($destToken, $destServer, array($destRecordId)); + } + $config = $forms[$instrument]; + $newInstance = REDCapManagement::getMaxInstance($destData, $instrument, $destRecordId) + 1; + foreach ($sourceData as $sourceRow) { + $continueToCopyFromSource = TRUE; + foreach ($destData as $destRow) { + if ($config["formType"] == "single") { + if ($destRow["redcap_repeat_instrument"] == "") { + if (!self::isValidToCopy($config["test_fields"], $sourceRow, $destRow, $choices[$sourcePid], $choices[$destPid])) { + // CareerDev::log("Not valid to copy single for $instrument in dest $destPid $destRecordId ".$completeData[$destPid][$destRecordId]." and source $sourcePid $sourceRecordId ".$completeData[$sourcePid][$sourceRecordId]); + $continueToCopyFromSource = FALSE; + } + } + } else if ($config["formType"] == "repeating") { + if ($destRow["redcap_repeat_instrument"] == $instrument) { + if (!self::isValidToCopy($config["test_fields"], $sourceRow, $destRow, $choices[$sourcePid], $choices[$destPid])) { + // CareerDev::log("Not valid to repeating single for $instrument in dest $destPid $destRecordId ".$completeData[$destPid][$destRecordId]." and source $sourcePid $sourceRecordId ".$completeData[$sourcePid][$sourceRecordId]); + $continueToCopyFromSource = FALSE; + } + } + } + } + if ($continueToCopyFromSource + && ($config["always_copy"] + || (in_array($instrument, $sharedFormsForDest) && in_array($instrument, $sharedFormsForSource)))) { + if ($config["formType"] == "single") { + if ($sourceRow["redcap_repeat_instrument"] == "") { + // CareerDev::log("copyDataFromRowToNormative for $instrument in dest $destPid $destRecordId ".$completeData[$destPid][$destRecordId]." and source $sourcePid $sourceRecordId ".$completeData[$sourcePid][$sourceRecordId]); + $hasChanged = self::copyDataFromRowToNormative($sourceRow, + $completeData[$sourcePid][$sourceRecordId], + $config["prefix"], + $metadataFields[$destPid], + $choices[$destPid], + $normativeRow, + $instrument); + if ($hasChanged) { + // CareerDev::log("uploadNormativeRow for $instrument in dest $destPid $destRecordId ".$completeData[$destPid][$destRecordId]." and source $sourcePid $sourceRecordId ".$completeData[$sourcePid][$sourceRecordId]); + $uploadNormativeRow = TRUE; + } + } + } else if ($config["formType"] == "repeating") { + if ($sourceRow["redcap_repeat_instrument"] == $instrument) { + // CareerDev::log("copyDataFromRowToNewRow for $instrument in dest $destPid $destRecordId ".$completeData[$destPid][$destRecordId]." and source $sourcePid $sourceRecordId ".$completeData[$sourcePid][$sourceRecordId]); + $repeatingRow = self::copyDataFromRowToNewRow($sourceRow, + $completeData[$sourcePid][$sourceRecordId], + $config["prefix"], + $metadataFields[$destPid], + $choices[$destPid], + $destRecordId, + $instrument, + $newInstance); + if ($repeatingRow && is_array($repeatingRow)) { + CareerDev::log("add repeatingRow for $instrument in dest $destPid $destRecordId ".$completeData[$destPid][$destRecordId]." and source $sourcePid $sourceRecordId ".$completeData[$sourcePid][$sourceRecordId]); + $repeatingRows[] = $repeatingRow; + $newInstance++; + } + } + } + } + } + $upload = []; + if ($uploadNormativeRow) { + $upload[] = $normativeRow; + } + $upload = array_merge($upload, $repeatingRows); + if (!empty($upload)) { + // Application::log("Uploading for $instrument in dest $destPid $destRecordId ".$completeData[$destPid][$destRecordId]." and source $sourcePid $sourceRecordId ".$completeData[$sourcePid][$sourceRecordId]); + try { + $feedback = Upload::rows($upload, $destToken, $destServer); + Application::log("$destPid: Uploaded ".count($upload)." rows for record $destRecordId from pid $sourcePid record $sourceRecordId"); + // Application::log(json_encode($upload)); + if (!in_array($destPid, $pidsUpdated)) { + $pidsUpdated[] = $destPid; + } + } catch (\Exception $e) { + Application::log("ERROR: Could not copy from $sourcePid record $sourceRecordId, into $destPid record $destRecordId"); + Application::log($e->getMessage()); + } + } else { + // CareerDev::log("Skipping uploading for $instrument in dest $destPid $destRecordId ".$completeData[$destPid][$destRecordId]." and source $sourcePid $sourceRecordId ".$completeData[$sourcePid][$sourceRecordId]); + } + } else { + // CareerDev::log("Could not match complete for $instrument in dest $destPid $destRecordId ".$completeData[$destPid][$destRecordId]." and source $sourcePid $sourceRecordId ".$completeData[$sourcePid][$sourceRecordId]); + } + } + } + break; // combos foreach + # if more than one match, match only first name matched + } + } + } + } + } + return $pidsUpdated; + } + + function cron() { $this->setupApplication(); $pids = $this->framework->getProjectsWithModuleEnabled(); CareerDev::log($this->getName()." running for pids ".json_encode($pids)); - foreach ($pids as $pid) { - $this->cleanupLogs($pid); - if (REDCapManagement::isActiveProject($pid)) { - $token = $this->getProjectSetting("token", $pid); - $server = $this->getProjectSetting("server", $pid); - $tokenName = $this->getProjectSetting("tokenName", $pid); - $adminEmail = $this->getProjectSetting("admin_email", $pid); - $GLOBALS['namesForMatch'] = array(); - CareerDev::setPid($pid); - CareerDev::log("Using $tokenName $adminEmail", $pid); - if ($token && $server) { - # only have token and server in initialized projects - $mgr = new CronManager($token, $server, $pid); - if ($this->getProjectSetting("run_tonight", $pid)) { - $this->setProjectSetting("run_tonight", FALSE, $pid); - loadInitialCrons($mgr, FALSE, $token, $server); - } else { - echo $this->getName().": Loading crons for pid $pid and '$token'\n"; - loadCrons($mgr, FALSE, $token, $server); - } - CareerDev::log($this->getName().": Running crons for pid $pid", $pid); - $mgr->run($adminEmail, $tokenName); - CareerDev::log($this->getName().": cron run complete for pid $pid", $pid); - } - # else project has not finished initialization + $activePids = REDCapManagement::getActiveProjects($pids); + $pidsUpdated = []; + CareerDev::log("Checking for redcaptest in ".SERVER_NAME); + if (preg_match("/redcaptest.vanderbilt.edu/", SERVER_NAME)) { + CareerDev::log("Sharing because redcaptest"); + try { + $pidsUpdated = $this->shareDataInternally($activePids); + } catch (\Exception $e) { + \REDCap::email("scott.j.pearson@vumc.org", "noreply@vumc.org", "Error in sharing surveys on redcaptest", $e->getMessage()); + } + } else if (date("N") == "6") { + # only on Saturdays + $pidsUpdated = $this->shareDataInternally($activePids); + } + foreach ($activePids as $pid) { + $this->cleanupLogs($pid); + $token = $this->getProjectSetting("token", $pid); + $server = $this->getProjectSetting("server", $pid); + $tokenName = $this->getProjectSetting("tokenName", $pid); + $adminEmail = $this->getProjectSetting("admin_email", $pid); + $GLOBALS['namesForMatch'] = array(); + CareerDev::setPid($pid); + CareerDev::log("Using $tokenName $adminEmail", $pid); + if ($token && $server) { + # only have token and server in initialized projects + $mgr = new CronManager($token, $server, $pid); + if ($this->getProjectSetting("run_tonight", $pid)) { + $this->setProjectSetting("run_tonight", FALSE, $pid); + loadInitialCrons($mgr, FALSE, $token, $server); + } else { + CareerDev::log($this->getName().": Loading crons for pid $pid with token of ".strlen($token)." characters"); + loadCrons($mgr, FALSE, $token, $server); + } + CareerDev::log($this->getName().": Running crons for pid $pid", $pid); + $addlEmailText = in_array($pid, $pidsUpdated) ? "Surveys shared from other Flight Tracker projects" : ""; + $mgr->run($adminEmail, $tokenName, $addlEmailText); + CareerDev::log($this->getName().": cron run complete for pid $pid", $pid); } } } diff --git a/add.php b/add.php index 96e957aa..8abe259f 100644 --- a/add.php +++ b/add.php @@ -57,11 +57,14 @@ } } } - list($upload, $emails) = processLines($lines, $recordId, $token, $server); + list($upload, $emails, $newRecordIds) = processLines($lines, $recordId, $token, $server); $feedback = array(); if (!empty($upload)) { $feedback = Upload::rows($upload, $token, $server); - } else { + foreach ($newRecordIds as $recordId) { + Application::refreshRecordSummary($token, $server, $pid, $recordId); + } + } else { $mssg = "No data specified."; header("Location: ".CareerDev::link("add.php")."&mssg=".urlencode($mssg)); } @@ -129,6 +132,7 @@ function processLines($lines, $nextRecordId, $token, $server) { $lineNum = 1; $metadata = Download::metadata($token, $server); $metadataFields = REDCapManagement::getFieldsFromMetadata($metadata); + $recordIds = []; foreach ($lines as $nodes) { if (count($nodes) >= 6) { $firstName = $nodes[0]; @@ -236,9 +240,19 @@ function processLines($lines, $nextRecordId, $token, $server) { $mentor = ""; } if ($nodes[14]) { - $trainingStart = importMDY2YMD($nodes[14], "Start of Training"); + if (REDCapManagement::isDate($nodes[14])) { + $trainingStart = importMDY2YMD($nodes[14], "Start of Training"); + $orcid = ""; + } else { + $trainingStart = ""; + $orcid = $nodes[14]; + } } else { $trainingStart = ""; + $orcid = ""; + } + if ($nodes[15]) { + $trainingStart = importMDY2YMD($nodes[15], "Start of Training"); } $uploadRow["imported_dob"] = $dob; @@ -251,13 +265,17 @@ function processLines($lines, $nextRecordId, $token, $server) { $uploadRow["imported_mentor"] = $mentor; if (in_array("identifier_start_of_training", $metadataFields)) { $uploadRow["identifier_start_of_training"] = $trainingStart; + } + if ($orcid && in_array("identifier_orcid", $metadataFields)) { + $uploadRow["identifier_orcid"] = $orcid; } } $upload[] = $uploadRow; + $recordIds[] = $uploadRow["record_id"]; } $lineNum++; } - return array($upload, $emails); + return array($upload, $emails, $recordIds); } function importMDY2YMD($mdyDate, $col) { diff --git a/addNewScholar.php b/addNewScholar.php index e39b3203..e4f5cca2 100644 --- a/addNewScholar.php +++ b/addNewScholar.php @@ -32,7 +32,8 @@ } $feedback = Upload::oneRow($uploadRow, $token, $server); \Vanderbilt\FlightTrackerExternalModule\queueUpInitialEmail($recordId); - if ($feedback['error']) { + Application::refreshRecordSummary($token, $server, $pid, $recordId); + if ($feedback['error']) { echo "
ERROR! ".$feedback['error']."
\n"; } else { echo "
Scholar successfully added to Record $recordId. They will be automatically processed and updated with each overnight run.
\n"; diff --git a/config.php b/config.php index 00abed89..435a25ce 100644 --- a/config.php +++ b/config.php @@ -5,6 +5,7 @@ use \Vanderbilt\CareerDevLibrary\Upload; use \Vanderbilt\CareerDevLibrary\Scholar; use \Vanderbilt\CareerDevLibrary\REDCapManagement; +use \Vanderbilt\FlightTrackerExternalModule\FlightTrackerExternalModule; require_once(dirname(__FILE__)."/CareerDev.php"); require_once(dirname(__FILE__)."/classes/Download.php"); @@ -325,7 +326,9 @@ function makeSettings($module) { array_push($ary["Installation Variables"], makeSetting("grant_number", "text", "Grant Number")); array_push($ary["Installation Variables"], makeSetting("departments", "textarea", "Department Names")); array_push($ary["Installation Variables"], makeSetting("resources", "textarea", "Resources")); - array_push($ary["Installation Variables"], makeSetting("send_error_logs", "yesno", "Report Fatal Errors to Development Team?")); + array_push($ary["Installation Variables"], makeSetting("send_error_logs", "yesno", "Report Fatal Errors to Development Team?")); + array_push($ary["Installation Variables"], makeCheckboxes("shared_forms", FlightTrackerExternalModule::getConfigurableForms(), "Report Fatal Errors to Development Team?")); + array_push($ary["Installation Variables"], makeSetting("auto_recalculate", "yesno", "Automatically Re-summarize After Data Saves? (No waits until overnight.)", 0)); $ary["Automated Emails"] = array(); array_push($ary["Automated Emails"], makeHelperText("An initial email can automatically be sent out during the first month after the new record is added to the database. If you desire to use this feature, please complete the following fields.")); @@ -361,6 +364,25 @@ function makeHelperText($str) { return "".$str.""; } +function makeCheckboxes($var, $fieldChoices, $label, $defaultChecked = []) { + $sharedForms = CareerDev::getSetting($var); + if (!$sharedForms) { + $sharedForms = $defaultChecked; + } + $html = ""; + $html .= "\n"; + foreach ($fieldChoices as $idx => $fieldLabel) { + if (in_array($idx, $sharedForms)) { + $selected = " checked"; + } else { + $selected = ""; + } + $html .= "\n"; + } + $html .= "\n"; + return $html; +} + function makeSetting($var, $type, $label, $default = "", $fieldChoices = array()) { $value = CareerDev::getSetting($var); $html = ""; diff --git a/cronLoad.php b/cronLoad.php index 62d558ea..0a1d1a68 100644 --- a/cronLoad.php +++ b/cronLoad.php @@ -87,16 +87,20 @@ function checkMetadataForFields($token, $server) { $vars = array(); $vars['coeus'] = FALSE; $vars['vfrs'] = FALSE; - $vars['news'] = FALSE; + $vars['news'] = FALSE; + $vars['ldap'] = FALSE; foreach ($metadata as $row) { $field = $row['field_name']; if (preg_match("/^coeus_/", $field)) { $vars['coeus'] = TRUE; } - if (preg_match("/^vfrs_/", $field)) { - $vars['vfrs'] = TRUE; - } + if (preg_match("/^vfrs_/", $field)) { + $vars['vfrs'] = TRUE; + } + if (preg_match("/^ldap_/", $field)) { + $vars['ldap'] = TRUE; + } if ($field == "summary_news") { $vars['news'] = TRUE; } diff --git a/drivers/2n_updateReporters.php b/drivers/2n_updateReporters.php index e457f9c3..420eb4ea 100644 --- a/drivers/2n_updateReporters.php +++ b/drivers/2n_updateReporters.php @@ -98,8 +98,8 @@ function updateReporter($token, $server, $pid) { $listOfNames[] = $fn." ".$ln; } } - } - if ($firstName && $lastName && !in_array($firstName." ".$lastName, $listOfNames)) { + } + if ($firstName && $lastName && !in_array($firstName." ".$lastName, $listOfNames)) { $listOfNames[] = strtoupper($firstName." ".$lastName); } diff --git a/hooks/saveHook.php b/hooks/saveHook.php index da92fc60..0f86cf01 100644 --- a/hooks/saveHook.php +++ b/hooks/saveHook.php @@ -14,6 +14,8 @@ require_once(dirname(__FILE__)."/../classes/Citation.php"); require_once(dirname(__FILE__)."/../classes/Download.php"); +global $token, $server; + if ($instrument == "identifiers") { $sql = "SELECT field_name FROM redcap_data WHERE project_id = ".db_real_escape_string($project_id)." AND record = '".db_real_escape_string($record)."' AND field_name LIKE '%_complete'"; $q = db_query($sql); @@ -25,98 +27,5 @@ } } } -} else if (in_array($instrument, array("followup", "initial_survey"))) { - $pubFields = array(); - if ($instrument == "followup") { - $pubFields = array( - "followup_accepted_pubs" => "Final", - "followup_not_associated_pubs" => "Omit", - "followup_not_addressed_pubs" => "Not Done", - ); - } else if ($instrument == "initial_survey") { - $pubFields = array( - "check_accepted_pubs" => "Final", - "check_not_associated_pubs" => "Omit", - "check_not_addressed_pubs" => "Not Done", - ); - } - - $redcapData = array(); - if (!empty($pubFields)) { - $fields = array_merge(Application::$citationFields, $pubFields); - $json = \REDCap::getData($project_id, "json", array($record), $fields); - $redcapData = json_decode($json, TRUE); - } - - $normativeRow = array(); - if ($instrument == "followup") { - foreach ($redcapData as $row) { - if (($row['redcap_repeat_instrument'] == "followup") && ($row['redcap_repeat_instance'] == $repeat_instance)) { - $normativeRow = $row; - break; - } - } - } else if ($instrument == "initial_survey") { - foreach ($redcapData as $row) { - if ($row['redcap_repeat_instrument'] == "") { - $normativeRow = $row; - break; - } - } - } - - $metadata = Download::metadata($token, $server); - $pubs = new Publications($token, $server, $metadata); - $pubs->setRows($redcapData); - - $functions = array( - "Final" => "includePub", - "Omit" => "omit", - "Not Done" => "stageForReview", - ); - foreach ($pubFields as $pubField => $citCollName) { - $citColls[$citCollName] = $pubs->getCitationCollection($citCollName); - } - foreach ($pubFields as $pubField => $citCollName1) { - if ($row[$pubField]) { - $currIds = json_decode($row[$pubField], TRUE); - - # add and change; never delete - foreach ($currIds as $currPmid) { - $status = "New"; - foreach ($citColls as $citCollName2 => $citColl) { - if ($citColl->has($currPmid)) { - if ($citCollName2 == $citCollName1) { - $status = "Keep"; - } else { - $status = "Move"; - } - break; - } - } - - $func = $functions[$citCollName1]; - switch($status) { - case "New": - $instance = Citation::findMaxInstance($token, $server, $record, $redcapData); - $citation = new Citation($token, $server, $record, $instance); - $citation->$func(); // writes to REDCap - $citColls[$citCollName1]->addCitation($citation); - break; - case "Move": - $citation = $citColls[$citCollName1]->getCitation($currPmid); - $citation->$func(); // writes to REDCap - - # these calls affect future calculations, but do not write them to REDCap - $citColls[$citCollName2]->addCitation($citation); - $citColls[$citCollName1]->removePMID($currPmid); - break; - case "Keep": - break; - default: - break; - } - } - } - } } +Application::refreshRecordSummary($token, $server, $project_id, $record); diff --git a/index.php b/index.php index 80aabd91..f084a16e 100644 --- a/index.php +++ b/index.php @@ -158,7 +158,7 @@ function makeWarning($str) {

Why Use Flight Tracker?

How to Use Flight Tracker?

Enable Help on All Pages

-

Release Notes

+

Release Log (Old Releases)

Consortium

About the Consortium

Monthly Planning Meetings

diff --git a/install.php b/install.php index f54256a0..1b625393 100644 --- a/install.php +++ b/install.php @@ -89,6 +89,8 @@ 'run_tonight' => FALSE, 'grant_class' => $_POST['grant_class'], 'grant_number' => $_POST['grant_number'], + 'auto_recalculate' => '1', + 'shared_forms' => [], ); setupModuleSettings($projectId, $settingFields); diff --git a/metadata.json b/metadata.json index b4d278b5..3c4b85bb 100644 --- a/metadata.json +++ b/metadata.json @@ -816,7 +816,7 @@ {"field_name":"followup_name_middle","form_name":"followup","section_header":"","field_type":"text","field_label":"Middle Name","select_choices_or_calculations":"","field_note":"","text_validation_type_or_show_slider_number":"","text_validation_min":"","text_validation_max":"","identifier":"y","branching_logic":"","required_field":"","custom_alignment":"","question_number":"","matrix_group_name":"","matrix_ranking":"","field_annotation":""}, {"field_name":"followup_name_last","form_name":"followup","section_header":"","field_type":"text","field_label":"Last Name","select_choices_or_calculations":"","field_note":"","text_validation_type_or_show_slider_number":"","text_validation_min":"","text_validation_max":"","identifier":"y","branching_logic":"","required_field":"y","custom_alignment":"","question_number":"","matrix_group_name":"","matrix_ranking":"","field_annotation":""}, {"field_name":"followup_email","form_name":"followup","section_header":"","field_type":"text","field_label":"Email","select_choices_or_calculations":"","field_note":"","text_validation_type_or_show_slider_number":"email","text_validation_min":"","text_validation_max":"","identifier":"y","branching_logic":"","required_field":"","custom_alignment":"","question_number":"","matrix_group_name":"","matrix_ranking":"","field_annotation":""}, -{"field_name":"followup_name_preferred","form_name":"followup","section_header":"","field_type":"yesno","field_label":"Do you prefer to be called a name other than [check_name_first]?","select_choices_or_calculations":"","field_note":"","text_validation_type_or_show_slider_number":"","text_validation_min":"","text_validation_max":"","identifier":"","branching_logic":"[followup_name_first] <> ''","required_field":"","custom_alignment":"","question_number":"","matrix_group_name":"","matrix_ranking":"","field_annotation":""}, +{"field_name":"followup_name_preferred","form_name":"followup","section_header":"","field_type":"yesno","field_label":"Do you prefer to be called a name other than [followup_name_first]?","select_choices_or_calculations":"","field_note":"","text_validation_type_or_show_slider_number":"","text_validation_min":"","text_validation_max":"","identifier":"","branching_logic":"[followup_name_first] <> ''","required_field":"","custom_alignment":"","question_number":"","matrix_group_name":"","matrix_ranking":"","field_annotation":""}, {"field_name":"followup_name_preferred_enter","form_name":"followup","section_header":"","field_type":"text","field_label":"Preferred Name","select_choices_or_calculations":"","field_note":"","text_validation_type_or_show_slider_number":"","text_validation_min":"","text_validation_max":"","identifier":"y","branching_logic":"[followup_name_preferred] = '1'","required_field":"","custom_alignment":"","question_number":"","matrix_group_name":"","matrix_ranking":"","field_annotation":""}, {"field_name":"followup_name_maiden","form_name":"followup","section_header":"","field_type":"yesno","field_label":"Do you have a maiden name?","select_choices_or_calculations":"","field_note":"","text_validation_type_or_show_slider_number":"","text_validation_min":"","text_validation_max":"","identifier":"","branching_logic":"","required_field":"","custom_alignment":"","question_number":"","matrix_group_name":"","matrix_ranking":"","field_annotation":""}, {"field_name":"followup_name_maiden_enter","form_name":"followup","section_header":"","field_type":"text","field_label":"Maiden Name","select_choices_or_calculations":"","field_note":"","text_validation_type_or_show_slider_number":"","text_validation_min":"","text_validation_max":"","identifier":"y","branching_logic":"[followup_name_maiden] = '1'","required_field":"","custom_alignment":"","question_number":"","matrix_group_name":"","matrix_ranking":"","field_annotation":""}, @@ -1143,4 +1143,5 @@ {"field_name":"imported_disadvantaged","form_name":"manual_import","section_header":"","field_type":"dropdown","field_label":"Disadvantaged? Criteria:1) comes from low-income background and/or 2) comes from social/ cultural/ educational environment (i.e.: certain inner-city/rural that demonstrably and recently directly inhibited individuals from obtaining knowledge, skills and abilities necessary to develop and participate in research careers)","select_choices_or_calculations":"1, Yes|2, No|3, Prefer not to answer","field_note":"","text_validation_type_or_show_slider_number":"","text_validation_min":"","text_validation_max":"","identifier":"","branching_logic":"","required_field":"","custom_alignment":"","question_number":"","matrix_group_name":"","matrix_ranking":"","field_annotation":""}, {"field_name":"imported_disabled","form_name":"manual_import","section_header":"","field_type":"dropdown","field_label":"Disability? (Those with physical or mental impairments that substantially limits one or more major life activities)","select_choices_or_calculations":"1, Yes|2, No|3, Prefer not to answer","field_note":"","text_validation_type_or_show_slider_number":"","text_validation_min":"","text_validation_max":"","identifier":"","branching_logic":"","required_field":"","custom_alignment":"","question_number":"","matrix_group_name":"","matrix_ranking":"","field_annotation":""}, {"field_name":"imported_citizenship","form_name":"manual_import","section_header":"","field_type":"dropdown","field_label":"Citizenship:","select_choices_or_calculations":"1, U.S. citizen|2, Non U.S. citizen with permanent residency|3, Non U.S. citizen, will have permanent residency by July 1, 2014|4, Non U.S. citizen, temporary visa","field_note":"","text_validation_type_or_show_slider_number":"","text_validation_min":"","text_validation_max":"","identifier":"","branching_logic":"","required_field":"","custom_alignment":"","question_number":"","matrix_group_name":"","matrix_ranking":"","field_annotation":""}, -{"field_name":"imported_mentor","form_name":"manual_import","section_header":"","field_type":"text","field_label":"Primary Mentor","select_choices_or_calculations":"","field_note":"","text_validation_type_or_show_slider_number":"","text_validation_min":"","text_validation_max":"","identifier":"","branching_logic":"","required_field":"","custom_alignment":"","question_number":"","matrix_group_name":"","matrix_ranking":"","field_annotation":""}] +{"field_name":"imported_mentor","form_name":"manual_import","section_header":"","field_type":"text","field_label":"Primary Mentor","select_choices_or_calculations":"","field_note":"","text_validation_type_or_show_slider_number":"","text_validation_min":"","text_validation_max":"","identifier":"","branching_logic":"","required_field":"","custom_alignment":"","question_number":"","matrix_group_name":"","matrix_ranking":"","field_annotation":""}, +{"field_name":"imported_institution","form_name":"manual_import","section_header":"Institution","field_type":"text","field_label":"All Other Institutions/Organizations [Institution Name(s); Comma-Separated]","select_choices_or_calculations":"","field_note":"","text_validation_type_or_show_slider_number":"","text_validation_min":"","text_validation_max":"","identifier":"","branching_logic":"","required_field":"","custom_alignment":"","question_number":"","matrix_group_name":"","matrix_ranking":"","field_annotation":""}] diff --git a/newFaculty.php b/newFaculty.php index e1793055..22e70ed1 100644 --- a/newFaculty.php +++ b/newFaculty.php @@ -13,7 +13,7 @@ array_push($additionalFields, "First Day of Training at Institution (MM-DD-YYYY)"); } -echo "First Name,Preferred Name,Middle Name,Last Name,Email,Additional Institution(s),Gender [Male or Female],Date-of-Birth [MM-DD-YYYY],Race [American Indian or Alaska Native; Asian; Native Hawaiian or Other Pacific Islander; Black or African American; White; More Than One Race; or Other],Ethnicity [Hispanic/Latino or Non-Hispanic],Disadvantaged [Y; N; or Prefer Not To Answer],Disability [Y or N],Citizenship [US born; Acquired US; Permanent US Residency; or Temporary Visa],Primary Mentor"; +echo "First Name,Preferred Name,Middle Name,Last Name,Email,Additional Institution(s),Gender [Male or Female],Date-of-Birth [MM-DD-YYYY],Race [American Indian or Alaska Native; Asian; Native Hawaiian or Other Pacific Islander; Black or African American; White; More Than One Race; or Other],Ethnicity [Hispanic/Latino or Non-Hispanic],Disadvantaged [Y; N; or Prefer Not To Answer],Disability [Y or N],Citizenship [US born; Acquired US; Permanent US Residency; or Temporary Visa],Primary Mentor,ORCID"; foreach ($additionalFields as $field) { echo ",".$field; } diff --git a/wrangler/index.php b/wrangler/index.php index e25e008e..105d6df2 100644 --- a/wrangler/index.php +++ b/wrangler/index.php @@ -6,14 +6,14 @@ use \Vanderbilt\CareerDevLibrary\Download; use \Vanderbilt\CareerDevLibrary\Links; use \Vanderbilt\CareerDevLibrary\Upload; -use \Vanderbilt\FlightTrackerExternalModule\CareerDev; +use \Vanderbilt\CareerDevLibrary\Application; require_once(dirname(__FILE__)."/../charts/baseWeb.php"); require_once(dirname(__FILE__)."/baseSelect.php"); require_once(dirname(__FILE__)."/../classes/Links.php"); require_once(dirname(__FILE__)."/../classes/Download.php"); require_once(dirname(__FILE__)."/../classes/Upload.php"); -require_once(dirname(__FILE__)."/../CareerDev.php"); +require_once(dirname(__FILE__)."/../Application.php"); $daysForNew = 60; if (isset($_GET['new']) && is_numeric($_GET['new'])) { @@ -21,7 +21,7 @@ } $_GLOBALS['daysForNew'] = $daysForNew; -$url = CareerDev::link("wrangler/index.php"); +$url = Application::link("wrangler/index.php"); if (isset($_GET['headers']) && ($_GET['headers'] == "false")) { $url .= "&headers=false"; } @@ -43,7 +43,8 @@ echo "

"; if (isset($outputData['count']) || isset($outputData['item_count'])) { echo "Upload Success!"; - } + Application::refreshRecordSummary($token, $server, $pid, $record); + } echo "

\n"; } else { echo "

Error! ".json_encode($outputData)."

"; diff --git a/wrangler/pubs.php b/wrangler/pubs.php index a0a419ad..59fb40ab 100644 --- a/wrangler/pubs.php +++ b/wrangler/pubs.php @@ -5,15 +5,16 @@ use \Vanderbilt\CareerDevLibrary\Publications; use \Vanderbilt\CareerDevLibrary\Links; use \Vanderbilt\CareerDevLibrary\Download; -use \Vanderbilt\FlightTrackerExternalModule\CareerDev; +use \Vanderbilt\CareerDevLibrary\Application; require_once(dirname(__FILE__)."/../small_base.php"); require_once(dirname(__FILE__)."/../CareerDev.php"); +require_once(dirname(__FILE__)."/../Application.php"); require_once(dirname(__FILE__)."/../classes/Publications.php"); require_once(dirname(__FILE__)."/../classes/Download.php"); require_once(dirname(__FILE__)."/../classes/Links.php"); -$url = CareerDev::link("wrangler/pubs.php"); +$url = Application::link("wrangler/pubs.php"); if (isset($_GET['headers']) && ($_GET['headers'] == "false")) { $url .= "&headers=false"; }