511 lines
19 KiB
PHP
511 lines
19 KiB
PHP
<?php
|
|
|
|
class PlanningAndZoningFacts extends Base {
|
|
|
|
// Variables
|
|
|
|
private $current_user_serial = 0;
|
|
private $current_user_id = "";
|
|
private $current_user_name = "";
|
|
private $current_user_email = "";
|
|
private $pagination_size = 20;
|
|
private $pagination_page = 1;
|
|
|
|
// =================
|
|
// Class Constructor
|
|
// =================
|
|
|
|
public function __construct() {
|
|
|
|
set_error_handler(array($this, "displayError"));
|
|
|
|
date_default_timezone_set(self::TIMEZONE);
|
|
|
|
$this->current_user_serial = $_SESSION[self::USER_SERIAL] ?? 0;
|
|
$this->current_user_id = $_SESSION[self::USER_ID] ?? "";
|
|
$this->current_user_name = $_SESSION[self::USER_NAME] ?? "";
|
|
$this->current_user_email = $_SESSION[self::USER_EMAIL] ?? "";
|
|
|
|
$this->pagination_page = $_SESSION[self::PAGINATION_PAGE] ?? 1;
|
|
}
|
|
|
|
// ================================
|
|
// Search Planning And Zoning Facts
|
|
// ================================
|
|
|
|
public function searchPlanningAndZoningFacts() {
|
|
|
|
$subscription_serial = $_SESSION['subscription_serial'];
|
|
$usersubscription_serial = $_SESSION['usersubscription_serial'];
|
|
|
|
$subscription = (new Subscriptions())->getSubscription($subscription_serial);
|
|
|
|
if ($subscription->count() === 0) {
|
|
$this->logError("Invalid Subscription ({$subscription_serial}): " . __METHOD__);
|
|
}
|
|
|
|
$userssubscription = (new Users())->getUserSubscription($usersubscription_serial);
|
|
|
|
$user_subscription_start_date = $userssubscription->record->usersubscription_start_date;
|
|
$user_subscription_end_date = $userssubscription->record->usersubscription_end_date;
|
|
|
|
$searchhistory = (new SearchHistory())->getUsersPreviousSearchParameters($this->current_user_serial, $subscription_serial);
|
|
|
|
// Update Search History Record
|
|
if (empty($searchhistory)) {
|
|
(new SearchHistory())->saveUserSearchParameters($_POST);
|
|
} else {
|
|
(new SearchHistory())->updateUserSearchParameters($_POST);
|
|
}
|
|
|
|
$planningzoningfacts = $this->getPZFactsPage($user_subscription_start_date, $user_subscription_end_date);
|
|
$searchhistory = (new SearchHistory())->getUsersPreviousSearchParameters($this->current_user_serial, $subscription_serial);
|
|
|
|
$searchhistory->record->searchhistory_parameters->pzfact_entry_date_from = $planningzoningfacts->record->pzfact_entry_date_from;
|
|
$searchhistory->record->searchhistory_parameters->pzfact_entry_date_to = $planningzoningfacts->record->pzfact_entry_date_to;
|
|
|
|
$popovers = (new Popovers())->getPopovers();
|
|
$dropdowns = (new Dropdowns())->getDropdowns();
|
|
// $cities = (new Cities())->getSubscriptionCities($subscription_serial);
|
|
$cities = $this->getPzCities();
|
|
// $counties = (new Counties())->getSubscriptionCounties($subscription_serial);
|
|
$counties = $this->getPzCounties();
|
|
|
|
$availablerecordcount = $this->userTotalAvailableRecordCount($user_subscription_start_date, $user_subscription_end_date);
|
|
|
|
$XML = $this->mergeXML($planningzoningfacts, "<XML/>");
|
|
$XML = $this->mergeXML($subscription, $XML);
|
|
$XML = $this->mergeXML($popovers, $XML);
|
|
$XML = $this->mergeXML($dropdowns, $XML);
|
|
$XML = $this->mergeXML($cities, $XML);
|
|
$XML = $this->mergeXML($counties, $XML);
|
|
$XML = $this->mergeXML($searchhistory, $XML);
|
|
$XML = $this->mergeXML($availablerecordcount, $XML);
|
|
|
|
$content = $this->applyXSL($XML, $this->getXSL("searchPZFacts"));
|
|
$this->displayContent($content);
|
|
}
|
|
|
|
// =======================
|
|
// Return PZ Cities Object
|
|
// =======================
|
|
|
|
public function getPzCities() {
|
|
|
|
$SQL = "select view_pzfacts.pzfact_project_city, view_pzfacts.project_city_verbose from view_pzfacts where pzfact_in_city_limit = 1 and project_city_verbose is not null group by project_city_verbose";
|
|
|
|
return $this->getTable("subscriptioncities", $SQL);
|
|
}
|
|
|
|
// =========================
|
|
// Return PZ Counties Object
|
|
// =========================
|
|
|
|
public function getPzCounties() {
|
|
|
|
$SQL = "select view_pzfacts.pzfact_county, view_pzfacts.pzfact_county_name_verbose from view_pzfacts where pzfact_county_name_verbose is not null group by pzfact_county_name_verbose";
|
|
|
|
return $this->getTable("subscriptioncounties", $SQL);
|
|
}
|
|
|
|
// =============================
|
|
// Return a PZ Facts Page Object
|
|
// =============================
|
|
|
|
public function getPZFactsPage($user_subscription_start_date, $user_subscription_end_date) {
|
|
|
|
// ---------------------------
|
|
// Read + normalize inputs
|
|
// ---------------------------
|
|
$pzfact_project_name = trim((string) filter_input(INPUT_POST, "pzfact_project_name")) ?: "";
|
|
$pzfact_project_city = trim((string) filter_input(INPUT_POST, "pzfact_project_city")) ?: "";
|
|
$pzfact_county = trim((string) filter_input(INPUT_POST, "pzfact_county")) ?: "";
|
|
$pzfact_project_type = trim((string) filter_input(INPUT_POST, "pzfact_project_type")) ?: "";
|
|
$pzfact_action_code = trim((string) filter_input(INPUT_POST, "pzfact_action_code")) ?: "";
|
|
$pzfact_acres_from = trim((string) filter_input(INPUT_POST, "pzfact_acres_from")) ?: "";
|
|
$pzfact_acres_to = trim((string) filter_input(INPUT_POST, "pzfact_acres_to")) ?: "";
|
|
$pzfact_entry_date_from = trim((string) filter_input(INPUT_POST, "pzfact_entry_date_from")) ?: "";
|
|
$pzfact_entry_date_to = trim((string) filter_input(INPUT_POST, "pzfact_entry_date_to")) ?: "";
|
|
|
|
// Pagination
|
|
$pagination = trim((string) filter_input(INPUT_POST, "pagination")) ?: "";
|
|
$this->pagination_size = filter_input(INPUT_POST, "pzfacts_per_page", FILTER_SANITIZE_NUMBER_INT) ?: $this->pagination_size;
|
|
|
|
// --------------------------------------------------------------
|
|
// Clamp subscription start to last year (your existing behavior)
|
|
// --------------------------------------------------------------
|
|
|
|
$oneYearAgo = date('Y-m-d', strtotime('-1 year'));
|
|
if ($user_subscription_start_date <= $oneYearAgo) {
|
|
$user_subscription_start_date = $oneYearAgo;
|
|
}
|
|
|
|
// Safety: make sure subscription end isn't before start
|
|
if ($user_subscription_end_date < $user_subscription_start_date) {
|
|
$this->displayNotice("Your authorized access period is invalid (end date is before start date).");
|
|
return;
|
|
}
|
|
|
|
// --------------------------------------
|
|
// Normalize entry dates to Y-m-d or null
|
|
// --------------------------------------
|
|
$entryFrom = ($pzfact_entry_date_from === "") ? null : date("Y-m-d", strtotime($pzfact_entry_date_from));
|
|
$entryTo = ($pzfact_entry_date_to === "") ? null : date("Y-m-d", strtotime($pzfact_entry_date_to));
|
|
|
|
// -------
|
|
// Connect
|
|
// -------
|
|
$connection = $this->connect();
|
|
|
|
// -------------------------------------
|
|
// Build WHERE clause once + params once
|
|
// -------------------------------------
|
|
$clauses = [];
|
|
$params = [];
|
|
|
|
// Always enforce subscription window
|
|
$clauses[] = "view_pzfacts.pzfact_entry_date BETWEEN :sub_start AND :sub_end";
|
|
$params[':sub_start'] = $user_subscription_start_date;
|
|
$params[':sub_end'] = $user_subscription_end_date;
|
|
|
|
// Project Name (REGEXP)
|
|
if ($pzfact_project_name !== '') {
|
|
$clauses[] = "view_pzfacts.pzfact_project_name REGEXP :project_name";
|
|
$params[':project_name'] = $pzfact_project_name;
|
|
}
|
|
|
|
// Helper: build safe IN (...) lists from a CSV string of numeric ids
|
|
$bindIn = function (string $field, string $csv, string $paramPrefix) use (&$clauses, &$params) {
|
|
|
|
// Accept "30,31, 32" etc.
|
|
$raw = array_values(array_filter(array_map('trim', explode(',', $csv)), 'strlen'));
|
|
|
|
// Keep digits only (prevents injection)
|
|
$items = [];
|
|
foreach ($raw as $v) {
|
|
if (ctype_digit($v)) {
|
|
$items[] = (int) $v;
|
|
}
|
|
}
|
|
|
|
if (!$items) {
|
|
return;
|
|
}
|
|
|
|
$placeholders = [];
|
|
foreach ($items as $i => $val) {
|
|
$ph = ":{$paramPrefix}{$i}";
|
|
$placeholders[] = $ph;
|
|
$params[$ph] = $val;
|
|
}
|
|
|
|
$clauses[] = "{$field} IN (" . implode(',', $placeholders) . ")";
|
|
};
|
|
|
|
// Safe IN filters
|
|
if ($pzfact_project_city !== '') {
|
|
$bindIn("view_pzfacts.pzfact_project_city", $pzfact_project_city, "city_");
|
|
|
|
|
|
}
|
|
|
|
if ($pzfact_county !== '') {
|
|
$bindIn("view_pzfacts.pzfact_county", $pzfact_county, "county_");
|
|
}
|
|
|
|
if ($pzfact_project_type !== '') {
|
|
$bindIn("view_pzfacts.pzfact_project_type", $pzfact_project_type, "ptype_");
|
|
}
|
|
|
|
if ($pzfact_action_code !== '') {
|
|
$bindIn("view_pzfacts.pzfact_action_code", $pzfact_action_code, "acode_");
|
|
}
|
|
|
|
// Acres filter (numeric; bind params)
|
|
if ($pzfact_acres_from !== '' && $pzfact_acres_to !== '') {
|
|
$clauses[] = "view_pzfacts.pzfact_acres BETWEEN :acres_from AND :acres_to";
|
|
$params[':acres_from'] = (float) $pzfact_acres_from;
|
|
$params[':acres_to'] = (float) $pzfact_acres_to;
|
|
} elseif ($pzfact_acres_from !== '') {
|
|
$clauses[] = "view_pzfacts.pzfact_acres = :acres_eq";
|
|
$params[':acres_eq'] = (float) $pzfact_acres_from;
|
|
} elseif ($pzfact_acres_to !== '') {
|
|
$clauses[] = "view_pzfacts.pzfact_acres = :acres_eq";
|
|
$params[':acres_eq'] = (float) $pzfact_acres_to;
|
|
}
|
|
|
|
// -----------------------------------------------------
|
|
// Entry Date Logic (User selection within subscription)
|
|
// -----------------------------------------------------
|
|
$subStart = $user_subscription_start_date;
|
|
$subEnd = $user_subscription_end_date;
|
|
|
|
if ($entryFrom !== null || $entryTo !== null) {
|
|
|
|
// Treat single date as a one-day range
|
|
if ($entryFrom !== null && $entryTo === null) {
|
|
$entryTo = $entryFrom;
|
|
}
|
|
if ($entryTo !== null && $entryFrom === null) {
|
|
$entryFrom = $entryTo;
|
|
}
|
|
|
|
// Swap if reversed
|
|
if ($entryFrom > $entryTo) {
|
|
$tmp = $entryFrom;
|
|
$entryFrom = $entryTo;
|
|
$entryTo = $tmp;
|
|
}
|
|
|
|
// Enforce subscription window with your existing policy (reject + notice)
|
|
if ($entryFrom < $subStart || $entryTo > $subEnd) {
|
|
$this->displayNotice("The Entry date or date range {$this->current_user_name} selected is outside your authorized access period.");
|
|
return;
|
|
}
|
|
|
|
// Add user range filter (intersection with subscription range)
|
|
$clauses[] = "view_pzfacts.pzfact_entry_date BETWEEN :entry_from AND :entry_to";
|
|
$params[':entry_from'] = $entryFrom;
|
|
$params[':entry_to'] = $entryTo;
|
|
}
|
|
|
|
// Build final WHERE SQL
|
|
$whereSql = "WHERE " . implode(" AND ", $clauses);
|
|
|
|
if ($pzfact_project_city !== '') {
|
|
$whereSql .= ' and pzfact_in_city_limit = 1';
|
|
}
|
|
|
|
// -----------
|
|
// COUNT query
|
|
// -----------
|
|
$sqlCount = "SELECT COUNT(*) AS count
|
|
FROM view_pzfacts
|
|
{$whereSql}";
|
|
|
|
$stmtCount = $connection->prepare($sqlCount);
|
|
|
|
foreach ($params as $k => $v) {
|
|
$type = is_int($v) ? PDO::PARAM_INT : PDO::PARAM_STR;
|
|
$stmtCount->bindValue($k, $v, $type);
|
|
}
|
|
|
|
$stmtCount->execute();
|
|
$recordset = $stmtCount->fetch(PDO::FETCH_ASSOC);
|
|
$count = $recordset ? (int) $recordset["count"] : 0;
|
|
|
|
// ---------------------------
|
|
// Pagination logic
|
|
// ---------------------------
|
|
switch ($pagination) {
|
|
case "first":
|
|
$this->pagination_page = 1;
|
|
break;
|
|
case "previous":
|
|
$this->pagination_page = max($this->pagination_page - 1, 1);
|
|
break;
|
|
case "next":
|
|
$this->pagination_page = min($this->pagination_page + 1, (int) ceil($count / $this->pagination_size));
|
|
break;
|
|
case "last":
|
|
$this->pagination_page = (int) ceil($count / $this->pagination_size);
|
|
break;
|
|
}
|
|
|
|
$offset = max(0, ($this->pagination_page - 1) * $this->pagination_size);
|
|
$start = $offset + 1;
|
|
$end = min($count, $start + $this->pagination_size - 1);
|
|
|
|
// ----------
|
|
// DATA query
|
|
// ----------
|
|
$sqlData = "SELECT view_pzfacts.*
|
|
FROM view_pzfacts
|
|
{$whereSql}
|
|
LIMIT :limit
|
|
OFFSET :offset";
|
|
|
|
$stmtData = $connection->prepare($sqlData);
|
|
|
|
// Bind the same params again
|
|
foreach ($params as $k => $v) {
|
|
$type = is_int($v) ? PDO::PARAM_INT : PDO::PARAM_STR;
|
|
$stmtData->bindValue($k, $v, $type);
|
|
}
|
|
|
|
$stmtData->bindValue(":limit", (int) $this->pagination_size, PDO::PARAM_INT);
|
|
$stmtData->bindValue(":offset", (int) $offset, PDO::PARAM_INT);
|
|
|
|
$stmtData->execute();
|
|
|
|
$pzfacts = $this->getTableXML("pzfacts", $stmtData);
|
|
|
|
// ------------------------------------------------------------------------------------
|
|
// Add searched entry_dates for search history
|
|
// (keep your original behavior: default to subscription window if user didn't specify)
|
|
// ------------------------------------------------------------------------------------
|
|
$pzfacts->record->pzfact_entry_date_from = !empty($entryFrom) ? date('m/d/Y', strtotime($entryFrom)) : date('m/d/Y', strtotime($user_subscription_start_date));
|
|
|
|
$pzfacts->record->pzfact_entry_date_to = !empty($entryTo) ? date('m/d/Y', strtotime($entryTo)) : date('m/d/Y', strtotime($user_subscription_end_date));
|
|
|
|
// ---------------------
|
|
// Pagination attributes
|
|
// ---------------------
|
|
$pzfacts->addAttribute("count", $count);
|
|
$pzfacts->addAttribute("start", $start);
|
|
$pzfacts->addAttribute("end", $end);
|
|
$pzfacts->addAttribute("page", $this->pagination_page);
|
|
|
|
$_SESSION[self::PAGINATION_PAGE] = $this->pagination_page;
|
|
|
|
return $pzfacts;
|
|
}
|
|
|
|
// ==================================
|
|
// Users Total Available Record Count
|
|
// ==================================
|
|
|
|
public function userTotalAvailableRecordCount($user_subscription_start_date, $user_subscription_end_date) {
|
|
|
|
// determine real date range based upon users subscription start date
|
|
|
|
if ($user_subscription_start_date <= date('Y-m-d', strtotime('-1 year'))) {
|
|
$user_subscription_start_date = date('Y-m-d', strtotime('-1 year'));
|
|
}
|
|
|
|
$SQL = "select count(*) as count
|
|
from view_pzfacts
|
|
where view_pzfacts.pzfact_entry_date between '{$user_subscription_start_date}' and '{$user_subscription_end_date}' ";
|
|
|
|
// $counties = (new Counties())->getSubscriptionCounties($_SESSION['subscription_serial']);
|
|
//
|
|
// $countyNames = [];
|
|
//
|
|
// foreach ($counties as $county) {
|
|
// $countyNames[] = " " . (string) $county->subscriptioncounty_county . " ";
|
|
// }
|
|
//
|
|
// $pzfact_county = ' ' . implode(', ', $countyNames) . ' ';
|
|
//
|
|
// $SQL .= " and view_pzfacts.pzfact_county in ($pzfact_county) ";
|
|
|
|
return $this->getTable("userstotalavailablerecords", $SQL);
|
|
}
|
|
|
|
// =====================
|
|
// Search Business Facts
|
|
// =====================
|
|
|
|
public function planningAndZoningFactsBasic() {
|
|
|
|
$step = filter_input(INPUT_POST, "step") ?: "prompt";
|
|
|
|
switch ($step) {
|
|
|
|
case "prompt":
|
|
|
|
$dateranges = $this->getDateRangeXML();
|
|
|
|
$content = $this->applyXSL($dateranges, $this->getXSL("pzFactsBasic"));
|
|
|
|
$this->displayContent($content);
|
|
|
|
break;
|
|
|
|
case "results":
|
|
|
|
$week_start_raw = trim((string) filter_input(INPUT_POST, 'week_start'));
|
|
$week_end_raw = trim((string) filter_input(INPUT_POST, 'week_end'));
|
|
|
|
$week_start_db = $week_start_raw !== '' ? date('Y-m-d', strtotime($week_start_raw)) : null;
|
|
$week_end_db = $week_end_raw !== '' ? date('Y-m-d', strtotime($week_end_raw)) : null;
|
|
|
|
$pzfacts = $this->getPZFactsBasic($week_start_db, $week_end_db);
|
|
|
|
$dateranges = $this->getDateRangeXML();
|
|
|
|
$searched = $dateranges->addChild('searchedranges');
|
|
$searched->addChild('week_start', $week_start_db ? date('m/d/Y', strtotime($week_start_db)) : '');
|
|
$searched->addChild('week_end', $week_end_db ? date('m/d/Y', strtotime($week_end_db)) : '');
|
|
|
|
$xml = $this->mergeXML($pzfacts, '<XML/>');
|
|
$xml = $this->mergeXML($dateranges, $xml);
|
|
|
|
$content = $this->applyXSL($xml, $this->getXSL("pzFactsBasic"));
|
|
|
|
$this->displayContent($content);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
$this->displayHome();
|
|
}
|
|
}
|
|
|
|
public function getDateRangeXML() {
|
|
|
|
$ranges = $this->getFridayThursdayRanges(2, new DateTimeImmutable('today'));
|
|
|
|
$xml = new SimpleXMLElement('<ranges/>');
|
|
|
|
$labels = [
|
|
0 => 'week_prior',
|
|
1 => 'two_weeks_prior',
|
|
];
|
|
|
|
foreach ($ranges as $i => $range) {
|
|
$name = $labels[$i] ?? 'prior_' . ($i + 1);
|
|
|
|
$node = $xml->addChild($name);
|
|
|
|
$start = date('m/d/Y', strtotime($range['start']));
|
|
$end = date('m/d/Y', strtotime($range['end']));
|
|
|
|
$node->addChild('start', $start);
|
|
$node->addChild('end', $end);
|
|
}
|
|
|
|
$xml->subscription_serial = $_SESSION['subscription_serial'];
|
|
|
|
return $xml;
|
|
}
|
|
|
|
public function getFridayThursdayRanges(int $weeks = 2, ?DateTimeInterface $today = null): array {
|
|
|
|
$today = $today ? DateTimeImmutable::createFromInterface($today) : new DateTimeImmutable('today');
|
|
|
|
// "last thursday" gives the most recent Thursday strictly before today.
|
|
// If today IS Thursday, we want today as the end date.
|
|
$end = ($today->format('N') === '4') ? $today : $today->modify('last thursday');
|
|
|
|
$ranges = [];
|
|
for ($i = 0; $i < $weeks; $i++) {
|
|
$start = $end->modify('-6 days');
|
|
$ranges[] = [
|
|
'start' => $start->format('Y-m-d'),
|
|
'end' => $end->format('Y-m-d'),
|
|
];
|
|
$end = $start->modify('-1 day'); // move to previous block
|
|
}
|
|
|
|
return $ranges;
|
|
}
|
|
|
|
// ======================================
|
|
// Return an Planning/Zoning Facts Object
|
|
// ======================================
|
|
|
|
public function getPZFactsBasic($week_start, $week_end) {
|
|
|
|
$SQL = "select view_pzfacts.*
|
|
from view_pzfacts
|
|
where view_pzfacts.pzfact_entry_date between '{$week_start}' and '{$week_end}'
|
|
order by view_pzfacts.pzfact_county, view_pzfacts.pzfact_project_name";
|
|
|
|
return $this->getTable("pzfacts", $SQL);
|
|
}
|
|
}
|
|
|
|
?>
|