diff --git a/lib/AJAXInterface.php b/lib/AJAXInterface.php index fc22b077c..3b455eb22 100755 --- a/lib/AJAXInterface.php +++ b/lib/AJAXInterface.php @@ -204,7 +204,7 @@ public function __construct() /* Give the session a unique name to avoid conflicts and start the * session. */ @session_name(CATS_SESSION_NAME); - session_start(); + if (session_status() === PHP_SESSION_NONE) { session_start(); } /* Validate the session. */ if (!$this->isSessionLoggedIn()) diff --git a/lib/CareerPortal.php b/lib/CareerPortal.php index f420d0d85..d621df410 100755 --- a/lib/CareerPortal.php +++ b/lib/CareerPortal.php @@ -458,13 +458,20 @@ public function sendEmail($userID, $destination, $subject, $body) /* Send e-mail notification. */ //FIXME: Make subject configurable. - $mailer = new Mailer($this->_siteID, $userID); - $mailerStatus = $mailer->sendToOne( - array($destination, ''), - $subject, - $body, - true - ); + try { + $mailer = new Mailer($this->_siteID, $userID); + $mailerStatus = $mailer->sendToOne( + array($destination, ''), + $subject, + $body, + true + ); + } catch (Exception $e) { + // Mail not configured or failed - log error for debugging + $mailerStatus = false; + + error_log('OpenCATS Mailer error (site=' . $this->_siteID . '): ' . $e->getMessage()); + } } } diff --git a/lib/ZipLookup.php b/lib/ZipLookup.php index b19d35d02..0ce811a7d 100755 --- a/lib/ZipLookup.php +++ b/lib/ZipLookup.php @@ -1,82 +1,78 @@ lookupZip($zip); + } + + public function lookupZip($zip) + { + $aAddress = array(0, '', '', ''); + if ($zip == '') { + $aAddress[0] = 2; + return $aAddress; + } - $aAddress[0] = 0; - $aAddress[1] = ''; - $aAddress[2] = ''; - $aAddress[3] = ''; + $sUrl = 'https://maps.googleapis.com/maps/api/geocode/xml?sensor=false&address='; + $oXml = simplexml_load_file($sUrl . rawurlencode($zip)); - $sUrl = 'http://maps.googleapis.com/maps/api/geocode/xml?sensor=false&address='; + if ($oXml === false || !isset($oXml->result->address_component)) { + $aAddress[0] = 1; + return $aAddress; + } - if ($zip != '') { - if (($oXml = simplexml_load_file($sUrl . $zip))) { - foreach($oXml->result->address_component as $value) { - if ($value->type == 'route') { - $aAddress[1] = (string) $value->long_name; - } - if ($value->type[0] == 'postal_town') { - $loc_level_1 = (string) $value->long_name; - } - if ($value->type[0] == 'locality') { - $loc_level_1 = (string) $value->long_name; - } - if ($value->type[0] == 'administrative_area_level_1') { - $loc_level_2 = (string) $value->long_name; - } - if ($value->type[0] == 'administrative_area_level_2') { - $loc_level_3 = (string) $value->long_name; - } - if ($value->type[0] == 'country') { - $loc_level_4 = (string) $value->long_name; - } - } - } else { - $aAddress[0] = 1; - } - } else { - $aAddress[0] = 2; - } + $levels = $this->parseAddressComponents($oXml->result->address_component, $aAddress); + $aAddress[2] = $levels['loc_level_1']; + $aAddress[3] = ($levels['loc_level_4'] == 'United States') ? $levels['loc_level_3'] : $levels['loc_level_2']; - // Set the state based on US or non-US location - $aAddress[2] = $loc_level_1; - if ($loc_level_4 == 'United States') { - $aAddress[3] = $loc_level_3; - } else { - $aAddress[3] = $loc_level_2; - } - - return $aAddress; + return $aAddress; + } + + private function parseAddressComponents($components, &$aAddress) + { + $levels = array('loc_level_1' => '', 'loc_level_2' => '', 'loc_level_3' => '', 'loc_level_4' => ''); + $typeMap = array( + 'postal_town' => 'loc_level_1', + 'locality' => 'loc_level_1', + 'administrative_area_level_1'=> 'loc_level_2', + 'administrative_area_level_2'=> 'loc_level_3', + 'country' => 'loc_level_4', + ); + + foreach ($components as $value) { + if ($value->type == 'route') { + $aAddress[1] = (string) $value->long_name; + } + if (isset($value->type[0]) && isset($typeMap[(string)$value->type[0]])) { + $levels[$typeMap[(string)$value->type[0]]] = (string) $value->long_name; + } + } + return $levels; } - - /** - * Returns an array of SQL clauses that returns the distance from a zipcode for each record. - * - * @param integer United States Zip code (55303) - * @param string record Zip Code Column (candidate.zip) - * @return string SQL select clause - */ + public function getDistanceFromPointQuery($zipcode, $zipcodeColumn) { - //based on kilometers = (3958*3.1415926*sqrt(($lat2-$lat1)*($lat2-$lat1) + cos($lat2/57.29578)*cos($lat1/57.29578)*($lon2-$lon1)*($lon2-$lon1))/180); - - $select = "(3958*3.1415926*sqrt((zipcode_searching.lat-zipcode_record.lat)*(zipcode_searching.lat-zipcode_record.lat) + cos(zipcode_searching.lat/57.29578)*cos(zipcode_record.lat/57.29578)*(zipcode_searching.lng-zipcode_record.lng)*(zipcode_searching.lng-zipcode_record.lng))/180) as distance_km"; - $join = "LEFT JOIN zipcodes as zipcode_searching ON zipcode_searching.zipcode = ".$zipcode." LEFT JOIN zipcodes as zipcode_record ON zipcode_record.zipcode = ".$zipcodeColumn; + // Legacy wrapper - returns expected select/join keys for distance filtering + // Fix: use 6371 (km radius) to match distance_km alias + // Fix: cast $zipcode to int to prevent SQL injection + $safeZipcode = (int) $zipcode; + + // $zipcodeColumn must be a known column name - validate against allowlist + $allowedColumns = array('candidate.zip', 'zipcode', 'zip'); + if (!in_array($zipcodeColumn, $allowedColumns, true)) { + return array("select" => "0 as distance_km", "join" => ""); + } + + $select = "(6371*3.1415926*sqrt((zipcode_searching.lat-zipcode_record.lat)*(zipcode_searching.lat-zipcode_record.lat) + cos(zipcode_searching.lat/57.29578)*cos(zipcode_record.lat/57.29578)*(zipcode_searching.lng-zipcode_record.lng)*(zipcode_searching.lng-zipcode_record.lng))/180) as distance_km"; + $join = "LEFT JOIN zipcodes as zipcode_searching ON zipcode_searching.zipcode = " . $safeZipcode . " LEFT JOIN zipcodes as zipcode_record ON zipcode_record.zipcode = " . $zipcodeColumn; return array("select" => $select, "join" => $join); } } -?> diff --git a/modules/candidates/Add.tpl b/modules/candidates/Add.tpl index 6f222d227..fb9e13334 100755 --- a/modules/candidates/Add.tpl +++ b/modules/candidates/Add.tpl @@ -122,6 +122,12 @@ + parsingStatus) && count($this->parsingStatus) && + $this->parsingStatus['parseUsed'] >= $this->parsingStatus['parseLimit'] && + $this->parsingStatus['parseLimit'] >= 0): ?> + All daily resume imports used. For more, upgrade to CATS professional. + Cut and paste freeform address here.
'; ?> @@ -187,8 +193,12 @@
contents != '' ? 'style="cursor: pointer;"' : ''); ?> border="0" alt="Import Resume" onclick="parseDocumentFileContents();" />
+ parsingStatus) && $this->parsingStatus['parseLimit'] >= 0 && $this->parsingStatus['parseUsed'] >= $this->parsingStatus['parseLimit']): ?>
+
+
+ isModal): ?>
+
contents != '' ? 'style="cursor: pointer;"' : ''); ?> border="0" alt="Import Resume" onclick="parseDocumentFileContents();" />
+
@@ -283,7 +293,7 @@
associatedAttachment == 0): ?>
Other