Compliance Tracker module
Below are two samples from the Compliance Tracker add-on module I wrote for LiquidMedium, the enterprise recruitment product my team worked on while I was at CDI. Three global energy companies used this new module to maintain required certification/compliance information about their contractors.
This project also involved the addition of several new models, which are not shown here as they are deeper in the system architecture and may expose sensitive information.
certification.json.php:
<?php
$root_include = $_SERVER['DOCUMENT_ROOT'] . '/../liquid2-includes/';
require_once $root_include . '/config.php';
header('Content-type: text/json');
$debug->override_off();
$tpl = $file_to_root . 'certification/templates/certification.json.tpl';
/*
* Get user whose certs we're looking at, either from passed account id
* or session. (Contractors also usually use the former now that Jeff
* changed the menu.)
*/
// Perform candidate lookup
$em_hdr = new em_hdr();
$em_hdr->fetch_by_em_account_id($em_account_id);
$pb_entity_hdr_id = $em_hdr->pb_entity_hdr_id->value;
// Check to make sure user is either looking at their own certs or is in the PMO group
$authorized = true;
if ($em_account_id != $_SESSION['id']) {
$em_role = new em_role();
if (!$em_role->check_role('pmo')) {
$authorized = false;
}
}
// Prevent E_STRICT notices
$certs =
$expired_certs =
$expiring_certs =
$optional_certs = [];
if ($authorized) {
// Get user's assignments
$am_hdr = new am_hdr();
$assignments = $am_hdr->fetch_assignments($em_account_id);
// Build list of jobs
$job_ids = [];
if (!empty($assignments)) {
foreach ($assignments as $assignment) {
$job_ids[] = $assignment['xlat_job_id'];
}
}
// Build list of orgs
$em_org = new em_org();
$org_ids = $em_org->get_org_tree($em_account_id);
// Use jobs/orgs lists to build list of required certs and applicable submissions
$dm_cert = new dm_cert();
$required_cert_ids = $dm_cert->get_required_cert_ids($org_ids, $job_ids);
$certs = $dm_cert->get_certifications($required_cert_ids, $pb_entity_hdr_id);
// Set up vars for loop
$certs_len = count($certs);
$date_format = 'n/j/Y';
// Loop the certifications and manipulate certain data for use by template
for ($i=0; $i<$certs_len; ++$i) {
$expired = false;
// Generate note buttons for those submissions that have notes attached
if ($certs[$i]['submission_status'] != 'missing') {
if (!$note_hdr) {
$note_hdr = new note_hdr();
}
$certs[$i]['notebutton'] = $note_hdr->get_note_button(
'pb_cert_submissions',
$certs[$i]['id'],
$em_account_id
);
}
// Derive the expiration date for display
switch ($certs[$i]['date_type']) {
case 'Issue':
case 'Effective':
if ($certs[$i]['days_valid']) {
if ($certs[$i]['id']) {
// Calculate expiration date using validity period
$certs[$i]['expiration_date'] = date(
$date_format,
strtotime(
$certs[$i]['cert_date'] . ' + ' .
$certs[$i]['days_valid'] . ' days'
)
);
} else {
$certs[$i]['expiration_date'] = '-';
}
} else {
// Show that this type of certification never expires
$certs[$i]['expiration_date'] = 'N/A';
}
break;
case 'Expiration':
// Use the submission's date; leave blank if no submission
if ($certs[$i]['id']) {
$certs[$i]['expiration_date'] = date(
$date_format,
strtotime($certs[$i]['cert_date'])
);
}
break;
default:
/*
* Explicitly set date column blank; keeps wonkiness from
* happening if we get some bad data
*/
$certs[$i]['expiration_date'] = '';
}
// Perform the following checks only for certs with visible, numeric expiration dates
if (!in_array($certs[$i]['expiration_date'], ['', '-', 'N/A'])) {
// Determine whether the cert is expired
if (
strtotime($certs[$i]['expiration_date']) . ' - 90 days') < time() &&
$certs[$i]['submission_status'] !== 'submitted'
) {
$expired = true;
// Flag the cert if it's expiring soon
if (strtotime($certs[$i]['expiration_date']) > time()) {
$certs[$i]['expires_soon'] = true;
}
}
}
// Drop expired/rejected certs that have been resubmitted
if ($certs[$i]['submission_status'] == 'rejected' || $expired === true) {
for ($j=0; $j<$certs_len; ++$j) {
if (
in_array($certs[$j]['submission_status'], ['submitted', 'approved']) &&
$certs[$i]['hdr_id'] == $certs[$j]['hdr_id'] &&
strtotime($certs[$i]['create_dttm']) < strtotime($certs[$j]['create_dttm'])
) {
unset($certs[$i]);
}
}
}
/*
* Put expired/expiring certs not deleted by resubmission logic
* onto prepend arrays and remove them from this one
*/
if ($certs[$i] && $expired) {
if ($certs[$i]['expires_soon']) {
$certs[$i]['submission_status'] = 'expiring';
$expiring_certs[] = $certs[$i];
} else {
$certs[$i]['submission_status'] = 'expired';
$expired_certs[] = $certs[$i];
}
unset($certs[$i]);
}
}
// Get optional certs
$optional_cert_ids = $dm_cert->get_optional_cert_ids($required_cert_ids);
$optional_certs = $dm_cert->get_certifications($optional_cert_ids, $pb_entity_hdr_id);
// Declare custom sort function
function sort_by_expiration($a, $b) {
return strtotime($a['expiration_date']) <=> strtotime($b['expiration_date']);
}
// Sort prepend/append arrays by expiration date
usort($expired_certs, 'sort_by_expiration');
usort($expiring_certs, 'sort_by_expiration');
// Combine prepend arrays w/ main one
$certs = array_merge($expired_certs, $expiring_certs, $certs);
// Parent array to avoid template redundancy
$certs = [
[
'type' => 'Required',
'show' => true,
'data' => $certs
],
[
'type' => 'Optional',
'show' => false,
'data' => $optional_certs
]
];
}
// JSON-only response if mobile device
if ($is_mobile) {
echo json_encode($certs);
exit;
}
$smarty->assign('certs', $certs);
$smarty->assign('base_url', $base_url);
$smarty->assign('authorized', $authorized);
$output = $smarty->fetch($tpl);
$out = json_encode(['content' => $output]);
echo $out;save_certification.php:
<?php
$root_include = $_SERVER['DOCUMENT_ROOT'] . '/../liquid2-includes';
require_once $root_include . '/config.php';
// Setup: get cert type and create submission object
$submission = new pb_cert_submissions();
$submission->dm_cert_hdr_id = $_REQUEST['dm_cert_hdr_id'];
// Set ID if updating an existing submission
if ($_REQUEST['submission_id']) {
$submission->id = $_REQUEST['submission_id'];
}
// Set account ID from submission if present or default to current user
if ($_REQUEST['em_account_id']) {
$submission->em_account_id = $_REQUEST['em_account_id'];
}
// Set PB ID based on submission or lookup if not present
if ($_REQUEST['pb_entity_hdr_id']) {
$submission->pb_entity_hdr_id = $_REQUEST['pb_entity_hdr_id'];
// Set account ID if it exists based on reverse lookup
if (!$submission->em_account_id) {
$em_hdr = new em_hdr();
$em_hdr->fetch_by_pb_entity_hdr_id($pb_entity_hdr_id);
if ($em_hdr->em_account_id) {
$submission->em_account_id = $em_hdr->em_account_id;
} else {
$submission->em_account_id = 0;
}
} else {
$em_hdr = new em_hdr();
$em_hdr->fetch_by_em_account_id($submission->em_account_id);
$submission->pb_entity_hdr_id = $em_hdr->pb_entity_hdr_id;
}
}
// Set cert date
$submission->cert_date = $_REQUEST['cert_date'];
// Set submission status (currently save is disabled in UI but may need to handle down the road)
/*if ($_REQUEST['action'] == 'Save') {
$submission->status = 'saved';
$submission->save();
} elseif ($_REQUEST['action'] == 'Submit') {
$submission->submit();
}*/
// Firefox FormData fix (will need to revisit if we ever re-enable the Save button)
$submission->status = 'saved';
$submission->save();
// Check if file was uploaded
if (!empty($_FILES)) {
// Create and set up upload object
$upload = new upload();
$upload->settings_file_types = 'pdf,doc,docx,xls,xlx,rtf,txt,jpeg,jpg,png,gif,tif,tiff';
$upload->settings_subdirectory = 'pb';
$upload->settings_prefix = 'pb-' . $submission->id;
// Upload file and get resulting info about it
list($name, $result, $json_content) = $upload->perform_upload('fileup');
// If file upload succeeded, create reord for it in attachments table
if ($name) {
$json_data = json_decode($json_content, true);
$attachment = new pb_cert_submissions_attachments();
$attachment->pb_cert_submissions_id = $submission->id;
$attachment->saved_name = $json_data['save_name'];
$attachment->file_name = $json_data['file_name'];
$attachment->size = $json_data['size'];
$attachment->type_id = 0; // TODO: Make this a real value when there is lookup data for it.
$success = $attachment->save();
}
}
// Submit the certification
$submission->submit();
// Store initial notes if any were entered
if ($_REQUEST['notes']) {
$note_hdr = new note_hdr();
$note_hdr->parent_id = 0;
$note_hdr->tbl_name = 'pb_cert_submissions';
$note_hdr->trans_id = $submission->id;
$note_hdr->from_em_account_id = $auth->id;
$note_hdr->subject = 'Certification Note'; // TODO: Make this more meaningful? What info should be included?
$note_hdr->note = $_REQUEST['notes'];
$note_hdr->save();
}
// If form wasn't submitted via AJAX, redirect back to certs view (IE fix)
if (!isset($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) !== 'xmlhttprequest') {
// Get the data from the submitting page
list($referrer_url, $referrer_query) = explode('?', $_SERVER['HTTP_REFERER']);
parse_str($referrer_query);
// Perform the redirect
header('Location: ' . $referrer_url . '?id=' . $id . '&certification_view=true');
}