$name created"); redirect(); } if (isset($_POST['edit_location'])) { validateCSRFToken(); enforceUserPermission('module_client', 2); $row = locationForRequest($_POST['location_id'] ?? 0); $location_id = intval($row['location_id']); $set = locationFormSet($_POST, $client_id); if ($set === null) { flashAlert('A location needs a name.', 'error'); redirect(); } mysqli_query($mysqli, "UPDATE locations SET $set WHERE location_id = $location_id"); // Only a form that showed the tags changes them if (isset($_POST['tags_form'])) { locationSetTags($location_id, $_POST['tags'] ?? []); } if (!empty($_POST['location_primary'])) { locationMakePrimary($location_id, $client_id); } if (!empty($_POST['remove_photo']) && $row['location_photo']) { $photo = (string) $row['location_photo']; if (basename($photo) === $photo && is_file("../uploads/clients/$client_id/$photo")) { unlink("../uploads/clients/$client_id/$photo"); } mysqli_query($mysqli, "UPDATE locations SET location_photo = NULL WHERE location_id = $location_id"); } locationSavePhoto($location_id, $client_id, $_FILES['file'] ?? []); $name = trim($_POST['name']); logAudit("Location", "Edit", "$session_name edited location " . escapeSql($name), $client_id, $location_id); flashAlert("Location $name updated"); redirect(); } if (isset($_GET['make_location_primary'])) { validateCSRFToken(); enforceUserPermission('module_client', 2); $row = locationForRequest($_GET['make_location_primary']); $location_id = intval($row['location_id']); if ($row['location_archived_at']) { flashAlert('Restore the location before making it primary.', 'error'); redirect(); } locationMakePrimary($location_id, $client_id); logAudit("Location", "Edit", "$session_name made " . escapeSql($row['location_name']) . " the primary location", $client_id, $location_id); flashAlert("{$row['location_name']} is now the primary location"); redirect(); } if (isset($_POST['set_location_records'])) { validateCSRFToken(); $row = locationForRequest($_POST['location_id'] ?? 0); $kind = (string) ($_POST['kind'] ?? ''); if (!isset(LOCATION_KINDS[$kind])) { flashAlert('Nothing to move.', 'error'); redirect(); } enforceUserPermission(LOCATION_KINDS[$kind]['module'], 2); if ($row['location_archived_at']) { flashAlert('Restore the location before moving anything to it.', 'error'); redirect(); } $moved = locationAssign($row, $kind, $_POST['ids'] ?? []); $k = LOCATION_KINDS[$kind]; if (!$moved) { flashAlert("Nothing new to move to {$row['location_name']}.", 'warning'); } elseif (count($moved) === 1) { flashAlert(ucfirst($k['label']) . " {$moved[0]} moved to {$row['location_name']}"); } else { flashAlert("Moved " . count($moved) . " " . strtolower($k['plural']) . " to {$row['location_name']}"); } redirect(); } if (isset($_GET['unset_location_record'])) { validateCSRFToken(); $row = locationForRequest($_GET['location_id'] ?? 0); $kind = (string) ($_GET['kind'] ?? ''); if (!isset(LOCATION_KINDS[$kind])) { redirect(); } enforceUserPermission(LOCATION_KINDS[$kind]['module'], 2); $name = locationUnassign($row, $kind, intval($_GET['unset_location_record'])); if ($name !== '') { flashAlert(ucfirst(LOCATION_KINDS[$kind]['label']) . " $name is no longer at {$row['location_name']}", 'error'); } redirect(); } if (isset($_GET['archive_location'])) { validateCSRFToken(); enforceUserPermission('module_client', 2); $row = locationForRequest($_GET['archive_location']); $location_id = intval($row['location_id']); // The client header and overview show the primary location - another one has to take over first if (intval($row['location_primary'])) { flashAlert("{$row['location_name']} is the primary location - make another location primary first.", 'error'); redirect(); } mysqli_query($mysqli, "UPDATE locations SET location_archived_at = NOW() WHERE location_id = $location_id"); logAudit("Location", "Archive", "$session_name archived location " . escapeSql($row['location_name']), $client_id, $location_id); flashAlert("Location {$row['location_name']} archived", 'error'); redirect(); } if (isset($_GET['restore_location'])) { validateCSRFToken(); enforceUserPermission('module_client', 2); $row = locationForRequest($_GET['restore_location']); $location_id = intval($row['location_id']); mysqli_query($mysqli, "UPDATE locations SET location_archived_at = NULL WHERE location_id = $location_id"); logAudit("Location", "Restore", "$session_name restored location " . escapeSql($row['location_name']), $client_id, $location_id); flashAlert("Location {$row['location_name']} restored"); redirect(); } if (isset($_GET['delete_location'])) { validateCSRFToken(); enforceUserPermission('module_client', 3); $row = locationForRequest($_GET['delete_location']); if (intval($row['location_primary'])) { flashAlert("{$row['location_name']} is the primary location - make another location primary first.", 'error'); redirect(); } locationDeleteForGood($row); logAudit("Location", "Delete", "$session_name deleted location " . escapeSql($row['location_name']), $client_id); flashAlert("Location {$row['location_name']} deleted", 'error'); // From its own page, back to the client's archived locations - that page is gone if (strpos($_SERVER['HTTP_REFERER'] ?? '', '/location.php') !== false) { redirect("locations.php?client_id=$client_id&archived=1"); } redirect(); } if (isset($_POST['bulk_archive_locations'])) { validateCSRFToken(); enforceUserPermission('module_client', 2); $count = 0; $skipped = []; foreach (array_unique(array_map('intval', (array) ($_POST['location_ids'] ?? []))) as $location_id) { $row = locationRow($location_id); if (!$row) { continue; } $client_id = intval($row['location_client_id']); enforceClientAccess(); if (intval($row['location_primary'])) { $skipped[] = $row['location_name']; continue; } mysqli_query($mysqli, "UPDATE locations SET location_archived_at = NOW() WHERE location_id = $location_id"); logAudit("Location", "Archive", "$session_name archived location " . escapeSql($row['location_name']), $client_id, $location_id); $count++; } flashAlert("Archived $count location" . ($count === 1 ? '' : 's') . ($skipped ? ' - kept ' . implode(', ', $skipped) . ', the primary location' : ''), 'error'); redirect(); } if (isset($_POST['bulk_restore_locations'])) { validateCSRFToken(); enforceUserPermission('module_client', 2); $count = 0; foreach (array_unique(array_map('intval', (array) ($_POST['location_ids'] ?? []))) as $location_id) { $row = locationRow($location_id); if (!$row) { continue; } $client_id = intval($row['location_client_id']); enforceClientAccess(); mysqli_query($mysqli, "UPDATE locations SET location_archived_at = NULL WHERE location_id = $location_id"); logAudit("Location", "Restore", "$session_name restored location " . escapeSql($row['location_name']), $client_id, $location_id); $count++; } flashAlert("Restored $count location" . ($count === 1 ? '' : 's')); redirect(); } if (isset($_POST['bulk_delete_locations'])) { validateCSRFToken(); enforceUserPermission('module_client', 3); $count = 0; foreach (array_unique(array_map('intval', (array) ($_POST['location_ids'] ?? []))) as $location_id) { $row = locationRow($location_id); if (!$row || intval($row['location_primary'])) { continue; } $client_id = intval($row['location_client_id']); enforceClientAccess(); locationDeleteForGood($row); logAudit("Location", "Delete", "$session_name deleted location " . escapeSql($row['location_name']), $client_id); $count++; } flashAlert("Deleted $count location" . ($count === 1 ? '' : 's'), 'error'); redirect(); } if (isExportRequest('export_locations')) { validateCSRFToken(); // Exports are reads - see CONTRIBUTING.md enforceUserPermission('module_client'); $format = resolveExportFormat($_POST['export_locations']); // Filters inherited from the locations page - mirrors agent/locations.php $filter_summary = []; // Archived Filter $archived = (isset($_POST['archived']) && $_POST['archived'] == 1); if ($archived) { $filter_summary['Archived'] = 'Archived only'; } if (!empty($_POST['client_id'])) { $client_id = intval($_POST['client_id']); $client_query = "AND location_client_id = $client_id"; $client_name = getFieldById('clients', $client_id, 'client_name'); $file_name_prepend = "$client_name-"; $filter_summary['Client'] = $client_name; enforceClientAccess(); $archive_query = $archived ? "location_archived_at IS NOT NULL" : "location_archived_at IS NULL"; } else { $client_query = ''; $client_id = 0; // for Logging $file_name_prepend = "$session_company_name-"; // Client Filter if (!empty($_POST['client'])) { $filter_client_id = intval($_POST['client']); $client_query = "AND (location_client_id = $filter_client_id)"; $filter_summary['Client'] = exportClientName($filter_client_id); } $archive_query = $archived ? "(client_archived_at IS NOT NULL OR location_archived_at IS NOT NULL)" : "(client_archived_at IS NULL AND location_archived_at IS NULL)"; } // Tags Filter - a location with any of them $tag_query = ''; if (isset($_POST['tags']) && is_array($_POST['tags']) && !empty($_POST['tags'])) { $tag_filter = implode(",", array_map('intval', $_POST['tags'])); $tag_query = "AND locations.location_id IN (SELECT location_id FROM location_tags WHERE tag_id IN ($tag_filter))"; $tag_names = []; $sql_tags = mysqli_query($mysqli, "SELECT tag_name FROM tags WHERE tag_id IN ($tag_filter) ORDER BY tag_name ASC"); while ($tag_row = mysqli_fetch_assoc($sql_tags)) { $tag_names[] = $tag_row['tag_name']; } $filter_summary['Tags'] = implode(', ', $tag_names); } // Search Filter $q = escapeSql($_POST['q'] ?? ''); if (!empty($q)) { $filter_summary['Search'] = $_POST['q']; } $sql = mysqli_query( $mysqli, "SELECT location_name, location_description, location_address, location_city, location_state, location_zip, location_country, location_phone, location_phone_country_code, location_hours, location_notes, IF(location_primary = 1, 'Yes', '') AS location_primary_text, client_name, contact_name, (SELECT COUNT(*) FROM assets WHERE asset_location_id = locations.location_id AND asset_archived_at IS NULL) AS asset_count, (SELECT COUNT(*) FROM contacts AS people WHERE people.contact_location_id = locations.location_id AND people.contact_archived_at IS NULL) AS contact_count FROM locations LEFT JOIN clients ON client_id = location_client_id LEFT JOIN contacts ON contact_id = location_contact_id WHERE $archive_query $tag_query AND (location_name LIKE '%$q%' OR location_description LIKE '%$q%' OR location_address LIKE '%$q%' OR location_city LIKE '%$q%' OR location_state LIKE '%$q%' OR location_zip LIKE '%$q%' OR location_country LIKE '%$q%' OR location_phone LIKE '%$q%' OR client_name LIKE '%$q%' OR locations.location_id IN (SELECT location_id FROM location_tags JOIN tags USING (tag_id) WHERE tag_name LIKE '%$q%')) " . clientScopeSql('location_client_id') . " $client_query ORDER BY location_primary DESC, location_name ASC" ); $num_rows = mysqli_num_rows($sql); guardExportHasRows($num_rows, 'locations'); if ($num_rows > 0) { guardExportPdfRowCount($format, $num_rows); $export = beginExport('locations', $format, $file_name_prepend . 'Locations', 'Locations', summarizeExportFilters($filter_summary)); while ($row = mysqli_fetch_assoc($sql)) { addExportRow($export, $row); } finishExport($export); } logAudit("Location", "Export", "$session_name exported $num_rows location(s) to a " . strtoupper($format) . " file", $client_id); exit; } if (isset($_POST["import_locations_csv"])) { validateCSRFToken(); enforceUserPermission('module_client', 2); $client_id = intval($_POST['client_id']); enforceClientAccess(); if (empty($_FILES["file"]["tmp_name"])) { flashAlert("Please select a file to upload.", 'error'); redirect(); } $file_extension = strtolower(pathinfo((string) $_FILES['file']['name'], PATHINFO_EXTENSION)); if ($file_extension !== 'csv') { flashAlert("Bad file extension", 'error'); redirect(); } if ($_FILES["file"]["size"] < 1) { flashAlert("Bad file size (empty?)", 'error'); redirect(); } $file = fopen($_FILES["file"]["tmp_name"], "r"); $header = fgetcsv($file, 1000, ","); if (!is_array($header) || count($header) != 8) { fclose($file); flashAlert("Bad column count - use the template: Name, Description, Address, City, State, Postal Code, Phone, Hours.", 'error'); redirect(); } $has_primary = intval(mysqli_fetch_row(mysqli_query($mysqli, "SELECT COUNT(*) FROM locations WHERE location_client_id = $client_id AND location_primary = 1 AND location_archived_at IS NULL"))[0]); $row_count = 0; $duplicate_count = 0; while (($column = fgetcsv($file, 1000, ",")) !== false) { // Each row starts blank - a short row never inherits the row before it $column = array_pad($column, 8, ''); $name = trim((string) $column[0]); if ($name === '') { continue; } $name_sql = escapeSql(mb_substr($name, 0, 200)); if (mysqli_num_rows(mysqli_query($mysqli, "SELECT 1 FROM locations WHERE location_name = '$name_sql' AND location_client_id = $client_id"))) { $duplicate_count++; continue; } $description = escapeSql((string) $column[1]); $address = escapeSql(mb_substr((string) $column[2], 0, 200)); $city = escapeSql(mb_substr((string) $column[3], 0, 200)); $state = escapeSql(mb_substr((string) $column[4], 0, 200)); $zip = escapeSql(mb_substr((string) $column[5], 0, 200)); $phone = substr(preg_replace("/[^0-9]/", '', (string) $column[6]), 0, 200); $hours = escapeSql(mb_substr((string) $column[7], 0, 200)); mysqli_query($mysqli, "INSERT INTO locations SET location_name = '$name_sql', location_description = '$description', location_address = '$address', location_city = '$city', location_state = '$state', location_zip = '$zip', location_phone = '$phone', location_hours = '$hours', location_client_id = $client_id"); // A client with no locations yet gets its first one as primary if (!$has_primary) { locationMakePrimary(mysqli_insert_id($mysqli), $client_id); $has_primary = 1; } $row_count++; } fclose($file); logAudit("Location", "Import", "$session_name imported $row_count location(s). $duplicate_count duplicate(s) found and not imported", $client_id); flashAlert("$row_count Location(s) imported, $duplicate_count duplicate(s) detected and not imported"); redirect(); } if (isset($_GET['download_locations_csv_template'])) { $delimiter = ","; $enclosure = '"'; $escape = '\\'; $filename = "Locations-Template.csv"; //create a file pointer $f = fopen('php://memory', 'w'); //set column headers $fields = array('Name', 'Description', 'Address', 'City', 'State', 'Postal Code', 'Phone', 'Hours'); fputcsv($f, $fields, $delimiter, $enclosure, $escape); //move back to beginning of file fseek($f, 0); //set headers to download file rather than displayed header('Content-Type: text/csv'); header('Content-Disposition: attachment; filename="' . $filename . '";'); //output all remaining data on a file pointer fpassthru($f); exit; }