2026-05-29 14:52:16 -04:00
<? 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 ) {
2026-06-13 11:13:36 -04:00
// -----------------------
2026-05-29 14:52:16 -04:00
// Read + normalize inputs
2026-06-13 11:13:36 -04:00
// --------------------------
$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" )) ?: "" ;
2026-05-29 14:52:16 -04:00
// Pagination
2026-06-13 11:13:36 -04:00
$pagination = trim (( string ) filter_input ( INPUT_POST , "pagination" )) ?: "" ;
2026-05-29 14:52:16 -04:00
$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 ));
2026-06-13 11:13:36 -04:00
$entryTo = ( $pzfact_entry_date_to === "" ) ? null : date ( "Y-m-d" , strtotime ( $pzfact_entry_date_to ));
2026-05-29 14:52:16 -04:00
// -------
// 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 !== '' ) {
2026-06-13 11:13:36 -04:00
$clauses [] = "view_pzfacts.pzfact_acres BETWEEN :acres_from AND :acres_to" ;
2026-05-29 14:52:16 -04:00
$params [ ':acres_from' ] = ( float ) $pzfact_acres_from ;
2026-06-13 11:13:36 -04:00
$params [ ':acres_to' ] = ( float ) $pzfact_acres_to ;
2026-05-29 14:52:16 -04:00
} elseif ( $pzfact_acres_from !== '' ) {
2026-06-13 11:13:36 -04:00
$clauses [] = "view_pzfacts.pzfact_acres = :acres_eq" ;
$params [ ':acres_eq' ] = ( float ) $pzfact_acres_from ;
2026-05-29 14:52:16 -04:00
} elseif ( $pzfact_acres_to !== '' ) {
2026-06-13 11:13:36 -04:00
$clauses [] = "view_pzfacts.pzfact_acres = :acres_eq" ;
$params [ ':acres_eq' ] = ( float ) $pzfact_acres_to ;
2026-05-29 14:52:16 -04:00
}
// -----------------------------------------------------
// 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 ) {
2026-06-13 11:13:36 -04:00
$tmp = $entryFrom ;
2026-05-29 14:52:16 -04:00
$entryFrom = $entryTo ;
2026-06-13 11:13:36 -04:00
$entryTo = $tmp ;
2026-05-29 14:52:16 -04:00
}
// 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 ;
2026-06-13 11:13:36 -04:00
// ----------------
2026-05-29 14:52:16 -04:00
// Pagination logic
2026-06-13 11:13:36 -04:00
// ----------------
2026-05-29 14:52:16 -04:00
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
2026-06-13 11:13:36 -04:00
{ $whereSql } " ;
if ( $pagination !== 'off' ) {
$sqlData .= " limit :limit
offset :offset" ;
}
2026-05-29 14:52:16 -04:00
$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 );
}
2026-06-13 11:13:36 -04:00
if ( $pagination !== 'off' ) {
$stmtData -> bindValue ( ":limit" , ( int ) $this -> pagination_size , PDO :: PARAM_INT );
$stmtData -> bindValue ( ":offset" , ( int ) $offset , PDO :: PARAM_INT );
}
2026-05-29 14:52:16 -04:00
$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 );
}
}
?>